@cssfn/cssfn
Advanced tools
Comparing version 2.0.32 to 2.0.33
@@ -1,53 +0,64 @@ | ||
const decodeRuleData = (ruleData) => { | ||
const [selector, styles] = ruleData; | ||
return [ | ||
selector, | ||
decodeStyles(styles) | ||
]; | ||
}; | ||
const decodeNestedRule = (ruleData) => { | ||
return [ | ||
Symbol(), | ||
decodeRuleData(ruleData) | ||
]; | ||
}; | ||
export const decodeStyle = (style) => { | ||
if (!style || (style === true)) | ||
return undefined; // falsy style => ignore | ||
const ruleDatas = style['']; // an empty string key is a special property for storing (nested) rules | ||
const decodedStyle = style; // no need to clone to improve performance | ||
if (ruleDatas && ruleDatas.length) { | ||
// delete style['']; // expensive op! causing chrome's to re-create hidden class | ||
style[''] = undefined; // assigning to undefined instead of deleting, to improve performance | ||
const nestedRules = (ruleDatas | ||
.map(decodeNestedRule)); | ||
Object.assign(decodedStyle, Object.fromEntries(nestedRules)); | ||
return undefined; // ignore : falsy style | ||
const nestedRules = style['']; // an empty string key is a special property for storing (nested) rules | ||
if (nestedRules /* ignore null|undefined marker */ && nestedRules.length) { | ||
delete style['']; // expensive op! causing chrome's to re-create hidden class | ||
for (let index = 0, max = nestedRules.length, encodedRuleData; index < max; index++) { | ||
encodedRuleData = nestedRules[index]; | ||
const decodedStyles = decodeStyles(// mutate : EncodedCssStyleCollection => CssStyleCollection | ||
encodedRuleData[1] // type : EncodedCssStyleCollection | ||
); | ||
if (!decodedStyles || (decodedStyles === true)) { | ||
nestedRules[index] = undefined; // mutate : falsy style => undefined (delete) | ||
} | ||
else { | ||
encodedRuleData[1] = [ | ||
encodedRuleData[0], | ||
decodedStyles // type : CssStyleCollection | ||
]; | ||
encodedRuleData[0] = Symbol(); // mutate : undefined|CssRawSelector|CssFinalSelector => new symbol | ||
} // if | ||
} // for | ||
// cleanup blank entries: | ||
for (let index = nestedRules.length - 1; index >= 0; index--) { | ||
if (!nestedRules[index]) | ||
nestedRules.splice(index, 1); | ||
} // for | ||
// expensive op! causing chrome's to re-create hidden class: | ||
Object.assign(style, Object.fromEntries(nestedRules)); | ||
} // if | ||
return decodedStyle; | ||
return style; | ||
}; | ||
function unwrapStyles(styles, result) { | ||
for (const style of styles) { | ||
if (!style || (style === true)) | ||
continue; // falsy style(s) => ignore | ||
const unwrapStyles = (styles) => { | ||
for (let index = 0, max = styles.length, style; index < max; index++) { | ||
style = styles[index]; | ||
// handle falsy item: | ||
if (!style || (style === true)) { | ||
styles[index] = undefined; // mutate : falsy style => undefined (delete) | ||
continue; // handled => continue to next loop | ||
} // if | ||
// handle single item: | ||
if (!Array.isArray(style)) { | ||
const decodedStyle = decodeStyle(style); | ||
if (!decodedStyle || (decodedStyle === true)) | ||
continue; // falsy style(s) => ignore | ||
result.push(decodedStyle); | ||
continue; | ||
const decodedStyle = decodeStyle(style); // mutate : EncodedCssStyle => CssStyle | ||
if (!decodedStyle || (decodedStyle === true)) { | ||
styles[index] = undefined; // mutate : falsy style => undefined (delete) | ||
} | ||
else { | ||
styles[index] = decodedStyle; // mutate : EncodedCssStyle => CssStyle | ||
} // if | ||
continue; // handled => continue to next loop | ||
} // if | ||
// handle multi item(s): | ||
unwrapStyles(style, result); | ||
unwrapStyles(style); // mutate : EncodedCssStyle(s) => CssStyle(s) | ||
} // for | ||
} | ||
}; | ||
export const decodeStyles = (styles) => { | ||
// statically handle single item: | ||
if (!Array.isArray(styles)) { | ||
return decodeStyle(styles); | ||
return decodeStyle(styles); // mutate : EncodedCssStyle => CssStyle | ||
} // if | ||
// dynamically handle multi item(s): | ||
const result = []; | ||
unwrapStyles(styles, result); | ||
return result; | ||
unwrapStyles(styles); // mutate : EncodedCssStyle(s) => CssStyle(s) | ||
return styles; | ||
}; |
import type { OptionalOrBoolean, ProductOrFactory } from '@cssfn/types'; | ||
import type { CssStyle, CssStyleCollection } from '@cssfn/css-types'; | ||
import type { EncodedCssRuleData, EncodedCssStyle, EncodedCssStyleCollection } from './cssfn-encoded-types.js'; | ||
export declare function encodeNestedRule(this: CssStyle, symbolProp: symbol): EncodedCssRuleData; | ||
import type { EncodedCssStyle, EncodedCssStyleCollection } from './cssfn-encoded-types.js'; | ||
export declare const encodeStyle: (style: ProductOrFactory<OptionalOrBoolean<CssStyle>>) => OptionalOrBoolean<EncodedCssStyle>; | ||
export declare const encodeStyles: (styles: CssStyleCollection) => EncodedCssStyleCollection; |
const isTransferablePrimitive = (propValue) => { | ||
if (propValue === undefined) | ||
return true; // undefined => *transferable* | ||
if (propValue === null) | ||
return true; // null => *transferable* | ||
return true; // null object => *transferable* // the only object that transferable | ||
switch (typeof (propValue)) { | ||
case 'string': // string => *transferable* | ||
case 'number': // number => *transferable* | ||
return true; | ||
case 'object': // any object => *NON-transferable* | ||
case 'symbol': // primitive symbol => *NON-transferable* // the only primitive that NON-transferable | ||
return false; | ||
default: | ||
return true; // any primitive => *transferable* | ||
} // switch | ||
return false; // unknown => assumes as *NON-transferable* | ||
}; | ||
const isTransferableDeepArray = (propValue) => { | ||
for (const propSubValue of propValue) { | ||
if (Array.isArray(propSubValue)) { | ||
if (!isTransferableDeepArray(propSubValue)) | ||
return false; | ||
} | ||
else { | ||
if (!isTransferablePrimitive(propSubValue)) | ||
return false; | ||
} // if | ||
} // for | ||
return true; // all subValues are passed -or- empty array | ||
}; | ||
const isTransferableProp = (propValue) => { | ||
if (isTransferablePrimitive(propValue)) | ||
return true; | ||
if (Array.isArray(propValue)) | ||
return isTransferableDeepArray(propValue); | ||
return false; // CssCustomKeyframesRef => *NON-transferable* => false | ||
}; | ||
const encodePropSimpleValue = (propValue) => { | ||
if (isTransferablePrimitive(propValue)) | ||
return propValue; | ||
return propValue.toString(); // CssCustomKeyframesRef => *NON-transferable* => make *transferable* => .toString() | ||
}; | ||
const encodePropSubValue = (propSubValue) => { | ||
if (!Array.isArray(propSubValue)) | ||
return encodePropSimpleValue(propSubValue); | ||
if (propSubValue.every(isTransferablePrimitive)) | ||
return propSubValue; // all items in the array are *transferable* -or- empty array => no need to mutate | ||
// some item(s) in the array is/are *NON-transferable* => NEED to mutate the array with *encoded* value(s): | ||
return (propSubValue | ||
.map(encodePropSimpleValue) // expensive op! | ||
); | ||
}; | ||
const encodeRuleData = (ruleData) => { | ||
// SLOW: | ||
// const [selector, styles] = ruleData; | ||
// return [ | ||
// selector, | ||
// encodeStyles(styles) // expensive op! | ||
// ]; | ||
// FASTER: | ||
return [ | ||
ruleData[0], | ||
encodeStyles(ruleData[1]) // expensive op! | ||
]; | ||
}; | ||
export function encodeNestedRule(symbolProp) { | ||
return encodeRuleData(this[symbolProp]); // expensive op! | ||
} | ||
export const encodeStyle = (style) => { | ||
if (!style || (style === true)) | ||
return undefined; // falsy style => ignore | ||
return undefined; // ignore : falsy style | ||
const styleValue = (typeof (style) === 'function') ? style() : style; | ||
if (!styleValue || (styleValue === true)) | ||
return undefined; // falsy style => ignore | ||
// SLOW: | ||
// const encodedStyle = Object.fromEntries( | ||
// Object.entries(styleValue) // take all string keys (excluding symbol keys) | ||
// .map(encodeProp) // expensive op! | ||
// ) as EncodedCssProps as EncodedCssStyle; | ||
// FASTER: | ||
const encodedStyle = styleValue; // hack: re-use the style object as encoded object; the symbol keys will be ignored when transfering | ||
return undefined; // ignore : falsy style | ||
// an empty string key is a special property for storing (nested) rules | ||
// if exists => assumes as already encoded: | ||
if (encodedStyle[''] !== undefined) | ||
return encodedStyle; | ||
for (const propName in encodedStyle) { // iterates string keys, ignoring symbol keys | ||
const propValue = encodedStyle[propName]; | ||
if (isTransferableProp(propValue)) | ||
continue; // ignore *transferable* prop, no need to mutate | ||
// *NON-transferable* => NEED to mutate the array with *encoded* value(s): | ||
encodedStyle[propName] = (Array.isArray(propValue) | ||
? | ||
// some item(s) in the array is/are *NON-transferable* => NEED to mutate the array with *encoded* value(s): | ||
propValue.map(encodePropSubValue) // expensive op! | ||
: | ||
propValue.toString() // CssCustomKeyframesRef => *NON-transferable* => make *transferable* => .toString() | ||
); | ||
if (styleValue[''] !== undefined) | ||
return styleValue; | ||
for (const propName in styleValue) { // iterates string keys, ignoring symbol keys | ||
const propValue = styleValue[propName]; | ||
if (!Array.isArray(propValue)) { | ||
if (isTransferablePrimitive(propValue)) | ||
continue; // ignore : *transferable* propValue | ||
styleValue[propName] = propValue.toString(); // mutate : CssCustomKeyframesRef => .toString() | ||
} | ||
else { | ||
for (let subIndex = 0, subMax = propValue.length, propSubValue; subIndex < subMax; subIndex++) { | ||
propSubValue = propValue[subIndex]; | ||
if (!Array.isArray(propSubValue)) { | ||
if (isTransferablePrimitive(propSubValue)) | ||
continue; // ignore : *transferable* propSubValue | ||
propValue[subIndex] = propSubValue.toString(); // mutate : CssCustomKeyframesRef => .toString() | ||
} | ||
else { | ||
for (let subSubIndex = 0, subSubMax = propSubValue.length, propSubSubValue; subSubIndex < subSubMax; subSubIndex++) { | ||
propSubSubValue = propSubValue[subSubIndex]; | ||
if (isTransferablePrimitive(propSubSubValue)) | ||
continue; // ignore : *transferable* propSubSubValue | ||
propSubValue[subSubIndex] = propSubSubValue.toString(); // mutate : CssCustomKeyframesRef => .toString() | ||
} // for | ||
} // if | ||
} // for | ||
} // if | ||
} // for | ||
const symbolProps = Object.getOwnPropertySymbols(styleValue); // take all symbol keys | ||
if (symbolProps.length) { | ||
const nestedRules = (symbolProps | ||
.map(encodeNestedRule.bind(styleValue)) // expensive op! | ||
); | ||
const nestedRules = Object.getOwnPropertySymbols(styleValue); // take all symbol keys | ||
if (nestedRules.length) { | ||
for (let index = 0, max = nestedRules.length, ruleData; index < max; index++) { | ||
ruleData = styleValue[nestedRules[index]]; | ||
const encodedStyles = encodeStyles(// mutate : CssStyleCollection => EncodedCssStyleCollection | ||
ruleData[1] // type : CssStyleCollection | ||
); | ||
if (!encodedStyles || (encodedStyles === true)) { | ||
nestedRules[index] = undefined; // mutate : falsy style => undefined (delete) | ||
} | ||
else { | ||
ruleData[1] = encodedStyles; // mutate : CssStyleCollection => EncodedCssStyleCollection | ||
// ruleData[0] = ruleData[0]; // unchanged : undefined|CssRawSelector|CssFinalSelector | ||
nestedRules[index] = ruleData; // mutate : symbol => EncodedCssRuleData | ||
} // if | ||
} // for | ||
// mark as already converted & store the nestedRules: | ||
// expensive op! causing chrome's to re-create hidden class: | ||
encodedStyle[''] = nestedRules; // an empty string key is a special property for storing (nested) rules | ||
styleValue[''] = nestedRules; // an empty string key is a special property for storing (nested) rules | ||
} | ||
else { | ||
// mark as already converted: | ||
// expensive op! causing chrome's to re-create hidden class: | ||
styleValue[''] = null; // an empty string key is a special property for storing (nested) rules | ||
} // if | ||
return encodedStyle; | ||
return styleValue; | ||
}; | ||
function unwrapStyles(styles, result) { | ||
for (const style of styles) { | ||
if (!style || (style === true)) | ||
continue; // falsy style(s) => ignore | ||
const unwrapStyles = (styles) => { | ||
for (let index = 0, max = styles.length, style; index < max; index++) { | ||
style = styles[index]; | ||
// handle falsy item: | ||
if (!style || (style === true)) { | ||
styles[index] = undefined; // mutate : falsy style => undefined (delete) | ||
continue; // handled => continue to next loop | ||
} // if | ||
// handle single item: | ||
if (!Array.isArray(style)) { | ||
const encodedStyle = encodeStyle(style); // expensive op! | ||
if (!encodedStyle || (encodedStyle === true)) | ||
continue; // falsy style(s) => ignore | ||
result.push(encodedStyle); | ||
continue; | ||
const encodedStyle = encodeStyle(style); // mutate : CssStyle => EncodedCssStyle | ||
if (!encodedStyle || (encodedStyle === true)) { | ||
styles[index] = undefined; // mutate : falsy style => undefined (delete) | ||
} | ||
else { | ||
styles[index] = encodedStyle; // mutate : CssStyle => EncodedCssStyle | ||
} // if | ||
continue; // handled => continue to next loop | ||
} // if | ||
// handle multi item(s): | ||
unwrapStyles(style, result); // expensive op! | ||
unwrapStyles(style); // mutate : CssStyle(s) => EncodedCssStyle(s) | ||
} // for | ||
} | ||
}; | ||
export const encodeStyles = (styles) => { | ||
// statically handle single item: | ||
if (!Array.isArray(styles)) { | ||
return encodeStyle(styles); // expensive op! | ||
return encodeStyle(styles); // mutate : CssStyle => EncodedCssStyle | ||
} // if | ||
// dynamically handle multi item(s): | ||
const result = []; | ||
unwrapStyles(styles, result); // expensive op! | ||
return result; | ||
unwrapStyles(styles); // mutate : CssStyle(s) => EncodedCssStyle(s) | ||
return styles; | ||
}; |
import type { CssCustomValue } from '@cssfn/css-types'; | ||
export declare const renderValue: (value: CssCustomValue) => string; | ||
export declare const renderValue: (propValue: CssCustomValue) => string; |
// processors: | ||
const renderSimpleValue = (value) => { | ||
if (typeof (value) === 'number') | ||
return `${value}`; // CssSimpleNumericValue => number => string | ||
if (typeof (value) === 'string') | ||
return value; // CssSimpleLiteralValue|CssCustomRef => string | ||
return value.toString(); // CssCustomKeyframesRef => .toString() | ||
const renderSimpleValue = (propValue) => { | ||
switch (typeof (propValue)) { | ||
case 'string': return propValue; // CssSimpleLiteralValue|CssCustomRef => string | ||
case 'number': return '' + propValue; // CssSimpleNumericValue => number => string | ||
default: return propValue.toString(); // CssCustomKeyframesRef => .toString() | ||
} // switch | ||
}; | ||
const reducedRenderSubValues = { hasImportant: false, rendered: [] }; | ||
const reduceRenderSubValues = (accum, subValue, index, array) => { | ||
if (!Array.isArray(subValue)) { | ||
if (typeof (subValue) === 'number') { | ||
accum.rendered.push(`${subValue}` // CssSimpleNumericValue => number => string | ||
); | ||
} | ||
else if ((index === (array.length - 1)) && (subValue === '!important')) { | ||
accum.hasImportant = true; | ||
} | ||
else if (typeof (subValue) === 'string') { | ||
accum.rendered.push(subValue // CssSimpleLiteralValue|CssCustomRef => string | ||
); | ||
} | ||
else { | ||
accum.rendered.push(subValue.toString() // CssCustomKeyframesRef => .toString() | ||
); | ||
} // if | ||
export const renderValue = (propValue) => { | ||
if (!Array.isArray(propValue)) { | ||
return renderSimpleValue(propValue); | ||
} | ||
else { | ||
accum.rendered.push(subValue | ||
.map(renderSimpleValue) | ||
.join(' ') // [[double array]] => join separated with [space] | ||
); | ||
let hasImportant = false; | ||
let result = ''; // for a small array : a string concatenation is faster than array.join('') | ||
for (let subIndex = 0, subMax = propValue.length, propSubValue; subIndex < subMax; subIndex++) { | ||
propSubValue = propValue[subIndex]; | ||
if (!Array.isArray(propSubValue)) { | ||
if ((subIndex >= 1) && (subIndex === (subMax - 1)) && (propSubValue === '!important')) { | ||
hasImportant = true; | ||
} | ||
else { | ||
if (subIndex >= 1) | ||
result += ', '; // comma separated values | ||
result += renderSimpleValue(propSubValue); | ||
} // if | ||
} | ||
else { | ||
for (let subSubIndex = 0, subSubMax = propSubValue.length, propSubSubValue; subSubIndex < subSubMax; subSubIndex++) { | ||
propSubSubValue = propSubValue[subSubIndex]; | ||
if ((subSubIndex >= 1) && (subSubIndex === (subSubMax - 1)) && (propSubSubValue === '!important')) { | ||
hasImportant = true; | ||
} | ||
else { | ||
if ((subIndex >= 1) && (subSubIndex === 0)) | ||
result += ', '; // comma separated values | ||
if (subSubIndex >= 1) | ||
result += ' '; // space separated values | ||
result += renderSimpleValue(propSubSubValue); | ||
} // if | ||
} // for | ||
} // if | ||
} // for | ||
if (hasImportant) | ||
result += ' !important'; | ||
return result; | ||
} // if | ||
return accum; | ||
}; | ||
export const renderValue = (value) => { | ||
if (!Array.isArray(value)) | ||
return renderSimpleValue(value); // CssComplexBaseValueOf<CssSimpleValue> | ||
try { | ||
value.reduce(reduceRenderSubValues, reducedRenderSubValues); | ||
return (reducedRenderSubValues.rendered | ||
.join(', ') // comma_separated_values | ||
+ | ||
(reducedRenderSubValues.hasImportant ? ' !important' : '')); | ||
} | ||
finally { | ||
// reset the accumulator to be used later: | ||
reducedRenderSubValues.hasImportant = false; | ||
reducedRenderSubValues.rendered.splice(0); | ||
} // try | ||
}; |
{ | ||
"name": "@cssfn/cssfn", | ||
"version": "2.0.32", | ||
"version": "2.0.33", | ||
"description": "Writes, imports, and exports css stylesheets as javascript modules.", | ||
@@ -33,3 +33,3 @@ "keywords": [ | ||
"@cssfn/css-prop-auto-prefix": "^2.0.2", | ||
"@cssfn/css-selectors": "^2.0.9", | ||
"@cssfn/css-selectors": "^2.0.10", | ||
"@cssfn/css-types": "^2.0.7", | ||
@@ -51,3 +51,3 @@ "@cssfn/types": "^2.0.1", | ||
}, | ||
"gitHead": "cb9755c614f545159f304f7db3753478955de44d" | ||
"gitHead": "7848ea5c0049e71d98ad03325a18f2569e5e1056" | ||
} |
@@ -23,38 +23,50 @@ // cssfn: | ||
const decodeRuleData = (ruleData: EncodedCssRuleData): CssRuleData => { | ||
const [selector, styles] = ruleData; | ||
return [ | ||
selector, | ||
decodeStyles(styles) | ||
]; | ||
} | ||
const decodeNestedRule = (ruleData: EncodedCssRuleData): readonly [symbol, CssRuleData] => { | ||
return [ | ||
Symbol(), | ||
decodeRuleData(ruleData) | ||
]; | ||
} | ||
export const decodeStyle = (style: OptionalOrBoolean<EncodedCssStyle>): OptionalOrBoolean<CssStyle> => { | ||
if (!style || (style === true)) return undefined; // falsy style => ignore | ||
if (!style || (style === true)) return undefined; // ignore : falsy style | ||
const ruleDatas = style['']; // an empty string key is a special property for storing (nested) rules | ||
const decodedStyle = style as CssStyle; // no need to clone to improve performance | ||
if (ruleDatas && ruleDatas.length) { | ||
// delete style['']; // expensive op! causing chrome's to re-create hidden class | ||
style[''] = undefined; // assigning to undefined instead of deleting, to improve performance | ||
type CssRuleEntry = readonly [symbol, CssRuleData]; | ||
const nestedRules : (EncodedCssRuleData|CssRuleEntry|undefined)[]|null|undefined = style['']; // an empty string key is a special property for storing (nested) rules | ||
if (nestedRules /* ignore null|undefined marker */ && nestedRules.length) { | ||
delete style['']; // expensive op! causing chrome's to re-create hidden class | ||
const nestedRules = ( | ||
ruleDatas | ||
.map(decodeNestedRule) | ||
); | ||
type MutableEncodedCssRuleData = [...EncodedCssRuleData]|[...CssRuleEntry]; | ||
for (let index = 0, max = nestedRules.length, encodedRuleData: MutableEncodedCssRuleData; index < max; index++) { | ||
encodedRuleData = nestedRules[index] as MutableEncodedCssRuleData; | ||
const decodedStyles = decodeStyles( // mutate : EncodedCssStyleCollection => CssStyleCollection | ||
(encodedRuleData as EncodedCssRuleData)[1] // type : EncodedCssStyleCollection | ||
); | ||
if (!decodedStyles || (decodedStyles === true)) { | ||
nestedRules[index] = undefined; // mutate : falsy style => undefined (delete) | ||
} | ||
else { | ||
encodedRuleData[1] = [ // mutate : EncodedCssStyleCollection => CssRuleData | ||
(encodedRuleData as EncodedCssRuleData)[0], // type : undefined|CssRawSelector|CssFinalSelector | ||
decodedStyles // type : CssStyleCollection | ||
] as CssRuleData; | ||
encodedRuleData[0] = Symbol(); // mutate : undefined|CssRawSelector|CssFinalSelector => new symbol | ||
} // if | ||
} // for | ||
// cleanup blank entries: | ||
for (let index = nestedRules.length - 1; index >= 0; index--) { | ||
if (!nestedRules[index]) nestedRules.splice(index, 1); | ||
} // for | ||
// expensive op! causing chrome's to re-create hidden class: | ||
Object.assign( | ||
decodedStyle, | ||
Object.fromEntries(nestedRules) | ||
style, | ||
Object.fromEntries( | ||
nestedRules as CssRuleEntry[] | ||
) | ||
); | ||
@@ -65,16 +77,34 @@ } // if | ||
return decodedStyle; | ||
return style as CssStyle; | ||
} | ||
function unwrapStyles(styles: Extract<EncodedCssStyleCollection, any[]>, result: CssStyle[]): void { | ||
for (const style of styles) { | ||
if (!style || (style === true)) continue; // falsy style(s) => ignore | ||
const unwrapStyles = (styles: Extract<EncodedCssStyleCollection, any[]>): void => { | ||
for (let index = 0, max = styles.length, style : typeof styles[number]; index < max; index++) { | ||
style = styles[index]; | ||
// handle falsy item: | ||
if (!style || (style === true)) { | ||
styles[index] = undefined; // mutate : falsy style => undefined (delete) | ||
continue; // handled => continue to next loop | ||
} // if | ||
// handle single item: | ||
if (!Array.isArray(style)) { | ||
const decodedStyle = decodeStyle(style); | ||
if (!decodedStyle || (decodedStyle === true)) continue; // falsy style(s) => ignore | ||
result.push(decodedStyle); | ||
continue; | ||
const decodedStyle = decodeStyle(style); // mutate : EncodedCssStyle => CssStyle | ||
if (!decodedStyle || (decodedStyle === true)) { | ||
styles[index] = undefined; // mutate : falsy style => undefined (delete) | ||
} | ||
else { | ||
styles[index] = decodedStyle as CssStyle as any; // mutate : EncodedCssStyle => CssStyle | ||
} // if | ||
continue; // handled => continue to next loop | ||
} // if | ||
@@ -85,3 +115,3 @@ | ||
// handle multi item(s): | ||
unwrapStyles(style, result); | ||
unwrapStyles(style); // mutate : EncodedCssStyle(s) => CssStyle(s) | ||
} // for | ||
@@ -92,3 +122,3 @@ } | ||
if (!Array.isArray(styles)) { | ||
return decodeStyle(styles); | ||
return decodeStyle(styles); // mutate : EncodedCssStyle => CssStyle | ||
} // if | ||
@@ -99,5 +129,4 @@ | ||
// dynamically handle multi item(s): | ||
const result: CssStyle[] = []; | ||
unwrapStyles(styles, result); | ||
return result; | ||
unwrapStyles(styles); // mutate : EncodedCssStyle(s) => CssStyle(s) | ||
return styles as CssStyle[]; | ||
} |
@@ -5,13 +5,5 @@ // cssfn: | ||
DeepArray, | ||
ProductOrFactory, | ||
} from '@cssfn/types' | ||
import type { | ||
// css values: | ||
CssSimpleValue, | ||
CssComplexBaseValueOf, | ||
// css custom properties: | ||
@@ -33,14 +25,3 @@ CssCustomValue, | ||
import type { | ||
// css values: | ||
EncodedCssSimpleValue, | ||
// css custom properties: | ||
EncodedCssCustomValue, | ||
// cssfn properties: | ||
// EncodedCssProps, | ||
EncodedCssRuleData, | ||
@@ -55,3 +36,2 @@ EncodedCssStyle, | ||
type TransferablePrimitive = undefined|null|string|number | ||
type TransferableDeepArray = DeepArray<TransferablePrimitive> | ||
@@ -61,115 +41,65 @@ | ||
const isTransferablePrimitive = <TPropValue extends CssCustomValue|undefined|null>(propValue : TPropValue): propValue is (TPropValue & TransferablePrimitive) => { | ||
if (propValue === undefined) return true; // undefined => *transferable* | ||
if (propValue === null ) return true; // null => *transferable* | ||
if (propValue === null) return true; // null object => *transferable* // the only object that transferable | ||
switch (typeof(propValue)) { | ||
case 'string': // string => *transferable* | ||
case 'number': // number => *transferable* | ||
return true; | ||
case 'object': // any object => *NON-transferable* | ||
case 'symbol': // primitive symbol => *NON-transferable* // the only primitive that NON-transferable | ||
return false; | ||
default: | ||
return true; // any primitive => *transferable* | ||
} // switch | ||
return false; // unknown => assumes as *NON-transferable* | ||
} | ||
const isTransferableDeepArray = <TPropValue extends Extract<CssCustomValue, any[]>>(propValue : TPropValue): propValue is (TPropValue & TransferableDeepArray) => { | ||
for (const propSubValue of propValue) { | ||
if (Array.isArray(propSubValue)) { | ||
if (!isTransferableDeepArray(propSubValue)) return false; | ||
} | ||
else { | ||
if (!isTransferablePrimitive(propSubValue)) return false; | ||
} // if | ||
} // for | ||
return true; // all subValues are passed -or- empty array | ||
} | ||
const isTransferableProp = <TPropValue extends CssCustomValue|undefined|null>(propValue : TPropValue): propValue is ((TPropValue & TransferablePrimitive) | (TPropValue & TransferableDeepArray)) => { | ||
if (isTransferablePrimitive(propValue)) return true; | ||
if (Array.isArray(propValue)) return isTransferableDeepArray(propValue); | ||
return false; // CssCustomKeyframesRef => *NON-transferable* => false | ||
} | ||
const encodePropSimpleValue = (propValue: CssComplexBaseValueOf<CssSimpleValue>): CssComplexBaseValueOf<EncodedCssSimpleValue> => { | ||
if (isTransferablePrimitive(propValue)) return propValue; | ||
return propValue.toString(); // CssCustomKeyframesRef => *NON-transferable* => make *transferable* => .toString() | ||
} | ||
const encodePropSubValue = (propSubValue: Extract<CssCustomValue, any[]>[number]): Extract<EncodedCssCustomValue, any[]>[number] => { | ||
if (!Array.isArray(propSubValue)) return encodePropSimpleValue(propSubValue); | ||
if (propSubValue.every(isTransferablePrimitive)) return propSubValue; // all items in the array are *transferable* -or- empty array => no need to mutate | ||
// some item(s) in the array is/are *NON-transferable* => NEED to mutate the array with *encoded* value(s): | ||
return ( | ||
propSubValue | ||
.map(encodePropSimpleValue) // expensive op! | ||
); | ||
} | ||
const encodeRuleData = (ruleData: CssRuleData): EncodedCssRuleData => { | ||
// SLOW: | ||
// const [selector, styles] = ruleData; | ||
// return [ | ||
// selector, | ||
// encodeStyles(styles) // expensive op! | ||
// ]; | ||
// FASTER: | ||
return [ | ||
ruleData[0], | ||
encodeStyles(ruleData[1]) // expensive op! | ||
]; | ||
} | ||
export function encodeNestedRule(this: CssStyle, symbolProp: symbol): EncodedCssRuleData { | ||
return encodeRuleData(this[symbolProp]); // expensive op! | ||
} | ||
export const encodeStyle = (style: ProductOrFactory<OptionalOrBoolean<CssStyle>>): OptionalOrBoolean<EncodedCssStyle> => { | ||
if (!style || (style === true)) return undefined; // falsy style => ignore | ||
if (!style || (style === true)) return undefined; // ignore : falsy style | ||
const styleValue = (typeof(style) === 'function') ? style() : style; | ||
if (!styleValue || (styleValue === true)) return undefined; // falsy style => ignore | ||
if (!styleValue || (styleValue === true)) return undefined; // ignore : falsy style | ||
// SLOW: | ||
// const encodedStyle = Object.fromEntries( | ||
// Object.entries(styleValue) // take all string keys (excluding symbol keys) | ||
// .map(encodeProp) // expensive op! | ||
// ) as EncodedCssProps as EncodedCssStyle; | ||
// FASTER: | ||
const encodedStyle = styleValue; // hack: re-use the style object as encoded object; the symbol keys will be ignored when transfering | ||
// an empty string key is a special property for storing (nested) rules | ||
// if exists => assumes as already encoded: | ||
if (encodedStyle['' as any] !== undefined) return encodedStyle as EncodedCssStyle; | ||
if (styleValue['' as any] !== undefined) return styleValue as EncodedCssStyle; | ||
for (const propName in encodedStyle) { // iterates string keys, ignoring symbol keys | ||
const propValue : CssCustomValue|undefined|null = encodedStyle[propName as keyof CssProps]; | ||
for (const propName in styleValue) { // iterates string keys, ignoring symbol keys | ||
const propValue : CssCustomValue|undefined|null = styleValue[propName as keyof CssProps]; | ||
if (isTransferableProp(propValue)) continue; // ignore *transferable* prop, no need to mutate | ||
// *NON-transferable* => NEED to mutate the array with *encoded* value(s): | ||
encodedStyle[propName as any] = ( | ||
Array.isArray(propValue) | ||
? | ||
// some item(s) in the array is/are *NON-transferable* => NEED to mutate the array with *encoded* value(s): | ||
(propValue.map(encodePropSubValue) as any) // expensive op! | ||
: | ||
(propValue.toString() as any) // CssCustomKeyframesRef => *NON-transferable* => make *transferable* => .toString() | ||
); | ||
if (!Array.isArray(propValue)) { | ||
if (isTransferablePrimitive(propValue)) continue; // ignore : *transferable* propValue | ||
styleValue[propName as any] = propValue.toString() as any; // mutate : CssCustomKeyframesRef => .toString() | ||
} | ||
else { | ||
for (let subIndex = 0, subMax = propValue.length, propSubValue : typeof propValue[number]; subIndex < subMax; subIndex++) { | ||
propSubValue = propValue[subIndex]; | ||
if (!Array.isArray(propSubValue)) { | ||
if (isTransferablePrimitive(propSubValue)) continue; // ignore : *transferable* propSubValue | ||
propValue[subIndex] = propSubValue.toString(); // mutate : CssCustomKeyframesRef => .toString() | ||
} | ||
else { | ||
for (let subSubIndex = 0, subSubMax = propSubValue.length, propSubSubValue : typeof propSubValue[number]; subSubIndex < subSubMax; subSubIndex++) { | ||
propSubSubValue = propSubValue[subSubIndex]; | ||
if (isTransferablePrimitive(propSubSubValue)) continue; // ignore : *transferable* propSubSubValue | ||
propSubValue[subSubIndex] = propSubSubValue.toString(); // mutate : CssCustomKeyframesRef => .toString() | ||
} // for | ||
} // if | ||
} // for | ||
} // if | ||
} // for | ||
@@ -179,13 +109,36 @@ | ||
const symbolProps = Object.getOwnPropertySymbols(styleValue); // take all symbol keys | ||
if (symbolProps.length) { | ||
const nestedRules = ( | ||
symbolProps | ||
.map(encodeNestedRule.bind(styleValue)) // expensive op! | ||
); | ||
const nestedRules : (symbol|EncodedCssRuleData|undefined)[] = Object.getOwnPropertySymbols(styleValue); // take all symbol keys | ||
if (nestedRules.length) { | ||
type MutableCssRuleData = [...CssRuleData]|[...EncodedCssRuleData]; | ||
for (let index = 0, max = nestedRules.length, ruleData: MutableCssRuleData; index < max; index++) { | ||
ruleData = styleValue[nestedRules[index] as symbol] as MutableCssRuleData; | ||
const encodedStyles = encodeStyles( // mutate : CssStyleCollection => EncodedCssStyleCollection | ||
(ruleData as CssRuleData)[1] // type : CssStyleCollection | ||
); | ||
if (!encodedStyles || (encodedStyles === true)) { | ||
nestedRules[index] = undefined; // mutate : falsy style => undefined (delete) | ||
} | ||
else { | ||
ruleData[1] = encodedStyles; // mutate : CssStyleCollection => EncodedCssStyleCollection | ||
// ruleData[0] = ruleData[0]; // unchanged : undefined|CssRawSelector|CssFinalSelector | ||
nestedRules[index] = ruleData as EncodedCssRuleData; // mutate : symbol => EncodedCssRuleData | ||
} // if | ||
} // for | ||
// mark as already converted & store the nestedRules: | ||
// expensive op! causing chrome's to re-create hidden class: | ||
encodedStyle['' as any] = nestedRules as any; // an empty string key is a special property for storing (nested) rules | ||
styleValue['' as any] = nestedRules as any; // an empty string key is a special property for storing (nested) rules | ||
} | ||
else { | ||
// mark as already converted: | ||
// expensive op! causing chrome's to re-create hidden class: | ||
styleValue['' as any] = null as any; // an empty string key is a special property for storing (nested) rules | ||
} // if | ||
@@ -195,16 +148,34 @@ | ||
return encodedStyle as EncodedCssStyle; | ||
return styleValue as EncodedCssStyle; | ||
} | ||
function unwrapStyles(styles: Extract<CssStyleCollection, any[]>, result: EncodedCssStyle[]): void { | ||
for (const style of styles) { | ||
if (!style || (style === true)) continue; // falsy style(s) => ignore | ||
const unwrapStyles = (styles: Extract<CssStyleCollection, any[]>): void => { | ||
for (let index = 0, max = styles.length, style : typeof styles[number]; index < max; index++) { | ||
style = styles[index]; | ||
// handle falsy item: | ||
if (!style || (style === true)) { | ||
styles[index] = undefined; // mutate : falsy style => undefined (delete) | ||
continue; // handled => continue to next loop | ||
} // if | ||
// handle single item: | ||
if (!Array.isArray(style)) { | ||
const encodedStyle = encodeStyle(style); // expensive op! | ||
if (!encodedStyle || (encodedStyle === true)) continue; // falsy style(s) => ignore | ||
result.push(encodedStyle); | ||
continue; | ||
const encodedStyle = encodeStyle(style); // mutate : CssStyle => EncodedCssStyle | ||
if (!encodedStyle || (encodedStyle === true)) { | ||
styles[index] = undefined; // mutate : falsy style => undefined (delete) | ||
} | ||
else { | ||
styles[index] = encodedStyle as EncodedCssStyle as any; // mutate : CssStyle => EncodedCssStyle | ||
} // if | ||
continue; // handled => continue to next loop | ||
} // if | ||
@@ -215,3 +186,3 @@ | ||
// handle multi item(s): | ||
unwrapStyles(style, result); // expensive op! | ||
unwrapStyles(style); // mutate : CssStyle(s) => EncodedCssStyle(s) | ||
} // for | ||
@@ -222,3 +193,3 @@ } | ||
if (!Array.isArray(styles)) { | ||
return encodeStyle(styles); // expensive op! | ||
return encodeStyle(styles); // mutate : CssStyle => EncodedCssStyle | ||
} // if | ||
@@ -229,5 +200,4 @@ | ||
// dynamically handle multi item(s): | ||
const result: EncodedCssStyle[] = []; | ||
unwrapStyles(styles, result); // expensive op! | ||
return result; | ||
unwrapStyles(styles); // mutate : CssStyle(s) => EncodedCssStyle(s) | ||
return styles as EncodedCssStyle[]; | ||
} |
@@ -16,66 +16,56 @@ // cssfn: | ||
// processors: | ||
const renderSimpleValue = (value: CssComplexBaseValueOf<CssSimpleValue>): string => { | ||
if (typeof(value) === 'number') return `${value}`; // CssSimpleNumericValue => number => string | ||
if (typeof(value) === 'string') return value; // CssSimpleLiteralValue|CssCustomRef => string | ||
return value.toString(); // CssCustomKeyframesRef => .toString() | ||
const renderSimpleValue = (propValue: CssComplexBaseValueOf<CssSimpleValue>): string => { | ||
switch (typeof(propValue)) { | ||
case 'string' : return propValue; // CssSimpleLiteralValue|CssCustomRef => string | ||
case 'number' : return '' + propValue; // CssSimpleNumericValue => number => string | ||
default : return propValue.toString(); // CssCustomKeyframesRef => .toString() | ||
} // switch | ||
}; | ||
type ReducedRenderSubValues = { hasImportant: boolean, rendered: string[] } | ||
const reducedRenderSubValues : ReducedRenderSubValues = { hasImportant: false, rendered: [] } | ||
const reduceRenderSubValues = (accum: ReducedRenderSubValues, subValue: Extract<CssCustomValue, Array<any>>[number], index: number, array: Extract<CssCustomValue, Array<any>>[number][]): ReducedRenderSubValues => { | ||
if (!Array.isArray(subValue)) { | ||
if (typeof(subValue) === 'number') { | ||
accum.rendered.push( | ||
`${subValue}` // CssSimpleNumericValue => number => string | ||
); | ||
} | ||
else if ((index === (array.length - 1)) && (subValue === '!important')) { | ||
accum.hasImportant = true; | ||
} | ||
else if (typeof(subValue) === 'string') { | ||
accum.rendered.push( | ||
subValue // CssSimpleLiteralValue|CssCustomRef => string | ||
); | ||
} | ||
else { | ||
accum.rendered.push( | ||
subValue.toString() // CssCustomKeyframesRef => .toString() | ||
); | ||
} // if | ||
export const renderValue = (propValue: CssCustomValue): string => { | ||
if (!Array.isArray(propValue)) { | ||
return renderSimpleValue(propValue); | ||
} | ||
else { | ||
accum.rendered.push( | ||
subValue | ||
.map(renderSimpleValue) | ||
.join(' ') // [[double array]] => join separated with [space] | ||
); | ||
} // if | ||
return accum; | ||
}; | ||
export const renderValue = (value: CssCustomValue): string => { | ||
if (!Array.isArray(value)) return renderSimpleValue(value); // CssComplexBaseValueOf<CssSimpleValue> | ||
try { | ||
(value as Extract<CssCustomValue, Array<any>>[number][]).reduce(reduceRenderSubValues, reducedRenderSubValues); | ||
let hasImportant = false; | ||
let result = ''; // for a small array : a string concatenation is faster than array.join('') | ||
return ( | ||
reducedRenderSubValues.rendered | ||
.join(', ') // comma_separated_values | ||
for (let subIndex = 0, subMax = propValue.length, propSubValue : typeof propValue[number]; subIndex < subMax; subIndex++) { | ||
propSubValue = propValue[subIndex]; | ||
+ | ||
(reducedRenderSubValues.hasImportant ? ' !important' : '') | ||
); | ||
} | ||
finally { | ||
// reset the accumulator to be used later: | ||
reducedRenderSubValues.hasImportant = false; | ||
reducedRenderSubValues.rendered.splice(0); | ||
} // try | ||
if (!Array.isArray(propSubValue)) { | ||
if ((subIndex >= 1) && (subIndex === (subMax - 1)) && (propSubValue === '!important')) { | ||
hasImportant = true; | ||
} | ||
else { | ||
if (subIndex >= 1) result += ', '; // comma separated values | ||
result += renderSimpleValue(propSubValue); | ||
} // if | ||
} | ||
else { | ||
for (let subSubIndex = 0, subSubMax = propSubValue.length, propSubSubValue : typeof propSubValue[number]; subSubIndex < subSubMax; subSubIndex++) { | ||
propSubSubValue = propSubValue[subSubIndex]; | ||
if ((subSubIndex >= 1) && (subSubIndex === (subSubMax - 1)) && (propSubSubValue === '!important')) { | ||
hasImportant = true; | ||
} | ||
else { | ||
if ((subIndex >= 1) && (subSubIndex === 0)) result += ', '; // comma separated values | ||
if (subSubIndex >= 1) result += ' '; // space separated values | ||
result += renderSimpleValue(propSubSubValue); | ||
} // if | ||
} // for | ||
} // if | ||
} // for | ||
if (hasImportant) result += ' !important'; | ||
return result; | ||
} // if | ||
}; |
687728
18021
Updated@cssfn/css-selectors@^2.0.10