@wix/zero-config-implementation 1.65.0 → 1.66.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.
@@ -1,4 +1,4 @@
1
- import { g as x } from "./index-BLr9vmnH.js";
1
+ import { g as x } from "./index-BEdi2EUy.js";
2
2
  function h(r, a) {
3
3
  for (var i = 0; i < a.length; i++) {
4
4
  const o = a[i];
package/dist/index.d.ts CHANGED
@@ -2369,6 +2369,8 @@ export declare interface PropTrackerData {
2369
2369
  role?: string;
2370
2370
  boundProps: string[];
2371
2371
  concatenatedAttrs: Map<string, string>;
2372
+ /** on*-handler prop names whose value is a function on this element (e.g. ['onClick', 'onChange']). */
2373
+ eventHandlers: string[];
2372
2374
  }
2373
2375
 
2374
2376
  export declare interface PropTrackerExtractorState {
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { B as t, D as e, E as o, I as n, N as c, P as l, R as i, a as p, V as E, b as m, c as x, d as f, e as d, f as u, h as C, i as I, j as k, k as y, l as A, m as D, n as P, o as R, p as h, q as B, r as M, s as T, t as b, w } from "./index-BLr9vmnH.js";
1
+ import { B as t, D as e, E as o, I as n, N as c, P as l, R as i, a as p, V as E, b as m, c as x, d as f, e as d, f as u, h as C, i as I, j as k, k as y, l as A, m as D, n as P, o as R, p as h, q as B, r as M, s as T, t as b, w } from "./index-BEdi2EUy.js";
2
2
  import "react";
3
3
  export {
4
4
  t as BaseError,
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "registry": "https://registry.npmjs.org/",
5
5
  "access": "public"
6
6
  },
7
- "version": "1.65.0",
7
+ "version": "1.66.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",
@@ -81,5 +81,5 @@
81
81
  ]
82
82
  }
83
83
  },
84
- "falconPackageHash": "978c6e182cced111caaef8f5bc88bdaadfdd813f14c0f9280352d625"
84
+ "falconPackageHash": "2a3fd8ab08ce40ac87e3bf2f07ad6928e67ddb1262856b12f593624b"
85
85
  }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Builds the `states` block for the root EditorElement and inline elements.
3
+ *
4
+ * Native states only (hover/focus/disabled/invalid). Signals come from the
5
+ * React extractor (tag, role, event handlers). The CSS extractor is not
6
+ * consulted.
7
+ *
8
+ * className follows BEM by appending `--<state>` to the element's own
9
+ * semantic class. Component authors are expected to write inner elements
10
+ * with full BEM class names (e.g. `toggle__label`), so the resulting state
11
+ * className is `<semanticClass>--<state>`:
12
+ * - root `toggle` → `toggle--hover`
13
+ * - inner `toggle__label` → `toggle__label--hover`
14
+ *
15
+ * `props` is never emitted on native-state entries — that field is reserved
16
+ * for functional (non-native) states.
17
+ */
18
+
19
+ import type { States } from '@wix/react-component-schema'
20
+ import { NATIVE_STATE_TYPE } from '@wix/react-component-schema'
21
+ import {
22
+ type ExtractedElement,
23
+ type SupportedNativeStates,
24
+ inferSupportedNativeStates,
25
+ } from '../information-extractors/react'
26
+
27
+ const STATE_DISPLAY_NAMES = {
28
+ hover: 'Hover',
29
+ focus: 'Focus',
30
+ disabled: 'Disabled',
31
+ invalid: 'Invalid',
32
+ } as const
33
+
34
+ type NativeStateName = keyof SupportedNativeStates
35
+
36
+ const PSEUDO_CLASS_BY_STATE: Record<NativeStateName, NATIVE_STATE_TYPE> = {
37
+ hover: NATIVE_STATE_TYPE.hover,
38
+ focus: NATIVE_STATE_TYPE.focus,
39
+ disabled: NATIVE_STATE_TYPE.disabled,
40
+ invalid: NATIVE_STATE_TYPE.invalid,
41
+ }
42
+
43
+ export function buildStatesBlock(element: ExtractedElement, semanticClass: string): States | undefined {
44
+ const supported = inferSupportedNativeStates(element)
45
+ const states: States = {}
46
+
47
+ for (const stateName of Object.keys(supported) as NativeStateName[]) {
48
+ if (!supported[stateName]) continue
49
+
50
+ states[stateName] = {
51
+ displayName: STATE_DISPLAY_NAMES[stateName],
52
+ className: `${semanticClass}--${stateName}`,
53
+ pseudoClass: PSEUDO_CLASS_BY_STATE[stateName],
54
+ }
55
+ }
56
+
57
+ return Object.keys(states).length > 0 ? states : undefined
58
+ }
@@ -22,6 +22,7 @@ import { getDefaultDisplayForTag, resolveDisplayValue } from '../information-ext
22
22
  import { findPreferredSemanticClass } from '../utils/css-class'
