@wix/zero-config-implementation 1.71.0 → 1.72.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.
Files changed (39) hide show
  1. package/dist/{index-DT1jTZEG.js → index-CSi4SZpQ.js} +1 -1
  2. package/dist/{index-C4cP1dwy.js → index-CdHu4-o-.js} +20459 -18722
  3. package/dist/index.d.ts +15 -8
  4. package/dist/index.js +1 -1
  5. package/package.json +4 -3
  6. package/src/__fixtures__/cjs-ref-element-entry.cjs +1 -0
  7. package/src/__fixtures__/cjs-ref-element-lazy-entry.cjs +3 -0
  8. package/src/__fixtures__/cjs-ref-element-order-entry.cjs +8 -0
  9. package/src/__fixtures__/cjs-ref-element-order-target.cjs +7 -0
  10. package/src/__fixtures__/cjs-ref-element-target.cjs +5 -0
  11. package/src/__fixtures__/esm-ref-element-entry.mjs +3 -0
  12. package/src/component-renderer.ts +12 -9
  13. package/src/converters/to-editor-component.ts +24 -6
  14. package/src/index.ts +121 -54
  15. package/src/information-extractors/react/extractors/core/tree-builder.ts +3 -3
  16. package/src/information-extractors/react/extractors/core/types.ts +4 -5
  17. package/src/information-extractors/react/extractors/css-properties.ts +2 -0
  18. package/src/information-extractors/react/extractors/prop-tracker.test.ts +251 -0
  19. package/src/information-extractors/react/extractors/prop-tracker.ts +78 -14
  20. package/src/information-extractors/react/types.ts +1 -1
  21. package/src/information-extractors/react/utils/mock-generator.test.ts +131 -0
  22. package/src/information-extractors/react/utils/mock-generator.ts +38 -13
  23. package/src/manifest-pipeline.ts +19 -14
  24. package/src/module-loader.test.ts +150 -0
  25. package/src/module-loader.ts +48 -8
  26. package/src/react-runtime-interceptor.ts +64 -0
  27. package/src/react-runtime-loader.ts +656 -0
  28. package/src/ref-elements/component-tag.ts +105 -0
  29. package/src/ref-elements/context.test.ts +179 -0
  30. package/src/ref-elements/context.ts +280 -0
  31. package/src/ref-elements/eligible-paths.ts +45 -0
  32. package/src/ref-elements/module-resolution.test.ts +50 -0
  33. package/src/ref-elements/module-resolution.ts +21 -0
  34. package/src/ref-elements/module-specifier.ts +17 -0
  35. package/src/ref-elements/path-utils.test.ts +14 -0
  36. package/src/ref-elements/path-utils.ts +55 -0
  37. package/src/ref-elements/types.ts +17 -0
  38. package/src/utils/css-class.ts +15 -0
  39. package/src/jsx-runtime-interceptor.ts +0 -245
package/dist/index.d.ts CHANGED
@@ -302,7 +302,10 @@ export declare interface CoupledComponentInfo {
302
302
  componentName: string;
303
303
  props: Record<string, CoupledProp>;
304
304
  elements: ExtractedElement[];
305
- innerElementProps?: Map<string, Record<string, CoupledProp>>;
305
+ innerElementProps?: Map<string, {
306
+ elementPropsPath: string;
307
+ props: Record<string, CoupledProp>;
308
+ }>;
306
309
  propUsages: TrackingStores['propUsages'];
307
310
  }
308
311
 
