@wix/zero-config-implementation 1.96.0 → 1.98.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/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "registry": "https://registry.npmjs.org/",
5
5
  "access": "public"
6
6
  },
7
- "version": "1.96.0",
7
+ "version": "1.98.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": "d3a6a9c74757e90d4bf8667577b7d75107131acdedfc47058f1a69da"
109
+ "falconPackageHash": "741408430f509ba8b4ac3b6f2ff22c4fae1bf06d9f8b98e2a9e8e079"
110
110
  }
@@ -0,0 +1,32 @@
1
+ import { DATA } from '@wix/react-component-schema'
2
+ import { describe, expect, it } from 'vitest'
3
+ import type { PropWriteInfo, TrackingStores } from '../information-extractors/react/types'
4
+ import { collectUsedA11yAttributes } from './a11y-builder'
5
+
6
+ function propUsagesFor(propPaths: string[]): TrackingStores['propUsages'] {
7
+ const emptyWriteInfo = (): PropWriteInfo => ({ elements: new Map(), attributes: new Map() })
8
+ return new Map(propPaths.map((propPath) => [propPath, emptyWriteInfo()]))
9
+ }
10
+
11
+ describe('collectUsedA11yAttributes', () => {
12
+ it('returns an empty array when propUsages or propPath is unavailable', () => {
13
+ expect(collectUsedA11yAttributes(undefined, 'props.a11y')).toEqual([])
14
+ expect(collectUsedA11yAttributes(propUsagesFor(['props.a11y.role']), undefined)).toEqual([])
15
+ })
16
+
17
+ it('returns an empty array when no accessibility fields are recorded', () => {
18
+ expect(collectUsedA11yAttributes(propUsagesFor([]), 'props.a11y')).toEqual([])
19
+ })
20
+
21
+ it('allows only the current editor-configurable fields', () => {
22
+ const allAttributes = Object.values(DATA.A11Y_ATTRIBUTES).map((attribute) => `props.a11y.${attribute}`)
23
+
24
+ expect(collectUsedA11yAttributes(propUsagesFor(allAttributes), 'props.a11y')).toEqual(['ariaLabel', 'tag'])
25
+ })
26
+
27
+ it('keeps allowed fields in schema order', () => {
28
+ const propUsages = propUsagesFor(['props.a11y.tag', 'props.a11y.role', 'props.a11y.ariaLabel'])
29
+
30
+ expect(collectUsedA11yAttributes(propUsages, 'props.a11y')).toEqual(['ariaLabel', 'tag'])
31
+ })
32
+ })
@@ -1,25 +1,31 @@
1
1
  import { DATA } from '@wix/react-component-schema'
2
2
  import type { TrackingStores } from '../information-extractors/react'
3
3
 
4
+ type A11yAttribute = (typeof DATA.A11Y_ATTRIBUTES)[keyof typeof DATA.A11Y_ATTRIBUTES]
5
+
6
+ const EDITOR_CONFIGURABLE_A11Y_ATTRIBUTES: ReadonlySet<A11yAttribute> = new Set([
7
+ DATA.A11Y_ATTRIBUTES.ariaLabel,
8
+ DATA.A11Y_ATTRIBUTES.tag,
9
+ ])
10
+
4
11
  /**
5
- * Walks the known A11y attributes (from DATA.A11Y_ATTRIBUTES) in deterministic
6
- * enum order and returns the subset whose `${propPath}.${attribute}` key was
7
- * recorded in propUsages by the prop-tracker. When propUsages or propPath is
8
- * unavailable, returns an empty array — per the schema, an empty list means
9
- * "all possible A11Y values", which is a safe default.
12
+ * Returns the editor-configurable attributes the component reads, in schema
13
+ * order. The caller must omit the a11y data item when this returns an empty
14
+ * list because the manifest schema interprets an empty list as all attributes.
10
15
  */
11
16
  export function collectUsedA11yAttributes(
12
17
  propUsages: TrackingStores['propUsages'] | undefined,
13
18
  propPath: string | undefined,
14
- ): (typeof DATA.A11Y_ATTRIBUTES)[keyof typeof DATA.A11Y_ATTRIBUTES][] {
19
+ ): A11yAttribute[] {
15
20
  if (!propUsages || !propPath) return []
16
21
 
17
- const used: (typeof DATA.A11Y_ATTRIBUTES)[keyof typeof DATA.A11Y_ATTRIBUTES][] = []
22
+ const usedAttributes: A11yAttribute[] = []
18
23
  for (const attribute of Object.values(DATA.A11Y_ATTRIBUTES)) {
19
- if (attribute === DATA.A11Y_ATTRIBUTES.Unknown_AriaAttributes) continue
24
+ if (!EDITOR_CONFIGURABLE_A11Y_ATTRIBUTES.has(attribute)) continue
20
25
  if (propUsages.has(`${propPath}.${attribute}`)) {
21
- used.push(attribute)
26
+ usedAttributes.push(attribute)
22
27
  }
23
28
  }
24
- return used
29
+
30
+ return usedAttributes
25
31
  }
