@wix/zero-config-implementation 1.109.0 → 1.110.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/dist/index.d.ts +4 -0
- package/dist/index.js +7059 -7047
- package/package.json +2 -2
- package/src/converters/data-item-builder.test.ts +17 -0
- package/src/converters/data-item-builder.ts +5 -0
- package/src/information-extractors/react/extractors/css-properties.test.ts +32 -0
- package/src/information-extractors/react/extractors/css-properties.ts +28 -0
- package/src/information-extractors/react/extractors/index.ts +1 -0
- package/src/information-extractors/react/extractors/prop-tracker.test.ts +89 -0
- package/src/information-extractors/react/extractors/prop-tracker.ts +23 -8
- package/src/information-extractors/react/index.ts +1 -0
- package/src/information-extractors/react/types.ts +2 -0
- package/src/information-extractors/react/utils/mock-generator.ts +25 -17
- package/src/manifest-pipeline.ts +4 -0
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"registry": "https://registry.npmjs.org/",
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
7
|
-
"version": "1.
|
|
7
|
+
"version": "1.110.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": "
|
|
109
|
+
"falconPackageHash": "47599e898cd311d543a036e8af54090473a39ec45beb3a9f760fd806"
|
|
110
110
|
}
|
|
@@ -122,3 +122,20 @@ describe('buildDataItem — container dataType', () => {
|
|
|
122
122
|
expect(result._unsafeUnwrap().container).toEqual(expectedContainerData)
|
|
123
123
|
})
|
|
124
124
|
})
|
|
125
|
+
|
|
126
|
+
describe('buildDataItem — vectorArt', () => {
|
|
127
|
+
const vectorArtProp = (): PropInfo => ({
|
|
128
|
+
name: 'icon',
|
|
129
|
+
type: 'VectorArt',
|
|
130
|
+
required: false,
|
|
131
|
+
resolvedType: { kind: 'semantic', value: 'VectorArt', source: '@wix/editor-react-types' },
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('sets the vectorArt oneof member so the editor can resolve the replace panel', () => {
|
|
135
|
+
const dataItem = buildDataItem(vectorArtProp())._unsafeUnwrap()
|
|
136
|
+
|
|
137
|
+
expect(dataItem.dataType).toBe('vectorArt')
|
|
138
|
+
// Fixed to basic shapes: they are single-fill, so an icon's colour control always applies.
|
|
139
|
+
expect(dataItem.vectorArt).toEqual({ category: 'SHAPE_BASIC' })
|
|
140
|
+
})
|
|
141
|
+
})
|
|
@@ -534,6 +534,11 @@ function applyDataToBuilderType({
|
|
|
534
534
|
category: MEDIA.IMAGE_CATEGORY.IMAGE,
|
|
535
535
|
}
|
|
536
536
|
break
|
|
537
|
+
case 'vectorArt':
|
|
538
|
+
dataItem.dataType = DATA_TYPE.vectorArt
|
|
539
|
+
// Required for the Replace Vector Art panel; basic shapes are single-fill, so the colour control applies.
|
|
540
|
+
dataItem.vectorArt = { category: MEDIA.VECTOR_ART_CATEGORY.SHAPE_BASIC }
|
|
541
|
+
break
|
|
537
542
|
case 'a11y':
|
|
538
543
|
dataItem.dataType = DATA_TYPE.a11y
|
|
539
544
|
dataItem.a11y = {
|
|
@@ -7,11 +7,13 @@ import {
|
|
|
7
7
|
enrichContainerProperties,
|
|
8
8
|
enrichFlexProperties,
|
|
9
9
|
enrichMarginProperties,
|
|
10
|
+
enrichVectorArtProperties,
|
|
10
11
|
getCssPropertiesForTag,
|
|
11
12
|
getDefaultDisplayForTag,
|
|
12
13
|
hasContainerLikeDisplay,
|
|
13
14
|
hasFlexOrGridDisplay,
|
|
14
15
|
} from './css-properties'
|
|
16
|
+
import type { PropTrackerData } from './prop-tracker'
|
|
15
17
|
|
|
16
18
|
function matcherDataFromCss(declarations: string): MatchedCssData {
|
|
17
19
|
const properties = parseCss(`.test { ${declarations} }`).getPropertiesForSelector('.test')
|
|
@@ -610,3 +612,33 @@ describe('enrichMarginProperties', () => {
|
|
|
610
612
|
expect(cssData.relevant).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.marginBottom)
|
|
611
613
|
})
|
|
612
614
|
})
|
|
615
|
+
|
|
616
|
+
describe('enrichVectorArtProperties', () => {
|
|
617
|
+
const asVectorArtHost = (element: ExtractedElement): ExtractedElement => {
|
|
618
|
+
element.extractorData.set('prop-tracker', { rendersVectorArt: true } as PropTrackerData)
|
|
619
|
+
return element
|
|
620
|
+
}
|
|
621
|
+
const relevantOf = (element: ExtractedElement) =>
|
|
622
|
+
(element.extractorData.get('css-properties') as { relevant: string[] }).relevant
|
|
623
|
+
|
|
624
|
+
it('narrows a vector-art host to display, dropping the box controls of its tag', () => {
|
|
625
|
+
const host = asVectorArtHost(createElementWithCssProperties('div', 'display: block'))
|
|
626
|
+
expect(relevantOf(host)).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.background)
|
|
627
|
+
|
|
628
|
+
const [enrichedHost] = enrichVectorArtProperties([host])
|
|
629
|
+
|
|
630
|
+
expect(relevantOf(enrichedHost)).toEqual([CSS_PROPERTIES.CSS_PROPERTY_TYPE.display])
|
|
631
|
+
})
|
|
632
|
+
|
|
633
|
+
it('narrows a nested host after the flex pass, so its alignSelf does not survive', () => {
|
|
634
|
+
const parent: ExtractedElement = {
|
|
635
|
+
...createElementWithCssProperties('div', 'display: flex'),
|
|
636
|
+
children: [asVectorArtHost(createElementWithCssProperties('span', ''))],
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const [enrichedParent] = enrichVectorArtProperties(enrichFlexProperties([parent]))
|
|
640
|
+
|
|
641
|
+
expect(relevantOf(enrichedParent.children[0])).toEqual([CSS_PROPERTIES.CSS_PROPERTY_TYPE.display])
|
|
642
|
+
expect(relevantOf(enrichedParent)).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.flexDirection)
|
|
643
|
+
})
|
|
644
|
+
})
|
|
@@ -10,6 +10,7 @@ import { resolveCssPropertyValue } from '../../css/parse'
|
|
|
10
10
|
import type { MatchedCssData } from '../../css/types'
|
|
11
11
|
import type { ExtractedElement } from './core/tree-builder'
|
|
12
12
|
import type { CreateElementEvent, ReactExtractor } from './core/types'
|
|
13
|
+
import type { PropTrackerData } from './prop-tracker'
|
|
13
14
|
|
|
14
15
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
15
16
|
// Types
|
|
@@ -188,6 +189,11 @@ const TEXT_CSS_PROPERTIES = [
|
|
|
188
189
|
CSS_PROPERTY_TYPE.color,
|
|
189
190
|
]
|
|
190
191
|
|
|
192
|
+
/**
|
|
193
|
+
* CSS properties for vector-art hosts. Size, colour and rotation come from CSS custom properties.
|
|
194
|
+
*/
|
|
195
|
+
const VECTOR_ART_CSS_PROPERTIES: string[] = [CSS_PROPERTY_TYPE.display]
|
|
196
|
+
|
|
191
197
|
/**
|
|
192
198
|
* CSS properties for Media elements (excluding svg)
|
|
193
199
|
*/
|
|
@@ -706,6 +712,28 @@ export function enrichMarginProperties(elements: ExtractedElement[]): ExtractedE
|
|
|
706
712
|
})
|
|
707
713
|
}
|
|
708
714
|
|
|
715
|
+
/**
|
|
716
|
+
* Narrows vector-art hosts to `VECTOR_ART_CSS_PROPERTIES`.
|
|
717
|
+
* Must run after every other enrichment pass, so none of them can add box controls back.
|
|
718
|
+
*/
|
|
719
|
+
export function enrichVectorArtProperties(elements: ExtractedElement[]): ExtractedElement[] {
|
|
720
|
+
return elements.map((element) => {
|
|
721
|
+
const propTrackerData = element.extractorData.get('prop-tracker') as PropTrackerData | undefined
|
|
722
|
+
const cssData = element.extractorData.get('css-properties') as CssPropertiesData | undefined
|
|
723
|
+
|
|
724
|
+
if (propTrackerData?.rendersVectorArt && cssData) {
|
|
725
|
+
element.extractorData.set('css-properties', {
|
|
726
|
+
relevant: cssData.relevant.filter((property) => VECTOR_ART_CSS_PROPERTIES.includes(property)),
|
|
727
|
+
})
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
return {
|
|
731
|
+
...element,
|
|
732
|
+
children: enrichVectorArtProperties(element.children),
|
|
733
|
+
}
|
|
734
|
+
})
|
|
735
|
+
}
|
|
736
|
+
|
|
709
737
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
710
738
|
// Factory
|
|
711
739
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
2
|
import { createRefElementMarker } from '../../../extensions/ref-elements/path-utils'
|
|
3
3
|
import type { RefElementMatch } from '../../../extensions/ref-elements/types'
|
|
4
|
+
import type { ResolvedType } from '../../ts/types'
|
|
4
5
|
import { ExtractorStore } from './core/store'
|
|
5
6
|
import { type PropTrackerData, createPropTrackerExtractor } from './prop-tracker'
|
|
6
7
|
|
|
@@ -267,3 +268,91 @@ describe('createPropTrackerExtractor', () => {
|
|
|
267
268
|
expect(store.get<PropTrackerData>('trace-1', 'prop-tracker')?.hasEmptySlot).toBe(true)
|
|
268
269
|
})
|
|
269
270
|
})
|
|
271
|
+
|
|
272
|
+
describe('createPropTrackerExtractor — vector-art hosts', () => {
|
|
273
|
+
const vectorArtType: ResolvedType = { kind: 'semantic', value: 'VectorArt', source: '@wix/editor-react-types' }
|
|
274
|
+
|
|
275
|
+
/** Mocks an `icon` prop of `resolvedType`, renders one element with `renderProps`, returns its tracker data. */
|
|
276
|
+
function renderIcon<MockedIcon = Record<string, string>>(
|
|
277
|
+
resolvedType: ResolvedType,
|
|
278
|
+
renderProps: (icon: MockedIcon) => Record<string, unknown>,
|
|
279
|
+
): PropTrackerData | undefined {
|
|
280
|
+
const store = new ExtractorStore()
|
|
281
|
+
const { extractor } = createPropTrackerExtractor()
|
|
282
|
+
const context = {
|
|
283
|
+
componentInfo: {
|
|
284
|
+
componentName: 'Icon',
|
|
285
|
+
props: { icon: { name: 'icon', type: 'VectorArt', required: false, resolvedType } },
|
|
286
|
+
},
|
|
287
|
+
component: () => null,
|
|
288
|
+
props: {} as Record<string, unknown>,
|
|
289
|
+
store,
|
|
290
|
+
}
|
|
291
|
+
extractor.onBeforeRender?.(context)
|
|
292
|
+
|
|
293
|
+
extractor.onCreateElement?.({
|
|
294
|
+
type: 'div',
|
|
295
|
+
isDomElement: true,
|
|
296
|
+
tag: 'div',
|
|
297
|
+
traceId: 'trace-1',
|
|
298
|
+
props: renderProps(context.props.icon as MockedIcon),
|
|
299
|
+
children: [],
|
|
300
|
+
store,
|
|
301
|
+
})
|
|
302
|
+
return store.get<PropTrackerData>('trace-1', 'prop-tracker')
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
it('flags an element that injects svgContent as raw markup', () => {
|
|
306
|
+
const data = renderIcon(vectorArtType, (icon) => ({ injected: { __html: icon.svgContent } }))
|
|
307
|
+
|
|
308
|
+
expect(data?.rendersVectorArt).toBe(true)
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
it('does not flag a vector-art field bound to an ordinary attribute', () => {
|
|
312
|
+
const data = renderIcon(vectorArtType, (icon) => ({ 'data-src': icon.uri }))
|
|
313
|
+
|
|
314
|
+
expect(data?.rendersVectorArt).toBe(false)
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
it('does not flag raw markup that does not come from vector art', () => {
|
|
318
|
+
const data = renderIcon({ kind: 'primitive', value: 'string' }, (markup) => ({
|
|
319
|
+
injected: { __html: markup },
|
|
320
|
+
}))
|
|
321
|
+
|
|
322
|
+
expect(data?.rendersVectorArt).toBe(false)
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
it('flags a host when vector art is the union member the data item describes', () => {
|
|
326
|
+
const data = renderIcon({ kind: 'union', types: [vectorArtType, { kind: 'literal', value: 'none' }] }, (icon) => ({
|
|
327
|
+
injected: { __html: icon.svgContent },
|
|
328
|
+
}))
|
|
329
|
+
|
|
330
|
+
expect(data?.rendersVectorArt).toBe(true)
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
/** `{ name: string; badge: VectorArt }` */
|
|
334
|
+
const badgeHolderType: ResolvedType = {
|
|
335
|
+
kind: 'object',
|
|
336
|
+
properties: {
|
|
337
|
+
name: { name: 'name', type: 'string', required: true, resolvedType: { kind: 'primitive', value: 'string' } },
|
|
338
|
+
badge: { name: 'badge', type: 'VectorArt', required: true, resolvedType: vectorArtType },
|
|
339
|
+
},
|
|
340
|
+
}
|
|
341
|
+
type BadgeHolder = { name: string; badge: Record<string, string> }
|
|
342
|
+
|
|
343
|
+
it('flags a host of vector art nested in an object prop', () => {
|
|
344
|
+
const data = renderIcon<BadgeHolder>(badgeHolderType, (avatar) => ({
|
|
345
|
+
injected: { __html: avatar.badge.svgContent },
|
|
346
|
+
}))
|
|
347
|
+
|
|
348
|
+
expect(data?.rendersVectorArt).toBe(true)
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
it('flags a host of vector art nested in array items', () => {
|
|
352
|
+
const data = renderIcon<BadgeHolder[]>({ kind: 'array', elementType: badgeHolderType }, (items) => ({
|
|
353
|
+
injected: { __html: items[0].badge.svgContent },
|
|
354
|
+
}))
|
|
355
|
+
|
|
356
|
+
expect(data?.rendersVectorArt).toBe(true)
|
|
357
|
+
})
|
|
358
|
+
})
|
|
@@ -28,6 +28,15 @@ export interface PropTrackerData {
|
|
|
28
28
|
eventHandlers: string[]
|
|
29
29
|
/** True when a child slot rendered nothing, so the content is unknown. */
|
|
30
30
|
hasEmptySlot: boolean
|
|
31
|
+
/** True when the element injects a `VectorArt` value's `svgContent` as raw markup. */
|
|
32
|
+
rendersVectorArt?: boolean
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface SpyMatch {
|
|
36
|
+
propName: string
|
|
37
|
+
path: string
|
|
38
|
+
embedded: boolean
|
|
39
|
+
semantic?: string
|
|
31
40
|
}
|
|
32
41
|
|
|
33
42
|
export interface PropTrackerExtractorState {
|
|
@@ -68,8 +77,8 @@ export function createPropTrackerExtractor(options?: CreatePropTrackerExtractorO
|
|
|
68
77
|
const pendingRefElementsByPath = new Map<string, PendingRefElement[]>()
|
|
69
78
|
|
|
70
79
|
const registrar: PropSpyRegistrar = {
|
|
71
|
-
registerString(path, propName, value) {
|
|
72
|
-
stringMeta.set(value, { path, propName, originalValue: value })
|
|
80
|
+
registerString(path, propName, value, semantic) {
|
|
81
|
+
stringMeta.set(value, { path, propName, originalValue: value, semantic })
|
|
73
82
|
},
|
|
74
83
|
registerNumber(path, propName, value) {
|
|
75
84
|
numberMeta.set(value, { path, propName, originalValue: value })
|
|
@@ -79,16 +88,13 @@ export function createPropTrackerExtractor(options?: CreatePropTrackerExtractorO
|
|
|
79
88
|
},
|
|
80
89
|
}
|
|
81
90
|
|
|
82
|
-
const extractSpies = (
|
|
83
|
-
value: unknown,
|
|
84
|
-
seen = new WeakSet<object>(),
|
|
85
|
-
): { propName: string; path: string; embedded: boolean }[] => {
|
|
91
|
+
const extractSpies = (value: unknown, seen = new WeakSet<object>()): SpyMatch[] => {
|
|
86
92
|
if (typeof value === 'string') {
|
|
87
|
-
const results:
|
|
93
|
+
const results: SpyMatch[] = []
|
|
88
94
|
for (const [key, meta] of stringMeta) {
|
|
89
95
|
if (value.includes(key)) {
|
|
90
96
|
// embedded = spy marker was part of a longer string (e.g. `mailto:${email}`)
|
|
91
|
-
results.push({ propName: meta.propName, path: meta.path, embedded: value !== key })
|
|
97
|
+
results.push({ propName: meta.propName, path: meta.path, embedded: value !== key, semantic: meta.semantic })
|
|
92
98
|
}
|
|
93
99
|
}
|
|
94
100
|
return results
|
|
@@ -138,6 +144,7 @@ export function createPropTrackerExtractor(options?: CreatePropTrackerExtractorO
|
|
|
138
144
|
const boundProps = new Set<string>()
|
|
139
145
|
const concatenatedAttrs = new Map<string, string>()
|
|
140
146
|
const eventHandlers: string[] = []
|
|
147
|
+
let rendersVectorArt = false
|
|
141
148
|
|
|
142
149
|
Object.entries(props).forEach(([key, value]) => {
|
|
143
150
|
if (key.startsWith('__') || key === TRACE_ATTR) return
|
|
@@ -146,7 +153,14 @@ export function createPropTrackerExtractor(options?: CreatePropTrackerExtractorO
|
|
|
146
153
|
eventHandlers.push(key)
|
|
147
154
|
}
|
|
148
155
|
|
|
156
|
+
// Injected markup is identified by React's `{ __html }` wrapper, not by the prop's name.
|
|
157
|
+
const valueKeys = typeof value === 'object' && value !== null ? Object.keys(value) : []
|
|
158
|
+
const isRawHtmlValue = valueKeys.length === 1 && valueKeys[0] === '__html'
|
|
159
|
+
|
|
149
160
|
const spies = extractSpies(value)
|
|
161
|
+
if (isRawHtmlValue && spies.some((spy) => spy.semantic === 'VectorArt')) {
|
|
162
|
+
rendersVectorArt = true
|
|
163
|
+
}
|
|
150
164
|
|
|
151
165
|
spies.forEach((spy) => {
|
|
152
166
|
boundProps.add(spy.propName)
|
|
@@ -173,6 +187,7 @@ export function createPropTrackerExtractor(options?: CreatePropTrackerExtractorO
|
|
|
173
187
|
concatenatedAttrs,
|
|
174
188
|
eventHandlers,
|
|
175
189
|
hasEmptySlot: event.hasEmptySlot ?? false,
|
|
190
|
+
rendersVectorArt,
|
|
176
191
|
} satisfies PropTrackerData)
|
|
177
192
|
|
|
178
193
|
attachMatchedRefElement(traceId, props, store)
|
|
@@ -13,6 +13,8 @@ export interface PropSpyMeta {
|
|
|
13
13
|
path: string
|
|
14
14
|
propName: string
|
|
15
15
|
originalValue: unknown
|
|
16
|
+
/** Semantic type the value was mocked for (e.g. `VectorArt`). */
|
|
17
|
+
semantic?: string
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -41,7 +41,7 @@ export function resetMockCounter(): void {
|
|
|
41
41
|
* Satisfied structurally by PropSpyContext.
|
|
42
42
|
*/
|
|
43
43
|
export interface PropSpyRegistrar {
|
|
44
|
-
registerString(path: string, propName: string, value: string): void
|
|
44
|
+
registerString(path: string, propName: string, value: string, semantic?: string): void
|
|
45
45
|
registerNumber(path: string, propName: string, value: number): void
|
|
46
46
|
registerFunction(path: string, propName: string, value: (...args: unknown[]) => unknown): void
|
|
47
47
|
}
|
|
@@ -168,7 +168,7 @@ function generateSemanticValue(
|
|
|
168
168
|
case 'A11y':
|
|
169
169
|
return generateMockA11y(path, registrar)
|
|
170
170
|
case 'VectorArt':
|
|
171
|
-
return generateMockVectorArt()
|
|
171
|
+
return generateMockVectorArt(path, registrar)
|
|
172
172
|
case 'Audio':
|
|
173
173
|
case 'AudioUri':
|
|
174
174
|
return generateMockAudio()
|
|
@@ -249,19 +249,20 @@ function generateUnionValue(
|
|
|
249
249
|
): unknown {
|
|
250
250
|
const types = resolvedType.types ?? []
|
|
251
251
|
|
|
252
|
-
// Prefer string literals for textEnum-like unions
|
|
253
|
-
const stringLiteral = types.find(
|
|
254
|
-
(candidateType) => candidateType.kind === 'literal' && typeof candidateType.value === 'string',
|
|
255
|
-
)
|
|
256
|
-
if (stringLiteral) {
|
|
257
|
-
return stringLiteral.value
|
|
258
|
-
}
|
|
259
|
-
|
|
260
252
|
// Skip undefined/null when picking a representative type — Partial<T> produces T | undefined
|
|
261
253
|
// unions where TypeScript may place undefined first, which would generate useless mock values
|
|
262
254
|
const isNullish = (candidateType: ResolvedType) =>
|
|
263
255
|
candidateType.kind === 'primitive' && (candidateType.value === 'undefined' || candidateType.value === 'null')
|
|
264
|
-
const
|
|
256
|
+
const definedTypes = types.filter((candidateType) => !isNullish(candidateType))
|
|
257
|
+
|
|
258
|
+
// Mirror the data-item builder: textEnum-like unions mock a literal, others their first defined member.
|
|
259
|
+
const isStringLiteral = (candidateType: ResolvedType) =>
|
|
260
|
+
candidateType.kind === 'literal' && typeof candidateType.value === 'string'
|
|
261
|
+
if (definedTypes.length > 0 && definedTypes.every(isStringLiteral)) {
|
|
262
|
+
return definedTypes[0].value
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const representative = definedTypes[0] ?? types[0]
|
|
265
266
|
if (representative) {
|
|
266
267
|
return generateValueFromResolvedType(representative, propName, path, registrar, options)
|
|
267
268
|
}
|
|
@@ -465,12 +466,19 @@ function generateMockA11y(path: string, registrar?: PropSpyRegistrar): Record<st
|
|
|
465
466
|
return a11y
|
|
466
467
|
}
|
|
467
468
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
469
|
+
/**
|
|
470
|
+
* Mocks `VectorArt`. `svgContent` is registered as `VectorArt` so the prop tracker can detect its host element.
|
|
471
|
+
*/
|
|
472
|
+
function generateMockVectorArt(path: string, registrar?: PropSpyRegistrar): Record<string, unknown> {
|
|
473
|
+
const viewBox = '0 0 24 24'
|
|
474
|
+
// Spies match by substring, so each registered value must be unique across props.
|
|
475
|
+
const token = faker.string.alphanumeric(8)
|
|
476
|
+
const uri = `mock_vector_${token}.svg`
|
|
477
|
+
const svgContent = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${viewBox}" data-mock="${token}"><circle cx="12" cy="12" r="10"/></svg>`
|
|
478
|
+
registrar?.registerString(`${path}.uri`, 'uri', uri)
|
|
479
|
+
registrar?.registerString(`${path}.svgContent`, 'svgContent', svgContent, 'VectorArt')
|
|
480
|
+
|
|
481
|
+
return { viewBox, contentBox: viewBox, contentType: 'shape', colors: {}, uri, svgContent }
|
|
474
482
|
}
|
|
475
483
|
|
|
476
484
|
function generateMockAudio(): Record<string, unknown> {
|
package/src/manifest-pipeline.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
enrichFlexProperties,
|
|
14
14
|
enrichGapProperties,
|
|
15
15
|
enrichMarginProperties,
|
|
16
|
+
enrichVectorArtProperties,
|
|
16
17
|
runExtractors,
|
|
17
18
|
} from './information-extractors/react'
|
|
18
19
|
import type { CoupledComponentInfo, CoupledProp, DOMBinding, TrackingStores } from './information-extractors/react'
|
|
@@ -171,6 +172,9 @@ export function processComponent(
|
|
|
171
172
|
}
|
|
172
173
|
}
|
|
173
174
|
|
|
175
|
+
// Runs last so no earlier pass can add box controls back to a vector-art host.
|
|
176
|
+
enhancedInfo = { ...enhancedInfo, elements: enrichVectorArtProperties(enhancedInfo.elements) }
|
|
177
|
+
|
|
174
178
|
return {
|
|
175
179
|
ok: true,
|
|
176
180
|
component: {
|