@cssfn/css-selectors
Advanced tools
Comparing version 2.0.9 to 2.0.10
@@ -700,28 +700,47 @@ const whitespaceList = [' ', '\n', '\r', '\t', '\f', '\v']; | ||
}; | ||
const convertOptionalSelectorEntryToString = (optionalSelectorEntry) => { | ||
if (!isNotEmptySelectorEntry(optionalSelectorEntry)) | ||
return ''; // nullish => empty string | ||
// SimpleSelector: | ||
if (isSimpleSelector(optionalSelectorEntry)) { | ||
const [selectorToken, selectorName, selectorParams,] = optionalSelectorEntry; | ||
if (selectorToken === '[') { // AttrSelectorToken | ||
return selectorParamsToString(selectorParams); | ||
} | ||
else { | ||
return `${selectorToken}${selectorName ?? ''}${(selectorParams === undefined) ? '' : selectorParamsToString(selectorParams)}`; | ||
export const selectorToString = (selector) => { | ||
const result = []; | ||
// for (const selectorEntry of selector) { // inefficient : triggering too many garbage collector of creating & destroying `const selectorEntry` | ||
for (let selectorEntryIndex = 0, maxSelectorEntryIndex = selector.length, selectorEntry; selectorEntryIndex < maxSelectorEntryIndex; selectorEntryIndex++) { | ||
selectorEntry = selector[selectorEntryIndex]; | ||
// conditions: | ||
if (!isNotEmptySelectorEntry(selectorEntry)) | ||
continue; // falsy selectorEntry => ignore | ||
// render: | ||
// SimpleSelector: | ||
if (isSimpleSelector(selectorEntry)) { | ||
const [selectorToken, selectorName, selectorParams,] = selectorEntry; | ||
if (selectorToken === '[') { // AttrSelectorToken | ||
result.push(selectorParamsToString(selectorParams)); | ||
} | ||
else { | ||
result.push(`${selectorToken}${selectorName ?? ''}${(selectorParams === undefined) ? '' : selectorParamsToString(selectorParams)}`); | ||
} // if | ||
continue; // handled => continue to next loop | ||
} // if | ||
} // if | ||
// Combinator: | ||
return optionalSelectorEntry; | ||
// Combinator: | ||
result.push(selectorEntry); | ||
} // for | ||
switch (result.length) { | ||
case 1: return result[0]; | ||
case 0: return ''; | ||
default: return result.join(''); // merge (SimpleSelector|Combinator)+ without altering any space | ||
} // switch | ||
}; | ||
export const selectorToString = (selector) => { | ||
return (selector | ||
.map(convertOptionalSelectorEntryToString) | ||
.join('') // merge (SimpleSelector|Combinator)+ | ||
); | ||
}; | ||
export const selectorsToString = (selectors) => { | ||
return (convertSelectorGroupToPureSelectorGroup(selectors) // remove empty Selector(s) in SelectorGroup, we don't want to join some rendered empty string '' => `.boo , , #foo` | ||
.map(selectorToString) | ||
.join(', ')); | ||
const result = []; | ||
// for (const selector of selectors) { // inefficient : triggering too many garbage collector of creating & destroying `const selector` | ||
for (let selectorIndex = 0, maxSelectorIndex = selectors.length, selector; selectorIndex < maxSelectorIndex; selectorIndex++) { | ||
selector = selectors[selectorIndex]; | ||
// conditions: | ||
if (!isNotEmptySelector(selector)) | ||
continue; // falsy or empty selector => ignore | ||
// render: | ||
result.push(selectorToString(selector)); | ||
} // for | ||
switch (result.length) { | ||
case 1: return result[0]; | ||
case 0: return ''; | ||
default: return result.join(', '); | ||
} // switch | ||
}; | ||
@@ -790,22 +809,65 @@ function convertOptionalSelectorEntryToSelectorWithReplacement(optionalSelectorEntry) { | ||
}; | ||
const isNotContainPseudoElement = (selector) => selector.every(isNotPseudoElementSelector); | ||
const isContainPseudoElement = (selector) => selector.some(isPseudoElementSelector); | ||
export const groupSelectors = (selectors, options = defaultGroupSelectorOptions) => { | ||
if (!isNotEmptySelectors(selectors)) | ||
return pureSelectorGroup(selector( | ||
if (!selectors || (selectors === true)) | ||
return pureSelectorGroup(createSelector( | ||
/* an empty Selector */ | ||
)); // empty selectors => nothing to group => return a SelectorGroup with an empty Selector | ||
const pureSelectors = convertSelectorGroupToPureSelectorGroup(selectors).map(convertSelectorToPureSelector); | ||
const selectorsWithoutPseudoElm = pureSelectors.filter(isNotContainPseudoElement); // not contain ::pseudo-element | ||
const selectorsWithPseudoElm = pureSelectors.filter(isContainPseudoElement); // is contain ::pseudo-element | ||
if (!isNotEmptySelectors(selectorsWithoutPseudoElm)) | ||
return pureSelectorGroup(selector( | ||
const selectorsWithPseudoElm = []; | ||
const selectorsWithoutPseudoElm = []; | ||
let selectorEntries = undefined; | ||
let hasPseudoElement; | ||
let selectorIndex; | ||
let maxSelectorIndex; | ||
let selector; | ||
let selectorEntryIndex; | ||
let maxSelectorEntryIndex; | ||
let selectorEntry; | ||
// for (const selector of selectors) { // inefficient : triggering too many garbage collector of creating & destroying `const selector` | ||
for (selectorIndex = 0, maxSelectorIndex = selectors.length; selectorIndex < maxSelectorIndex; selectorIndex++) { | ||
selector = selectors[selectorIndex]; | ||
// conditions: | ||
// if (!isNotEmptySelector(selector)) continue; // falsy or empty selector => ignore // inefficient : intrinsically call `isNotEmptySelectorEntry` | ||
if (!selector || (selector === true)) | ||
continue; // falsy selector => ignore | ||
if (!selectorEntries) | ||
selectorEntries = []; // create a new array if not was collected in previous loop | ||
hasPseudoElement = false; | ||
// for (const selectorEntry of selector) { // inefficient : triggering too many garbage collector of creating & destroying `const selectorEntry` | ||
for (selectorEntryIndex = 0, maxSelectorEntryIndex = selector.length; selectorEntryIndex < maxSelectorEntryIndex; selectorEntryIndex++) { | ||
selectorEntry = selector[selectorEntryIndex]; | ||
// conditions: | ||
if (!isNotEmptySelectorEntry(selectorEntry)) | ||
continue; // falsy selectorEntry => ignore | ||
// collect: | ||
selectorEntries.push(selectorEntry); | ||
// tests: | ||
if (!hasPseudoElement && isPseudoElementSelector(selectorEntry)) | ||
hasPseudoElement = true; | ||
} // for | ||
// collect: | ||
if (selectorEntries.length) { | ||
if (hasPseudoElement) { | ||
selectorsWithPseudoElm.push(selectorEntries); | ||
} | ||
else { | ||
selectorsWithoutPseudoElm.push(selectorEntries); | ||
} // if | ||
selectorEntries = undefined; // mark as collected, so at the beginning of the next loop will create a new array | ||
} // if | ||
} // for | ||
if (!selectorsWithoutPseudoElm.length) | ||
return pureSelectorGroup(createSelector( | ||
/* an empty Selector */ | ||
), ...selectorsWithPseudoElm); // empty selectors => nothing to group => return a SelectorGroup with an empty Selector + additional pseudoElm(s) (if any) | ||
const { selectorName: targetSelectorName = defaultGroupSelectorOptions.selectorName, cancelGroupIfSingular = defaultGroupSelectorOptions.cancelGroupIfSingular, } = options; | ||
return pureSelectorGroup(((cancelGroupIfSingular && (selectorsWithoutPseudoElm.length < 2)) | ||
return pureSelectorGroup(((cancelGroupIfSingular && (selectorsWithoutPseudoElm.length <= 1)) | ||
? | ||
selectorsWithoutPseudoElm?.[0] | ||
// singular & no grouping is needed: | ||
// if singular => take it || if zero => undefined => fallback to an empty selector | ||
selectorsWithoutPseudoElm?.[0] ?? createSelector( | ||
/* an empty Selector */ | ||
) | ||
: | ||
selector(pseudoClassSelector(targetSelectorName, selectorsWithoutPseudoElm))), ...selectorsWithPseudoElm); | ||
// plural: | ||
createSelector(pseudoClassSelector(targetSelectorName, selectorsWithoutPseudoElm))), ...selectorsWithPseudoElm); | ||
}; | ||
@@ -819,55 +881,67 @@ export const groupSelector = (selector, options = defaultGroupSelectorOptions) => { | ||
export const ungroupSelector = (selector, options = defaultUngroupSelectorOptions) => { | ||
if (!isNotEmptySelector(selector)) | ||
if (!selector || (selector === true)) | ||
return pureSelectorGroup( | ||
/* an empty SelectorGroup */ | ||
); // empty selector => nothing to ungroup => return an empty SelectorGroup | ||
const pureSelector = convertSelectorToPureSelector(selector); | ||
if (pureSelector.length === 1) { | ||
const selectorEntry = pureSelector[0]; // get the only one SelectorEntry | ||
if (isPseudoClassSelector(selectorEntry)) { | ||
const { selectorName: targetSelectorName = defaultUngroupSelectorOptions.selectorName, } = options; | ||
const [ | ||
/* | ||
selector tokens: | ||
'&' = parent selector | ||
'*' = universal selector | ||
'[' = attribute selector | ||
'' = element selector | ||
'#' = ID selector | ||
'.' = class selector | ||
':' = pseudo class selector | ||
'::' = pseudo element selector | ||
*/ | ||
// selectorToken | ||
, | ||
/* | ||
selector name: | ||
string = the name of [element, ID, class, pseudo class, pseudo element] selector | ||
*/ | ||
selectorName, | ||
/* | ||
selector parameter(s): | ||
string = the parameter of pseudo class selector, eg: nth-child(2n+3) => '2n+3' | ||
array = [name, operator, value, options] of attribute selector, eg: [data-msg*="you & me" i] => ['data-msg', '*=', 'you & me', 'i'] | ||
SelectorGroup = nested selector(s) of pseudo class [:is(...), :where(...), :not(...)] | ||
*/ | ||
selectorParams,] = selectorEntry; | ||
if (targetSelectorName.includes(selectorName) | ||
&& | ||
selectorParams && isSelectors(selectorParams)) { | ||
return ungroupSelectors(selectorParams, options); // recursively ungroup sub-selectors | ||
} // if | ||
let theOnlySelectorEntry = undefined; | ||
// for (const selectorEntry of selector) { // inefficient : triggering too many garbage collector of creating & destroying `const selectorEntry` | ||
for (let selectorEntryIndex = 0, maxSelectorEntryIndex = selector.length, selectorEntry; selectorEntryIndex < maxSelectorEntryIndex; selectorEntryIndex++) { | ||
selectorEntry = selector[selectorEntryIndex]; | ||
// conditions: | ||
if (!isNotEmptySelectorEntry(selectorEntry)) | ||
continue; // falsy selectorEntry => ignore | ||
// collect: | ||
if (theOnlySelectorEntry !== undefined) { // multiple selectorEntries detected => unable to ungroup | ||
return pureSelectorGroup(convertSelectorToPureSelector(selector)); | ||
} // if | ||
theOnlySelectorEntry = selectorEntry; | ||
} // for | ||
if (theOnlySelectorEntry && isPseudoClassSelector(theOnlySelectorEntry)) { | ||
const { selectorName: targetSelectorName = defaultUngroupSelectorOptions.selectorName, } = options; | ||
const [ | ||
/* | ||
selector tokens: | ||
'&' = parent selector | ||
'*' = universal selector | ||
'[' = attribute selector | ||
'' = element selector | ||
'#' = ID selector | ||
'.' = class selector | ||
':' = pseudo class selector | ||
'::' = pseudo element selector | ||
*/ | ||
// selectorToken | ||
, | ||
/* | ||
selector name: | ||
string = the name of [element, ID, class, pseudo class, pseudo element] selector | ||
*/ | ||
selectorName, | ||
/* | ||
selector parameter(s): | ||
string = the parameter of pseudo class selector, eg: nth-child(2n+3) => '2n+3' | ||
array = [name, operator, value, options] of attribute selector, eg: [data-msg*="you & me" i] => ['data-msg', '*=', 'you & me', 'i'] | ||
SelectorGroup = nested selector(s) of pseudo class [:is(...), :where(...), :not(...)] | ||
*/ | ||
selectorParams,] = theOnlySelectorEntry; | ||
if (targetSelectorName.includes(selectorName) | ||
&& | ||
selectorParams && isSelectors(selectorParams)) { | ||
return ungroupSelectors(selectorParams, options); // recursively ungroup sub-selectors | ||
} // if | ||
} // if | ||
return pureSelectorGroup(pureSelector); | ||
// unable to ungroup: | ||
return pureSelectorGroup(convertSelectorToPureSelector(selector)); | ||
}; | ||
function ungroupSelectorWithOptions(selector) { | ||
return ungroupSelector(selector, this); | ||
} | ||
export const ungroupSelectors = (selectors, options = defaultUngroupSelectorOptions) => { | ||
if (!isNotEmptySelectors(selectors)) | ||
if (!selectors || (selectors === true)) | ||
return pureSelectorGroup( | ||
/* an empty SelectorGroup */ | ||
); // empty selectors => nothing to ungroup => return an empty SelectorGroup | ||
return selectors.flatMap(ungroupSelectorWithOptions.bind(options)); | ||
const result = []; | ||
// for (const selector of selectors) { // inefficient : triggering too many garbage collector of creating & destroying `const selector` | ||
for (let selectorIndex = 0, maxSelectorIndex = selectors.length; selectorIndex < maxSelectorIndex; selectorIndex++) { | ||
result.push(...ungroupSelector(selectors[selectorIndex], options)); | ||
} // for | ||
return result; | ||
}; | ||
@@ -874,0 +948,0 @@ const reduceGetSpecificity = (accum, simpleSelector) => { |
{ | ||
"name": "@cssfn/css-selectors", | ||
"version": "2.0.9", | ||
"version": "2.0.10", | ||
"description": "Manipulates css selector - parse, transform, calculate specificity, and more.", | ||
@@ -34,3 +34,3 @@ "keywords": [ | ||
}, | ||
"gitHead": "cb9755c614f545159f304f7db3753478955de44d" | ||
"gitHead": "7848ea5c0049e71d98ad03325a18f2569e5e1056" | ||
} |
@@ -837,41 +837,83 @@ // cssfn: | ||
}; | ||
const convertOptionalSelectorEntryToString = (optionalSelectorEntry: OptionalOrBoolean<SelectorEntry>): string => { | ||
if (!isNotEmptySelectorEntry(optionalSelectorEntry)) return ''; // nullish => empty string | ||
export const selectorToString = (selector: Selector): string => { | ||
const result : string[] = []; | ||
// SimpleSelector: | ||
if (isSimpleSelector(optionalSelectorEntry)) { | ||
const [ | ||
selectorToken, | ||
selectorName, | ||
selectorParams, | ||
] = optionalSelectorEntry; | ||
// for (const selectorEntry of selector) { // inefficient : triggering too many garbage collector of creating & destroying `const selectorEntry` | ||
for (let selectorEntryIndex = 0, maxSelectorEntryIndex = selector.length, selectorEntry : OptionalOrBoolean<SelectorEntry>; selectorEntryIndex < maxSelectorEntryIndex; selectorEntryIndex++) { | ||
selectorEntry = selector[selectorEntryIndex]; | ||
if (selectorToken === '[') { // AttrSelectorToken | ||
return selectorParamsToString(selectorParams); | ||
} | ||
else { | ||
return `${selectorToken}${selectorName ?? ''}${(selectorParams === undefined) ? '' : selectorParamsToString(selectorParams)}`; | ||
// conditions: | ||
if (!isNotEmptySelectorEntry(selectorEntry)) continue; // falsy selectorEntry => ignore | ||
// render: | ||
// SimpleSelector: | ||
if (isSimpleSelector(selectorEntry)) { | ||
const [ | ||
selectorToken, | ||
selectorName, | ||
selectorParams, | ||
] = selectorEntry; | ||
if (selectorToken === '[') { // AttrSelectorToken | ||
result.push( | ||
selectorParamsToString(selectorParams) | ||
); | ||
} | ||
else { | ||
result.push( | ||
`${selectorToken}${selectorName ?? ''}${(selectorParams === undefined) ? '' : selectorParamsToString(selectorParams)}` | ||
); | ||
} // if | ||
continue; // handled => continue to next loop | ||
} // if | ||
} // if | ||
// Combinator: | ||
result.push(selectorEntry); | ||
} // for | ||
// Combinator: | ||
return optionalSelectorEntry; | ||
} | ||
export const selectorToString = (selector: Selector): string => { | ||
return ( | ||
selector | ||
.map(convertOptionalSelectorEntryToString) | ||
.join('') // merge (SimpleSelector|Combinator)+ | ||
); | ||
switch (result.length) { | ||
case 1 : return result[0]; | ||
case 0 : return ''; | ||
default : return result.join(''); // merge (SimpleSelector|Combinator)+ without altering any space | ||
} // switch | ||
}; | ||
export const selectorsToString = (selectors: SelectorGroup): string => { | ||
return ( | ||
convertSelectorGroupToPureSelectorGroup(selectors) // remove empty Selector(s) in SelectorGroup, we don't want to join some rendered empty string '' => `.boo , , #foo` | ||
.map(selectorToString) | ||
.join(', ') | ||
); | ||
const result : string[] = []; | ||
// for (const selector of selectors) { // inefficient : triggering too many garbage collector of creating & destroying `const selector` | ||
for (let selectorIndex = 0, maxSelectorIndex = selectors.length, selector : OptionalOrBoolean<Selector>; selectorIndex < maxSelectorIndex; selectorIndex++) { | ||
selector = selectors[selectorIndex]; | ||
// conditions: | ||
if (!isNotEmptySelector(selector)) continue; // falsy or empty selector => ignore | ||
// render: | ||
result.push( | ||
selectorToString(selector) | ||
); | ||
} // for | ||
switch (result.length) { | ||
case 1 : return result[0]; | ||
case 0 : return ''; | ||
default : return result.join(', '); | ||
} // switch | ||
}; | ||
@@ -972,7 +1014,5 @@ | ||
}; | ||
const isNotContainPseudoElement = (selector: PureSelector) => selector.every(isNotPseudoElementSelector); | ||
const isContainPseudoElement = (selector: PureSelector) => selector.some(isPseudoElementSelector); | ||
export const groupSelectors = (selectors: OptionalOrBoolean<SelectorGroup>, options: GroupSelectorOptions = defaultGroupSelectorOptions): PureSelectorGroup & [Selector, ...Selector[]] => { | ||
if (!isNotEmptySelectors(selectors)) return pureSelectorGroup( | ||
selector( | ||
if (!selectors || (selectors === true)) return pureSelectorGroup( | ||
createSelector( | ||
/* an empty Selector */ | ||
@@ -984,10 +1024,68 @@ ), | ||
const pureSelectors : PureSelector[] = convertSelectorGroupToPureSelectorGroup(selectors).map(convertSelectorToPureSelector); | ||
const selectorsWithoutPseudoElm : PureSelector[] = pureSelectors.filter(isNotContainPseudoElement); // not contain ::pseudo-element | ||
const selectorsWithPseudoElm : PureSelector[] = pureSelectors.filter(isContainPseudoElement); // is contain ::pseudo-element | ||
const selectorsWithPseudoElm : PureSelector[] = []; | ||
const selectorsWithoutPseudoElm : PureSelector[] = []; | ||
let selectorEntries : SelectorEntry[]|undefined = undefined; | ||
let hasPseudoElement : boolean; | ||
let selectorIndex : number | ||
let maxSelectorIndex : number | ||
let selector : OptionalOrBoolean<Selector> | ||
if (!isNotEmptySelectors(selectorsWithoutPseudoElm)) return pureSelectorGroup( | ||
selector( | ||
let selectorEntryIndex : number | ||
let maxSelectorEntryIndex : number | ||
let selectorEntry : OptionalOrBoolean<SelectorEntry> | ||
// for (const selector of selectors) { // inefficient : triggering too many garbage collector of creating & destroying `const selector` | ||
for (selectorIndex = 0, maxSelectorIndex = selectors.length; selectorIndex < maxSelectorIndex; selectorIndex++) { | ||
selector = selectors[selectorIndex]; | ||
// conditions: | ||
// if (!isNotEmptySelector(selector)) continue; // falsy or empty selector => ignore // inefficient : intrinsically call `isNotEmptySelectorEntry` | ||
if (!selector || (selector === true)) continue; // falsy selector => ignore | ||
if (!selectorEntries) selectorEntries = []; // create a new array if not was collected in previous loop | ||
hasPseudoElement = false; | ||
// for (const selectorEntry of selector) { // inefficient : triggering too many garbage collector of creating & destroying `const selectorEntry` | ||
for (selectorEntryIndex = 0, maxSelectorEntryIndex = selector.length; selectorEntryIndex < maxSelectorEntryIndex; selectorEntryIndex++) { | ||
selectorEntry = selector[selectorEntryIndex]; | ||
// conditions: | ||
if (!isNotEmptySelectorEntry(selectorEntry)) continue; // falsy selectorEntry => ignore | ||
// collect: | ||
selectorEntries.push(selectorEntry); | ||
// tests: | ||
if (!hasPseudoElement && isPseudoElementSelector(selectorEntry)) hasPseudoElement = true; | ||
} // for | ||
// collect: | ||
if (selectorEntries.length) { | ||
if (hasPseudoElement) { | ||
selectorsWithPseudoElm.push(selectorEntries as PureSelector); | ||
} | ||
else { | ||
selectorsWithoutPseudoElm.push(selectorEntries as PureSelector); | ||
} // if | ||
selectorEntries = undefined; // mark as collected, so at the beginning of the next loop will create a new array | ||
} // if | ||
} // for | ||
if (!selectorsWithoutPseudoElm.length) return pureSelectorGroup( | ||
createSelector( | ||
/* an empty Selector */ | ||
@@ -1010,7 +1108,12 @@ ), | ||
( | ||
(cancelGroupIfSingular && (selectorsWithoutPseudoElm.length < 2)) | ||
(cancelGroupIfSingular && (selectorsWithoutPseudoElm.length <= 1)) | ||
? | ||
selectorsWithoutPseudoElm?.[0] | ||
// singular & no grouping is needed: | ||
// if singular => take it || if zero => undefined => fallback to an empty selector | ||
selectorsWithoutPseudoElm?.[0] ?? createSelector( | ||
/* an empty Selector */ | ||
) | ||
: | ||
selector( | ||
// plural: | ||
createSelector( | ||
pseudoClassSelector(targetSelectorName, selectorsWithoutPseudoElm), | ||
@@ -1035,3 +1138,3 @@ ) | ||
export const ungroupSelector = (selector : OptionalOrBoolean<Selector> , options: UngroupSelectorOptions = defaultUngroupSelectorOptions): PureSelectorGroup => { | ||
if (!isNotEmptySelector(selector)) return pureSelectorGroup( | ||
if (!selector || (selector === true)) return pureSelectorGroup( | ||
/* an empty SelectorGroup */ | ||
@@ -1042,48 +1145,67 @@ ); // empty selector => nothing to ungroup => return an empty SelectorGroup | ||
const pureSelector = convertSelectorToPureSelector(selector); | ||
if (pureSelector.length === 1) { | ||
const selectorEntry = pureSelector[0]; // get the only one SelectorEntry | ||
if (isPseudoClassSelector(selectorEntry)) { | ||
const { | ||
selectorName : targetSelectorName = defaultUngroupSelectorOptions.selectorName, | ||
} = options; | ||
let theOnlySelectorEntry : SelectorEntry|undefined = undefined; | ||
// for (const selectorEntry of selector) { // inefficient : triggering too many garbage collector of creating & destroying `const selectorEntry` | ||
for (let selectorEntryIndex = 0, maxSelectorEntryIndex = selector.length, selectorEntry : OptionalOrBoolean<SelectorEntry>; selectorEntryIndex < maxSelectorEntryIndex; selectorEntryIndex++) { | ||
selectorEntry = selector[selectorEntryIndex]; | ||
// conditions: | ||
if (!isNotEmptySelectorEntry(selectorEntry)) continue; // falsy selectorEntry => ignore | ||
// collect: | ||
if (theOnlySelectorEntry !== undefined) { // multiple selectorEntries detected => unable to ungroup | ||
return pureSelectorGroup( | ||
convertSelectorToPureSelector(selector), // no changes - just cleaned up | ||
); | ||
} // if | ||
theOnlySelectorEntry = selectorEntry; | ||
} // for | ||
if (theOnlySelectorEntry && isPseudoClassSelector(theOnlySelectorEntry)) { | ||
const { | ||
selectorName : targetSelectorName = defaultUngroupSelectorOptions.selectorName, | ||
} = options; | ||
const [ | ||
/* | ||
selector tokens: | ||
'&' = parent selector | ||
'*' = universal selector | ||
'[' = attribute selector | ||
'' = element selector | ||
'#' = ID selector | ||
'.' = class selector | ||
':' = pseudo class selector | ||
'::' = pseudo element selector | ||
*/ | ||
// selectorToken | ||
, | ||
/* | ||
selector name: | ||
string = the name of [element, ID, class, pseudo class, pseudo element] selector | ||
*/ | ||
selectorName, | ||
const [ | ||
/* | ||
selector tokens: | ||
'&' = parent selector | ||
'*' = universal selector | ||
'[' = attribute selector | ||
'' = element selector | ||
'#' = ID selector | ||
'.' = class selector | ||
':' = pseudo class selector | ||
'::' = pseudo element selector | ||
*/ | ||
// selectorToken | ||
, | ||
/* | ||
selector name: | ||
string = the name of [element, ID, class, pseudo class, pseudo element] selector | ||
*/ | ||
selectorName, | ||
/* | ||
selector parameter(s): | ||
string = the parameter of pseudo class selector, eg: nth-child(2n+3) => '2n+3' | ||
array = [name, operator, value, options] of attribute selector, eg: [data-msg*="you & me" i] => ['data-msg', '*=', 'you & me', 'i'] | ||
SelectorGroup = nested selector(s) of pseudo class [:is(...), :where(...), :not(...)] | ||
*/ | ||
selectorParams, | ||
] = selectorEntry; | ||
if ( | ||
targetSelectorName.includes(selectorName) | ||
&& | ||
selectorParams && isSelectors(selectorParams) | ||
) { | ||
return ungroupSelectors(selectorParams, options); // recursively ungroup sub-selectors | ||
} // if | ||
/* | ||
selector parameter(s): | ||
string = the parameter of pseudo class selector, eg: nth-child(2n+3) => '2n+3' | ||
array = [name, operator, value, options] of attribute selector, eg: [data-msg*="you & me" i] => ['data-msg', '*=', 'you & me', 'i'] | ||
SelectorGroup = nested selector(s) of pseudo class [:is(...), :where(...), :not(...)] | ||
*/ | ||
selectorParams, | ||
] = theOnlySelectorEntry; | ||
if ( | ||
targetSelectorName.includes(selectorName) | ||
&& | ||
selectorParams && isSelectors(selectorParams) | ||
) { | ||
return ungroupSelectors(selectorParams, options); // recursively ungroup sub-selectors | ||
} // if | ||
@@ -1094,11 +1216,9 @@ } // if | ||
// unable to ungroup: | ||
return pureSelectorGroup( | ||
pureSelector, // no changes - just cleaned up | ||
convertSelectorToPureSelector(selector), // no changes - just cleaned up | ||
); | ||
} | ||
function ungroupSelectorWithOptions(this: UngroupSelectorOptions, selector : OptionalOrBoolean<Selector>) { | ||
return ungroupSelector(selector, this); | ||
} | ||
export const ungroupSelectors = (selectors: OptionalOrBoolean<SelectorGroup>, options: UngroupSelectorOptions = defaultUngroupSelectorOptions): PureSelectorGroup => { | ||
if (!isNotEmptySelectors(selectors)) return pureSelectorGroup( | ||
if (!selectors || (selectors === true)) return pureSelectorGroup( | ||
/* an empty SelectorGroup */ | ||
@@ -1109,3 +1229,14 @@ ); // empty selectors => nothing to ungroup => return an empty SelectorGroup | ||
return selectors.flatMap(ungroupSelectorWithOptions.bind(options)); | ||
const result : Selector[] = []; | ||
// for (const selector of selectors) { // inefficient : triggering too many garbage collector of creating & destroying `const selector` | ||
for (let selectorIndex = 0, maxSelectorIndex = selectors.length; selectorIndex < maxSelectorIndex; selectorIndex++) { | ||
result.push( | ||
...ungroupSelector(selectors[selectorIndex], options) | ||
); | ||
} // for | ||
return result as PureSelectorGroup; | ||
} | ||
@@ -1112,0 +1243,0 @@ |
366419
10003