@@ -43,17 +43,71 @@ function createElementWithDisplayMatcher(tag: string, matcherData: MatchedCssDat
43
43
  }
44
44
  }
45
45
 
46
- function createComponent(rootElement: ExtractedElement): ComponentInfoWithCss {
46
+ function createComponent(rootElement: ExtractedElement, renderDisplayElementAsChild = false): ComponentInfoWithCss {
47
+ const componentRoot = renderDisplayElementAsChild
48
+ ? {
49
+ traceId: 'trace-container',
50
+ name: 'container',
51
+ tag: 'div',
52
+ attributes: {},
53
+ extractorData: new Map<string, unknown>(),
54
+ children: [{ ...rootElement, traceId: 'trace-display', name: 'display' }],
55
+ }
56
+ : rootElement
57
+
47
58
  return {
48
59
  componentName: 'DisplayComponent',
49
60
  props: {},
50
- elements: [rootElement],
61
+ elements: [componentRoot],
51
62
  propUsages: new Map(),
52
63
  css: [],
53
64
  varUsedByTraceId: new Map(),
54
65
  }
55
66
  }
56
67
 
68
+ function createComponentWithA11yUsages(propPaths: string[]): ComponentInfoWithCss {
69
+ return {
70
+ ...createComponent(createElementWithDisplayMatcher('div', matcherDataFromCss(''))),
71
+ props: {
72
+ a11y: {
73
+ name: 'a11y',
74
+ type: 'A11y',
75
+ required: false,
76
+ resolvedType: { kind: 'semantic', value: 'A11y', source: '@wix/editor-react-types' },
77
+ propPath: 'props.a11y',
78
+ logicOnly: false,
79
+ },
80
+ },
81
+ propUsages: new Map(propPaths.map((propPath) => [propPath, { elements: new Map(), attributes: new Map() }])),
82
+ }
83
+ }
84
+
85
+ function getDisplayProperty(result: ReturnType<typeof toEditorReactComponent>) {
86
+ return result.editorElement?.elements?.display?.inlineElement?.cssProperties?.display
87
+ }
88
+
89
+ describe('toEditorReactComponent accessibility conversion', () => {
90
+ it('omits the a11y item when the component reads only component-owned fields', () => {
91
+ const component = createComponentWithA11yUsages(['props.a11y.role', 'props.a11y.tabIndex'])
92
+
93
+ expect(toEditorReactComponent(component).editorElement?.data?.a11y).toBeUndefined()
94
+ })
95
+
96
+ it('emits only the allowed fields when reads are mixed', () => {
97
+ const component = createComponentWithA11yUsages([
98
+ 'props.a11y.role',
99
+ 'props.a11y.ariaLabel',
100
+ 'props.a11y.ariaExpanded',
101
+ 'props.a11y.tag',
102
+ ])
103
+
104
+ expect(toEditorReactComponent(component).editorElement?.data?.a11y).toMatchObject({
105
+ dataType: 'a11y',
106
+ a11y: { attributes: ['ariaLabel', 'tag'] },
107
+ })
108
+ })
109
+ })
110
+
57
111
  function createSemanticElement({
58
112
  traceId,
59
113
  name,
@@ -76,6 +130,14 @@ function createSemanticElement({
76
130
  }
77
131
 
78
132
  describe('toEditorReactComponent display conversion', () => {
133
+ it('omits display from the root editor element', () => {
134
+ const rootElement = createElementWithDisplayMatcher('div', matcherDataFromCss('display: flex'))
135
+
136
+ const result = toEditorReactComponent(createComponent(rootElement))
137
+
138
+ expect(result.editorElement?.cssProperties?.display).toBeUndefined()
139
+ })
140
+
79
141
  it('resolves local display fallback chains before emitting the manifest display value', () => {
80
142
  const rootElement = createElementWithDisplayMatcher(
81
143
  'span',
@@ -84,10 +146,10 @@ describe('toEditorReactComponent display conversion', () => {
84
146
  ),
85
147
  )
86
148
 
87
- const result = toEditorReactComponent(createComponent(rootElement))
149
+ const result = toEditorReactComponent(createComponent(rootElement, true))
88
150
  expect(result.editorElement).toBeDefined()
89
151
 
90
- expect(result.editorElement?.cssProperties?.display).toEqual({
152
+ expect(getDisplayProperty(result)).toEqual({
91
153
  display: {
92
154
  displayValues: [CSS_PROPERTIES.DISPLAY_VALUE.none, 'inlineFlex'],
93
155
  },
@@ -97,10 +159,10 @@ describe('toEditorReactComponent display conversion', () => {
97
159
  it('keeps using the tag default when no display declaration exists', () => {
98
160
  const rootElement = createElementWithDisplayMatcher('span', matcherDataFromCss(''))
99
161
 
100
- const result = toEditorReactComponent(createComponent(rootElement))
162
+ const result = toEditorReactComponent(createComponent(rootElement, true))
101
163
  expect(result.editorElement).toBeDefined()
102
164
 
103
- expect(result.editorElement?.cssProperties?.display).toEqual({
165
+ expect(getDisplayProperty(result)).toEqual({
104
166
  display: {
105
167
  displayValues: [CSS_PROPERTIES.DISPLAY_VALUE.none, 'inline'],
106
168
  },
@@ -127,9 +189,9 @@ describe('toEditorReactComponent display conversion', () => {
127
189
  it.each(tagDefaults)('maps a <%s> tag default (%s) onto its enum name', (tag, _cssDisplay, expectedEnumValue) => {
128
190
  const rootElement = createElementWithDisplayMatcher(tag, matcherDataFromCss(''))
129
191
 
130
- const result = toEditorReactComponent(createComponent(rootElement))
192
+ const result = toEditorReactComponent(createComponent(rootElement, true))
131
193
 
132
- expect(result.editorElement?.cssProperties?.display).toEqual({
194
+ expect(getDisplayProperty(result)).toEqual({
133
195
  display: {
134
196
  displayValues: [CSS_PROPERTIES.DISPLAY_VALUE.none, expectedEnumValue],
135
197
  },
@@ -138,14 +200,14 @@ describe('toEditorReactComponent display conversion', () => {
138
200
 
139
201
  it('treats an explicit `display: table-cell` declaration the same as the tag default', () => {
140
202
  const fromTag = toEditorReactComponent(
141
- createComponent(createElementWithDisplayMatcher('td', matcherDataFromCss(''))),
203
+ createComponent(createElementWithDisplayMatcher('td', matcherDataFromCss('')), true),
142
204
  )
143
205
  const fromCss = toEditorReactComponent(
144
- createComponent(createElementWithDisplayMatcher('div', matcherDataFromCss('display: table-cell'))),
206
+ createComponent(createElementWithDisplayMatcher('div', matcherDataFromCss('display: table-cell')), true),
145
207
  )
146
208
 
147
- expect(fromCss.editorElement?.cssProperties?.display).toEqual(fromTag.editorElement?.cssProperties?.display)
148
- expect(fromCss.editorElement?.cssProperties?.display).toEqual({
209
+ expect(getDisplayProperty(fromCss)).toEqual(getDisplayProperty(fromTag))
210
+ expect(getDisplayProperty(fromCss)).toEqual({
149
211
  display: {
150
212
  displayValues: [CSS_PROPERTIES.DISPLAY_VALUE.none, CSS_PROPERTIES.DISPLAY_VALUE.tableCell],
151
213
  },
@@ -155,9 +217,9 @@ describe('toEditorReactComponent display conversion', () => {
155
217
  it('still maps kebab-case values that do have an enum representation', () => {
156
218
  const rootElement = createElementWithDisplayMatcher('div', matcherDataFromCss('display: inline-block'))
157
219
 
158
- const result = toEditorReactComponent(createComponent(rootElement))
220
+ const result = toEditorReactComponent(createComponent(rootElement, true))
159
221
 
160
- expect(result.editorElement?.cssProperties?.display).toEqual({
222
+ expect(getDisplayProperty(result)).toEqual({
161
223
  display: {
162
224
  displayValues: [CSS_PROPERTIES.DISPLAY_VALUE.none, CSS_PROPERTIES.DISPLAY_VALUE.inlineBlock],
163
225
  },
@@ -169,8 +231,8 @@ describe('toEditorReactComponent display conversion', () => {
169
231
 
170
232
  for (const [tag] of tagDefaults) {
171
233
  const rootElement = createElementWithDisplayMatcher(tag, matcherDataFromCss(''))
172
- const result = toEditorReactComponent(createComponent(rootElement))
173
- const emittedValues = result.editorElement?.cssProperties?.display?.display?.displayValues ?? []
234
+ const result = toEditorReactComponent(createComponent(rootElement, true))
235
+ const emittedValues = getDisplayProperty(result)?.display?.displayValues ?? []
174
236
 
175
237
  expect(emittedValues.filter((displayValue) => !allowedValues.has(displayValue))).toEqual([])
176
238
  }
@@ -195,9 +257,9 @@ describe('toEditorReactComponent display conversion', () => {
195
257
  'div',
196
258
  matcherDataFromCss(`display: ${toCssSpelling(enumValue)}`),
197
259
  )
198
- const result = toEditorReactComponent(createComponent(rootElement))
260
+ const result = toEditorReactComponent(createComponent(rootElement, true))
199
261
 
200
- return result.editorElement?.cssProperties?.display?.display?.displayValues?.[1] !== enumValue
262
+ return getDisplayProperty(result)?.display?.displayValues?.[1] !== enumValue
201
263
  })
202
264
 
203
265
  expect(unreachable).toEqual([])
@@ -10,7 +10,7 @@ import type {
10
10
  ElementItem,
11
11
  States,
12
12
  } from '@wix/react-component-schema'
13
- import { CSS_PROPERTIES, ELEMENTS } from '@wix/react-component-schema'
13
+ import { CSS_PROPERTIES, DATA, ELEMENTS } from '@wix/react-component-schema'
14
14
  import { camelCase } from 'case-anything'
15
15
  import { IoError } from '../errors'
16
16
  import { buildRefElementManifestKey } from '../extensions/ref-elements/path-utils'
@@ -127,7 +127,7 @@ function buildEditorElement(
127
127
  component.innerElementProps,
128
128
  component.propUsages,
129
129
  ),
130
- cssProperties: buildCssProperties(rootElement),
130
+ cssProperties: buildCssProperties(rootElement, true),
131
131
  cssCustomProperties: rootCustomProps,
132
132
  ...(rootStates && { states: rootStates }),
133
133
  ...(displayGroups && { displayGroups }),
@@ -184,6 +184,7 @@ function buildData(
184
184
 
185
185
  const result = buildDataItem(prop, defaultValue, propUsages, prop.propPath)
186
186
  if (result.isErr()) throw result.error
187
+ if (result.value.dataType === DATA.DATA_TYPE.a11y && !result.value.a11y?.attributes?.length) continue
187
188
  // ActiveItemIndex props default to 0 when no default is specified
188
189
  if (prop.activeItemIndexTarget && result.value.defaultValue === undefined) {
189
190
  result.value.defaultValue = 0
@@ -223,7 +224,7 @@ function buildElements(
223
224
  const semanticClass = getSemanticBlockName(element)
224
225
 
225
226
  const data = innerEntry && propUsages ? buildData(innerEntry.props, propUsages) : undefined
226
- const cssProps = buildCssProperties(element)
227
+ const cssProps = buildCssProperties(element, false)
227
228
  const cssCustomProps = nearestCommonAncestorCustomProps.get(element.traceId) ?? {}
228
229
 
229
230
  // Inner-element custom states come from custom-class triggers only; prop
@@ -562,7 +563,10 @@ function toDisplayEnumValue(cssValue: string): DisplayEnumValue {
562
563
  return (CSS_DISPLAY_TO_ENUM[cssValue] ?? cssValue) as DisplayEnumValue
563
564
  }
564
565
 
565
- function buildCssProperties(element: ExtractedElement | undefined): Record<string, CssPropertyItem> {
566
+ function buildCssProperties(
567
+ element: ExtractedElement | undefined,
568
+ isRootElement: boolean,
569
+ ): Record<string, CssPropertyItem> {
566
570
  const result: Record<string, CssPropertyItem> = {}
567
571
 
568
572
  const cssData = element?.extractorData.get('css-properties') as CssPropertiesData | undefined
@@ -573,7 +577,9 @@ function buildCssProperties(element: ExtractedElement | undefined): Record<strin
573
577
  const cssPropertyValues = element ? getMatchedPropertyValues(element) : new Map<string, string>()
574
578
  for (const propName of decidedProperties) {
575
579
  if (propName === CSS_PROPERTY_TYPE.display && element) {
576
- result[propName] = buildDisplayProperty(element)
580
+ if (!isRootElement) {
581
+ result[propName] = buildDisplayProperty(element)
582
+ }
577
583
  continue
578
584
  }
579
585
  const resolvedValue = resolveLonghand(propName, cssPropertyValues)