@wix/zero-config-implementation 1.72.0 → 1.73.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{index-CdHu4-o-.js → index-K3lIO4FC.js} +11986 -11771
- package/dist/{index-CSi4SZpQ.js → index-NbI-_ozJ.js} +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/src/converters/to-editor-component.test.ts +88 -0
- package/src/converters/to-editor-component.ts +11 -0
- package/src/information-extractors/css/parse.test.ts +49 -1
- package/src/information-extractors/css/parse.ts +167 -3
- package/src/information-extractors/css/selector-matcher.ts +3 -1
- package/src/information-extractors/css/types.ts +11 -0
- package/src/information-extractors/react/extractors/css-properties.test.ts +294 -2
- package/src/information-extractors/react/extractors/css-properties.ts +246 -98
- package/src/information-extractors/react/extractors/index.ts +1 -0
- package/src/information-extractors/react/index.ts +1 -0
- package/src/manifest-pipeline.ts +3 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ComponentType } from 'react';
|
|
2
|
+
import { CssNode } from 'css-tree';
|
|
2
3
|
import { default as default_2 } from 'typescript';
|
|
3
4
|
import { EditorReactComponent } from '@wix/react-component-schema';
|
|
4
5
|
import { Program } from 'typescript';
|
|
@@ -419,6 +420,11 @@ export declare interface CSSParserAPI {
|
|
|
419
420
|
* @returns DOM-matchable selector string or null
|
|
420
421
|
*/
|
|
421
422
|
getDomSelector: (selector: string) => string | null;
|
|
423
|
+
/**
|
|
424
|
+
* Gets the selector specificity tuple as [ids, classes/attrs/pseudo-classes, elements/pseudo-elements].
|
|
425
|
+
* Returns null when the selector was not parsed.
|
|
426
|
+
*/
|
|
427
|
+
getSelectorSpecificity: (selector: string) => [number, number, number] | null;
|
|
422
428
|
/**
|
|
423
429
|
* Determines the CSS property type for a custom property based on how it is used.
|
|
424
430
|
* If all usages of varName are within the same CSS property, returns that property name.
|
|
@@ -444,6 +450,8 @@ export declare interface CssPropertiesData {
|
|
|
444
450
|
declare interface CSSProperty {
|
|
445
451
|
name: string;
|
|
446
452
|
value: string;
|
|
453
|
+
/** Parsed css-tree AST for the property's value. */
|
|
454
|
+
valueAst?: CssNode;
|
|
447
455
|
/** CSS variable names referenced via var() in this property's value */
|
|
448
456
|
varRefs?: string[];
|
|
449
457
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as t, D as e, E as o, I as n, N as c, P as l, R as i, a as p, V as E, b as m, c as x, d as f, e as d, f as u, h as C, i as I, j as k, k as y, l as A, m as D, n as P, o as R, p as h, q as B, r as M, s as T, t as b, w } from "./index-
|
|
1
|
+
import { B as t, D as e, E as o, I as n, N as c, P as l, R as i, a as p, V as E, b as m, c as x, d as f, e as d, f as u, h as C, i as I, j as k, k as y, l as A, m as D, n as P, o as R, p as h, q as B, r as M, s as T, t as b, w } from "./index-K3lIO4FC.js";
|
|
2
2
|
import "react";
|
|
3
3
|
export {
|
|
4
4
|
t as BaseError,
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"registry": "https://registry.npmjs.org/",
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
7
|
-
"version": "1.
|
|
7
|
+
"version": "1.73.0",
|
|
8
8
|
"description": "Core library for extracting component manifests from JS and CSS files",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"main": "dist/index.js",
|
|
@@ -82,5 +82,5 @@
|
|
|
82
82
|
]
|
|
83
83
|
}
|
|
84
84
|
},
|
|
85
|
-
"falconPackageHash": "
|
|
85
|
+
"falconPackageHash": "398dcee4762e04fdaa11211df36bfb4ac7e2ea328d412a68f2e363f8"
|
|
86
86
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { CSS_PROPERTIES } from '@wix/react-component-schema'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { parseCss } from '../information-extractors/css/parse'
|
|
4
|
+
import type { MatchedCssData } from '../information-extractors/css/types'
|
|
5
|
+
import type { ExtractedElement } from '../information-extractors/react'
|
|
6
|
+
import type { ComponentInfoWithCss } from '../manifest-pipeline'
|
|
7
|
+
import { toEditorReactComponent } from './to-editor-component'
|
|
8
|
+
|
|
9
|
+
function matcherDataFromCss(declarations: string): MatchedCssData {
|
|
10
|
+
const properties = parseCss(`.test { ${declarations} }`).getPropertiesForSelector('.test')
|
|
11
|
+
const customProperties: Record<string, string> = {}
|
|
12
|
+
|
|
13
|
+
for (const property of properties) {
|
|
14
|
+
if (property.name.startsWith('--')) {
|
|
15
|
+
customProperties[property.name] = property.value
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
matches: [
|
|
21
|
+
{
|
|
22
|
+
selector: '.test',
|
|
23
|
+
properties: properties.filter((property) => !property.name.startsWith('--')),
|
|
24
|
+
specificity: [0, 1, 0],
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
customProperties,
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function createElementWithDisplayMatcher(tag: string, matcherData: MatchedCssData): ExtractedElement {
|
|
32
|
+
const extractorData = new Map<string, unknown>()
|
|
33
|
+
extractorData.set('css-matcher', matcherData)
|
|
34
|
+
extractorData.set('css-properties', { relevant: [CSS_PROPERTIES.CSS_PROPERTY_TYPE.display] })
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
traceId: 'trace-root',
|
|
38
|
+
name: 'root',
|
|
39
|
+
tag,
|
|
40
|
+
attributes: {},
|
|
41
|
+
extractorData,
|
|
42
|
+
children: [],
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function createComponent(rootElement: ExtractedElement): ComponentInfoWithCss {
|
|
47
|
+
return {
|
|
48
|
+
componentName: 'DisplayComponent',
|
|
49
|
+
props: {},
|
|
50
|
+
elements: [rootElement],
|
|
51
|
+
propUsages: new Map(),
|
|
52
|
+
css: [],
|
|
53
|
+
varUsedByTraceId: new Map(),
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
describe('toEditorReactComponent display conversion', () => {
|
|
58
|
+
it('resolves local display fallback chains before emitting the manifest display value', () => {
|
|
59
|
+
const rootElement = createElementWithDisplayMatcher(
|
|
60
|
+
'span',
|
|
61
|
+
matcherDataFromCss(
|
|
62
|
+
'display: var(--component-display, inline); --component-display: var(--theme-display, inline-flex)',
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
const result = toEditorReactComponent(createComponent(rootElement))
|
|
67
|
+
expect(result.editorElement).toBeDefined()
|
|
68
|
+
|
|
69
|
+
expect(result.editorElement?.cssProperties?.display).toEqual({
|
|
70
|
+
display: {
|
|
71
|
+
displayValues: [CSS_PROPERTIES.DISPLAY_VALUE.none, 'inlineFlex'],
|
|
72
|
+
},
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('keeps using the tag default when no display declaration exists', () => {
|
|
77
|
+
const rootElement = createElementWithDisplayMatcher('span', matcherDataFromCss(''))
|
|
78
|
+
|
|
79
|
+
const result = toEditorReactComponent(createComponent(rootElement))
|
|
80
|
+
expect(result.editorElement).toBeDefined()
|
|
81
|
+
|
|
82
|
+
expect(result.editorElement?.cssProperties?.display).toEqual({
|
|
83
|
+
display: {
|
|
84
|
+
displayValues: [CSS_PROPERTIES.DISPLAY_VALUE.none, 'inline'],
|
|
85
|
+
},
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
})
|
|
@@ -436,6 +436,17 @@ function buildCssProperties(element: ExtractedElement | undefined): Record<strin
|
|
|
436
436
|
function buildDisplayProperty(element: ExtractedElement): CssPropertyItem {
|
|
437
437
|
const matcherData = element.extractorData.get('css-matcher') as MatchedCssData | undefined
|
|
438
438
|
const resolvedFromCss = matcherData ? resolveDisplayValue(matcherData) : undefined
|
|
439
|
+
const hasExplicitDisplayDeclaration =
|
|
440
|
+
matcherData?.matches.some((match) => match.properties.some((property) => property.name === 'display')) ?? false
|
|
441
|
+
|
|
442
|
+
if (hasExplicitDisplayDeclaration && resolvedFromCss === undefined) {
|
|
443
|
+
return {
|
|
444
|
+
display: {
|
|
445
|
+
displayValues: [DISPLAY_VALUE.none],
|
|
446
|
+
},
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
439
450
|
const currentValue = resolvedFromCss ?? getDefaultDisplayForTag(element.tag)
|
|
440
451
|
const currentEnumValue = toDisplayEnumValue(currentValue)
|
|
441
452
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { parseCss } from './parse'
|
|
2
|
+
import { parseCss, resolveCssPropertyValue } from './parse'
|
|
3
3
|
|
|
4
4
|
describe('parseCss — getStateClasses', () => {
|
|
5
5
|
it('extracts a custom-class modifier with no paired pseudo', () => {
|
|
@@ -120,3 +120,51 @@ describe('parseCss — getStateClasses', () => {
|
|
|
120
120
|
expect(stateClasses).toEqual([])
|
|
121
121
|
})
|
|
122
122
|
})
|
|
123
|
+
|
|
124
|
+
describe('resolveCssPropertyValue', () => {
|
|
125
|
+
it('uses the fallback when a referenced custom property is missing from the property-level map', () => {
|
|
126
|
+
const [displayProperty] = parseCss(`
|
|
127
|
+
.title {
|
|
128
|
+
display: var(--layout-display, inline);
|
|
129
|
+
}
|
|
130
|
+
`).getPropertiesForSelector('.title')
|
|
131
|
+
|
|
132
|
+
expect(resolveCssPropertyValue(displayProperty, {})).toBe('inline')
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('uses the outer fallback when a referenced custom property is known locally but resolves cyclically', () => {
|
|
136
|
+
const [displayProperty] = parseCss(`
|
|
137
|
+
.title {
|
|
138
|
+
display: var(--layout-display, inline);
|
|
139
|
+
}
|
|
140
|
+
`).getPropertiesForSelector('.title')
|
|
141
|
+
|
|
142
|
+
expect(resolveCssPropertyValue(displayProperty, { '--layout-display': 'var(--layout-display)' })).toBe('inline')
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('does not use an inner fallback from a cyclic custom property definition', () => {
|
|
146
|
+
const [displayProperty] = parseCss(`
|
|
147
|
+
.title {
|
|
148
|
+
display: var(--layout-display, inline);
|
|
149
|
+
}
|
|
150
|
+
`).getPropertiesForSelector('.title')
|
|
151
|
+
|
|
152
|
+
expect(resolveCssPropertyValue(displayProperty, { '--layout-display': 'var(--layout-display, inline-flex)' })).toBe(
|
|
153
|
+
'inline',
|
|
154
|
+
)
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('resolves a local custom property chain through its nested fallback when the missing variable is local to the chain', () => {
|
|
158
|
+
const [displayProperty] = parseCss(`
|
|
159
|
+
.title {
|
|
160
|
+
display: var(--component-display, inline);
|
|
161
|
+
}
|
|
162
|
+
`).getPropertiesForSelector('.title')
|
|
163
|
+
|
|
164
|
+
expect(
|
|
165
|
+
resolveCssPropertyValue(displayProperty, {
|
|
166
|
+
'--component-display': 'var(--theme-display, inline-flex)',
|
|
167
|
+
}),
|
|
168
|
+
).toBe('inline-flex')
|
|
169
|
+
})
|
|
170
|
+
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CSS_PROPERTIES } from '@wix/react-component-schema'
|
|
2
2
|
import { camelCase } from 'case-anything'
|
|
3
3
|
import { generate, lexer, parse, walk } from 'css-tree'
|
|
4
|
-
import type { Atrule, CssNode, Declaration, FunctionNode, Rule, Selector } from 'css-tree'
|
|
4
|
+
import type { Atrule, CssNode, Declaration, FunctionNode, Rule, Selector, Value } from 'css-tree'
|
|
5
5
|
import { stateNameFromModifier } from '../../utils/css-class'
|
|
6
6
|
import type { CSSParserAPI, CSSProperty, NativePseudoClass, StateClassRule } from './types'
|
|
7
7
|
|
|
@@ -70,6 +70,12 @@ export function parseCss(cssString: string): CSSParserAPI {
|
|
|
70
70
|
return buildDomSelector(parsed)
|
|
71
71
|
},
|
|
72
72
|
|
|
73
|
+
getSelectorSpecificity(selector: string): [number, number, number] | null {
|
|
74
|
+
const parsed = parsedSelectors.get(selector)
|
|
75
|
+
if (!parsed) return null
|
|
76
|
+
return getSelectorSpecificity(parsed)
|
|
77
|
+
},
|
|
78
|
+
|
|
73
79
|
getVarPropertyType(varName: string, defaultValue: string): string | undefined {
|
|
74
80
|
const usages = this.getVarUsages(varName)
|
|
75
81
|
if (usages.length === 0) return undefined
|
|
@@ -173,14 +179,133 @@ function extractProperty(declaration: Declaration, varUsagesByProperty: Map<stri
|
|
|
173
179
|
}
|
|
174
180
|
|
|
175
181
|
if (varRefs.length > 0) {
|
|
176
|
-
return { name, value, varRefs }
|
|
182
|
+
return { name, value, valueAst: declaration.value, varRefs }
|
|
177
183
|
}
|
|
178
|
-
return { name, value }
|
|
184
|
+
return { name, value, valueAst: declaration.value }
|
|
179
185
|
} catch {
|
|
180
186
|
return null
|
|
181
187
|
}
|
|
182
188
|
}
|
|
183
189
|
|
|
190
|
+
type ResolvedCssValue = { kind: 'resolved'; value: string } | { kind: 'unresolved' } | { kind: 'invalid' }
|
|
191
|
+
|
|
192
|
+
function resolvedCssValue(value: string): ResolvedCssValue {
|
|
193
|
+
return { kind: 'resolved', value }
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function unresolvedCssValue(): ResolvedCssValue {
|
|
197
|
+
return { kind: 'unresolved' }
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function invalidCssValue(): ResolvedCssValue {
|
|
201
|
+
return { kind: 'invalid' }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function resolveValueSequence(
|
|
205
|
+
valueNodes: CssNode[],
|
|
206
|
+
customProperties: Record<string, string>,
|
|
207
|
+
seenVariables: Set<string>,
|
|
208
|
+
): ResolvedCssValue {
|
|
209
|
+
const resolvedParts: string[] = []
|
|
210
|
+
|
|
211
|
+
for (const valueNode of valueNodes) {
|
|
212
|
+
const resolvedPart = resolveValueNode(valueNode, customProperties, seenVariables)
|
|
213
|
+
if (resolvedPart.kind !== 'resolved') return resolvedPart
|
|
214
|
+
if (resolvedPart.value.length > 0) {
|
|
215
|
+
resolvedParts.push(resolvedPart.value)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return resolvedCssValue(resolvedParts.join(' '))
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function resolveVariableFunction(
|
|
223
|
+
functionNode: FunctionNode,
|
|
224
|
+
customProperties: Record<string, string>,
|
|
225
|
+
seenVariables: Set<string>,
|
|
226
|
+
): ResolvedCssValue {
|
|
227
|
+
const functionArguments = [...functionNode.children]
|
|
228
|
+
const firstCommaIndex = functionArguments.findIndex(
|
|
229
|
+
(argumentNode) => argumentNode.type === 'Operator' && argumentNode.value === ',',
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
const variableReferenceNodes =
|
|
233
|
+
firstCommaIndex === -1 ? functionArguments : functionArguments.slice(0, firstCommaIndex)
|
|
234
|
+
const fallbackNodes = firstCommaIndex === -1 ? [] : functionArguments.slice(firstCommaIndex + 1)
|
|
235
|
+
|
|
236
|
+
const variableReference = variableReferenceNodes
|
|
237
|
+
.map((argumentNode) => generate(argumentNode))
|
|
238
|
+
.join('')
|
|
239
|
+
.trim()
|
|
240
|
+
const normalizedVariableName = variableReference.startsWith('--') ? variableReference : `--${variableReference}`
|
|
241
|
+
|
|
242
|
+
if (seenVariables.has(normalizedVariableName)) {
|
|
243
|
+
return invalidCssValue()
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const customPropertyValue = customProperties[normalizedVariableName]
|
|
247
|
+
if (customPropertyValue !== undefined) {
|
|
248
|
+
try {
|
|
249
|
+
const customPropertyValueAst = parse(customPropertyValue, { context: 'value' }) as Value
|
|
250
|
+
const nextSeenVariables = new Set(seenVariables)
|
|
251
|
+
nextSeenVariables.add(normalizedVariableName)
|
|
252
|
+
const resolvedCustomPropertyValue = resolveValueSequence(
|
|
253
|
+
[...customPropertyValueAst.children],
|
|
254
|
+
customProperties,
|
|
255
|
+
nextSeenVariables,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
if (resolvedCustomPropertyValue.kind === 'resolved') {
|
|
259
|
+
return resolvedCustomPropertyValue
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (resolvedCustomPropertyValue.kind === 'invalid') {
|
|
263
|
+
return fallbackNodes.length > 0
|
|
264
|
+
? resolveValueSequence(fallbackNodes, customProperties, seenVariables)
|
|
265
|
+
: invalidCssValue()
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return unresolvedCssValue()
|
|
269
|
+
} catch {
|
|
270
|
+
return invalidCssValue()
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (fallbackNodes.length > 0) {
|
|
275
|
+
return resolveValueSequence(fallbackNodes, customProperties, seenVariables)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return unresolvedCssValue()
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function resolveValueNode(
|
|
282
|
+
valueNode: CssNode,
|
|
283
|
+
customProperties: Record<string, string>,
|
|
284
|
+
seenVariables: Set<string>,
|
|
285
|
+
): ResolvedCssValue {
|
|
286
|
+
if (valueNode.type === 'Value') {
|
|
287
|
+
return resolveValueSequence([...valueNode.children], customProperties, seenVariables)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (valueNode.type === 'Function' && valueNode.name === 'var') {
|
|
291
|
+
return resolveVariableFunction(valueNode, customProperties, seenVariables)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return resolvedCssValue(generate(valueNode).trim())
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export function resolveCssPropertyValue(
|
|
298
|
+
property: CSSProperty,
|
|
299
|
+
customProperties: Record<string, string>,
|
|
300
|
+
): string | undefined {
|
|
301
|
+
if (!property.valueAst || !property.varRefs || property.varRefs.length === 0) {
|
|
302
|
+
return property.value
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const resolvedPropertyValue = resolveValueNode(property.valueAst, customProperties, new Set())
|
|
306
|
+
return resolvedPropertyValue.kind === 'resolved' ? resolvedPropertyValue.value : undefined
|
|
307
|
+
}
|
|
308
|
+
|
|
184
309
|
/**
|
|
185
310
|
* Extracts all CSS custom property names referenced via var() by walking
|
|
186
311
|
* the declaration value AST for Function nodes named "var".
|
|
@@ -395,6 +520,45 @@ function buildDomSelector(selector: Selector): string | null {
|
|
|
395
520
|
return result || null
|
|
396
521
|
}
|
|
397
522
|
|
|
523
|
+
function getSelectorSpecificity(selector: Selector): [number, number, number] {
|
|
524
|
+
let idCount = 0
|
|
525
|
+
let classLikeCount = 0
|
|
526
|
+
let typeLikeCount = 0
|
|
527
|
+
|
|
528
|
+
for (const component of selector.children) {
|
|
529
|
+
if (component.type === 'IdSelector') {
|
|
530
|
+
idCount += 1
|
|
531
|
+
continue
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (component.type === 'ClassSelector' || component.type === 'AttributeSelector') {
|
|
535
|
+
classLikeCount += 1
|
|
536
|
+
continue
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (component.type === 'TypeSelector') {
|
|
540
|
+
if (component.name !== '*') {
|
|
541
|
+
typeLikeCount += 1
|
|
542
|
+
}
|
|
543
|
+
continue
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (component.type === 'PseudoElementSelector') {
|
|
547
|
+
typeLikeCount += 1
|
|
548
|
+
continue
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (component.type === 'PseudoClassSelector') {
|
|
552
|
+
if (component.name === 'where') {
|
|
553
|
+
continue
|
|
554
|
+
}
|
|
555
|
+
classLikeCount += 1
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
return [idCount, classLikeCount, typeLikeCount]
|
|
560
|
+
}
|
|
561
|
+
|
|
398
562
|
/**
|
|
399
563
|
* Checks whether a CSS value matches a given CSS type (e.g. 'color', 'length').
|
|
400
564
|
* Uses css-tree's lexer; returns true when the value conforms to the type grammar.
|
|
@@ -67,6 +67,8 @@ export function matchCssSelectors(
|
|
|
67
67
|
if (!domSelector) continue
|
|
68
68
|
|
|
69
69
|
try {
|
|
70
|
+
const selectorSpecificity = cssInfo.api.getSelectorSpecificity(selector) ?? [0, 0, 0]
|
|
71
|
+
|
|
70
72
|
findMatchingElements($, domSelector, cssInfo.isCssModule).each((_index, element) => {
|
|
71
73
|
const traceId = $(element).attr(TRACE_ATTR)
|
|
72
74
|
if (!traceId) return
|
|
@@ -89,7 +91,7 @@ export function matchCssSelectors(
|
|
|
89
91
|
|
|
90
92
|
if (regular.length > 0) {
|
|
91
93
|
const existing = matchesByTraceId.get(traceId) ?? []
|
|
92
|
-
existing.push({ selector, properties: regular })
|
|
94
|
+
existing.push({ selector, properties: regular, specificity: selectorSpecificity })
|
|
93
95
|
matchesByTraceId.set(traceId, existing)
|
|
94
96
|
|
|
95
97
|
// Track which CSS custom properties are used (via var()) by this element
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import type { CssNode } from 'css-tree'
|
|
2
|
+
|
|
1
3
|
export interface CSSProperty {
|
|
2
4
|
name: string
|
|
3
5
|
value: string
|
|
6
|
+
/** Parsed css-tree AST for the property's value. */
|
|
7
|
+
valueAst?: CssNode
|
|
4
8
|
/** CSS variable names referenced via var() in this property's value */
|
|
5
9
|
varRefs?: string[]
|
|
6
10
|
}
|
|
@@ -8,6 +12,7 @@ export interface CSSProperty {
|
|
|
8
12
|
export interface CssSelectorMatch {
|
|
9
13
|
selector: string
|
|
10
14
|
properties: CSSProperty[]
|
|
15
|
+
specificity: [number, number, number]
|
|
11
16
|
}
|
|
12
17
|
|
|
13
18
|
export interface MatchedCssData {
|
|
@@ -76,6 +81,12 @@ export interface CSSParserAPI {
|
|
|
76
81
|
*/
|
|
77
82
|
getDomSelector: (selector: string) => string | null
|
|
78
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Gets the selector specificity tuple as [ids, classes/attrs/pseudo-classes, elements/pseudo-elements].
|
|
86
|
+
* Returns null when the selector was not parsed.
|
|
87
|
+
*/
|
|
88
|
+
getSelectorSpecificity: (selector: string) => [number, number, number] | null
|
|
89
|
+
|
|
79
90
|
/**
|
|
80
91
|
* Determines the CSS property type for a custom property based on how it is used.
|
|
81
92
|
* If all usages of varName are within the same CSS property, returns that property name.
|