@wix/zero-config-implementation 1.76.0 → 1.77.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-BsFTHTaQ.js → index-CuKmaUAA.js} +1 -1
- package/dist/{index-CukAHhd0.js → index-DRWoSsTZ.js} +9824 -9731
- package/dist/index.d.ts +42 -5
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/src/converters/to-editor-component.test.ts +154 -1
- package/src/converters/to-editor-component.ts +57 -6
- package/src/information-extractors/css/parse.test.ts +99 -0
- package/src/information-extractors/css/parse.ts +162 -1
- package/src/information-extractors/css/types.ts +43 -5
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ComponentType } from 'react';
|
|
2
|
+
import { CSS_PROPERTIES } from '@wix/react-component-schema';
|
|
2
3
|
import { CssNode } from 'css-tree';
|
|
3
4
|
import { default as default_2 } from 'typescript';
|
|
4
5
|
import { EditorReactComponent } from '@wix/react-component-schema';
|
|
@@ -384,6 +385,13 @@ MergeErrorProps<ErrorPropsArg, ChildProps>,
|
|
|
384
385
|
ChildCustomClass
|
|
385
386
|
>
|
|
386
387
|
|
|
388
|
+
/**
|
|
389
|
+
* The subset of manifest `cssPropertyType` values a `@property` `syntax` can resolve to —
|
|
390
|
+
* the schema's CSS data types (`color`, `number`, `length`, …, `customEnum`). Derived from
|
|
391
|
+
* the schema so it stays in sync with `CssCustomPropertyItem['cssPropertyType']`.
|
|
392
|
+
*/
|
|
393
|
+
declare type CssDataType = (typeof CSS_PROPERTIES.CSS_DATA_TYPE)[keyof typeof CSS_PROPERTIES.CSS_DATA_TYPE];
|
|
394
|
+
|
|
387
395
|
/**
|
|
388
396
|
* API returned by parseCss function for querying parsed CSS
|
|
389
397
|
*/
|
|
@@ -426,15 +434,23 @@ export declare interface CSSParserAPI {
|
|
|
426
434
|
*/
|
|
427
435
|
getSelectorSpecificity: (selector: string) => [number, number, number] | null;
|
|
428
436
|
/**
|
|
429
|
-
* Determines the CSS property type for a custom property
|
|
430
|
-
*
|
|
431
|
-
*
|
|
432
|
-
*
|
|
433
|
-
*
|
|
437
|
+
* Determines the CSS property type for a custom property.
|
|
438
|
+
* A `@property` registration is the top-priority source (declared type). Otherwise
|
|
439
|
+
* falls back to usage: if all usages of varName are within the same CSS property,
|
|
440
|
+
* returns that property name; if usages differ, returns the CSS data type inferred
|
|
441
|
+
* from the initial value ('color', 'length', 'number', or 'string').
|
|
442
|
+
* Returns undefined if the variable is neither registered nor used via var().
|
|
434
443
|
* @param varName - The CSS variable name (with or without --)
|
|
435
444
|
* @param defaultValue - The initial value string of the custom property
|
|
436
445
|
*/
|
|
437
446
|
getVarPropertyType: (varName: string, defaultValue: string) => string | undefined;
|
|
447
|
+
/**
|
|
448
|
+
* Returns every custom property registered via a `@property` at-rule, keyed by the
|
|
449
|
+
* variable name including its leading `--`. Each entry carries the type derived from
|
|
450
|
+
* `syntax`, the default from `initial-value`, and (for a pipe-separated ident list)
|
|
451
|
+
* the enum options.
|
|
452
|
+
*/
|
|
453
|
+
getRegisteredCustomProperties: () => Map<string, RegisteredCustomProperty>;
|
|
438
454
|
/**
|
|
439
455
|
* Returns every `:global(.modifier)` design-state class found in the CSS, paired
|
|
440
456
|
* with any native pseudo-class on the same base element within the same rule.
|
|
@@ -2423,6 +2439,27 @@ export declare interface ReactExtractor {
|
|
|
2423
2439
|
onRenderComplete?(event: RenderCompleteEvent): void;
|
|
2424
2440
|
}
|
|
2425
2441
|
|
|
2442
|
+
/**
|
|
2443
|
+
* A CSS custom property registered via the standard `@property` at-rule.
|
|
2444
|
+
* The `syntax` descriptor gives the type (and, for a pipe-separated ident list, a
|
|
2445
|
+
* finite set of options); the `initial-value` descriptor gives the default. Unlike
|
|
2446
|
+
* usage-inferred types, this is a declared source of truth and takes precedence.
|
|
2447
|
+
*/
|
|
2448
|
+
declare interface RegisteredCustomProperty {
|
|
2449
|
+
/**
|
|
2450
|
+
* The manifest `cssPropertyType` derived from the `@property` `syntax` descriptor
|
|
2451
|
+
* (e.g. `color`, `number`, `length`, or `customEnum` for a pipe-separated ident list).
|
|
2452
|
+
*/
|
|
2453
|
+
cssPropertyType: CssDataType;
|
|
2454
|
+
/** The default value from the `@property` `initial-value` descriptor, if declared. */
|
|
2455
|
+
defaultValue?: string;
|
|
2456
|
+
/**
|
|
2457
|
+
* The custom-ident options when `syntax` is a pipe-separated ident list
|
|
2458
|
+
* (`a | b | c`). Present only when `cssPropertyType` is `customEnum`.
|
|
2459
|
+
*/
|
|
2460
|
+
enumOptions?: string[];
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2426
2463
|
/**
|
|
2427
2464
|
* Event emitted after render completes.
|
|
2428
2465
|
*/
|
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-DRWoSsTZ.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.77.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": "29046e5eac674ef42dc21f8449f7f0b5c99691e835215a1133464500"
|
|
86
86
|
}
|
|
@@ -3,7 +3,7 @@ import { describe, expect, it } from 'vitest'
|
|
|
3
3
|
import { parseCss } from '../information-extractors/css/parse'
|
|
4
4
|
import type { MatchedCssData } from '../information-extractors/css/types'
|
|
5
5
|
import type { ExtractedElement } from '../information-extractors/react'
|
|
6
|
-
import type { ComponentInfoWithCss } from '../manifest-pipeline'
|
|
6
|
+
import type { ComponentInfoWithCss, ExtractedCssInfo } from '../manifest-pipeline'
|
|
7
7
|
import { toEditorReactComponent } from './to-editor-component'
|
|
8
8
|
|
|
9
9
|
function matcherDataFromCss(declarations: string): MatchedCssData {
|
|
@@ -86,3 +86,156 @@ describe('toEditorReactComponent display conversion', () => {
|
|
|
86
86
|
})
|
|
87
87
|
})
|
|
88
88
|
})
|
|
89
|
+
|
|
90
|
+
function cssInfoFromStylesheet(cssString: string): ExtractedCssInfo {
|
|
91
|
+
const api = parseCss(cssString)
|
|
92
|
+
const properties = new Map<string, string>()
|
|
93
|
+
const customProperties = new Map<string, string>()
|
|
94
|
+
|
|
95
|
+
for (const [, selectorProperties] of api.getAllProperties()) {
|
|
96
|
+
for (const property of selectorProperties) {
|
|
97
|
+
if (property.name.startsWith('--')) {
|
|
98
|
+
customProperties.set(property.name, property.value)
|
|
99
|
+
} else {
|
|
100
|
+
properties.set(property.name, property.value)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return { filePath: 'property-tokens.css', api, properties, customProperties, isCssModule: false }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function createComponentWithCss(cssString: string): ComponentInfoWithCss {
|
|
109
|
+
const rootElement: ExtractedElement = {
|
|
110
|
+
traceId: 'trace-root',
|
|
111
|
+
name: 'root',
|
|
112
|
+
tag: 'div',
|
|
113
|
+
attributes: {},
|
|
114
|
+
extractorData: new Map<string, unknown>(),
|
|
115
|
+
children: [],
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
componentName: 'PropertyTokens',
|
|
120
|
+
props: {},
|
|
121
|
+
elements: [rootElement],
|
|
122
|
+
propUsages: new Map(),
|
|
123
|
+
css: [cssInfoFromStylesheet(cssString)],
|
|
124
|
+
varUsedByTraceId: new Map(),
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
describe('toEditorReactComponent — @property design tokens', () => {
|
|
129
|
+
it('emits typed cssCustomProperties from @property, defaulting from initial-value with no element declaration', () => {
|
|
130
|
+
const component = createComponentWithCss(`
|
|
131
|
+
@property --sdf-safelight-color { syntax: "<color>"; inherits: true; initial-value: #F02011; }
|
|
132
|
+
@property --sdf-contrast { syntax: "<number>"; inherits: true; initial-value: 1.5; }
|
|
133
|
+
`)
|
|
134
|
+
|
|
135
|
+
const customProps = toEditorReactComponent(component).editorElement?.cssCustomProperties
|
|
136
|
+
|
|
137
|
+
expect(customProps?.['sdf-safelight-color']).toEqual({
|
|
138
|
+
displayName: 'Sdf Safelight Color',
|
|
139
|
+
defaultValue: '#F02011',
|
|
140
|
+
cssPropertyType: 'color',
|
|
141
|
+
})
|
|
142
|
+
expect(customProps?.['sdf-contrast']).toEqual({
|
|
143
|
+
displayName: 'Sdf Contrast',
|
|
144
|
+
defaultValue: '1.5',
|
|
145
|
+
cssPropertyType: 'number',
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('emits a customEnum dropdown for a pipe-separated ident-list @property', () => {
|
|
150
|
+
const component = createComponentWithCss(`
|
|
151
|
+
@property --sdf-palette { syntax: "warm | cool | neutral"; inherits: false; initial-value: warm; }
|
|
152
|
+
`)
|
|
153
|
+
|
|
154
|
+
const customProps = toEditorReactComponent(component).editorElement?.cssCustomProperties
|
|
155
|
+
|
|
156
|
+
expect(customProps?.['sdf-palette']).toEqual({
|
|
157
|
+
displayName: 'Sdf Palette',
|
|
158
|
+
defaultValue: 'warm',
|
|
159
|
+
cssPropertyType: 'customEnum',
|
|
160
|
+
customEnum: {
|
|
161
|
+
cssPropertyType: 'string',
|
|
162
|
+
options: [
|
|
163
|
+
{ value: 'warm', displayName: 'Warm' },
|
|
164
|
+
{ value: 'cool', displayName: 'Cool' },
|
|
165
|
+
{ value: 'neutral', displayName: 'Neutral' },
|
|
166
|
+
],
|
|
167
|
+
},
|
|
168
|
+
})
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('drops a @property whose type has no matching schema cssPropertyType', () => {
|
|
172
|
+
const component = createComponentWithCss(`
|
|
173
|
+
@property --sdf-hero-image { syntax: "<image>"; inherits: false; }
|
|
174
|
+
@property --sdf-safelight-color { syntax: "<color>"; inherits: true; initial-value: #F02011; }
|
|
175
|
+
`)
|
|
176
|
+
|
|
177
|
+
const customProps = toEditorReactComponent(component).editorElement?.cssCustomProperties
|
|
178
|
+
|
|
179
|
+
expect(customProps?.['sdf-hero-image']).toBeUndefined()
|
|
180
|
+
expect(customProps?.['sdf-safelight-color']).toBeDefined()
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('emits @property-typed and usage-inferred custom properties side by side', () => {
|
|
184
|
+
const matcherData: MatchedCssData = {
|
|
185
|
+
matches: [{ selector: '.root', properties: [], specificity: [0, 1, 0] }],
|
|
186
|
+
customProperties: { '--spacing': '8px' },
|
|
187
|
+
}
|
|
188
|
+
const rootElement: ExtractedElement = {
|
|
189
|
+
traceId: 'trace-root',
|
|
190
|
+
name: 'root',
|
|
191
|
+
tag: 'div',
|
|
192
|
+
attributes: {},
|
|
193
|
+
extractorData: new Map<string, unknown>([['css-matcher', matcherData]]),
|
|
194
|
+
children: [],
|
|
195
|
+
}
|
|
196
|
+
const component: ComponentInfoWithCss = {
|
|
197
|
+
componentName: 'MixedTokens',
|
|
198
|
+
props: {},
|
|
199
|
+
elements: [rootElement],
|
|
200
|
+
propUsages: new Map(),
|
|
201
|
+
css: [
|
|
202
|
+
cssInfoFromStylesheet(`
|
|
203
|
+
@property --brand-color { syntax: "<color>"; inherits: false; initial-value: #123456; }
|
|
204
|
+
.root { --spacing: 8px; padding: var(--spacing); color: var(--brand-color); }
|
|
205
|
+
`),
|
|
206
|
+
],
|
|
207
|
+
varUsedByTraceId: new Map([
|
|
208
|
+
['--brand-color', new Set(['trace-root'])],
|
|
209
|
+
['--spacing', new Set(['trace-root'])],
|
|
210
|
+
]),
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const customProps = toEditorReactComponent(component).editorElement?.cssCustomProperties
|
|
214
|
+
|
|
215
|
+
// --brand-color is typed from its @property declaration; --spacing keeps the existing
|
|
216
|
+
// usage-inference behavior (single usage in `padding` → the `padding` property type).
|
|
217
|
+
expect(customProps?.['brand-color']).toEqual({
|
|
218
|
+
displayName: 'Brand Color',
|
|
219
|
+
defaultValue: '#123456',
|
|
220
|
+
cssPropertyType: 'color',
|
|
221
|
+
})
|
|
222
|
+
expect(customProps?.spacing).toEqual({
|
|
223
|
+
displayName: 'Spacing',
|
|
224
|
+
defaultValue: '8px',
|
|
225
|
+
cssPropertyType: 'padding',
|
|
226
|
+
})
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
it('does not leak a registered @property variable into cssProperties', () => {
|
|
230
|
+
const component = createComponentWithCss(`
|
|
231
|
+
@property --sdf-safelight-color { syntax: "<color>"; inherits: true; initial-value: #F02011; }
|
|
232
|
+
.root { --sdf-safelight-color: #F02011; }
|
|
233
|
+
`)
|
|
234
|
+
|
|
235
|
+
const editorElement = toEditorReactComponent(component).editorElement
|
|
236
|
+
|
|
237
|
+
expect(editorElement?.cssCustomProperties?.['sdf-safelight-color']).toBeDefined()
|
|
238
|
+
expect(editorElement?.cssProperties?.['sdf-safelight-color']).toBeUndefined()
|
|
239
|
+
expect(editorElement?.cssProperties?.['--sdf-safelight-color']).toBeUndefined()
|
|
240
|
+
})
|
|
241
|
+
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
CssCustomPropertyItem,
|
|
3
3
|
CssPropertyItem,
|
|
4
|
+
CustomPropertyEnum,
|
|
4
5
|
DataItem,
|
|
5
6
|
Display,
|
|
6
7
|
EditorElement,
|
|
@@ -11,7 +12,7 @@ import type {
|
|
|
11
12
|
import { CSS_PROPERTIES, ELEMENTS } from '@wix/react-component-schema'
|
|
12
13
|
import { camelCase } from 'case-anything'
|
|
13
14
|
import type { ComponentInfoWithCss } from '../index'
|
|
14
|
-
import type { MatchedCssData } from '../information-extractors/css/types'
|
|
15
|
+
import type { MatchedCssData, RegisteredCustomProperty } from '../information-extractors/css/types'
|
|
15
16
|
import type {
|
|
16
17
|
CoupledComponentInfo,
|
|
17
18
|
CoupledProp,
|
|
@@ -220,6 +221,11 @@ function buildNearestCommonAncestorCustomProps(
|
|
|
220
221
|
component: ComponentInfoWithCss,
|
|
221
222
|
): Map<string, Record<string, CssCustomPropertyItem>> {
|
|
222
223
|
const parentMap = buildParentMap(component.elements)
|
|
224
|
+
const rootTraceId = component.elements[0]?.traceId
|
|
225
|
+
|
|
226
|
+
// Custom properties registered via the standard @property at-rule — the declared
|
|
227
|
+
// source of each variable's type, default, and (for ident lists) enum options.
|
|
228
|
+
const registeredByVar = collectRegisteredCustomProperties(component)
|
|
223
229
|
|
|
224
230
|
// Collect default values from matched elements only — this excludes values from theme/modifier
|
|
225
231
|
// selectors that were not applied during mock rendering (e.g. .component.dark when variant='light').
|
|
@@ -235,31 +241,47 @@ function buildNearestCommonAncestorCustomProps(
|
|
|
235
241
|
}
|
|
236
242
|
}
|
|
237
243
|
|
|
244
|
+
// Emit every declared variable plus every @property registration — a registered var
|
|
245
|
+
// may have no element-level declaration yet still needs a typed control.
|
|
246
|
+
const varNames = new Set<string>([...allCustomPropertyValues.keys(), ...registeredByVar.keys()])
|
|
247
|
+
|
|
238
248
|
const nearestCommonAncestorCustomProps = new Map<string, Record<string, CssCustomPropertyItem>>()
|
|
239
249
|
|
|
240
|
-
for (const
|
|
250
|
+
for (const varName of varNames) {
|
|
241
251
|
if (varName === '--display') continue
|
|
242
252
|
|
|
253
|
+
const registration = registeredByVar.get(varName)
|
|
254
|
+
// Prefer a concrete declared value; fall back to the @property initial-value so a
|
|
255
|
+
// component doesn't need an element-level `--x: ...` declaration.
|
|
256
|
+
const defaultValue = allCustomPropertyValues.get(varName) ?? registration?.defaultValue
|
|
257
|
+
|
|
243
258
|
const usedByTraceIds = component.varUsedByTraceId.get(varName) ?? new Set<string>()
|
|
244
259
|
|
|
245
260
|
let nearestCommonAncestorTraceId: string | undefined
|
|
246
261
|
if (usedByTraceIds.size > 0) {
|
|
247
262
|
nearestCommonAncestorTraceId = findNearestCommonAncestor(usedByTraceIds, parentMap)
|
|
248
263
|
} else {
|
|
249
|
-
// Fallback:
|
|
250
|
-
|
|
264
|
+
// Fallback: the element whose CSS selector defines the variable; failing that, a
|
|
265
|
+
// registered @property with no var() usage anchors on the component root.
|
|
266
|
+
nearestCommonAncestorTraceId =
|
|
267
|
+
findDefiningElement(component.elements, varName) ?? (registration ? rootTraceId : undefined)
|
|
251
268
|
}
|
|
252
269
|
|
|
253
270
|
if (!nearestCommonAncestorTraceId) continue
|
|
254
271
|
|
|
255
|
-
|
|
272
|
+
// A @property registration is the top-priority type source; otherwise infer from usage.
|
|
273
|
+
const cssPropertyType = registration
|
|
274
|
+
? registration.cssPropertyType
|
|
275
|
+
: getVarPropertyTypeFromCssInfos(varName, defaultValue ?? '', component.css)
|
|
276
|
+
const customEnum = buildCustomEnum(registration)
|
|
256
277
|
const cleanVarName = varName.startsWith('--') ? varName.slice(2) : varName
|
|
257
278
|
|
|
258
279
|
const existingProps = nearestCommonAncestorCustomProps.get(nearestCommonAncestorTraceId) ?? {}
|
|
259
280
|
existingProps[cleanVarName] = {
|
|
260
281
|
displayName: formatDisplayName(cleanVarName),
|
|
261
|
-
defaultValue,
|
|
282
|
+
...(defaultValue !== undefined && { defaultValue }),
|
|
262
283
|
...(cssPropertyType !== undefined && { cssPropertyType }),
|
|
284
|
+
...(customEnum && { customEnum }),
|
|
263
285
|
}
|
|
264
286
|
nearestCommonAncestorCustomProps.set(nearestCommonAncestorTraceId, existingProps)
|
|
265
287
|
}
|
|
@@ -267,6 +289,35 @@ function buildNearestCommonAncestorCustomProps(
|
|
|
267
289
|
return nearestCommonAncestorCustomProps
|
|
268
290
|
}
|
|
269
291
|
|
|
292
|
+
/**
|
|
293
|
+
* Merges @property registrations across all of the component's CSS files, keyed by the
|
|
294
|
+
* `--`-prefixed variable name. The first file to register a variable wins.
|
|
295
|
+
*/
|
|
296
|
+
function collectRegisteredCustomProperties(component: ComponentInfoWithCss): Map<string, RegisteredCustomProperty> {
|
|
297
|
+
const merged = new Map<string, RegisteredCustomProperty>()
|
|
298
|
+
for (const cssInfo of component.css) {
|
|
299
|
+
for (const [varName, registration] of cssInfo.api.getRegisteredCustomProperties()) {
|
|
300
|
+
if (!merged.has(varName)) merged.set(varName, registration)
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return merged
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Builds a `customEnum` (a named-option dropdown) from an @property whose `syntax` is a
|
|
308
|
+
* pipe-separated ident list. Option labels are humanized from the idents.
|
|
309
|
+
*/
|
|
310
|
+
function buildCustomEnum(registration: RegisteredCustomProperty | undefined): CustomPropertyEnum | undefined {
|
|
311
|
+
if (!registration?.enumOptions || registration.enumOptions.length === 0) return undefined
|
|
312
|
+
return {
|
|
313
|
+
cssPropertyType: CSS_PROPERTIES.CSS_DATA_TYPE.string,
|
|
314
|
+
options: registration.enumOptions.map((value) => ({
|
|
315
|
+
value,
|
|
316
|
+
displayName: formatDisplayName(value),
|
|
317
|
+
})),
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
270
321
|
/**
|
|
271
322
|
* Queries each CSS parser API for the property type of a variable and returns
|
|
272
323
|
* the first defined result.
|
|
@@ -121,6 +121,105 @@ describe('parseCss — getStateClasses', () => {
|
|
|
121
121
|
})
|
|
122
122
|
})
|
|
123
123
|
|
|
124
|
+
describe('parseCss — @property registration', () => {
|
|
125
|
+
it('maps each syntax type to its cssPropertyType and reads the initial-value default', () => {
|
|
126
|
+
const registered = parseCss(`
|
|
127
|
+
@property --sdf-safelight-color { syntax: "<color>"; inherits: true; initial-value: #F02011; }
|
|
128
|
+
@property --sdf-contrast { syntax: "<number>"; inherits: true; initial-value: 1.5; }
|
|
129
|
+
@property --sdf-radius { syntax: "<length>"; inherits: false; initial-value: 8px; }
|
|
130
|
+
@property --sdf-scale { syntax: "<percentage>"; inherits: false; initial-value: 50%; }
|
|
131
|
+
@property --sdf-tilt { syntax: "<angle>"; inherits: false; initial-value: 90deg; }
|
|
132
|
+
@property --sdf-delay { syntax: "<time>"; inherits: false; initial-value: 200ms; }
|
|
133
|
+
@property --sdf-inset { syntax: "<length-percentage>"; inherits: false; initial-value: 10px; }
|
|
134
|
+
`).getRegisteredCustomProperties()
|
|
135
|
+
|
|
136
|
+
expect(registered.get('--sdf-safelight-color')).toEqual({ cssPropertyType: 'color', defaultValue: '#F02011' })
|
|
137
|
+
expect(registered.get('--sdf-contrast')).toEqual({ cssPropertyType: 'number', defaultValue: '1.5' })
|
|
138
|
+
expect(registered.get('--sdf-radius')).toEqual({ cssPropertyType: 'length', defaultValue: '8px' })
|
|
139
|
+
expect(registered.get('--sdf-scale')).toEqual({ cssPropertyType: 'percentage', defaultValue: '50%' })
|
|
140
|
+
expect(registered.get('--sdf-tilt')).toEqual({ cssPropertyType: 'angle', defaultValue: '90deg' })
|
|
141
|
+
expect(registered.get('--sdf-delay')).toEqual({ cssPropertyType: 'time', defaultValue: '200ms' })
|
|
142
|
+
expect(registered.get('--sdf-inset')).toEqual({ cssPropertyType: 'lengthPercentage', defaultValue: '10px' })
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('maps <integer> to number (there is no dedicated integer control)', () => {
|
|
146
|
+
const registered = parseCss(`
|
|
147
|
+
@property --sdf-steps { syntax: "<integer>"; inherits: false; initial-value: 3; }
|
|
148
|
+
`).getRegisteredCustomProperties()
|
|
149
|
+
|
|
150
|
+
expect(registered.get('--sdf-steps')).toEqual({ cssPropertyType: 'number', defaultValue: '3' })
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('drops @property registrations whose syntax has no matching schema cssPropertyType', () => {
|
|
154
|
+
// No matching schema cssPropertyType → dropped, not emitted with a fallback type.
|
|
155
|
+
const registered = parseCss(`
|
|
156
|
+
@property --sdf-universal { syntax: "*"; inherits: false; initial-value: anything; }
|
|
157
|
+
@property --sdf-image { syntax: "<image>"; inherits: false; }
|
|
158
|
+
@property --sdf-url { syntax: "<url>"; inherits: false; }
|
|
159
|
+
@property --sdf-resolution { syntax: "<resolution>"; inherits: false; initial-value: 96dpi; }
|
|
160
|
+
@property --sdf-transform { syntax: "<transform-function>"; inherits: false; }
|
|
161
|
+
@property --sdf-ident { syntax: "<custom-ident>"; inherits: false; initial-value: foo; }
|
|
162
|
+
@property --sdf-lone-keyword { syntax: "auto"; inherits: false; initial-value: auto; }
|
|
163
|
+
`).getRegisteredCustomProperties()
|
|
164
|
+
|
|
165
|
+
for (const droppedVarName of [
|
|
166
|
+
'--sdf-universal',
|
|
167
|
+
'--sdf-image',
|
|
168
|
+
'--sdf-url',
|
|
169
|
+
'--sdf-resolution',
|
|
170
|
+
'--sdf-transform',
|
|
171
|
+
'--sdf-ident',
|
|
172
|
+
'--sdf-lone-keyword',
|
|
173
|
+
]) {
|
|
174
|
+
expect(registered.has(droppedVarName)).toBe(false)
|
|
175
|
+
}
|
|
176
|
+
expect(registered.size).toBe(0)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('picks the first recognized <type> from a mixed type/keyword list', () => {
|
|
180
|
+
const registered = parseCss(`
|
|
181
|
+
@property --sdf-size { syntax: "<length> | auto"; inherits: false; initial-value: auto; }
|
|
182
|
+
@property --sdf-scale { syntax: "small | medium | <length>"; inherits: false; initial-value: medium; }
|
|
183
|
+
`).getRegisteredCustomProperties()
|
|
184
|
+
|
|
185
|
+
expect(registered.get('--sdf-size')).toEqual({ cssPropertyType: 'length', defaultValue: 'auto' })
|
|
186
|
+
expect(registered.get('--sdf-scale')).toEqual({ cssPropertyType: 'length', defaultValue: 'medium' })
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it('turns a pipe-separated ident list into a customEnum with its option idents', () => {
|
|
190
|
+
const registered = parseCss(`
|
|
191
|
+
@property --sdf-palette { syntax: "warm | cool | neutral"; inherits: false; initial-value: warm; }
|
|
192
|
+
`).getRegisteredCustomProperties()
|
|
193
|
+
|
|
194
|
+
expect(registered.get('--sdf-palette')).toEqual({
|
|
195
|
+
cssPropertyType: 'customEnum',
|
|
196
|
+
enumOptions: ['warm', 'cool', 'neutral'],
|
|
197
|
+
defaultValue: 'warm',
|
|
198
|
+
})
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
it('strips list multipliers when resolving a type component', () => {
|
|
202
|
+
const registered = parseCss(`
|
|
203
|
+
@property --sdf-shadows { syntax: "<length>+"; inherits: false; initial-value: 0px; }
|
|
204
|
+
@property --sdf-stops { syntax: "<color>#"; inherits: false; initial-value: red; }
|
|
205
|
+
`).getRegisteredCustomProperties()
|
|
206
|
+
|
|
207
|
+
expect(registered.get('--sdf-shadows')).toEqual({ cssPropertyType: 'length', defaultValue: '0px' })
|
|
208
|
+
expect(registered.get('--sdf-stops')).toEqual({ cssPropertyType: 'color', defaultValue: 'red' })
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it('lets a @property registration override the usage-inferred type', () => {
|
|
212
|
+
const api = parseCss(`
|
|
213
|
+
@property --sdf-intensity { syntax: "<number>"; inherits: false; initial-value: 1; }
|
|
214
|
+
.root { --sdf-intensity: 1; background-color: var(--sdf-intensity); }
|
|
215
|
+
`)
|
|
216
|
+
|
|
217
|
+
// Used only in background-color, which would otherwise infer 'backgroundColor';
|
|
218
|
+
// the @property declaration wins.
|
|
219
|
+
expect(api.getVarPropertyType('--sdf-intensity', '1')).toBe('number')
|
|
220
|
+
})
|
|
221
|
+
})
|
|
222
|
+
|
|
124
223
|
describe('resolveCssPropertyValue', () => {
|
|
125
224
|
it('uses the fallback when a referenced custom property is missing from the property-level map', () => {
|
|
126
225
|
const [displayProperty] = parseCss(`
|