@wix/zero-config-implementation 1.100.0 → 1.101.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.100.0",
7
+ "version": "1.101.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": "80ef17ffc1549b6c4d3f51396955cb0517fe8eceb7b20eed65fc7e7d"
109
+ "falconPackageHash": "7aa5841bf5ff6a0fa0800fc3d71a267d6e96dbe084125f5fd56fc333"
110
110
  }
@@ -725,6 +725,65 @@ describe('toEditorReactComponent — @property design tokens', () => {
725
725
  expect(editorElement?.cssProperties?.['sdf-safelight-color']).toBeUndefined()
726
726
  expect(editorElement?.cssProperties?.['--sdf-safelight-color']).toBeUndefined()
727
727
  })
728
+
729
+ it('places a declared custom property on its declaring element even when its only tracked usage was pruned from the tree', () => {
730
+ const buttonElement: ExtractedElement = {
731
+ traceId: 'trace-button',
732
+ name: 'button',
733
+ tag: 'button',
734
+ attributes: { class: 'play-button' },
735
+ extractorData: new Map<string, unknown>([
736
+ ['css-matcher', { matches: [], customProperties: { '--icon-color': 'red' } } as MatchedCssData],
737
+ ]),
738
+ children: [],
739
+ }
740
+ const rootElement: ExtractedElement = {
741
+ traceId: 'trace-root',
742
+ name: 'root',
743
+ tag: 'div',
744
+ attributes: {},
745
+ extractorData: new Map<string, unknown>(),
746
+ children: [buttonElement],
747
+ }
748
+ const component: ComponentInfoWithCss = {
749
+ componentName: 'IconButton',
750
+ props: {},
751
+ elements: [rootElement],
752
+ propUsages: new Map(),
753
+ css: [],
754
+ // The icon that actually consumes --icon-color has no semantic class, so it was
755
+ // already filtered out of `elements` upstream — its trace id survives only here.
756
+ varUsedByTraceId: new Map([['--icon-color', new Set(['trace-pruned-icon'])]]),
757
+ }
758
+
759
+ const editorElement = toEditorReactComponent(component).editorElement
760
+
761
+ expect(editorElement?.elements?.button?.inlineElement?.cssCustomProperties?.['icon-color']).toEqual({
762
+ displayName: 'Icon Color',
763
+ defaultValue: 'red',
764
+ cssPropertyType: 'color',
765
+ })
766
+ expect(editorElement?.cssCustomProperties?.['icon-color']).toBeUndefined()
767
+ })
768
+
769
+ it('falls back to the component root when a variable has no surviving declaration and only orphaned usage', () => {
770
+ const component: ComponentInfoWithCss = {
771
+ ...createComponentWithCss(`
772
+ @property --gap { syntax: "<length>"; inherits: true; initial-value: 4px; }
773
+ `),
774
+ // No element declares --gap locally; its only recorded usage is a trace id that
775
+ // never made it into `elements` (simulating a pruned consumer).
776
+ varUsedByTraceId: new Map([['--gap', new Set(['trace-orphan'])]]),
777
+ }
778
+
779
+ const customProps = toEditorReactComponent(component).editorElement?.cssCustomProperties
780
+
781
+ expect(customProps?.gap).toEqual({
782
+ displayName: 'Gap',
783
+ defaultValue: '4px',
784
+ cssPropertyType: 'length',
785
+ })
786
+ })
728
787
  })
729
788
 
