@wix/zero-config-implementation 1.90.0 → 1.92.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.d.ts +17 -0
- package/dist/index.js +6050 -6017
- package/package.json +2 -2
- package/src/converters/to-editor-component.test.ts +150 -0
- package/src/converters/to-editor-component.ts +48 -3
- package/src/information-extractors/css/parse.ts +16 -0
- package/src/information-extractors/css/types.ts +17 -0
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.92.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",
|
|
@@ -106,5 +106,5 @@
|
|
|
106
106
|
]
|
|
107
107
|
}
|
|
108
108
|
},
|
|
109
|
-
"falconPackageHash": "
|
|
109
|
+
"falconPackageHash": "db2c3fec617a486d30e2d52ed7e239747365a40d14666a5ea04cf07d"
|
|
110
110
|
}
|
|
@@ -166,6 +166,71 @@ describe('toEditorReactComponent display conversion', () => {
|
|
|
166
166
|
})
|
|
167
167
|
})
|
|
168
168
|
|
|
169
|
+
describe('toEditorReactComponent selector conversion', () => {
|
|
170
|
+
it.each([
|
|
171
|
+
['a', 'a___'],
|
|
172
|
+
['div', 'div_'],
|
|
173
|
+
['span', 'span'],
|
|
174
|
+
])('pads the root selector %j to at least four characters', (tag, expectedSelector) => {
|
|
175
|
+
const rootElement = createElementWithDisplayMatcher(tag, matcherDataFromCss(''))
|
|
176
|
+
|
|
177
|
+
const editorElement = toEditorReactComponent(createComponent(rootElement)).editorElement
|
|
178
|
+
|
|
179
|
+
expect(editorElement?.selector).toBe(expectedSelector)
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it('pads a short semantic class selector', () => {
|
|
183
|
+
const rootElement = createSemanticElement({
|
|
184
|
+
traceId: 'trace-root',
|
|
185
|
+
name: 'root',
|
|
186
|
+
className: 'a',
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
const editorElement = toEditorReactComponent(createComponent(rootElement)).editorElement
|
|
190
|
+
|
|
191
|
+
expect(editorElement?.selector).toBe('.a__')
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('pads short inline and ref element selectors', () => {
|
|
195
|
+
const inlineElement: ExtractedElement = {
|
|
196
|
+
traceId: 'trace-link',
|
|
197
|
+
name: 'link',
|
|
198
|
+
tag: 'a',
|
|
199
|
+
attributes: {},
|
|
200
|
+
extractorData: new Map<string, unknown>(),
|
|
201
|
+
children: [],
|
|
202
|
+
}
|
|
203
|
+
const refElement: ExtractedElement = {
|
|
204
|
+
traceId: 'trace-action-button',
|
|
205
|
+
name: 'actionButton',
|
|
206
|
+
tag: 'div',
|
|
207
|
+
attributes: {},
|
|
208
|
+
extractorData: new Map<string, unknown>([
|
|
209
|
+
[
|
|
210
|
+
'ref-element',
|
|
211
|
+
{
|
|
212
|
+
elementPropsPath: 'actionButton',
|
|
213
|
+
refComponentType: 'test.Button',
|
|
214
|
+
selector: '.a',
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
]),
|
|
218
|
+
children: [],
|
|
219
|
+
}
|
|
220
|
+
const rootElement = createSemanticElement({
|
|
221
|
+
traceId: 'trace-root',
|
|
222
|
+
name: 'root',
|
|
223
|
+
className: 'root',
|
|
224
|
+
children: [inlineElement, refElement],
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
const elements = toEditorReactComponent(createComponent(rootElement)).editorElement?.elements
|
|
228
|
+
|
|
229
|
+
expect(elements?.link?.inlineElement?.selector).toBe('a___')
|
|
230
|
+
expect(elements?.actionButton?.refElement?.selector).toBe('.a__')
|
|
231
|
+
})
|
|
232
|
+
})
|
|
233
|
+
|
|
169
234
|
function cssInfoFromStylesheet(cssString: string): ExtractedCssInfo {
|
|
170
235
|
const api = parseCss(cssString)
|
|
171
236
|
const properties = new Map<string, string>()
|
|
@@ -225,6 +290,91 @@ describe('toEditorReactComponent — @property design tokens', () => {
|
|
|
225
290
|
})
|
|
226
291
|
})
|
|
227
292
|
|
|
293
|
+
it('carries the --min / --max / --step descriptors of a number @property into number limits', () => {
|
|
294
|
+
const component = createComponentWithCss(`
|
|
295
|
+
@property --speed { syntax: "<number>"; inherits: true; initial-value: 4; --min: 1; --max: 10; --step: 0.5; }
|
|
296
|
+
`)
|
|
297
|
+
|
|
298
|
+
const customProps = toEditorReactComponent(component).editorElement?.cssCustomProperties
|
|
299
|
+
|
|
300
|
+
expect(customProps?.speed).toEqual({
|
|
301
|
+
displayName: 'Speed',
|
|
302
|
+
defaultValue: '4',
|
|
303
|
+
cssPropertyType: 'number',
|
|
304
|
+
number: { min: '1', max: '10', multiplier: '0.5' },
|
|
305
|
+
})
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
it('emits only the usable half of a partial range and omits the rest', () => {
|
|
309
|
+
const component = createComponentWithCss(`
|
|
310
|
+
@property --weight { syntax: "<number>"; inherits: true; initial-value: 2; --min: 0; }
|
|
311
|
+
@property --tempo { syntax: "<number>"; inherits: true; initial-value: 3; --max: 9; --step: nonsense; }
|
|
312
|
+
@property --offset { syntax: "<number>"; inherits: true; initial-value: 0; --min: ; --max: 12; }
|
|
313
|
+
`)
|
|
314
|
+
|
|
315
|
+
const customProps = toEditorReactComponent(component).editorElement?.cssCustomProperties
|
|
316
|
+
|
|
317
|
+
expect(customProps?.weight).toEqual({
|
|
318
|
+
displayName: 'Weight',
|
|
319
|
+
defaultValue: '2',
|
|
320
|
+
cssPropertyType: 'number',
|
|
321
|
+
number: { min: '0' },
|
|
322
|
+
})
|
|
323
|
+
expect(customProps?.tempo).toEqual({
|
|
324
|
+
displayName: 'Tempo',
|
|
325
|
+
defaultValue: '3',
|
|
326
|
+
cssPropertyType: 'number',
|
|
327
|
+
number: { max: '9' },
|
|
328
|
+
})
|
|
329
|
+
expect(customProps?.offset).toEqual({
|
|
330
|
+
displayName: 'Offset',
|
|
331
|
+
defaultValue: '0',
|
|
332
|
+
cssPropertyType: 'number',
|
|
333
|
+
number: { max: '12' },
|
|
334
|
+
})
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
it('drops an inverted range and a non-positive step, omitting `number` when nothing is usable', () => {
|
|
338
|
+
const component = createComponentWithCss(`
|
|
339
|
+
@property --span { syntax: "<number>"; inherits: true; initial-value: 5; --min: 10; --max: 2; --step: 0; }
|
|
340
|
+
@property --frozen { syntax: "<number>"; inherits: true; initial-value: 5; --min: 5; --max: 5; --step: 1; }
|
|
341
|
+
`)
|
|
342
|
+
|
|
343
|
+
const customProps = toEditorReactComponent(component).editorElement?.cssCustomProperties
|
|
344
|
+
|
|
345
|
+
expect(customProps?.span).toEqual({
|
|
346
|
+
displayName: 'Span',
|
|
347
|
+
defaultValue: '5',
|
|
348
|
+
cssPropertyType: 'number',
|
|
349
|
+
})
|
|
350
|
+
expect(customProps?.frozen).toEqual({
|
|
351
|
+
displayName: 'Frozen',
|
|
352
|
+
defaultValue: '5',
|
|
353
|
+
cssPropertyType: 'number',
|
|
354
|
+
number: { multiplier: '1' },
|
|
355
|
+
})
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
it('ignores range descriptors on a non-number @property', () => {
|
|
359
|
+
const component = createComponentWithCss(`
|
|
360
|
+
@property --sdf-safelight-color { syntax: "<color>"; inherits: true; initial-value: #F02011; --min: 1; --max: 10; }
|
|
361
|
+
@property --gap { syntax: "<length>"; inherits: true; initial-value: 4px; --min: 1; --max: 10; --step: 1; }
|
|
362
|
+
`)
|
|
363
|
+
|
|
364
|
+
const customProps = toEditorReactComponent(component).editorElement?.cssCustomProperties
|
|
365
|
+
|
|
366
|
+
expect(customProps?.['sdf-safelight-color']).toEqual({
|
|
367
|
+
displayName: 'Sdf Safelight Color',
|
|
368
|
+
defaultValue: '#F02011',
|
|
369
|
+
cssPropertyType: 'color',
|
|
370
|
+
})
|
|
371
|
+
expect(customProps?.gap).toEqual({
|
|
372
|
+
displayName: 'Gap',
|
|
373
|
+
defaultValue: '4px',
|
|
374
|
+
cssPropertyType: 'length',
|
|
375
|
+
})
|
|
376
|
+
})
|
|
377
|
+
|
|
228
378
|
it('emits a customEnum dropdown for a pipe-separated ident-list @property', () => {
|
|
229
379
|
const component = createComponentWithCss(`
|
|
230
380
|
@property --sdf-palette { syntax: "warm | cool | neutral"; inherits: false; initial-value: warm; }
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
CssCustomPropertyItem,
|
|
3
|
+
CssNumber,
|
|
3
4
|
CssPropertyItem,
|
|
4
5
|
CustomPropertyEnum,
|
|
5
6
|
DataItem,
|
|
@@ -33,6 +34,8 @@ import type { StateClassTrigger } from './state-class-trigger'
|
|
|
33
34
|
import { buildStatesBlock } from './states-builder'
|
|
34
35
|
import { formatDisplayName } from './utils'
|
|
35
36
|
|
|
37
|
+
const MINIMUM_SELECTOR_LENGTH = 4
|
|
38
|
+
|
|
36
39
|
/**
|
|
37
40
|
* Merges native and custom state blocks. Native states take precedence on any
|
|
38
41
|
* key collision (e.g. an author wrapping `isDisabled` in `ElementState<>` does not
|
|
@@ -139,9 +142,13 @@ function getSemanticBlockName(element: ExtractedElement | undefined): string | u
|
|
|
139
142
|
function buildSelector(rootElement?: ExtractedElement): string {
|
|
140
143
|
if (rootElement?.attributes.class) {
|
|
141
144
|
const semanticClass = findPreferredSemanticClass(normalizeClassNames(rootElement.attributes.class))
|
|
142
|
-
if (semanticClass) return `.${semanticClass}`
|
|
145
|
+
if (semanticClass) return padSelector(`.${semanticClass}`)
|
|
143
146
|
}
|
|
144
|
-
return rootElement?.tag ?? ''
|
|
147
|
+
return padSelector(rootElement?.tag ?? '')
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function padSelector(selector: string): string {
|
|
151
|
+
return selector.padEnd(MINIMUM_SELECTOR_LENGTH, '_')
|
|
145
152
|
}
|
|
146
153
|
|
|
147
154
|
function buildInnerElementDisplayName(element: ExtractedElement, ancestorSemanticClasses: string[]): string {
|
|
@@ -205,7 +212,7 @@ function buildElements(
|
|
|
205
212
|
result[buildRefElementManifestKey(refElementMatch.elementPropsPath, parentElementPropsPath)] = {
|
|
206
213
|
elementType: ELEMENTS.ELEMENT_TYPE.refElement,
|
|
207
214
|
refElement: {
|
|
208
|
-
selector: refElementMatch.selector,
|
|
215
|
+
selector: padSelector(refElementMatch.selector),
|
|
209
216
|
type: refElementMatch.refComponentType,
|
|
210
217
|
},
|
|
211
218
|
}
|
|
@@ -324,6 +331,7 @@ function buildNearestCommonAncestorCustomProps(
|
|
|
324
331
|
? registration.cssPropertyType
|
|
325
332
|
: getVarPropertyTypeFromCssInfos(varName, defaultValue ?? '', component.css)
|
|
326
333
|
const customEnum = buildCustomEnum(registration)
|
|
334
|
+
const numberLimits = buildCssNumber(registration, cssPropertyType)
|
|
327
335
|
const cleanVarName = varName.startsWith('--') ? varName.slice(2) : varName
|
|
328
336
|
|
|
329
337
|
const existingProps = nearestCommonAncestorCustomProps.get(nearestCommonAncestorTraceId) ?? {}
|
|
@@ -332,6 +340,7 @@ function buildNearestCommonAncestorCustomProps(
|
|
|
332
340
|
...(defaultValue !== undefined && { defaultValue }),
|
|
333
341
|
...(cssPropertyType !== undefined && { cssPropertyType }),
|
|
334
342
|
...(customEnum && { customEnum }),
|
|
343
|
+
...(numberLimits && { number: numberLimits }),
|
|
335
344
|
}
|
|
336
345
|
nearestCommonAncestorCustomProps.set(nearestCommonAncestorTraceId, existingProps)
|
|
337
346
|
}
|
|
@@ -368,6 +377,42 @@ function buildCustomEnum(registration: RegisteredCustomProperty | undefined): Cu
|
|
|
368
377
|
}
|
|
369
378
|
}
|
|
370
379
|
|
|
380
|
+
function parseFiniteDescriptor(rawValue: string | undefined): number | undefined {
|
|
381
|
+
const trimmed = rawValue?.trim()
|
|
382
|
+
if (!trimmed) return undefined
|
|
383
|
+
const parsed = Number(trimmed)
|
|
384
|
+
return Number.isFinite(parsed) ? parsed : undefined
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Builds the clamp the panel's number control applies, from an @property's range
|
|
389
|
+
* descriptors. Anything unusable is dropped rather than passed through.
|
|
390
|
+
*/
|
|
391
|
+
function buildCssNumber(
|
|
392
|
+
registration: RegisteredCustomProperty | undefined,
|
|
393
|
+
cssPropertyType: CssCustomPropertyItem['cssPropertyType'],
|
|
394
|
+
): CssNumber | undefined {
|
|
395
|
+
if (!registration?.range || cssPropertyType !== CSS_PROPERTIES.CSS_DATA_TYPE.number) return undefined
|
|
396
|
+
|
|
397
|
+
let min = parseFiniteDescriptor(registration.range.min)
|
|
398
|
+
let max = parseFiniteDescriptor(registration.range.max)
|
|
399
|
+
if (min !== undefined && max !== undefined && min >= max) {
|
|
400
|
+
min = undefined
|
|
401
|
+
max = undefined
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const step = parseFiniteDescriptor(registration.range.step)
|
|
405
|
+
const multiplier = step !== undefined && step > 0 ? step : undefined
|
|
406
|
+
|
|
407
|
+
if (min === undefined && max === undefined && multiplier === undefined) return undefined
|
|
408
|
+
|
|
409
|
+
return {
|
|
410
|
+
...(min !== undefined && { min: String(min) }),
|
|
411
|
+
...(max !== undefined && { max: String(max) }),
|
|
412
|
+
...(multiplier !== undefined && { multiplier: String(multiplier) }),
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
371
416
|
/**
|
|
372
417
|
* Queries each CSS parser API for the property type of a variable and returns
|
|
373
418
|
* the first defined result.
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
CssDataType,
|
|
10
10
|
NativePseudoClass,
|
|
11
11
|
RegisteredCustomProperty,
|
|
12
|
+
RegisteredPropertyRange,
|
|
12
13
|
StateClassRule,
|
|
13
14
|
} from './types'
|
|
14
15
|
|
|
@@ -501,6 +502,19 @@ function extractAtruleDescriptors(block: CssNode): Map<string, string> {
|
|
|
501
502
|
return descriptors
|
|
502
503
|
}
|
|
503
504
|
|
|
505
|
+
/** Picks the range descriptors out of the block; css-tree collects them like any other declaration. */
|
|
506
|
+
function extractRangeDescriptors(descriptors: Map<string, string>): RegisteredPropertyRange | undefined {
|
|
507
|
+
const min = descriptors.get('--min')
|
|
508
|
+
const max = descriptors.get('--max')
|
|
509
|
+
const step = descriptors.get('--step')
|
|
510
|
+
if (min === undefined && max === undefined && step === undefined) return undefined
|
|
511
|
+
return {
|
|
512
|
+
...(min !== undefined && { min }),
|
|
513
|
+
...(max !== undefined && { max }),
|
|
514
|
+
...(step !== undefined && { step }),
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
504
518
|
/**
|
|
505
519
|
* Walks the stylesheet for `@property` at-rules and builds a map of registered custom
|
|
506
520
|
* properties keyed by the `--`-prefixed variable name.
|
|
@@ -528,11 +542,13 @@ function parseRegisteredProperties(cssString: string): Map<string, RegisteredCus
|
|
|
528
542
|
if (!resolved) return
|
|
529
543
|
const { cssPropertyType, enumOptions } = resolved
|
|
530
544
|
const initialValue = descriptors.get('initial-value')
|
|
545
|
+
const range = extractRangeDescriptors(descriptors)
|
|
531
546
|
|
|
532
547
|
registered.set(varName, {
|
|
533
548
|
cssPropertyType,
|
|
534
549
|
...(enumOptions && { enumOptions }),
|
|
535
550
|
...(initialValue !== undefined && { defaultValue: initialValue }),
|
|
551
|
+
...(range && { range }),
|
|
536
552
|
})
|
|
537
553
|
},
|
|
538
554
|
})
|
|
@@ -47,6 +47,23 @@ export interface RegisteredCustomProperty {
|
|
|
47
47
|
* (`a | b | c`). Present only when `cssPropertyType` is `customEnum`.
|
|
48
48
|
*/
|
|
49
49
|
enumOptions?: string[]
|
|
50
|
+
/**
|
|
51
|
+
* The numeric range for the control, as raw descriptor strings. Validated at
|
|
52
|
+
* conversion time. Meaningful only when `cssPropertyType` is `number`.
|
|
53
|
+
*/
|
|
54
|
+
range?: RegisteredPropertyRange
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* `--min` / `--max` / `--step` descriptors an importer may emit inside an `@property`
|
|
59
|
+
* block. The `--` prefix keeps them in the author namespace: browsers ignore descriptors
|
|
60
|
+
* they don't know, so the CSS stays valid and CSS can't standardise these names out from
|
|
61
|
+
* under us.
|
|
62
|
+
*/
|
|
63
|
+
export interface RegisteredPropertyRange {
|
|
64
|
+
min?: string
|
|
65
|
+
max?: string
|
|
66
|
+
step?: string
|
|
50
67
|
}
|
|
51
68
|
|
|
52
69
|
export type NativePseudoClass = 'hover' | 'focus' | 'disabled' | 'invalid'
|