mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
feat(core): css-what parser for CSS selectors + support for :not(), :is(), and :where() Level 4 and ~ (#10514)
This commit is contained in:
committed by
GitHub
parent
88a047254b
commit
2fb4f23670
@@ -612,161 +612,3 @@ export function parseBackground(text: string, start = 0): Parsed<Background> {
|
||||
|
||||
return { start, end, value };
|
||||
}
|
||||
|
||||
// Selectors
|
||||
|
||||
export type Combinator = '+' | '~' | '>' | ' ';
|
||||
|
||||
export interface UniversalSelector {
|
||||
type: '*';
|
||||
}
|
||||
export interface TypeSelector {
|
||||
type: '';
|
||||
identifier: string;
|
||||
}
|
||||
export interface ClassSelector {
|
||||
type: '.';
|
||||
identifier: string;
|
||||
}
|
||||
export interface IdSelector {
|
||||
type: '#';
|
||||
identifier: string;
|
||||
}
|
||||
export interface PseudoClassSelector {
|
||||
type: ':';
|
||||
identifier: string;
|
||||
}
|
||||
export type AttributeSelectorTest = '=' | '^=' | '$=' | '*=' | '~=' | '|=';
|
||||
export interface AttributeSelector {
|
||||
type: '[]';
|
||||
property: string;
|
||||
test?: AttributeSelectorTest;
|
||||
value?: string;
|
||||
}
|
||||
|
||||
export type SimpleSelector = UniversalSelector | TypeSelector | ClassSelector | IdSelector | PseudoClassSelector | AttributeSelector;
|
||||
export type SimpleSelectorSequence = SimpleSelector[];
|
||||
export type SelectorCombinatorPair = [SimpleSelectorSequence, Combinator];
|
||||
export type Selector = SelectorCombinatorPair[];
|
||||
|
||||
const universalSelectorRegEx = /\*/gy;
|
||||
export function parseUniversalSelector(text: string, start = 0): Parsed<UniversalSelector> {
|
||||
universalSelectorRegEx.lastIndex = start;
|
||||
const result = universalSelectorRegEx.exec(text);
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
const end = universalSelectorRegEx.lastIndex;
|
||||
|
||||
return { start, end, value: { type: '*' } };
|
||||
}
|
||||
|
||||
const simpleIdentifierSelectorRegEx = /(#|\.|:|\b)((?:[\w_-]|\\.)(?:[\w\d_-]|\\.)*)/guy;
|
||||
const unicodeEscapeRegEx = /\\([0-9a-fA-F]{1,5}\s|[0-9a-fA-F]{6})/g;
|
||||
export function parseSimpleIdentifierSelector(text: string, start = 0): Parsed<TypeSelector | ClassSelector | IdSelector | PseudoClassSelector> {
|
||||
simpleIdentifierSelectorRegEx.lastIndex = start;
|
||||
const result = simpleIdentifierSelectorRegEx.exec(text.replace(unicodeEscapeRegEx, (_, c) => '\\' + String.fromCodePoint(parseInt(c.trim(), 16))));
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
const end = simpleIdentifierSelectorRegEx.lastIndex;
|
||||
const type = <'#' | '.' | ':' | ''>result[1];
|
||||
const identifier: string = result[2].replace(/\\/g, '');
|
||||
const value = <TypeSelector | ClassSelector | IdSelector | PseudoClassSelector>{ type, identifier };
|
||||
|
||||
return { start, end, value };
|
||||
}
|
||||
|
||||
const attributeSelectorRegEx = /\[\s*([_\-\w][_\-\w\d]*)\s*(?:(=|\^=|\$=|\*=|\~=|\|=)\s*(?:([_\-\w][_\-\w\d]*)|"((?:[^\\"]|\\(?:"|n|r|f|\\|0-9a-f))*)"|'((?:[^\\']|\\(?:'|n|r|f|\\|0-9a-f))*)')\s*)?\]/gy;
|
||||
export function parseAttributeSelector(text: string, start: number): Parsed<AttributeSelector> {
|
||||
attributeSelectorRegEx.lastIndex = start;
|
||||
const result = attributeSelectorRegEx.exec(text);
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
const end = attributeSelectorRegEx.lastIndex;
|
||||
const property = result[1];
|
||||
if (result[2]) {
|
||||
const test = <AttributeSelectorTest>result[2];
|
||||
const value = result[3] || result[4] || result[5];
|
||||
|
||||
return { start, end, value: { type: '[]', property, test, value } };
|
||||
}
|
||||
|
||||
return { start, end, value: { type: '[]', property } };
|
||||
}
|
||||
|
||||
export function parseSimpleSelector(text: string, start = 0): Parsed<SimpleSelector> {
|
||||
return parseUniversalSelector(text, start) || parseSimpleIdentifierSelector(text, start) || parseAttributeSelector(text, start);
|
||||
}
|
||||
|
||||
export function parseSimpleSelectorSequence(text: string, start: number): Parsed<SimpleSelector[]> {
|
||||
let simpleSelector = parseSimpleSelector(text, start);
|
||||
if (!simpleSelector) {
|
||||
return null;
|
||||
}
|
||||
let end = simpleSelector.end;
|
||||
const value = <SimpleSelectorSequence>[];
|
||||
while (simpleSelector) {
|
||||
value.push(simpleSelector.value);
|
||||
end = simpleSelector.end;
|
||||
simpleSelector = parseSimpleSelector(text, end);
|
||||
}
|
||||
|
||||
return { start, end, value };
|
||||
}
|
||||
|
||||
const combinatorRegEx = /\s*([+~>])?\s*/gy;
|
||||
export function parseCombinator(text: string, start = 0): Parsed<Combinator> {
|
||||
combinatorRegEx.lastIndex = start;
|
||||
const result = combinatorRegEx.exec(text);
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
const end = combinatorRegEx.lastIndex;
|
||||
const value = <Combinator>result[1] || ' ';
|
||||
|
||||
return { start, end, value };
|
||||
}
|
||||
|
||||
const whiteSpaceRegEx = /\s*/gy;
|
||||
export function parseSelector(text: string, start = 0): Parsed<Selector> {
|
||||
let end = start;
|
||||
whiteSpaceRegEx.lastIndex = end;
|
||||
const leadingWhiteSpace = whiteSpaceRegEx.exec(text);
|
||||
if (leadingWhiteSpace) {
|
||||
end = whiteSpaceRegEx.lastIndex;
|
||||
}
|
||||
const value = <Selector>[];
|
||||
let combinator: Parsed<Combinator>;
|
||||
let expectSimpleSelector = true; // Must have at least one
|
||||
let pair: SelectorCombinatorPair;
|
||||
do {
|
||||
const simpleSelectorSequence = parseSimpleSelectorSequence(text, end);
|
||||
if (!simpleSelectorSequence) {
|
||||
if (expectSimpleSelector) {
|
||||
return null;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
end = simpleSelectorSequence.end;
|
||||
if (combinator) {
|
||||
// This logic looks weird; this `if` statement would occur on the next LOOP, so it effects the prior `pair`
|
||||
// variable which is already pushed into the `value` array is going to have its `undefined` set to this
|
||||
// value before the following statement creates a new `pair` memory variable.
|
||||
// noinspection JSUnusedAssignment
|
||||
pair[1] = combinator.value;
|
||||
}
|
||||
pair = [simpleSelectorSequence.value, undefined];
|
||||
value.push(pair);
|
||||
|
||||
combinator = parseCombinator(text, end);
|
||||
if (combinator) {
|
||||
end = combinator.end;
|
||||
}
|
||||
expectSimpleSelector = combinator && combinator.value !== ' '; // Simple selector must follow non trailing white space combinator
|
||||
} while (combinator);
|
||||
|
||||
return { start, end, value };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user