@eightshift/frontend-libs
Version:
A collection of useful frontend utility modules. powered by Eightshift
976 lines (825 loc) • 27.7 kB
JavaScript
import { subscribe, select, dispatch } from '@wordpress/data';
import { getAttrKey } from './attributes';
import { STORE_NAME } from './store';
import { camelCase, debounce, isEmpty, isObject, isPlainObject, kebabCase } from '@eightshift/ui-components/utilities';
/**
* Get Global manifest.json and return global variables as CSS variables.
*
* @param {object} globalManifest - (Optional) Global variable data. - Deprecated.
*
* @access public
*
* @return {string|void}
*
* Global Manifest:
* ```js
* const manifestGlobal = {
* "globalVariables": {
* "maxCols": 12,
* "breakpoints": {
* "mobile": 479,
* "tablet": 1279,
* "desktop": 1919,
* "large": 1920
* },
* "containers": {
* "default": "1330px"
* },
* "gutters": {
* "none": "0",
* "default": "25px",
* "big": "50px"
* },
* "sectionSpacing": {
* "min": -300,
* "max": 300,
* "step": 10
* },
* "sectionInSpacing": {
* "min": 0,
* "max": 300,
* "step": 10
* },
* "colors": [
* {
* "name": "Infinum",
* "slug": "infinum",
* "color": "#D8262C"
* },
* {
* "name": "Black",
* "slug": "black",
* "color": "#111111"
* }
* ]
* }
* };
* ```
*
* Usage:
* ```js
* import globalSettings from './../../manifest.json';
*
* outputCssVariablesGlobal(globalSettings);
* ```
*
* Output:
* ```js
* <style>
* :root {
* --global-max-cols: 12;
* --global-breakpoints-mobile: 479;
* --global-breakpoints-tablet: 1279;
* --global-breakpoints-desktop: 1919;
* --global-breakpoints-large: 1920;
* --global-containers-default: 1330px;
* --global-gutters-none: 0;
* --global-gutters-default: 25px;
* --global-gutters-big: 50px;
* --global-section-spacing-min: -300;
* --global-section-spacing-max: 300;
* --global-section-spacing-step: 10;
* --global-section-in-spacing-min: 0;
* --global-section-in-spacing-max: 300;
* --global-section-in-spacing-step: 10;
* --global-colors-infinum: #D8262C;
* --global-colors-black: #111111;
* --global-colors-white: #FFFFFF;
* }
* </style>
* ```
*/
export const outputCssVariablesGlobal = (_globalManifest = {}) => {
let output = '';
for (const [itemKey, itemValue] of Object.entries(select(STORE_NAME).getSettingsGlobalVariables())) {
const itemKeyInner = kebabCase(itemKey);
if (isObject(itemValue)) {
output += globalInner(itemValue, itemKeyInner);
} else {
output += `--global-${itemKeyInner}: ${itemValue};\n`;
}
}
output = output.replace(/\n/g, '');
// Set breakpoints cache for optimized load time.
setBreakpointsCacheData();
// If using inline css variables output them.
outputCssVariablesInline();
// Output style tag.
const styleEl = document.createElement('style');
styleEl.id = `${select(STORE_NAME).getConfigOutputCssSelectorName()}-global`;
styleEl.textContent = `:root {${output}}`;
document.head.prepend(styleEl);
};
/**
* Get component/block options and process them in CSS variables.
*
* @param {array} attributes - Built attributes.
* @param {array} manifest - Component/block manifest data.
* @param {string} unique - Unique key.
* @param {object} globalManifest - (Optional) Global variable data.
* @param {string} customSelector - Output custom selector to use as a style prefix.
*
* @access public
*
* @return {string}
*
* Usage:
* ```js
* import React, { useMemo } from 'react';
*
* const unique = useMemo(() => getUnique(), []);
*
* outputCssVariables(attributes, manifest, unique);
* ```
*/
export const outputCssVariables = (attributes, manifest, unique, _globalManifest = {}, customSelector = '') => {
// Define variables from manifest.
const variables = manifest?.variables;
const variablesEditor = manifest?.variablesEditor;
const responsiveAttributes = manifest?.responsiveAttributes;
if (!variables && !variablesEditor && !manifest?.variablesCustom && !manifest?.variablesCustomEditor) {
return '';
}
const { defaults: defaultBreakpoints, template } = getBreakpointData();
// Build a per-call lookup Map seeded from the cached template (iteration order matches template order).
const data = new Map();
for (const item of template) {
data.set(`${item.type}---${item.name}`, {
type: item.type,
name: item.name,
value: item.value,
variable: [],
});
}
if (typeof variables !== 'undefined') {
// Iterate each responsiveAttribute from responsiveAttributes that appears in variables field.
if (typeof responsiveAttributes !== 'undefined') {
setVariablesToBreakpoints(attributes, setupResponsiveVariables(responsiveAttributes, variables), data, manifest, defaultBreakpoints);
}
// Iterate each variable from variables field.
setVariablesToBreakpoints(attributes, variables, data, manifest, defaultBreakpoints);
}
// Iterate each responsiveAttribute from responsiveAttributes that appears in variablesEditor field.
if (typeof variablesEditor !== 'undefined') {
if (typeof responsiveAttributes !== 'undefined') {
setVariablesToBreakpoints(attributes, setupResponsiveVariables(responsiveAttributes, variablesEditor), data, manifest, defaultBreakpoints);
}
// Iterate each variable from variablesEditor field.
setVariablesToBreakpoints(attributes, variablesEditor, data, manifest, defaultBreakpoints);
}
// Check if component or block.
let name = manifest.componentClass ?? attributes.blockClass;
// Switch selector name.
if (customSelector !== '') {
name = customSelector;
}
return getCssVariablesTypeDefault(name, data, manifest, unique);
};
/**
* Convert hex color into RGB values.
*
* @param {string} input - Input hex color (either 3 or 6 characters).
*
* @access public
*
* @return {string}
*/
export const hexToRgb = (input) => {
if (!input) {
return '0 0 0';
}
const hex = input.replace('#', '').trim();
let r, g, b;
if (hex.length === 3 || hex.length === 4) {
r = parseInt(hex[0] + hex[0], 16);
g = parseInt(hex[1] + hex[1], 16);
b = parseInt(hex[2] + hex[2], 16);
} else if (hex.length === 6 || hex.length === 8) {
r = parseInt(hex.slice(0, 2), 16);
g = parseInt(hex.slice(2, 4), 16);
b = parseInt(hex.slice(4, 6), 16);
} else {
return '0 0 0';
}
if (isNaN(r) || isNaN(g) || isNaN(b)) {
return '0 0 0';
}
return `${r} ${g} ${b}`;
};
/**
* Returns a unique ID, generally used with CSS variable generation.
*
* @access public
*
* @return {string}
*
* Usage:
* ```js
* getUnique();
* ```
*
* Output:
* ```js
* mg2shbh9
* ```
*/
export const getUnique = () => {
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
const arr = new Uint32Array(1);
crypto.getRandomValues(arr);
return arr[0].toString(36);
}
return (Math.random() + 1).toString(36).substring(4);
};
// ------------------------------------------------------------------------------
// PRIVATE METHODS
// ------------------------------------------------------------------------------
// Internal variable for storing caches breakpoint data.
let breakpointsPreparedVariableDataCache = [];
let breakpointDataCache = null;
const responsiveVariablesCache = new WeakMap();
const componentNameCache = new WeakMap();
const normalizedCustomCache = new WeakMap();
const getBreakpointData = () => {
if (breakpointDataCache) {
return breakpointDataCache;
}
const rawBreakpoints = select(STORE_NAME).getSettingsGlobalVariablesBreakpoints();
const sorted = Object.entries(rawBreakpoints).sort((a, b) => a[1] - b[1]);
const defaults = {
min: sorted[0]?.[0] || '',
max: sorted[sorted.length - 1]?.[0] || '',
};
const template = prepareVariableData(sorted);
breakpointDataCache = { sorted, defaults, template };
return breakpointDataCache;
};
const getComponentCamelName = (manifest) => {
let cached = componentNameCache.get(manifest);
if (cached === undefined) {
cached = camelCase(manifest.componentName);
componentNameCache.set(manifest, cached);
}
return cached;
};
const normalizeCustomVariables = (arr) => {
if (!arr) {
return undefined;
}
let cached = normalizedCustomCache.get(arr);
if (!cached) {
cached = arr.map((v) => (v?.trim()?.endsWith(';') ? v : `${v};`));
normalizedCustomCache.set(arr, cached);
}
return cached;
};
/**
* Output CSS variables as one inline style tag.
*
* @access private
*
* @returns {void}
*/
export const outputCssVariablesInline = () => {
let currentStateBlocks = select('core/block-editor').__unstableGetClientIdsTree(); // eslint-disable-line no-underscore-dangle
const debouncedCssOutput = debounce(() => {
if (select(STORE_NAME).hasStylesUpdated()) {
outputCssVariablesCombinedInner(select(STORE_NAME).getStyles());
}
}, 50);
subscribe(() => {
debouncedCssOutput();
// Find updated state of blocks after render.
const newStateBlocks = select('core/block-editor').__unstableGetClientIdsTree(); // eslint-disable-line no-underscore-dangle
// Make changes only if blocks changed.
if (newStateBlocks !== currentStateBlocks) {
// Make all current and next blocks flat for easy checking.
const next = getAllBlocksFlat(newStateBlocks);
const previous = getAllBlocksFlat(currentStateBlocks);
// Find deleted blocks.
const deleted = getDifference(previous, next);
// If blocks are deleted remove them from the style store.
if (deleted) {
// Loop all styles.
select(STORE_NAME)
.getStyles()
.forEach((item, index) => {
// Find index of deleted item by clientId and remove it from the store.
deleted.forEach((element) => {
if (element.clientId === item.blockClientId) {
dispatch(STORE_NAME).unsetStyleByIndex(index);
}
});
});
}
}
// Update current state with the new one.
currentStateBlocks = newStateBlocks;
});
};
/**
* Find difference in two arrays with multiple nesting levels.
*
* @param {array} array1 Array to look.
* @param {array} array2 Array to check.
*
* @access private
*
* @returns {array}
*/
export const getDifference = (array1, array2) => {
return array1.filter((object1) => {
return !array2.some((object2) => {
return object1.clientId === object2.clientId;
});
});
};
/**
* Return CSS variables in default type. On the place where it was called.
*
* @param {string} name - Output css selector name.
* @param {array} data - Data prepared for checking.
* @param {array} manifest - Component/block manifest data.
* @param {string} unique - Unique key.
*
* @access private
*
* @returns {string}
*/
export const getCssVariablesTypeDefault = (name, data, manifest, unique) => {
let output = '';
const uniqueSelector = unique ? `[data-id='${unique}']` : '';
// data.variable items are guaranteed to end with ';' (set by variablesInner), so skip per-item normalization.
for (const { type, value, variable } of data.values()) {
// If breakpoint value is 0 then don't wrap the media query around it.
if (variable.length === 0) {
continue;
}
const variableOutput = variable.join('\n');
if (value === 0) {
// No breakpoint outputted.
output += `\n .${name}${uniqueSelector}{\n${variableOutput}\n}`;
} else {
// With breakpoint.
output += `\n @media (${type}-width: ${value}px) {\n.${name}${uniqueSelector}{\n${variableOutput}\n}\n}`;
}
}
// Cached normalization — manifest arrays are static, so each unique array gets normalized once.
const variablesCustom = normalizeCustomVariables(manifest?.variablesCustom);
const variablesCustomEditor = normalizeCustomVariables(manifest?.variablesCustomEditor);
const manual = variablesCustom ? variablesCustom.join('\n') : '';
const manualEditor = variablesCustomEditor ? variablesCustomEditor.join('\n') : '';
if (!output && !manual && !manualEditor) {
return '';
}
// Prepare output for manual variables.
let finalManualOutput = manual || manualEditor ? `.${name}${uniqueSelector}{ ${manual} ${manualEditor}}` : '';
// Implement some optimizations if necessary.
output = output.replace(/\n/g, '');
finalManualOutput = finalManualOutput.replace(/\n/g, '');
// Output the style for CSS variables.
return <style dangerouslySetInnerHTML={{ __html: `${output} ${finalManualOutput}` }} />;
};
/**
* Get css variables in inline type. In one place in dom.
*
* @param {string} name - Output css selector name.
* @param {array} data - Data prepared for checking.
* @param {array} manifest - Component/block manifest data.
* @param {string} unique - Unique key.
* @param {string} blockClientId - blockClientId key from attributes, corresponds to the original clientId from core.
*
* @access private
*
* @returns {array}
*/
export const getCssVariablesTypeInline = (name, data, manifest, unique, blockClientId) => {
// Prepare output style object.
const styles = {
name,
unique,
blockClientId,
variables: [],
};
// Loop data Map values (iteration order matches the cached template order).
for (const { type, value, variable } of data.values()) {
// If breakpoint value is 0 then don't wrap the media query around it.
if (variable.length === 0) {
continue;
}
// Push data to local state.
styles.variables.push({
type,
variable,
value,
});
}
// Push pre-normalized arrays so downstream output can skip per-element work.
const variablesCustom = normalizeCustomVariables(manifest?.variablesCustom);
if (variablesCustom !== undefined) {
styles.variables.push({
type: 'min',
variable: variablesCustom,
value: 0,
});
}
const variablesCustomEditor = normalizeCustomVariables(manifest?.variablesCustomEditor);
if (variablesCustomEditor !== undefined) {
styles.variables.push({
type: 'min',
variable: variablesCustomEditor,
value: 0,
});
}
return styles;
};
/**
* Process and return global CSS variables based on the type.
*
* @param {array} itemValues - Values to check.
* @param {string} itemKey - Item key to check.
*
* @access private
*
* @return {string}
*/
export const globalInner = (itemValues, itemKey) => {
let output = '';
for (const [key, value] of Object.entries(itemValues)) {
const innerKey = kebabCase(key);
const itemInnerKey = kebabCase(itemKey);
const { slug, color, gradient } = value;
switch (itemInnerKey) {
case 'colors':
if (typeof slug === 'undefined' || typeof color === 'undefined') {
break;
}
output += `--global-${itemInnerKey}-${value.slug}: ${value.color};\n`;
output += `--global-${itemInnerKey}-${value.slug}-values: ${hexToRgb(value.color)};\n`;
break;
case 'gradients':
if (typeof slug === 'undefined' || typeof gradient === 'undefined') {
break;
}
output += `--global-${itemInnerKey}-${value.slug}: ${value.gradient};\n`;
break;
case 'font-sizes':
if (typeof slug === 'undefined') {
break;
}
output += `--global-${itemInnerKey}-${value.slug}: ${value.slug};\n`;
break;
default:
output += `--global-${itemInnerKey}-${innerKey}: ${value};\n`;
break;
}
}
return output;
};
/**
* Sets up a breakpoint value to responsive attribute objects from responsiveAttribute object.
*
* @param {array} attributeVariables - Array of attribute variables object.
* @param {string} breakpointName - Breakpoint name from responsiveAttribute's breakpoint in block's/component's manifest.
* @param {number} breakpointIndex - Index of responsiveAttribute's breakpoint in manifest.
* @param {number} numberOfBreakpoints - Number of responsiveAttribute breakpoints in block's/component's manifest.
*
* @return {array}
*/
export const setBreakpointResponsiveVariables = (attributeVariables, breakpointName, breakpointIndex, numberOfBreakpoints) => {
return attributeVariables.map((attributeVariablesObject) => {
// Calculate default breakpoint index based on order of the breakpoint, inverse property and number of properties in responsiveAttributeObject.
const defaultBreakpointIndex = attributeVariablesObject.inverse ? 0 : numberOfBreakpoints - 1;
// Expanding an object with an additional breakpoint property.
return {
...attributeVariablesObject,
breakpoint: breakpointIndex === defaultBreakpointIndex ? 'default' : breakpointName,
};
});
};
/**
* Iterating through variables matching the keys from responsiveAttributes and translating it to responsive attributes names.
*
* @param {object} responsiveAttributes - Responsive attributes that are read from component's/block's manifest.
* @param {object} variables - Object containing objects with component's/block's attribute variables that are read from manifest.
*
* @return {object} Object prepared for setting all the variables to its breakpoints.
*/
export const setupResponsiveVariables = (responsiveAttributes, variables) => {
// Both inputs are static refs from the manifest — memoize aggressively.
let inner = responsiveVariablesCache.get(responsiveAttributes);
if (!inner) {
inner = new WeakMap();
responsiveVariablesCache.set(responsiveAttributes, inner);
}
const cached = inner.get(variables);
if (cached !== undefined) {
return cached;
}
const result = {};
for (const [responsiveAttributeName, responsiveAttributeObject] of Object.entries(responsiveAttributes)) {
if (!responsiveAttributeName || isEmpty(variables[responsiveAttributeName])) {
continue;
}
const numberOfBreakpoints = Object.keys(responsiveAttributeObject).length;
const responsiveAttributeVariables = {};
let breakpointIndex = 0;
for (const [breakpointName, breakpointVariableName] of Object.entries(responsiveAttributeObject)) {
if (Array.isArray(variables[responsiveAttributeName])) {
responsiveAttributeVariables[breakpointVariableName] = setBreakpointResponsiveVariables(variables[responsiveAttributeName], breakpointName, breakpointIndex, numberOfBreakpoints);
} else {
const breakpointVariables = {};
for (const [attributeValue, attributeObject] of Object.entries(variables[responsiveAttributeName])) {
breakpointVariables[attributeValue] = setBreakpointResponsiveVariables(attributeObject, breakpointName, breakpointIndex, numberOfBreakpoints);
}
responsiveAttributeVariables[breakpointVariableName] = breakpointVariables;
}
breakpointIndex++;
}
Object.assign(result, responsiveAttributeVariables);
}
inner.set(variables, result);
return result;
};
/**
* Setting defined variables to each breakpoint.
*
* @param {object} attributes - Attributes fetched from manifest.
* @param {object} variables - Variables fetched from manifest.
* @param {object} data - Preset objects separated in breakpoints.
* @param {array} manifest - Component/block manifest data.
* @param {object} defaultBreakpoints - Default breakpoints for mobile/desktop first.
*
* @access private
*
* @return {object} Filled object with variables data separated in breakpoints.
*/
export const setVariablesToBreakpoints = (attributes, variables, data, manifest, defaultBreakpoints) => {
// Iterate each variable.
for (const [variableName, variableValue] of Object.entries(variables)) {
// Constant for attributes set value (in db or default).
const attributeValue = attributes[getAttrKey(variableName, attributes, manifest)];
// Set internal breakpoints variable.
const internalBreakpoints = Array.isArray(variableValue) ? variableValue : (variableValue[attributeValue] ?? []);
// Iterate variable array to check breakpoints.
internalBreakpoints.forEach((breakpointItem) => {
// Define variables from breakpointItem.
const {
breakpoint: itemBreakpoint, // Put in temporary variable before checking the type of breakpointItem.
inverse = false, // If inverse is not set use mobile first.
variable = [],
} = breakpointItem;
// Check if we are using mobile or desktop first. Mobile first is the default.
const type = inverse ? 'max' : 'min';
// If breakpoint is not set or has default breakpoint value use default name.
const breakpoint = !itemBreakpoint || itemBreakpoint === defaultBreakpoints[type] ? 'default' : itemBreakpoint;
// O(1) lookup of the matching slot, then mutate the existing array in place.
const slot = data.get(`${type}---${breakpoint}`);
if (slot) {
const newVars = variablesInner(variable, attributeValue, attributes, manifest);
if (newVars.length) {
slot.variable.push(...newVars);
}
}
});
}
return data;
};
/**
* Create initial array of data to be able to populate later.
*
* @param {object} globalBreakpoints - Global breakpoints from global manifest to set the correct output.
*
* @access private
*
* @return {array}
*/
export const prepareVariableData = (globalBreakpoints) => {
// Define the min and max arrays.
const min = [];
const max = [];
let minBreakpointValue = 0;
// Loop the global breakpoints and populate the data.
Object.values(globalBreakpoints).forEach(([item, value]) => {
// Initial inner object.
const itemObject = {
name: item,
value: value,
variable: [],
};
// Inner object for min values.
const itemObjectMin = {
...itemObject,
type: 'min',
value: minBreakpointValue,
};
// Inner object for max values.
const itemObjectMax = {
...itemObject,
type: 'max',
};
// Transfer value to a larger breakpoint.
minBreakpointValue = value;
// Push both min and max to the defined arrays.
min.push(itemObjectMin);
max.push(itemObjectMax);
});
// Pop largest breakpoint out of min array.
min.shift();
// Add default object to the top of the array.
min.unshift({
type: 'min',
name: 'default',
value: 0,
variable: [],
});
// Reverse order of max array.
max.reverse();
// Throwout the largest.
max.shift();
// Add default object to the top of the array.
max.unshift({
type: 'max',
name: 'default',
value: 0,
variable: [],
});
// Merge both arrays.
return min.concat(max);
};
/**
* Internal helper to loop CSS Variables from array.
*
* @param {array} variables - Array of variables of CSS variables.
* @param {mixed} attributeValue - Original attribute value used in magic variable.
* @param {object} attributes - Attributes fetched from manifest.
* @param {array} manifest - Component/block manifest data.
*
* @access private
*
* @returns {array}
*/
export const variablesInner = (variables, attributeValue, attributes, manifest) => {
const output = [];
// Bailout if provided variables is not an object or if attribute value is empty or undefined, used to unset/reset value..
if (typeof attributeValue === 'undefined' || !isPlainObject(variables)) {
return output;
}
const prefix = attributes?.prefix;
const componentName = prefix ? getComponentCamelName(manifest) : '';
// Iterate each attribute and make corrections.
for (const [variableKey, variableValue] of Object.entries(variables)) {
let value = variableValue;
// If value contains magic variable swap that variable with original attribute value.
if (value.includes('%value%')) {
value = value.replace(/%value%/g, attributeValue);
}
// Single regex pass instead of scanning every attribute. Also preserves earlier replacements
// and handles multiple distinct %attr-X% references in the same value correctly.
if (value.includes('%attr-')) {
value = value.replace(/%attr-([a-zA-Z0-9_]+)%/g, (match, key) => {
const attrKey = prefix ? key.replace(componentName, prefix) : key;
return attributes[attrKey] !== undefined ? attributes[attrKey] : match;
});
}
// Bailout if value is empty or undefined.
if (value === 'undefined' || isEmpty(value)) {
continue;
}
// Output the custom CSS variable by adding the attribute key + custom object key.
output.push(`--${kebabCase(variableKey)}: ${value};`);
}
return output;
};
/**
* Set breakpoints cache for optimized load time.
*
* @access private
*
* @returns {void}
*/
export const setBreakpointsCacheData = () => {
// Prepare breakpoints.
const breakpoints = select(STORE_NAME).getSettingsGlobalVariablesBreakpoints();
const breakpointsCache = Object.entries(breakpoints).sort((a, b) => {
return a[1] - b[1]; // Sort from the smallest to the largest breakpoint.
});
// Prepare breakpoints data to output combined css variables.
const breakpointsMin = breakpointsCache.map((item) => {
return {
type: 'min',
value: item[1],
};
});
breakpointsMin.unshift({
type: 'min',
value: 0,
});
const breakpointsMax = breakpointsCache.reverse().map((item) => {
return {
type: 'max',
value: item[1],
};
});
breakpointsMax.unshift({
type: 'max',
value: 0,
});
breakpointsPreparedVariableDataCache = breakpointsMin.concat(breakpointsMax);
};
/**
* Get all blacks inner-blocks recursively in one flat array.
*
* @param {array} blocks - Array of blocks.
*
* @access private
*
* @returns {array}
*/
export const getAllBlocksFlat = (blocks) => {
let output = [];
// Loop all blocks.
blocks.forEach((block) => {
const { innerBlocks } = block;
// Internal output.
let innerOutput = [];
// Add current block to the state.
output.push(block);
// If inner blocks are listed do recursive add.
if (innerBlocks.length > 0) {
innerOutput = getAllBlocksFlat(innerBlocks);
}
// Output all.
output = output.concat(innerOutput);
});
return output;
};
/**
* Output css variables as a one inline style tag - inner.
*
* @access private
*
* @returns {string}
*/
export const outputCssVariablesCombinedInner = (styles) => {
const breakpoints = {};
// Loop styles.
for (const { name, unique, variables } of styles) {
// Bailout if variables are missing.
if (variables.length === 0) {
continue;
}
let uniqueSelector = `[data-id='${unique}']`;
if (!unique) {
uniqueSelector = '';
}
// Loop inner variables.
for (const { type, value, variable } of variables) {
// Bailout if variable is missing.
if (variable.length === 0) {
continue;
}
// Set breakpoint to empty if it is missing initially.
if (typeof breakpoints[`${type}---${value}`] === 'undefined') {
breakpoints[`${type}---${value}`] = '';
}
// All variable entries are pre-normalized (data.variable from variablesInner, custom via normalizeCustomVariables).
const variableOutput = variable.join('\n');
// Populate breakpoints output.
breakpoints[`${type}---${value}`] += `\n.${name}${uniqueSelector}{\n${variableOutput}\n} `;
}
}
// Prepare final output.
let output = '';
// Loop all breakpoints prepared for output.
breakpointsPreparedVariableDataCache.forEach(({ type, value }) => {
const breakpointValue = breakpoints[`${type}---${value}`] ?? '';
// Bailout if breakpoint is missing.
if (breakpointValue === '') {
return;
}
// Wrap media queries with correct selectors.
if (value === 0) {
output += `${breakpointValue}\n`;
} else {
output += `\n@media (${type}-width:${value}px){${breakpointValue}}\n `;
}
});
// Do optimizations if necessary.
output = output.replace(/\n|\r/g, '');
// Get style id name from store.
const selector = select(STORE_NAME).getConfigOutputCssSelectorName();
// Detect if style tag is present in dom.
const styleTag = document.getElementById(selector);
// Process styles.
if (!styleTag) {
const styleEl = document.createElement('style');
styleEl.id = selector;
styleEl.textContent = output;
document.body.append(styleEl);
} else {
styleTag.textContent = output;
}
// Reset state to original.
dispatch(STORE_NAME).unsetStylesUpdated();
};