23
23
  import { resolveLonghand } from './css-longhand-resolver'
24
24
  import { buildDataItem } from './data-item-builder'
25
+ import { buildStatesBlock } from './states-builder'
25
26
  import { formatDisplayName } from './utils'
26
27
 
27
28
  // Props to exclude from data items (same as in element-data.ts)
@@ -42,6 +43,9 @@ function buildEditorElement(
42
43
  const childElements = rootElement?.children ?? []
43
44
  const rootCustomProps = rootElement ? (nearestCommonAncestorCustomProps.get(rootElement.traceId) ?? {}) : {}
44
45
 
46
+ const rootSemanticClass = getSemanticBlockName(rootElement)
47
+ const rootStates = rootElement && rootSemanticClass ? buildStatesBlock(rootElement, rootSemanticClass) : undefined
48
+
45
49
  return {
46
50
  selector: buildSelector(rootElement),
47
51
  displayName: formatDisplayName(component.componentName),
@@ -54,9 +58,15 @@ function buildEditorElement(
54
58
  ),
55
59
  cssProperties: buildCssProperties(rootElement),
56
60
  cssCustomProperties: rootCustomProps,
61
+ ...(rootStates && { states: rootStates }),
57
62
  }
58
63
  }
59
64
 
65
+ function getSemanticBlockName(element: ExtractedElement | undefined): string | undefined {
66
+ if (!element?.attributes.class) return undefined
67
+ return findPreferredSemanticClass(element.attributes.class.split(' '))
68
+ }
69
+
60
70
  function buildSelector(rootElement?: ExtractedElement): string {
61
71
  if (rootElement?.attributes.class) {
62
72
  const semanticClass = findPreferredSemanticClass(rootElement.attributes.class.split(' '))
@@ -101,6 +111,9 @@ function buildElements(
101
111
  const cssProps = buildCssProperties(element)
102
112
  const cssCustomProps = nearestCommonAncestorCustomProps.get(element.traceId) ?? {}
103
113
 
114
+ const semanticClass = getSemanticBlockName(element)
115
+ const states = semanticClass ? buildStatesBlock(element, semanticClass) : undefined
116
+
104
117
  result[element.name] = {
105
118
  elementType: ELEMENTS.ELEMENT_TYPE.inlineElement,
106
119
  inlineElement: {
@@ -113,6 +126,7 @@ function buildElements(
113
126
  ...(Object.keys(cssProps).length > 0 && { cssProperties: cssProps }),
114
127
  // CSS custom properties placed on the Nearest Common Ancestor of all elements that use each variable
115
128
  ...(Object.keys(cssCustomProps).length > 0 && { cssCustomProperties: cssCustomProps }),
129
+ ...(states && { states }),
116
130
  // Recursively build nested elements
117
131
  elements:
118
132
  element.children.length > 0
@@ -31,3 +31,6 @@ export {
31
31
  getDefaultDisplayForTag,
32
32
  } from './css-properties'
33
33
  export type { CssPropertiesData } from './css-properties'
34
+
35
+ export { inferSupportedNativeStates } from './state-markers'
36
+ export type { SupportedNativeStates } from './state-markers'
@@ -20,6 +20,8 @@ export interface PropTrackerData {
20
20
  role?: string
21
21
  boundProps: string[]
22
22
  concatenatedAttrs: Map<string, string> // attrName → propName (for concatenated values)
23
+ /** on*-handler prop names whose value is a function on this element (e.g. ['onClick', 'onChange']). */
24
+ eventHandlers: string[]
23
25
  }
24
26
 
25
27
  export interface PropTrackerExtractorState {
@@ -110,10 +112,15 @@ export function createPropTrackerExtractor(): {
110
112
 
111
113
  const boundProps = new Set<string>()
112
114
  const concatenatedAttrs = new Map<string, string>()
115
+ const eventHandlers: string[] = []
113
116
 
114
117
  Object.entries(props).forEach(([key, value]) => {
115
118
  if (key.startsWith('__') || key === TRACE_ATTR) return
116
119
 
120
+ if (typeof value === 'function' && key.length > 2 && key.startsWith('on') && key[2] === key[2].toUpperCase()) {
121
+ eventHandlers.push(key)
122
+ }
123
+
117
124
  const spies = extractSpies(value)
118
125
 
119
126
  spies.forEach((spy) => {
@@ -141,6 +148,7 @@ export function createPropTrackerExtractor(): {
141
148
  role: htmlProps.role,
142
149
  boundProps: [...boundProps],
143
150
  concatenatedAttrs,
151
+ eventHandlers,
144
152
  }
145
153
  store.set(traceId, 'prop-tracker', data)
146
154
  },
@@ -0,0 +1,143 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import type { ExtractedElement } from './core/tree-builder'
3
+ import type { PropTrackerData } from './prop-tracker'
4
+ import { inferSupportedNativeStates } from './state-markers'
5
+
6
+ function makeElement(input: {
7
+ tag: string
8
+ attributes?: Record<string, string>
9
+ role?: string
10
+ eventHandlers?: string[]
11
+ }): ExtractedElement {
12
+ const propTrackerData: PropTrackerData = {
13
+ tag: input.tag,
14
+ role: input.role,
15
+ boundProps: [],
16
+ concatenatedAttrs: new Map(),
17
+ eventHandlers: input.eventHandlers ?? [],
18
+ }
19
+ return {
20
+ traceId: 'trace-0',
21
+ name: 'root',
22
+ tag: input.tag,
23
+ attributes: input.attributes ?? {},
24
+ extractorData: new Map([['prop-tracker', propTrackerData]]),
25
+ children: [],
26
+ }
27
+ }
28
+
29
+ describe('inferSupportedNativeStates', () => {
30
+ describe('hover + focus (interactive)', () => {
31
+ it('returns true for native form tags', () => {
32
+ for (const tag of ['button', 'a', 'input', 'select', 'textarea', 'summary']) {
33
+ const states = inferSupportedNativeStates(makeElement({ tag }))
34
+ expect(states.hover, `hover for <${tag}>`).toBe(true)
35
+ expect(states.focus, `focus for <${tag}>`).toBe(true)
36
+ }
37
+ })
38
+
39
+ it('returns true when role is interactive', () => {
40
+ const interactiveRoles = ['switch', 'checkbox', 'tab', 'menuitem', 'combobox', 'slider']
41
+ for (const role of interactiveRoles) {
42
+ const states = inferSupportedNativeStates(makeElement({ tag: 'div', role }))
43
+ expect(states.hover, `hover for role=${role}`).toBe(true)
44
+ expect(states.focus, `focus for role=${role}`).toBe(true)
45
+ }
46
+ })
47
+
48
+ it('returns true when any pointer/keyboard/form event handler is present', () => {
49
+ const handlers = ['onClick', 'onPointerDown', 'onFocus', 'onKeyDown', 'onChange']
50
+ for (const handler of handlers) {
51
+ const states = inferSupportedNativeStates(makeElement({ tag: 'div', eventHandlers: [handler] }))
52
+ expect(states.hover, `hover for ${handler}`).toBe(true)
53
+ expect(states.focus, `focus for ${handler}`).toBe(true)
54
+ }
55
+ })
56
+
57
+ it('returns false for a plain div with no signals', () => {
58
+ const states = inferSupportedNativeStates(makeElement({ tag: 'div' }))
59
+ expect(states.hover).toBe(false)
60
+ expect(states.focus).toBe(false)
61
+ })
62
+
63
+ it('reads role from the rendered attributes when prop-tracker data is absent', () => {
64
+ const element: ExtractedElement = {
65
+ traceId: 'trace-0',
66
+ name: 'root',
67
+ tag: 'div',
68
+ attributes: { role: 'button' },
69
+ extractorData: new Map(),
70
+ children: [],
71
+ }
72
+ const states = inferSupportedNativeStates(element)
73
+ expect(states.hover).toBe(true)
74
+ expect(states.focus).toBe(true)
75
+ })
76
+ })
77
+
78
+ describe('disabled', () => {
79
+ it('returns true for form-disableable native tags', () => {
80
+ for (const tag of ['button', 'input', 'select', 'textarea', 'fieldset']) {
81
+ const states = inferSupportedNativeStates(makeElement({ tag }))
82
+ expect(states.disabled, `disabled for <${tag}>`).toBe(true)
83
+ }
84
+ })
85
+
86
+ it('returns true for form-control roles', () => {
87
+ for (const role of ['button', 'switch', 'checkbox', 'radio', 'menuitem', 'tab', 'option', 'link']) {
88
+ const states = inferSupportedNativeStates(makeElement({ tag: 'div', role }))
89
+ expect(states.disabled, `disabled for role=${role}`).toBe(true)
90
+ }
91
+ })
92
+
93
+ it('returns false for plain divs and non-form roles', () => {
94
+ expect(inferSupportedNativeStates(makeElement({ tag: 'div' })).disabled).toBe(false)
95
+ expect(inferSupportedNativeStates(makeElement({ tag: 'div', role: 'navigation' })).disabled).toBe(false)
96
+ })
97
+ })
98
+
99
+ describe('invalid', () => {
100
+ it('returns true for form-validatable native tags', () => {
101
+ for (const tag of ['input', 'select', 'textarea', 'form']) {
102
+ const states = inferSupportedNativeStates(makeElement({ tag }))
103
+ expect(states.invalid, `invalid for <${tag}>`).toBe(true)
104
+ }
105
+ })
106
+
107
+ it('returns false for a plain button (not form-validatable)', () => {
108
+ expect(inferSupportedNativeStates(makeElement({ tag: 'button' })).invalid).toBe(false)
109
+ })
110
+
111
+ it('ignores the aria-invalid attribute on non-form tags', () => {
112
+ const states = inferSupportedNativeStates(makeElement({ tag: 'div', attributes: { 'aria-invalid': 'true' } }))
113
+ expect(states.invalid).toBe(false)
114
+ })
115
+ })
116
+
117
+ describe('role="presentation" / "none" opt-out', () => {
118
+ it('suppresses every native state when role is "presentation"', () => {
119
+ const states = inferSupportedNativeStates(makeElement({ tag: 'button', role: 'presentation' }))
120
+ expect(states).toEqual({ hover: false, focus: false, disabled: false, invalid: false })
121
+ })
122
+
123
+ it('suppresses every native state when role is "none"', () => {
124
+ const states = inferSupportedNativeStates(makeElement({ tag: 'input', role: 'none' }))
125
+ expect(states).toEqual({ hover: false, focus: false, disabled: false, invalid: false })
126
+ })
127
+ })
128
+
129
+ describe('tabindex="-1" suppresses focus', () => {
130
+ it('keeps hover and disabled but suppresses focus on an interactive tag with tabindex="-1"', () => {
131
+ const states = inferSupportedNativeStates(makeElement({ tag: 'button', attributes: { tabindex: '-1' } }))
132
+ expect(states.hover).toBe(true)
133
+ expect(states.focus).toBe(false)
134
+ expect(states.disabled).toBe(true)
135
+ })
136
+
137
+ it('does not affect non-interactive elements (no focus to suppress)', () => {
138
+ const states = inferSupportedNativeStates(makeElement({ tag: 'div', attributes: { tabindex: '-1' } }))
139
+ expect(states.focus).toBe(false)
140
+ expect(states.hover).toBe(false)
141
+ })
142
+ })
143
+ })
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Design-state inference from React-extractor signals.
3
+ *
4
+ * The new design (Notion: "New Design") supports only the four native states
5
+ * (hover/focus/disabled/invalid) and derives them purely from the React side:
6
+ * html tag, role attribute, and event handlers. The CSS extractor is not
7
+ * involved.
8
+ */
9
+
10
+ import type { ExtractedElement } from './core/tree-builder'
11
+ import type { PropTrackerData } from './prop-tracker'
12
+
13
+ // ─────────────────────────────────────────────────────────────────────────────
14
+ // Types
15
+ // ─────────────────────────────────────────────────────────────────────────────
16
+
17
+ export interface SupportedNativeStates {
18
+ hover: boolean
19
+ focus: boolean
20
+ disabled: boolean
21
+ invalid: boolean
22
+ }
23
+
24
+ // ─────────────────────────────────────────────────────────────────────────────
25
+ // Signal sets
26
+ // ─────────────────────────────────────────────────────────────────────────────
27
+
28
+ const INTERACTIVE_TAGS = new Set(['button', 'a', 'input', 'select', 'textarea', 'summary'])
29
+
30
+ const INTERACTIVE_ROLES = new Set([
31
+ 'button',
32
+ 'link',
33
+ 'switch',
34
+ 'checkbox',
35
+ 'radio',
36
+ 'tab',
37
+ 'option',
38
+ 'menuitem',
39
+ 'menuitemcheckbox',
40
+ 'menuitemradio',
41
+ 'treeitem',
42
+ 'combobox',
43
+ 'slider',
44
+ 'spinbutton',
45
+ 'searchbox',
46
+ 'textbox',
47
+ 'listbox',
48
+ ])
49
+
50
+ const INTERACTIVE_EVENT_HANDLERS = new Set([
51
+ 'onClick',
52
+ 'onMouseDown',
53
+ 'onMouseUp',
54
+ 'onMouseEnter',
55
+ 'onMouseLeave',
56
+ 'onPointerDown',
57
+ 'onPointerUp',
58
+ 'onFocus',
59
+ 'onBlur',
60
+ 'onKeyDown',
61
+ 'onKeyUp',
62
+ 'onKeyPress',
63
+ 'onChange',
64
+ 'onInput',
65
+ 'onSubmit',
66
+ ])
67
+
68
+ const DISABLEABLE_TAGS = new Set(['button', 'input', 'select', 'textarea', 'fieldset'])
69
+
70
+ const DISABLEABLE_ROLES = new Set(['button', 'switch', 'checkbox', 'radio', 'menuitem', 'tab', 'option', 'link'])
71
+
72
+ const VALIDATABLE_TAGS = new Set(['input', 'select', 'textarea', 'form'])
73
+
74
+ const PRESENTATION_ROLES = new Set(['presentation', 'none'])
75
+
76
+ // ─────────────────────────────────────────────────────────────────────────────
77
+ // Inference
78
+ // ─────────────────────────────────────────────────────────────────────────────
79
+
80
+ function getRole(element: ExtractedElement): string | undefined {
81
+ const propTrackerData = element.extractorData.get('prop-tracker') as PropTrackerData | undefined
82
+ return propTrackerData?.role ?? element.attributes.role
83
+ }
84
+
85
+ function getEventHandlers(element: ExtractedElement): string[] {
86
+ const propTrackerData = element.extractorData.get('prop-tracker') as PropTrackerData | undefined
87
+ return propTrackerData?.eventHandlers ?? []
88
+ }
89
+
90
+ /**
91
+ * `role="presentation"` and `role="none"` strip the element's implicit ARIA
92
+ * semantics — a `<button role="presentation">` is no longer a button. Treat
93
+ * either as an explicit opt-out from every native state.
94
+ */
95
+ function hasPresentationRole(role: string | undefined): boolean {
96
+ return role !== undefined && PRESENTATION_ROLES.has(role)
97
+ }
98
+
99
+ function hasInteractiveSignal(tag: string, role: string | undefined, eventHandlers: string[]): boolean {
100
+ if (INTERACTIVE_TAGS.has(tag)) return true
101
+ if (role !== undefined && INTERACTIVE_ROLES.has(role)) return true
102
+ return eventHandlers.some((handler) => INTERACTIVE_EVENT_HANDLERS.has(handler))
103
+ }
104
+
105
+ function hasDisableableSignal(tag: string, role: string | undefined): boolean {
106
+ if (DISABLEABLE_TAGS.has(tag)) return true
107
+ if (role !== undefined && DISABLEABLE_ROLES.has(role)) return true
108
+ return false
109
+ }
110
+
111
+ function hasValidatableSignal(tag: string): boolean {
112
+ return VALIDATABLE_TAGS.has(tag)
113
+ }
114
+
115
+ /**
116
+ * Decides which of the four native states the given element supports, based on
117
+ * its tag, role, and event handlers.
118
+ *
119
+ * Two attribute-level overrides apply:
120
+ * - `role="presentation"` / `role="none"` opts the element out of every state.
121
+ * - `tabindex="-1"` suppresses `focus` only (the element is programmatically
122
+ * focusable but not via keyboard tab).
123
+ */
124
+ export function inferSupportedNativeStates(element: ExtractedElement): SupportedNativeStates {
125
+ const tag = element.tag.toLowerCase()
126
+ const role = getRole(element)
127
+
128
+ if (hasPresentationRole(role)) {
129
+ return { hover: false, focus: false, disabled: false, invalid: false }
130
+ }
131
+
132
+ const eventHandlers = getEventHandlers(element)
133
+ const interactive = hasInteractiveSignal(tag, role, eventHandlers)
134
+ const isUntabbable = element.attributes.tabindex === '-1'
135
+
136
+ return {
137
+ hover: interactive,
138
+ focus: interactive && !isUntabbable,
139
+ disabled: hasDisableableSignal(tag, role),
140
+ invalid: hasValidatableSignal(tag),
141
+ }
142
+ }
@@ -19,6 +19,7 @@ export {
19
19
  enrichGapProperties,
20
20
  resolveDisplayValue,
21
21
  getDefaultDisplayForTag,
22
+ inferSupportedNativeStates,
22
23
  } from './extractors'
23
24
 
24
25
  export type {
@@ -34,6 +35,7 @@ export type {
34
35
  PropTrackerData,
35
36
  PropTrackerExtractorState,
36
37
  CssPropertiesData,
38
+ SupportedNativeStates,
37
39
  } from './extractors'
38
40
 
39
41
  // ─────────────────────────────────────────────────────────────────────────────