@@ -318,13 +321,12 @@ export declare interface CoupledProp extends PropInfo {
318
321
  */
319
322
  export declare function createCssPropertiesExtractor(): ReactExtractor;
320
323
 
321
- /**
322
- * Event emitted for each DOM element created during render.
323
- */
324
324
  export declare interface CreateElementEvent {
325
- tag: string;
325
+ type: unknown;
326
+ isDomElement: boolean;
327
+ tag?: string;
326
328
  props: Record<string, unknown>;
327
- traceId: string;
329
+ traceId?: string;
328
330
  children: unknown[];
329
331
  store: ExtractorStore;
330
332
  }
@@ -337,13 +339,18 @@ export declare interface CreateElementListener {
337
339
  * Creates a prop tracker extractor that:
338
340
  * 1. Generates spy-instrumented mock props during beforeRender
339
341
  * 2. Detects spy markers in element props during onCreateElement
340
- * 3. propUsages tracking data to the store namespaced by 'prop-tracker'
342
+ * 3. Tracks runtime refElement markers from composite components to rendered DOM
343
+ * 4. Writes prop usage data to the store namespaced by 'prop-tracker'
341
344
  */
342
- export declare function createPropTrackerExtractor(): {
345
+ export declare function createPropTrackerExtractor(options?: CreatePropTrackerExtractorOptions): {
343
346
  extractor: ReactExtractor;
344
347
  state: PropTrackerExtractorState;
345
348
  };
346
349
 
350
+ declare interface CreatePropTrackerExtractorOptions {
351
+ eligiblePaths?: string[];
352
+ }
353
+
347
354
  /**
348
355
  * `ErrorClass.subclass()`
349
356
  *
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-C4cP1dwy.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-CdHu4-o-.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.71.0",
7
+ "version": "1.72.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",
@@ -40,7 +40,8 @@
40
40
  },
41
41
  "dependencies": {
42
42
  "@wix/builder-services-wrapper": "^1.42.0",
43
- "@wix/react-component-schema": "1.7.0"
43
+ "@wix/react-component-schema": "1.7.0",
44
+ "import-meta-resolve": "^4.2.0"
44
45
  },
45
46
  "devDependencies": {
46
47
  "@faker-js/faker": "^10.2.0",
@@ -81,5 +82,5 @@
81
82
  ]
82
83
  }
83
84
  },
84
- "falconPackageHash": "d6c8e03493b96fe9947b6b075e997e8ba6e5fea0f79a417f6e26c201"
85
+ "falconPackageHash": "2d1621ee1db1cc5a90b528fc0d45129b0534a3ba693b766403f23b2f"
85
86
  }
@@ -0,0 +1 @@
1
+ module.exports = require('./cjs-ref-element-target.cjs')
@@ -0,0 +1,3 @@
1
+ exports.loadTargetModule = function loadTargetModule() {
2
+ return require('./cjs-ref-element-target.cjs')
3
+ }
@@ -0,0 +1,8 @@
1
+ globalThis.__zeroConfigRefElementEvaluationOrder = ['entry']
2
+
3
+ const target = require('./cjs-ref-element-order-target.cjs')
4
+
5
+ module.exports = {
6
+ evaluationOrder: globalThis.__zeroConfigRefElementEvaluationOrder,
7
+ target,
8
+ }
@@ -0,0 +1,7 @@
1
+ globalThis.__zeroConfigRefElementEvaluationOrder.push('target')
2
+
3
+ function FixtureRefElementOrderTarget() {
4
+ return null
5
+ }
6
+
7
+ module.exports = FixtureRefElementOrderTarget
@@ -0,0 +1,5 @@
1
+ function FixtureRefElementTarget() {
2
+ return null
3
+ }
4
+
5
+ module.exports = FixtureRefElementTarget
@@ -0,0 +1,3 @@
1
+ import RefElementTarget from '@wix/zero-config-ref-element-fixtures/BareRefButton'
2
+
3
+ export default RefElementTarget
@@ -10,7 +10,7 @@
10
10
  * Four complementary layers ensure interception regardless of how JSX is compiled
11
11
  * AND regardless of how many React module instances are loaded in the process:
12
12
  *
13
- * 1. **Node.js ESM loader hook** — registered by `registerJsxLoaderHook()` in
13
+ * 1. **Node.js ESM loader hook** — registered by `ensureReactRuntimeLoader()` in
14
14
  * `module-loader.ts` before any user bundle is imported. Redirects
15
15
  * `react/jsx-runtime` and `react/jsx-dev-runtime` bare-specifier imports to
16
16
  * an in-memory `data:` URL interceptor that reads mutable state from
@@ -75,7 +75,7 @@ import {
75
75
  getOriginals,
76
76
  setCreateElementInterceptor,
77
77
  setJsxInterceptors,
78
- } from './jsx-runtime-interceptor'
78
+ } from './react-runtime-interceptor'
79
79
 
80
80
  export const TRACE_ATTR = 'data-trace-id'
81
81
 
@@ -104,18 +104,17 @@ function createInterceptor(
104
104
  store: ExtractorStore,
105
105
  ): ElementCreator {
106
106
  return (type, elementProps, ...rest) => {
107
- if (!isDOMTag(type)) {
108
- return original(type, elementProps, ...rest)
109
- }
110
-
111
107
  const props = (elementProps ?? {}) as Record<string, unknown>
112
- const traceId = getNextId()
108
+ const isDomElement = isDOMTag(type)
109
+ const traceId = isDomElement ? getNextId() : undefined
113
110
 
114
111
  // Children location differs: createElement uses rest params, jsx/jsxs uses props.children
115
112
  const children = rest.length > 0 ? rest : props.children ? [props.children] : []
116
113
 
117
114
  const event: CreateElementEvent = {
118
- tag: type,
115
+ type,
116
+ isDomElement,
117
+ tag: isDomElement ? type : undefined,
119
118
  props: { ...props, children },
120
119
  traceId,
121
120
  children: children as unknown[],
@@ -124,6 +123,10 @@ function createInterceptor(
124
123
 
125
124
  listeners.forEach((listener) => listener.onCreateElement(event))
126
125
 
126
+ if (!isDomElement || !traceId) {
127
+ return original(type, elementProps, ...rest)
128
+ }
129
+
127
130
  const stampedProps = { ...props, [TRACE_ATTR]: traceId }
128
131
  return original(type, stampedProps, ...rest)
129
132
  }
@@ -248,7 +251,7 @@ export function renderWithExtractors(
248
251
  try {
249
252
  // @ts-expect-error Monkey-patch createElement for default-import callers
250
253
  React.createElement = interceptedCreateElement
251
- // The data: URL react shim (registered by registerJsxLoaderHook) reads this state on every
254
+ // The data: URL react shim (registered by ensureReactRuntimeLoader) reads this state on every
252
255
  // createElement call, covering Rspack/webpack bundles that use `import * as React from 'react'`.
253
256
  setCreateElementInterceptor(interceptedCreateElement)
254
257
  // Use the interceptor API for jsx-runtime (includes jsxDEV for dev mode)
@@ -20,7 +20,9 @@ import type {
20
20
  TrackingStores,
21
21
  } from '../information-extractors/react'
22
22
  import { getDefaultDisplayForTag, resolveDisplayValue } from '../information-extractors/react'
23
- import { findPreferredSemanticClass } from '../utils/css-class'
23
+ import { buildRefElementManifestKey } from '../ref-elements/path-utils'
24
+ import type { RefElementMatch } from '../ref-elements/types'
25
+ import { findPreferredSemanticClass, normalizeClassNames } from '../utils/css-class'
24
26
  import { resolveLonghand } from './css-longhand-resolver'
25
27
  import { type CustomClassTrigger, buildCustomStatesBlock } from './custom-states-builder'
26
28
  import { buildDataItem } from './data-item-builder'
@@ -102,12 +104,12 @@ function buildEditorElement(
102
104
 
103
105
  function getSemanticBlockName(element: ExtractedElement | undefined): string | undefined {
104
106
  if (!element?.attributes.class) return undefined
105
- return findPreferredSemanticClass(element.attributes.class.split(' '))
107
+ return findPreferredSemanticClass(normalizeClassNames(element.attributes.class))
106
108
  }
107
109
 
108
110
  function buildSelector(rootElement?: ExtractedElement): string {
109
111
  if (rootElement?.attributes.class) {
110
- const semanticClass = findPreferredSemanticClass(rootElement.attributes.class.split(' '))
112
+ const semanticClass = findPreferredSemanticClass(normalizeClassNames(rootElement.attributes.class))
111
113
  if (semanticClass) return `.${semanticClass}`
112
114
  }
113
115
  return rootElement?.tag ?? ''
@@ -141,16 +143,31 @@ function buildElements(
141
143
  customClassTriggers: CustomClassTrigger[],
142
144
  innerElementProps?: CoupledComponentInfo['innerElementProps'],
143
145
  propUsages?: TrackingStores['propUsages'],
146
+ parentElementPropsPath?: string,
144
147
  ): Record<string, ElementItem> {
145
148
  const result: Record<string, ElementItem> = {}
146
149
 
147
150
  for (const element of elements) {
148
- const elementData = innerElementProps?.get(element.traceId)
149
- const data = elementData && propUsages ? buildData(elementData, propUsages) : undefined
151
+ const innerEntry = innerElementProps?.get(element.traceId)
152
+ const refElementMatch = element.extractorData.get('ref-element') as RefElementMatch | undefined
153
+
154
+ if (refElementMatch) {
155
+ result[buildRefElementManifestKey(refElementMatch.elementPropsPath, parentElementPropsPath)] = {
156
+ elementType: ELEMENTS.ELEMENT_TYPE.refElement,
157
+ refElement: {
158
+ selector: refElementMatch.selector,
159
+ type: refElementMatch.refComponentType,
160
+ },
161
+ }
162
+ continue
163
+ }
164
+
165
+ const semanticClass = getSemanticBlockName(element)
166
+
167
+ const data = innerEntry && propUsages ? buildData(innerEntry.props, propUsages) : undefined
150
168
  const cssProps = buildCssProperties(element)
151
169
  const cssCustomProps = nearestCommonAncestorCustomProps.get(element.traceId) ?? {}
152
170
 
153
- const semanticClass = getSemanticBlockName(element)
154
171
  // Inner-element custom states come from custom-class triggers only; prop
155
172
  // triggers (`ElementState<>`) are component-level and attach to the root.
156
173
  const states = mergeStates(
@@ -180,6 +197,7 @@ function buildElements(
180
197
  customClassTriggers,
181
198
  innerElementProps,
182
199
  propUsages,
200
+ innerEntry?.elementPropsPath ?? parentElementPropsPath,
183
201
  )
184
202
  : undefined,
185
203
  },
package/src/index.ts CHANGED
@@ -1,16 +1,19 @@
1
1
  import { WixServicesWrapper } from '@wix/builder-services-wrapper'
2
2
  import type { EditorReactComponent } from '@wix/react-component-schema'
3
- import { Result, type ResultAsync, errAsync, okAsync } from 'neverthrow'
3
+ import { Result, ResultAsync, errAsync, okAsync } from 'neverthrow'
4
4
  import React, { type ComponentType } from 'react'
5
5
 
6
6
  import { toEditorReactComponent } from './converters'
7
- import { IoError, type NotFoundError, ParseError } from './errors'
7
+ import { BaseError, IoError, type NotFoundError, ParseError } from './errors'
8
8
  import type { ExtractionError } from './extraction-types'
9
9
  import type { RunExtractorsOptions } from './information-extractors/react'
10
10
  import { extractCssImports, extractDefaultComponentInfo } from './information-extractors/ts'
11
+ import type { ComponentInfo } from './information-extractors/ts/types'
11
12
  import { processComponent } from './manifest-pipeline'
12
- import { findComponent, findDefaultComponent, loadModule } from './module-loader'
13
+ import { findComponent, findDefaultComponent, loadModuleForExtraction } from './module-loader'
13
14
  import type { LoadModuleFailure } from './module-loader'
15
+ import { buildRefElementContext } from './ref-elements/context'
16
+ import type { RefElementContext } from './ref-elements/types'
14
17
  import { compileTsFile } from './ts-compiler'
15
18
 
16
19
  const defaultWrapper = (Component: ComponentType<unknown>): ComponentType<unknown> => {
@@ -65,7 +68,6 @@ export function extractComponentManifestResult(
65
68
  ...options,
66
69
  wrapper: options?.wrapper ?? defaultWrapper,
67
70
  }
68
-
69
71
  // Step 1: Compile TypeScript (fatal)
70
72
  return compileTsFile(componentPath)
71
73
  .andThen((program) => {
@@ -89,66 +91,131 @@ export function extractComponentManifestResult(
89
91
  .andThen(({ program, componentInfo }) => {
90
92
  const { componentName } = componentInfo
91
93
 
92
- // Step 3: Load the compiled package module (non-fatal) — done after TS extraction
93
- // so componentName is known when reporting failures
94
- return loadModule(compiledEntryPath)
95
- .map((moduleExports) => ({
96
- loadComponent: (name: string) => findComponent(moduleExports, name) ?? findDefaultComponent(moduleExports),
97
- failure: null as LoadModuleFailure | null,
98
- }))
99
- .orElse((failure) => okAsync({ loadComponent: () => null as ComponentType<unknown> | null, failure }))
100
- .andThen(({ loadComponent, failure }) => {
101
- if (failure) {
102
- if (failure.esmError) {
94
+ return ResultAsync.fromPromise(
95
+ buildRefElementContext(program, componentPath, componentInfo, compiledEntryPath),
96
+ (thrown) =>
97
+ new ParseError(
98
+ `Failed to build the ref-element context for "${componentPath}": ${thrown instanceof Error ? thrown.message : String(thrown)}`,
99
+ { cause: thrown instanceof Error ? thrown : undefined, props: { phase: 'extract' } },
100
+ ),
101
+ ).andThen((refElementContext) =>
102
+ // Step 3: Load the compiled package module (non-fatal) done after TS extraction
103
+ // so componentName is known when reporting failures
104
+ loadModuleForExtraction(compiledEntryPath, refElementContext.runtimeModules)
105
+ .map(({ cleanup, moduleExports }) => ({
106
+ loadComponent: (name: string) => findComponent(moduleExports, name) ?? findDefaultComponent(moduleExports),
107
+ failure: null as LoadModuleFailure | null,
108
+ cleanup,
109
+ }))
110
+ .orElse((failure) =>
111
+ okAsync({
112
+ loadComponent: () => null as ComponentType<unknown> | null,
113
+ cleanup: async () => {},
114
+ failure,
115
+ }),
116
+ )
117
+ .andThen(({ cleanup, failure, loadComponent }) => {
118
+ if (failure) {
119
+ if (failure.esmError) {
120
+ report({
121
+ componentName,
122
+ phase: 'loader',
123
+ error: new IoError('ESM import failed', { cause: failure.esmError, props: { phase: 'loader' } }),
124
+ })
125
+ }
103
126
  report({
104
127
  componentName,
105
128
  phase: 'loader',
106
- error: new IoError('ESM import failed', { cause: failure.esmError, props: { phase: 'loader' } }),
129
+ error: new IoError('CJS require failed', { cause: failure.cjsError, props: { phase: 'loader' } }),
107
130
  })
108
131
  }
109
- report({
110
- componentName,
111
- phase: 'loader',
112
- error: new IoError('CJS require failed', { cause: failure.cjsError, props: { phase: 'loader' } }),
113
- })
114
- }
115
132
 
116
- // Step 4: Extract CSS imports (non-fatal)
117
- let cssImportPaths: string[] = []
118
- const cssResult = Result.fromThrowable(extractCssImports, (thrown) => thrown)(program)
119
- if (cssResult.isOk()) {
120
- cssImportPaths = cssResult.value
121
- } else {
122
- const thrown = cssResult.error
123
- report({
124
- componentName,
125
- phase: 'css',
126
- error: new ParseError(
127
- `Failed to extract CSS imports: ${thrown instanceof Error ? thrown.message : String(thrown)}`,
128
- { cause: thrown instanceof Error ? thrown : undefined, props: { phase: 'css' } },
129
- ),
130
- })
131
- }
133
+ // Step 4: Extract CSS imports (non-fatal)
134
+ let cssImportPaths: string[] = []
135
+ const cssResult = Result.fromThrowable(extractCssImports, (thrown) => thrown)(program)
136
+ if (cssResult.isOk()) {
137
+ cssImportPaths = cssResult.value
138
+ } else {
139
+ const thrown = cssResult.error
140
+ report({
141
+ componentName,
142
+ phase: 'css',
143
+ error: new ParseError(
144
+ `Failed to extract CSS imports: ${thrown instanceof Error ? thrown.message : String(thrown)}`,
145
+ { cause: thrown instanceof Error ? thrown : undefined, props: { phase: 'css' } },
146
+ ),
147
+ })
148
+ }
132
149
 
133
- // Step 5: Process the default-exported component (non-fatal unless render fails after load)
134
- const processResult = processComponent(
135
- componentInfo,
136
- loadComponent,
137
- cssImportPaths,
138
- !!failure,
139
- report,
140
- optionsWithDefaults,
141
- compiledEntryPath,
142
- )
143
- if (!processResult.ok) {
144
- return errAsync(processResult.error)
145
- }
146
- const component = toEditorReactComponent(processResult.component)
147
- return okAsync({ component, errors })
148
- })
150
+ // Step 5: Process the default-exported component (non-fatal unless render fails after load)
151
+ return ResultAsync.fromPromise(
152
+ processComponentWithCleanup(
153
+ componentInfo,
154
+ loadComponent,
155
+ cssImportPaths,
156
+ !!failure,
157
+ report,
158
+ optionsWithDefaults,
159
+ compiledEntryPath,
160
+ refElementContext,
161
+ cleanup,
162
+ errors,
163
+ ),
164
+ normalizeComponentExtractionCleanupFailure,
165
+ )
166
+ }),
167
+ )
149
168
  })
150
169
  }
151
170
 
171
+ function normalizeComponentExtractionCleanupFailure(thrown: unknown): InstanceType<typeof BaseError> {
172
+ const normalizedError = BaseError.normalize(thrown)
173
+ if (normalizedError instanceof BaseError) {
174
+ return normalizedError
175
+ }
176
+
177
+ return new IoError('Unexpected failure during component extraction cleanup', {
178
+ cause: normalizedError,
179
+ props: { phase: 'loader' },
180
+ })
181
+ }
182
+
183
+ async function processComponentWithCleanup(
184
+ componentInfo: ComponentInfo,
185
+ loadComponent: (name: string) => ComponentType<unknown> | null,
186
+ cssImportPaths: string[],
187
+ didModuleLoadFail: boolean,
188
+ report: (error: ExtractionError) => void,
189
+ options: ExtractComponentManifestOptions,
190
+ compiledEntryPath: string,
191
+ refElementContext: RefElementContext,
192
+ cleanup: () => Promise<void>,
193
+ errors: ExtractionError[],
194
+ ): Promise<ManifestResult> {
195
+ try {
196
+ const processResult = processComponent(
197
+ componentInfo,
198
+ loadComponent,
199
+ cssImportPaths,
200
+ didModuleLoadFail,
201
+ report,
202
+ options,
203
+ compiledEntryPath,
204
+ refElementContext.eligiblePaths,
205
+ )
206
+ if (!processResult.ok) {
207
+ throw processResult.error
208
+ }
209
+
210
+ return {
211
+ component: toEditorReactComponent(processResult.component),
212
+ errors,
213
+ }
214
+ } finally {
215
+ await cleanup()
216
+ }
217
+ }
218
+
152
219
  export async function extractComponentManifest(
153
220
  componentPath: string,
154
221
  compiledEntryPath: string,
@@ -8,7 +8,7 @@
8
8
  import { camelCase } from 'case-anything'
9
9
  import { type DefaultTreeAdapterMap, parseFragment } from 'parse5'
10
10
  import { TRACE_ATTR } from '../../../../component-renderer'
11
- import { findPreferredSemanticClass } from '../../../../utils/css-class'
11
+ import { findPreferredSemanticClass, normalizeClassNames } from '../../../../utils/css-class'
12
12
  import { PRESETS_WRAPPER_CLASS_NAME } from '../../utils/mock-generator'
13
13
  import type { CssPropertiesData } from '../css-properties'
14
14
  import { addTextProperties } from '../css-properties'
@@ -174,7 +174,7 @@ function getElementNamePart(element: Element, getElementById: (id: string) => El
174
174
 
175
175
  const classAttr = getAttribute(element, 'class')
176
176
  if (classAttr) {
177
- const semanticClass = findPreferredSemanticClass(classAttr.split(' '))
177
+ const semanticClass = findPreferredSemanticClass(normalizeClassNames(classAttr))
178
178
  if (semanticClass) elementName = semanticClass
179
179
  }
180
180
 
@@ -197,7 +197,7 @@ function getElementNamePart(element: Element, getElementById: (id: string) => El
197
197
  function hasClassNameCriteria(element: Element, extractorData: Map<string, unknown>): boolean {
198
198
  const classAttr = getAttribute(element, 'class')
199
199
  if (classAttr) {
200
- const semanticClass = findPreferredSemanticClass(classAttr.split(' '))
200
+ const semanticClass = findPreferredSemanticClass(normalizeClassNames(classAttr))
201
201
  if (semanticClass) return true
202
202
  }
203
203
 
@@ -17,13 +17,12 @@ export interface RenderContext {
17
17
  store: ExtractorStore
18
18
  }
19
19
 
20
- /**
21
- * Event emitted for each DOM element created during render.
22
- */
23
20
  export interface CreateElementEvent {
24
- tag: string
21
+ type: unknown
22
+ isDomElement: boolean
23
+ tag?: string
25
24
  props: Record<string, unknown>
26
- traceId: string
25
+ traceId?: string
27
26
  children: unknown[]
28
27
  store: ExtractorStore
29
28
  }
@@ -329,6 +329,8 @@ export function createCssPropertiesExtractor(): ReactExtractor {
329
329
  name: 'css-properties',
330
330
 
331
331
  onCreateElement(event: CreateElementEvent): void {
332
+ if (!event.isDomElement || !event.tag || !event.traceId) return
333
+
332
334
  const { tag, props, traceId, store } = event
333
335
  const role = props.role as string | undefined
334
336