730
789
  describe('toEditorReactComponent — ActiveItemIndex display groups', () => {
@@ -248,7 +248,7 @@ function buildElements(
248
248
  ...(data && Object.keys(data).length > 0 && { data }),
249
249
  // CSS properties from heuristic + matched CSS files
250
250
  ...(Object.keys(cssProps).length > 0 && { cssProperties: cssProps }),
251
- // CSS custom properties placed on the Nearest Common Ancestor of all elements that use each variable
251
+ // CSS custom properties for this element
252
252
  ...(Object.keys(cssCustomProps).length > 0 && { cssCustomProperties: cssCustomProps }),
253
253
  ...(states && { states }),
254
254
  // Recursively build nested elements
@@ -277,8 +277,9 @@ function buildElements(
277
277
 
278
278
  /**
279
279
  * Pre-computes a map of traceId → custom property items to assign to each element.
280
- * Each custom property is placed on the nearest common ancestor (Nearest Common Ancestor) of all elements
281
- * that use it via var(). Falls back to the element that defines it if unused.
280
+ * A variable is placed on the element that declares it; if none survived pruning, it
281
+ * falls back to the nearest common ancestor of its usages, or the root for a registered
282
+ * but undeclared @property.
282
283
  */
283
284
  function buildNearestCommonAncestorCustomProps(
284
285
  component: ComponentInfoWithCss,
@@ -318,16 +319,25 @@ function buildNearestCommonAncestorCustomProps(
318
319
  // component doesn't need an element-level `--x: ...` declaration.
319
320
  const defaultValue = allCustomPropertyValues.get(varName) ?? registration?.defaultValue
320
321
 
321
- const usedByTraceIds = component.varUsedByTraceId.get(varName) ?? new Set<string>()
322
+ // Declaration wins over usage; multiple declaring elements resolve to their NCA.
323
+ const definingTraceIds = findDefiningElements(component.elements, varName)
322
324
 
323
325
  let nearestCommonAncestorTraceId: string | undefined
324
- if (usedByTraceIds.size > 0) {
325
- nearestCommonAncestorTraceId = findNearestCommonAncestor(usedByTraceIds, parentMap)
326
+ if (definingTraceIds.size > 0) {
327
+ nearestCommonAncestorTraceId = findNearestCommonAncestor(definingTraceIds, parentMap)
326
328
  } else {
327
- // Fallback: the element whose CSS selector defines the variable; failing that, a
328
- // registered @property with no var() usage anchors on the component root.
329
+ // No surviving declaration fall back to usage NCA, restricted to trace IDs still in the tree.
330
+ const usedByTraceIds = component.varUsedByTraceId.get(varName) ?? new Set<string>()
331
+ const survivingUsedByTraceIds = new Set(
332
+ [...usedByTraceIds].filter((traceId) => traceId === rootTraceId || parentMap.has(traceId)),
333
+ )
334
+
329
335
  nearestCommonAncestorTraceId =
330
- findDefiningElement(component.elements, varName) ?? (registration ? rootTraceId : undefined)
336
+ survivingUsedByTraceIds.size > 0
337
+ ? findNearestCommonAncestor(survivingUsedByTraceIds, parentMap)
338
+ : registration
339
+ ? rootTraceId
340
+ : undefined
331
341
  }
332
342
 
333
343
  if (!nearestCommonAncestorTraceId) continue
@@ -506,17 +516,16 @@ function findNearestCommonAncestor(traceIds: Set<string>, parentMap: Map<string,
506
516
  }
507
517
 
508
518
  /**
509
- * Finds the traceId of the first element in the tree whose matched CSS rules
510
- * define the given custom property (fallback when var() usage is not tracked).
519
+ * Finds every surviving element whose matched CSS declares the given custom property.
511
520
  */
512
- function findDefiningElement(elements: ExtractedElement[], varName: string): string | undefined {
521
+ function findDefiningElements(elements: ExtractedElement[], varName: string): Set<string> {
522
+ const definingTraceIds = new Set<string>()
513
523
  for (const element of elements) {
514
524
  const matcherData = element.extractorData.get('css-matcher') as MatchedCssData | undefined
515
- if (matcherData?.customProperties[varName] !== undefined) return element.traceId
516
- const childResult = findDefiningElement(element.children, varName)
517
- if (childResult !== undefined) return childResult
525
+ if (matcherData?.customProperties[varName] !== undefined) definingTraceIds.add(element.traceId)
526
+ for (const traceId of findDefiningElements(element.children, varName)) definingTraceIds.add(traceId)
518
527
  }
519
- return undefined
528
+ return definingTraceIds
520
529
  }
521
530
 
522
531
  // ─────────────────────────────────────────────────────────────────────────────