@wix/zero-config-implementation 1.103.0 → 1.105.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.103.0",
7
+ "version": "1.105.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": "37bdab4bfdcda072c3c9d9edbfc88017a9270a42765e4b943aa0f51b"
109
+ "falconPackageHash": "0c8ba866b638c1cacaeaecfc2e82c36a40e6307f5e3896c922c1dc25"
110
110
  }
@@ -117,7 +117,7 @@ function applyResolvedTypeToDataItem(
117
117
  return handleObjectType(dataItem, resolvedType, propUsages, propPath)
118
118
 
119
119
  case 'union':
120
- return handleUnionType(dataItem, resolvedType, propInfo)
120
+ return handleUnionType({ dataItem, resolvedType, propInfo, propUsages, propPath })
121
121
 
122
122
  case 'function':
123
123
  handleFunctionType(dataItem, propInfo, bindings)
@@ -290,11 +290,19 @@ function handleObjectType(
290
290
  * Handles union types by detecting special patterns (boolean literals,
291
291
  * string literal enums) or falling back to the first valid member type.
292
292
  */
293
- function handleUnionType(
294
- dataItem: DataItem,
295
- resolvedType: ResolvedType,
296
- propInfo: PropInfo,
297
- ): Result<void, ParseErrorInstance> {
293
+ function handleUnionType({
294
+ dataItem,
295
+ resolvedType,
296
+ propInfo,
297
+ propUsages,
298
+ propPath,
299
+ }: {
300
+ dataItem: DataItem
301
+ resolvedType: ResolvedType
302
+ propInfo: PropInfo
303
+ propUsages?: TrackingStores['propUsages']
304
+ propPath?: string
305
+ }): Result<void, ParseErrorInstance> {
298
306
  if (!resolvedType.types || resolvedType.types.length === 0) {
299
307
  dataItem.dataType = DATA_TYPE.text
300
308
  dataItem.text = {}
@@ -341,7 +349,7 @@ function handleUnionType(
341
349
  // For other unions, use the first valid type
342
350
  const firstValidType = validTypes[0]
343
351
  if (firstValidType) {
344
- return applyResolvedTypeToDataItem(dataItem, firstValidType, propInfo)
352
+ return applyResolvedTypeToDataItem(dataItem, firstValidType, propInfo, propUsages, propPath)
345
353
  }
346
354
 
347
355
  dataItem.dataType = DATA_TYPE.text
@@ -374,6 +374,48 @@ describe('toEditorReactComponent — cssProperties defaultValue', () => {
374
374
  expect(result.editorElement?.cssProperties?.overflow).toEqual({ defaultValue: 'hidden' })
375
375
  })
376
376
 
377
+ it("excludes margin from the root element but keeps it on inner elements (editor's layout positions the root on stage)", () => {
378
+ const marginProperties = [
379
+ CSS_PROPERTIES.CSS_PROPERTY_TYPE.margin,
380
+ CSS_PROPERTIES.CSS_PROPERTY_TYPE.marginTop,
381
+ CSS_PROPERTIES.CSS_PROPERTY_TYPE.marginRight,
382
+ CSS_PROPERTIES.CSS_PROPERTY_TYPE.marginBottom,
383
+ CSS_PROPERTIES.CSS_PROPERTY_TYPE.marginLeft,
384
+ CSS_PROPERTIES.CSS_PROPERTY_TYPE.marginInlineStart,
385
+ CSS_PROPERTIES.CSS_PROPERTY_TYPE.marginInlineEnd,
386
+ ]
387
+ const createExtractorData = () => {
388
+ const extractorData = new Map<string, unknown>()
389
+ extractorData.set('css-matcher', matcherDataFromCss(''))
390
+ extractorData.set('css-properties', { relevant: marginProperties })
391
+ return extractorData
392
+ }
393
+
394
+ const childElement: ExtractedElement = {
395
+ traceId: 'trace-icon',
396
+ name: 'icon',
397
+ tag: 'div',
398
+ attributes: {},
399
+ extractorData: createExtractorData(),
400
+ children: [],
401
+ }
402
+ const rootElement: ExtractedElement = {
403
+ traceId: 'trace-root',
404
+ name: 'root',
405
+ tag: 'div',
406
+ attributes: {},
407
+ extractorData: createExtractorData(),
408
+ children: [childElement],
409
+ }
410
+
411
+ const result = toEditorReactComponent(createComponent(rootElement))
412
+
413
+ for (const property of marginProperties) {
414
+ expect(result.editorElement?.cssProperties?.[property]).toBeUndefined()
415
+ expect(result.editorElement?.elements?.icon?.inlineElement?.cssProperties?.[property]).toEqual({})
416
+ }
417
+ })
418
+
377
419
  it('keeps width/height on inner (non-root) elements', () => {
378
420
  const rootExtractorData = new Map<string, unknown>()
379
421
  rootExtractorData.set('css-matcher', matcherDataFromCss(''))
@@ -576,9 +576,20 @@ function toDisplayEnumValue(cssValue: string): DisplayEnumValue {
576
576
  return (CSS_DISPLAY_TO_ENUM[cssValue] ?? cssValue) as DisplayEnumValue
577
577
  }
578
578
 
579
- // Root sizing is handled separately via layout config, so `width`/`height`
580
- // are excluded from the root element's own `cssProperties`.
581
- const ROOT_EXCLUDED_CSS_PROPERTIES = new Set<string>([CSS_PROPERTY_TYPE.width, CSS_PROPERTY_TYPE.height])
579
+ // CSS properties left out of the root element's own `cssProperties`:
580
+ // - `width`/`height`: root sizing is handled separately via layout config.
581
+ // - margins: the editor's layout positions the root on stage.
582
+ const ROOT_EXCLUDED_CSS_PROPERTIES = new Set<string>([
583
+ CSS_PROPERTY_TYPE.width,
584
+ CSS_PROPERTY_TYPE.height,
585
+ CSS_PROPERTY_TYPE.margin,
586
+ CSS_PROPERTY_TYPE.marginTop,
587
+ CSS_PROPERTY_TYPE.marginRight,
588
+ CSS_PROPERTY_TYPE.marginBottom,
589
+ CSS_PROPERTY_TYPE.marginLeft,
590
+ CSS_PROPERTY_TYPE.marginInlineStart,
591
+ CSS_PROPERTY_TYPE.marginInlineEnd,
592
+ ])
582
593
 
583
594
  function buildCssProperties(
584
595
  element: ExtractedElement | undefined,
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs'
2
2
 
3
3
  import type { ComponentInfo } from './information-extractors/ts'
4
- import type { PropInfo } from './information-extractors/ts/types'
4
+ import type { PropInfo, ResolvedType } from './information-extractors/ts/types'
5
5
 
6
6
  import {
7
7
  type ExtractedElement,
@@ -227,10 +227,11 @@ function processElementPropsWrites(
227
227
  if (!relativePath) continue
228
228
 
229
229
  // Resolve the PropInfo for this leaf prop by walking the type tree
230
- const propInfo = resolveInnerPropInfo(elementPropsInfo, relativePath)
231
- if (!propInfo) continue
230
+ const resolvedProp = resolveInnerPropInfo(elementPropsInfo, relativePath)
231
+ if (!resolvedProp) continue
232
+ const { propInfo, relativePropPath } = resolvedProp
232
233
 
233
- const elementPropsPath = extractParentElementPropsPath(relativePath)
234
+ const elementPropsPath = extractParentElementPropsPath(relativePropPath)
234
235
  if (!elementPropsPath) continue
235
236
  const bindings = extractBindings(writeInfo)
236
237
 
@@ -248,7 +249,7 @@ function processElementPropsWrites(
248
249
  entry.props[leafName] = {
249
250
  ...propInfo,
250
251
  logicOnly: false,
251
- propPath: path,
252
+ propPath: `props.elementProps.${relativePropPath}`,
252
253
  }
253
254
  }
254
255
  }
@@ -262,29 +263,44 @@ function processElementPropsWrites(
262
263
  * e.g., path "navbar.items" -> walk elementProps type -> navbar property -> items property
263
264
  * Handles recursive nesting: "hamburgerMenu.elementProps.hamburgerCloseButton.label"
264
265
  */
265
- function resolveInnerPropInfo(elementPropsPropInfo: PropInfo, relativePath: string): PropInfo | null {
266
+ function resolveInnerPropInfo(
267
+ elementPropsPropInfo: PropInfo,
268
+ relativePath: string,
269
+ ): { propInfo: PropInfo; relativePropPath: string } | null {
266
270
  const segments = relativePath.split('.')
267
- let currentType = elementPropsPropInfo.resolvedType
271
+ let currentType = unwrapOptionalType(elementPropsPropInfo.resolvedType)
268
272
 
269
- for (let i = 0; i < segments.length; i++) {
270
- const segment = segments[i]
273
+ for (let segmentIndex = 0; segmentIndex < segments.length; segmentIndex++) {
274
+ const segment = segments[segmentIndex]
271
275
  if (!currentType.properties) return null
272
276
 
273
277
  const propInfo = currentType.properties[segment]
274
278
  if (!propInfo) return null
275
279
 
276
- // If this is the last segment, return the PropInfo
277
- if (i === segments.length - 1) {
278
- return propInfo
280
+ // A11y is one semantic data item. Its tracked DOM writes point at fields
281
+ // such as next.a11y.ariaLabel, but the type has no property tree to descend.
282
+ // Keep the parent path so the builder can collect only the used attributes.
283
+ const propType = unwrapOptionalType(propInfo.resolvedType)
284
+ const isA11y = propType.kind === 'semantic' && propType.value === 'A11y'
285
+ if (segmentIndex === segments.length - 1 || isA11y) {
286
+ return { propInfo, relativePropPath: segments.slice(0, segmentIndex + 1).join('.') }
279
287
  }
280
288
 
281
289
  // Otherwise, descend into this property's resolved type
282
- currentType = propInfo.resolvedType
290
+ currentType = propType
283
291
  }
284
292
 
285
293
  return null
286
294
  }
287
295
 
296
+ function unwrapOptionalType(resolvedType: ResolvedType): ResolvedType {
297
+ if (resolvedType.kind !== 'union') return resolvedType
298
+ const definedTypes = resolvedType.types?.filter(
299
+ (member) => !(member.kind === 'primitive' && (member.value === 'undefined' || member.value === 'null')),
300
+ )
301
+ return definedTypes?.length === 1 ? definedTypes[0] : resolvedType
302
+ }
303
+
288
304
  function extractBindings(
289
305
  writeInfo: TrackingStores['propUsages'] extends Map<string, infer V> ? V : never,
290
306
  ): DOMBinding[] {