@wix/zero-config-implementation 1.92.0 → 1.94.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.js +6737 -6727
- package/package.json +3 -3
- package/src/converters/to-editor-component.test.ts +97 -0
- package/src/converters/to-editor-component.ts +21 -11
- package/src/information-extractors/react/extractors/css-properties.test.ts +32 -5
- package/src/information-extractors/react/extractors/css-properties.ts +31 -17
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.94.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",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
}
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@wix/react-component-schema": "1.
|
|
42
|
+
"@wix/react-component-schema": "1.9.0",
|
|
43
43
|
"@wix/seo-service": "^1.9.0",
|
|
44
44
|
"@wix/services-manager": "^1.0.7",
|
|
45
45
|
"@wix/services-manager-react": "^1.0.8",
|
|
@@ -106,5 +106,5 @@
|
|
|
106
106
|
]
|
|
107
107
|
}
|
|
108
108
|
},
|
|
109
|
-
"falconPackageHash": "
|
|
109
|
+
"falconPackageHash": "c359f3991c9148a6bb0473cbb1eef6bfef7fd8401e45720aaed8b44c"
|
|
110
110
|
}
|
|
@@ -107,6 +107,103 @@ describe('toEditorReactComponent display conversion', () => {
|
|
|
107
107
|
})
|
|
108
108
|
})
|
|
109
109
|
|
|
110
|
+
// CSS display values whose spelling differs from the enum's. Emitting one
|
|
111
|
+
// unmapped makes DevCenter reject the whole components override, permanently
|
|
112
|
+
// blocking Checkpoint for the project (ECL-18531).
|
|
113
|
+
describe('display value enum mapping', () => {
|
|
114
|
+
const { DISPLAY_VALUE } = CSS_PROPERTIES
|
|
115
|
+
const tagDefaults: [tag: string, cssDisplay: string, enumValue: string][] = [
|
|
116
|
+
['td', 'table-cell', DISPLAY_VALUE.tableCell],
|
|
117
|
+
['th', 'table-cell', DISPLAY_VALUE.tableCell],
|
|
118
|
+
['tr', 'table-row', DISPLAY_VALUE.tableRow],
|
|
119
|
+
['tbody', 'table-row-group', DISPLAY_VALUE.tableRowGroup],
|
|
120
|
+
['thead', 'table-header-group', DISPLAY_VALUE.tableHeaderGroup],
|
|
121
|
+
['tfoot', 'table-footer-group', DISPLAY_VALUE.tableFooterGroup],
|
|
122
|
+
['caption', 'table-caption', DISPLAY_VALUE.tableCaption],
|
|
123
|
+
['col', 'table-column', DISPLAY_VALUE.tableColumn],
|
|
124
|
+
['colgroup', 'table-column-group', DISPLAY_VALUE.tableColumnGroup],
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
it.each(tagDefaults)('maps a <%s> tag default (%s) onto its enum name', (tag, _cssDisplay, expectedEnumValue) => {
|
|
128
|
+
const rootElement = createElementWithDisplayMatcher(tag, matcherDataFromCss(''))
|
|
129
|
+
|
|
130
|
+
const result = toEditorReactComponent(createComponent(rootElement))
|
|
131
|
+
|
|
132
|
+
expect(result.editorElement?.cssProperties?.display).toEqual({
|
|
133
|
+
display: {
|
|
134
|
+
displayValues: [CSS_PROPERTIES.DISPLAY_VALUE.none, expectedEnumValue],
|
|
135
|
+
},
|
|
136
|
+
})
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it('treats an explicit `display: table-cell` declaration the same as the tag default', () => {
|
|
140
|
+
const fromTag = toEditorReactComponent(
|
|
141
|
+
createComponent(createElementWithDisplayMatcher('td', matcherDataFromCss(''))),
|
|
142
|
+
)
|
|
143
|
+
const fromCss = toEditorReactComponent(
|
|
144
|
+
createComponent(createElementWithDisplayMatcher('div', matcherDataFromCss('display: table-cell'))),
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
expect(fromCss.editorElement?.cssProperties?.display).toEqual(fromTag.editorElement?.cssProperties?.display)
|
|
148
|
+
expect(fromCss.editorElement?.cssProperties?.display).toEqual({
|
|
149
|
+
display: {
|
|
150
|
+
displayValues: [CSS_PROPERTIES.DISPLAY_VALUE.none, CSS_PROPERTIES.DISPLAY_VALUE.tableCell],
|
|
151
|
+
},
|
|
152
|
+
})
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('still maps kebab-case values that do have an enum representation', () => {
|
|
156
|
+
const rootElement = createElementWithDisplayMatcher('div', matcherDataFromCss('display: inline-block'))
|
|
157
|
+
|
|
158
|
+
const result = toEditorReactComponent(createComponent(rootElement))
|
|
159
|
+
|
|
160
|
+
expect(result.editorElement?.cssProperties?.display).toEqual({
|
|
161
|
+
display: {
|
|
162
|
+
displayValues: [CSS_PROPERTIES.DISPLAY_VALUE.none, CSS_PROPERTIES.DISPLAY_VALUE.inlineBlock],
|
|
163
|
+
},
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('never emits a display value outside the enum for any known tag default', () => {
|
|
168
|
+
const allowedValues = new Set<string>(Object.values(CSS_PROPERTIES.DISPLAY_VALUE))
|
|
169
|
+
|
|
170
|
+
for (const [tag] of tagDefaults) {
|
|
171
|
+
const rootElement = createElementWithDisplayMatcher(tag, matcherDataFromCss(''))
|
|
172
|
+
const result = toEditorReactComponent(createComponent(rootElement))
|
|
173
|
+
const emittedValues = result.editorElement?.cssProperties?.display?.display?.displayValues ?? []
|
|
174
|
+
|
|
175
|
+
expect(emittedValues.filter((displayValue) => !allowedValues.has(displayValue))).toEqual([])
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Completeness check driven by the enum itself, so a value added in a future
|
|
181
|
+
* `@wix/component-protocol` bump cannot sit unmapped — which is the gap that
|
|
182
|
+
* made ECL-18531 possible. Values whose CSS spelling already matches the enum
|
|
183
|
+
* name need no entry; those that differ must be mapped.
|
|
184
|
+
*
|
|
185
|
+
* Skips names containing `_`: `UNKNOWN_DisplayValue` and the deprecated
|
|
186
|
+
* snake_case aliases, none of which are CSS spellings.
|
|
187
|
+
*/
|
|
188
|
+
it('reaches every enum value from its CSS spelling', () => {
|
|
189
|
+
const toCssSpelling = (enumValue: string) => enumValue.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`)
|
|
190
|
+
|
|
191
|
+
const unreachable = Object.values(CSS_PROPERTIES.DISPLAY_VALUE)
|
|
192
|
+
.filter((enumValue) => !enumValue.includes('_'))
|
|
193
|
+
.filter((enumValue) => {
|
|
194
|
+
const rootElement = createElementWithDisplayMatcher(
|
|
195
|
+
'div',
|
|
196
|
+
matcherDataFromCss(`display: ${toCssSpelling(enumValue)}`),
|
|
197
|
+
)
|
|
198
|
+
const result = toEditorReactComponent(createComponent(rootElement))
|
|
199
|
+
|
|
200
|
+
return result.editorElement?.cssProperties?.display?.display?.displayValues?.[1] !== enumValue
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
expect(unreachable).toEqual([])
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
|
|
110
207
|
it('omits the nearest matching ancestor class prefix from inner element display names', () => {
|
|
111
208
|
const rootElement = createSemanticElement({
|
|
112
209
|
traceId: 'trace-root',
|
|
@@ -541,20 +541,30 @@ function getMatchedPropertyValues(element: ExtractedElement): Map<string, string
|
|
|
541
541
|
return values
|
|
542
542
|
}
|
|
543
543
|
|
|
544
|
-
const
|
|
545
|
-
'inline-block': 'inlineBlock',
|
|
546
|
-
'inline-flex': 'inlineFlex',
|
|
547
|
-
'inline-grid': 'inlineGrid',
|
|
548
|
-
'inline-table': 'inlineTable',
|
|
549
|
-
'list-item': 'listItem',
|
|
550
|
-
'flow-root': 'flowRoot',
|
|
551
|
-
}
|
|
544
|
+
const { CSS_PROPERTY_TYPE, DISPLAY_VALUE } = CSS_PROPERTIES
|
|
552
545
|
|
|
553
|
-
|
|
554
|
-
|
|
546
|
+
type DisplayEnumValue = NonNullable<Display['displayValues']>[number]
|
|
547
|
+
|
|
548
|
+
const CSS_DISPLAY_TO_ENUM: Record<string, DisplayEnumValue> = {
|
|
549
|
+
'inline-block': DISPLAY_VALUE.inlineBlock,
|
|
550
|
+
'inline-flex': DISPLAY_VALUE.inlineFlex,
|
|
551
|
+
'inline-grid': DISPLAY_VALUE.inlineGrid,
|
|
552
|
+
'inline-table': DISPLAY_VALUE.inlineTable,
|
|
553
|
+
'list-item': DISPLAY_VALUE.listItem,
|
|
554
|
+
'flow-root': DISPLAY_VALUE.flowRoot,
|
|
555
|
+
'table-row-group': DISPLAY_VALUE.tableRowGroup,
|
|
556
|
+
'table-header-group': DISPLAY_VALUE.tableHeaderGroup,
|
|
557
|
+
'table-footer-group': DISPLAY_VALUE.tableFooterGroup,
|
|
558
|
+
'table-row': DISPLAY_VALUE.tableRow,
|
|
559
|
+
'table-cell': DISPLAY_VALUE.tableCell,
|
|
560
|
+
'table-column-group': DISPLAY_VALUE.tableColumnGroup,
|
|
561
|
+
'table-column': DISPLAY_VALUE.tableColumn,
|
|
562
|
+
'table-caption': DISPLAY_VALUE.tableCaption,
|
|
555
563
|
}
|
|
556
564
|
|
|
557
|
-
|
|
565
|
+
function toDisplayEnumValue(cssValue: string): DisplayEnumValue {
|
|
566
|
+
return (CSS_DISPLAY_TO_ENUM[cssValue] ?? cssValue) as DisplayEnumValue
|
|
567
|
+
}
|
|
558
568
|
|
|
559
569
|
function buildCssProperties(element: ExtractedElement | undefined): Record<string, CssPropertyItem> {
|
|
560
570
|
const result: Record<string, CssPropertyItem> = {}
|
|
@@ -38,7 +38,7 @@ function createElementWithCssProperties(
|
|
|
38
38
|
): ExtractedElement {
|
|
39
39
|
const extractorData = new Map<string, unknown>()
|
|
40
40
|
extractorData.set('css-matcher', matcherDataFromCss(declarations))
|
|
41
|
-
extractorData.set('css-properties', { relevant: getCssPropertiesForTag(tag, attributes
|
|
41
|
+
extractorData.set('css-properties', { relevant: getCssPropertiesForTag(tag, attributes) })
|
|
42
42
|
|
|
43
43
|
return {
|
|
44
44
|
traceId: 'trace-1',
|
|
@@ -173,11 +173,38 @@ describe('getCssPropertiesForTag', () => {
|
|
|
173
173
|
expect(relevantProperties).not.toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.font)
|
|
174
174
|
})
|
|
175
175
|
|
|
176
|
-
it('
|
|
177
|
-
const
|
|
176
|
+
it('gives interactive controls both box and text properties', () => {
|
|
177
|
+
for (const tag of ['button', 'select', 'textarea']) {
|
|
178
|
+
const relevantProperties = getCssPropertiesForTag(tag)
|
|
179
|
+
|
|
180
|
+
expect(relevantProperties).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.paddingTop)
|
|
181
|
+
expect(relevantProperties).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.font)
|
|
182
|
+
expect(relevantProperties).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.color)
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
it('treats an input with no type as text-bearing', () => {
|
|
187
|
+
const relevantProperties = getCssPropertiesForTag('input')
|
|
178
188
|
|
|
179
189
|
expect(relevantProperties).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.paddingTop)
|
|
180
|
-
expect(relevantProperties).
|
|
190
|
+
expect(relevantProperties).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.font)
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it('treats text-entry and button-like input types as text-bearing', () => {
|
|
194
|
+
for (const inputType of ['text', 'email', 'password', 'search', 'number', 'submit', 'button', 'reset']) {
|
|
195
|
+
const relevantProperties = getCssPropertiesForTag('input', { type: inputType })
|
|
196
|
+
|
|
197
|
+
expect(relevantProperties).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.font)
|
|
198
|
+
}
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
it('keeps typography off input types that render no text of their own', () => {
|
|
202
|
+
for (const inputType of ['checkbox', 'radio', 'range', 'color', 'file', 'image', 'hidden']) {
|
|
203
|
+
const relevantProperties = getCssPropertiesForTag('input', { type: inputType })
|
|
204
|
+
|
|
205
|
+
expect(relevantProperties).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.paddingTop)
|
|
206
|
+
expect(relevantProperties).not.toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.font)
|
|
207
|
+
}
|
|
181
208
|
})
|
|
182
209
|
|
|
183
210
|
it('keeps media elements out of box properties even when their default display is non-inline', () => {
|
|
@@ -203,7 +230,7 @@ describe('getCssPropertiesForTag', () => {
|
|
|
203
230
|
})
|
|
204
231
|
|
|
205
232
|
it('keeps role-based heading text-only by default', () => {
|
|
206
|
-
const relevantProperties = getCssPropertiesForTag('div', 'heading')
|
|
233
|
+
const relevantProperties = getCssPropertiesForTag('div', { role: 'heading' })
|
|
207
234
|
|
|
208
235
|
expect(relevantProperties).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.font)
|
|
209
236
|
expect(relevantProperties).not.toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.paddingTop)
|
|
@@ -19,12 +19,18 @@ export interface CssPropertiesData {
|
|
|
19
19
|
relevant: string[]
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
export interface SemanticAttributes {
|
|
23
|
+
role?: string
|
|
24
|
+
type?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
22
27
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
23
28
|
// Semantic Traits
|
|
24
29
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
25
30
|
|
|
26
31
|
/**
|
|
27
|
-
* Text & Typography - elements where readability and hierarchy are primary
|
|
32
|
+
* Text & Typography - elements where readability and hierarchy are primary.
|
|
33
|
+
* Form controls render their own text; `input` varies by type (see below).
|
|
28
34
|
*/
|
|
29
35
|
const TEXT_TAGS = new Set([
|
|
30
36
|
'h1',
|
|
@@ -42,8 +48,14 @@ const TEXT_TAGS = new Set([
|
|
|
42
48
|
'em',
|
|
43
49
|
'b',
|
|
44
50
|
'i',
|
|
51
|
+
'button',
|
|
52
|
+
'select',
|
|
53
|
+
'textarea',
|
|
45
54
|
])
|
|
46
55
|
|
|
56
|
+
/** Anything else - including a missing or invalid `type` - defaults to text. */
|
|
57
|
+
const NON_TEXT_INPUT_TYPES = new Set(['checkbox', 'radio', 'range', 'color', 'file', 'image', 'hidden'])
|
|
58
|
+
|
|
47
59
|
const DEFAULT_NON_BOX_TEXT_TAGS = new Set(['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'])
|
|
48
60
|
|
|
49
61
|
/**
|
|
@@ -107,13 +119,15 @@ const MEDIA_CSS_PROPERTIES = [
|
|
|
107
119
|
CSS_PROPERTY_TYPE.boxShadow,
|
|
108
120
|
]
|
|
109
121
|
|
|
110
|
-
function hasTextSemantics(tag: string,
|
|
122
|
+
function hasTextSemantics(tag: string, attributes: SemanticAttributes = {}): boolean {
|
|
123
|
+
const normalizedRole = attributes.role?.toLowerCase()
|
|
124
|
+
if (normalizedRole === 'heading' || normalizedRole === 'paragraph') {
|
|
125
|
+
return true
|
|
126
|
+
}
|
|
127
|
+
|
|
111
128
|
const normalizedTag = tag.toLowerCase()
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
if (normalizedRole === 'heading' || normalizedRole === 'paragraph') {
|
|
115
|
-
return true
|
|
116
|
-
}
|
|
129
|
+
if (normalizedTag === 'input') {
|
|
130
|
+
return !NON_TEXT_INPUT_TYPES.has((attributes.type ?? 'text').toLowerCase())
|
|
117
131
|
}
|
|
118
132
|
return TEXT_TAGS.has(normalizedTag)
|
|
119
133
|
}
|
|
@@ -135,11 +149,11 @@ function hasMediaSemantics(tag: string, role?: string): boolean {
|
|
|
135
149
|
* Note: hasTextContent determination is deferred to tree building since
|
|
136
150
|
* we can't know during createElement if a child will have text content.
|
|
137
151
|
*/
|
|
138
|
-
export function getCssPropertiesForTag(tag: string,
|
|
152
|
+
export function getCssPropertiesForTag(tag: string, attributes: SemanticAttributes = {}): string[] {
|
|
139
153
|
const properties: string[] = []
|
|
140
|
-
const hasTextProperties = hasTextSemantics(tag,
|
|
141
|
-
const hasMediaProperties = hasMediaSemantics(tag, role)
|
|
142
|
-
const hasBoxProperties = hasDefaultBoxProperties(tag, role) && !hasMediaProperties
|
|
154
|
+
const hasTextProperties = hasTextSemantics(tag, attributes)
|
|
155
|
+
const hasMediaProperties = hasMediaSemantics(tag, attributes.role)
|
|
156
|
+
const hasBoxProperties = hasDefaultBoxProperties(tag, attributes.role) && !hasMediaProperties
|
|
143
157
|
|
|
144
158
|
if (hasTextProperties) {
|
|
145
159
|
properties.push(...TEXT_CSS_PROPERTIES)
|
|
@@ -465,12 +479,11 @@ export function enrichGapProperties(elements: ExtractedElement[]): ExtractedElem
|
|
|
465
479
|
*/
|
|
466
480
|
export function enrichContainerProperties(elements: ExtractedElement[]): ExtractedElement[] {
|
|
467
481
|
return elements.map((element) => {
|
|
468
|
-
|
|
469
|
-
if (hasTextSemantics(element.tag, role)) {
|
|
482
|
+
if (hasTextSemantics(element.tag, element.attributes)) {
|
|
470
483
|
const matcherData = element.extractorData.get('css-matcher') as MatchedCssData | undefined
|
|
471
484
|
const cssData = element.extractorData.get('css-properties') as CssPropertiesData | undefined
|
|
472
485
|
|
|
473
|
-
if (cssData && hasEffectiveContainerDisplay(element.tag, role, matcherData)) {
|
|
486
|
+
if (cssData && hasEffectiveContainerDisplay(element.tag, element.attributes.role, matcherData)) {
|
|
474
487
|
element.extractorData.set('css-properties', {
|
|
475
488
|
relevant: addBoxProperties(cssData.relevant),
|
|
476
489
|
})
|
|
@@ -500,9 +513,10 @@ export function createCssPropertiesExtractor(): ReactExtractor {
|
|
|
500
513
|
if (!event.isDomElement || !event.tag || !event.traceId) return
|
|
501
514
|
|
|
502
515
|
const { tag, props, traceId, store } = event
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
|
|
516
|
+
const relevant = getCssPropertiesForTag(tag, {
|
|
517
|
+
role: props.role as string | undefined,
|
|
518
|
+
type: props.type as string | undefined,
|
|
519
|
+
})
|
|
506
520
|
const data: CssPropertiesData = { relevant }
|
|
507
521
|
|
|
508
522
|
store.set(traceId, 'css-properties', data)
|