@wix/zero-config-implementation 1.67.0 → 1.68.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-Ps7eUbv5.js → index-BewAGOQY.js} +6300 -6254
- package/dist/{index-BfHROU60.js → index-DTB9iwv9.js} +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/src/converters/custom-states-builder.test.ts +66 -1
- package/src/converters/custom-states-builder.ts +53 -11
- package/src/converters/to-editor-component.ts +9 -3
- package/src/information-extractors/ts/components.test.ts +29 -0
- package/src/information-extractors/ts/components.ts +72 -3
- package/src/information-extractors/ts/types.ts +5 -0
package/dist/index.d.ts
CHANGED
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-
|
|
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-BewAGOQY.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.
|
|
7
|
+
"version": "1.68.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": "
|
|
84
|
+
"falconPackageHash": "5848ef0051ff536666dd46c6264f765b40f280afa13b5d120028a1a2"
|
|
85
85
|
}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
2
|
import type { ExtractedElement } from '../information-extractors/react'
|
|
3
|
+
import type { CoupledProp } from '../information-extractors/react/types'
|
|
3
4
|
import { type CustomClassTrigger, buildCustomStatesBlock } from './custom-states-builder'
|
|
4
5
|
|
|
5
6
|
function elementWithClass(classAttribute: string): ExtractedElement {
|
|
6
7
|
return { attributes: { class: classAttribute } } as unknown as ExtractedElement
|
|
7
8
|
}
|
|
8
9
|
|
|
10
|
+
function booleanStateProp(name: string): CoupledProp {
|
|
11
|
+
return { name, type: 'boolean', isStateTrigger: true } as unknown as CoupledProp
|
|
12
|
+
}
|
|
13
|
+
|
|
9
14
|
function classTrigger(baseSelector: string, modifier: string, isCssModule = false): CustomClassTrigger {
|
|
10
15
|
return { baseSelector, modifier, isCssModule }
|
|
11
16
|
}
|
|
@@ -14,6 +19,7 @@ describe('buildCustomStatesBlock', () => {
|
|
|
14
19
|
it('emits a custom-class state with className only (no props, no pseudoClass)', () => {
|
|
15
20
|
const states = buildCustomStatesBlock({
|
|
16
21
|
element: elementWithClass('custom-states'),
|
|
22
|
+
propTriggers: [],
|
|
17
23
|
classTriggers: [classTrigger('.custom-states', 'custom-states--featured')],
|
|
18
24
|
})
|
|
19
25
|
|
|
@@ -23,6 +29,7 @@ describe('buildCustomStatesBlock', () => {
|
|
|
23
29
|
it('derives a multi-word state name from the modifier and capital-cases the displayName', () => {
|
|
24
30
|
const states = buildCustomStatesBlock({
|
|
25
31
|
element: elementWithClass('widget'),
|
|
32
|
+
propTriggers: [],
|
|
26
33
|
classTriggers: [classTrigger('.widget', 'widget--in-progress')],
|
|
27
34
|
})
|
|
28
35
|
|
|
@@ -35,6 +42,7 @@ describe('buildCustomStatesBlock', () => {
|
|
|
35
42
|
it('does not apply a block-base class trigger to a BEM child element (plain CSS, exact match)', () => {
|
|
36
43
|
const states = buildCustomStatesBlock({
|
|
37
44
|
element: elementWithClass('custom-states__label'),
|
|
45
|
+
propTriggers: [],
|
|
38
46
|
classTriggers: [classTrigger('.custom-states', 'custom-states--featured')],
|
|
39
47
|
})
|
|
40
48
|
|
|
@@ -46,6 +54,7 @@ describe('buildCustomStatesBlock', () => {
|
|
|
46
54
|
// base element — not a BEM child. (Regression: this used to be dropped as a child.)
|
|
47
55
|
const states = buildCustomStatesBlock({
|
|
48
56
|
element: elementWithClass('root__IWjkn wixui-test-comp-card test-comp-card'),
|
|
57
|
+
propTriggers: [],
|
|
49
58
|
classTriggers: [classTrigger('.root', 'test-comp-card--featured', true)],
|
|
50
59
|
})
|
|
51
60
|
|
|
@@ -55,15 +64,71 @@ describe('buildCustomStatesBlock', () => {
|
|
|
55
64
|
it('still excludes a genuine BEM child (`root__label`) from a block-base trigger in a module', () => {
|
|
56
65
|
const states = buildCustomStatesBlock({
|
|
57
66
|
element: elementWithClass('root__label'),
|
|
67
|
+
propTriggers: [],
|
|
58
68
|
classTriggers: [classTrigger('.root', 'test-comp-card--featured', true)],
|
|
59
69
|
})
|
|
60
70
|
|
|
61
71
|
expect(states).toBeUndefined()
|
|
62
72
|
})
|
|
63
73
|
|
|
64
|
-
it('returns undefined when no
|
|
74
|
+
it('returns undefined when no trigger targets the element', () => {
|
|
75
|
+
const states = buildCustomStatesBlock({
|
|
76
|
+
element: elementWithClass('custom-states'),
|
|
77
|
+
propTriggers: [],
|
|
78
|
+
classTriggers: [],
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
expect(states).toBeUndefined()
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('emits a props-only state for a prop trigger with no matching class (no className synthesized)', () => {
|
|
85
|
+
const states = buildCustomStatesBlock({
|
|
86
|
+
element: elementWithClass('panel'),
|
|
87
|
+
propTriggers: [booleanStateProp('isExpanded')],
|
|
88
|
+
classTriggers: [],
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
expect(states).toEqual({
|
|
92
|
+
'is-expanded': { displayName: 'Is Expanded', props: { isExpanded: true } },
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('keeps the full prop name in the state key and displayName (no is/has stripping)', () => {
|
|
97
|
+
const states = buildCustomStatesBlock({
|
|
98
|
+
element: elementWithClass('widget'),
|
|
99
|
+
propTriggers: [booleanStateProp('isLoading')],
|
|
100
|
+
classTriggers: [],
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
expect(states?.['is-loading']).toEqual({
|
|
104
|
+
displayName: 'Is Loading',
|
|
105
|
+
props: { isLoading: true },
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('merges a prop trigger with a class trigger that resolves to the same state name', () => {
|
|
110
|
+
const states = buildCustomStatesBlock({
|
|
111
|
+
element: elementWithClass('prop-states'),
|
|
112
|
+
propTriggers: [booleanStateProp('isLoading')],
|
|
113
|
+
classTriggers: [classTrigger('.prop-states', 'prop-states--is-loading')],
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
expect(states).toEqual({
|
|
117
|
+
'is-loading': {
|
|
118
|
+
displayName: 'Is Loading',
|
|
119
|
+
className: 'prop-states--is-loading',
|
|
120
|
+
props: { isLoading: true },
|
|
121
|
+
},
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('ignores plain (non-ElementState) props and non-boolean state triggers', () => {
|
|
65
126
|
const states = buildCustomStatesBlock({
|
|
66
127
|
element: elementWithClass('custom-states'),
|
|
128
|
+
propTriggers: [
|
|
129
|
+
{ name: 'showLabel', type: 'boolean' } as unknown as CoupledProp,
|
|
130
|
+
{ name: 'variant', type: "'a' | 'b'", isStateTrigger: true } as unknown as CoupledProp,
|
|
131
|
+
],
|
|
67
132
|
classTriggers: [],
|
|
68
133
|
})
|
|
69
134
|
|
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Builds the custom (non-native) `states` entries for an element.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Two triggers feed this, per the agreed design — props are looked at first:
|
|
5
|
+
* - Prop — a boolean prop wrapped in `ElementState<>` (TS extractor sets
|
|
6
|
+
* `isStateTrigger`) IS a custom state, emitting
|
|
7
|
+
* `props: { <prop>: true }`. The CSS is then searched for a
|
|
8
|
+
* class trigger matching that state's name — when found it
|
|
9
|
+
* supplies the editor-trigger `className`; otherwise the entry
|
|
10
|
+
* is props-only (`className` is never synthesized).
|
|
11
|
+
* - Custom class — a `:global(.modifier)` class on the element with no paired
|
|
12
|
+
* native pseudo (CSS extractor `getStateClasses`) and not
|
|
13
|
+
* claimed by a prop trigger. Emits the modifier as a
|
|
14
|
+
* className-only state.
|
|
7
15
|
*
|
|
8
16
|
* Native states are built separately and take precedence on any key collision.
|
|
9
|
-
*
|
|
10
|
-
* Note: the prop trigger (`State<>` markers → `props` overrides) is intentionally
|
|
11
|
-
* not handled here yet — it is deferred to a follow-up PR, gated on the upstream
|
|
12
|
-
* marker-type agreement.
|
|
13
17
|
*/
|
|
14
18
|
|
|
15
19
|
import type { States } from '@wix/react-component-schema'
|
|
20
|
+
import { kebabCase } from 'case-anything'
|
|
16
21
|
import type { StateClassRule } from '../information-extractors/css/types'
|
|
17
|
-
import type { ExtractedElement } from '../information-extractors/react'
|
|
22
|
+
import type { CoupledProp, ExtractedElement } from '../information-extractors/react'
|
|
18
23
|
import { isGlobalSemanticClass, stateNameFromModifier } from '../utils/css-class'
|
|
19
24
|
import { formatDisplayName } from './utils'
|
|
20
25
|
|
|
@@ -23,17 +28,44 @@ export type CustomClassTrigger = StateClassRule & { isCssModule: boolean }
|
|
|
23
28
|
|
|
24
29
|
interface BuildCustomStatesParams {
|
|
25
30
|
element: ExtractedElement
|
|
31
|
+
/** Prop triggers in scope for this element (root-level `ElementState<>` props; `[]` for inner elements). */
|
|
32
|
+
propTriggers: CoupledProp[]
|
|
26
33
|
/** Custom-class triggers (modifiers with no paired native pseudo) across all CSS files. */
|
|
27
34
|
classTriggers: CustomClassTrigger[]
|
|
28
35
|
}
|
|
29
36
|
|
|
30
|
-
export function buildCustomStatesBlock({
|
|
37
|
+
export function buildCustomStatesBlock({
|
|
38
|
+
element,
|
|
39
|
+
propTriggers,
|
|
40
|
+
classTriggers,
|
|
41
|
+
}: BuildCustomStatesParams): States | undefined {
|
|
31
42
|
const states: States = {}
|
|
32
43
|
|
|
33
|
-
//
|
|
44
|
+
// Class triggers that target this element, keyed by their derived state name,
|
|
45
|
+
// so prop triggers can look up their matching class.
|
|
46
|
+
const elementClassTriggers = new Map<string, CustomClassTrigger>()
|
|
34
47
|
for (const rule of classTriggers) {
|
|
35
48
|
if (!elementMatchesBase(element, rule.baseSelector, rule.isCssModule)) continue
|
|
36
|
-
|
|
49
|
+
elementClassTriggers.set(stateNameFromModifier(rule.modifier), rule)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Prop triggers first: each boolean `ElementState<>` prop is a custom state.
|
|
53
|
+
// Then search the CSS for the class trigger matching that state's name — when
|
|
54
|
+
// found it supplies the editor-trigger className; otherwise props-only.
|
|
55
|
+
for (const prop of propTriggers) {
|
|
56
|
+
if (!prop.isStateTrigger || prop.type !== 'boolean') continue
|
|
57
|
+
const stateName = stateNameFromProp(prop.name)
|
|
58
|
+
const matchingClassTrigger = elementClassTriggers.get(stateName)
|
|
59
|
+
states[stateName] = {
|
|
60
|
+
displayName: formatDisplayName(stateName),
|
|
61
|
+
...(matchingClassTrigger && { className: matchingClassTrigger.modifier }),
|
|
62
|
+
props: { [prop.name]: true },
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Remaining class triggers — those not claimed by a prop — are className-only states.
|
|
67
|
+
for (const [stateName, rule] of elementClassTriggers) {
|
|
68
|
+
if (states[stateName] !== undefined) continue
|
|
37
69
|
states[stateName] = {
|
|
38
70
|
displayName: formatDisplayName(stateName),
|
|
39
71
|
className: rule.modifier,
|
|
@@ -43,6 +75,16 @@ export function buildCustomStatesBlock({ element, classTriggers }: BuildCustomSt
|
|
|
43
75
|
return Object.keys(states).length > 0 ? states : undefined
|
|
44
76
|
}
|
|
45
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Derives the state name from a boolean prop by kebab-casing the full prop name
|
|
80
|
+
* (`isLoading` → `is-loading`, so the displayName reads "Is Loading"). The prop
|
|
81
|
+
* name is kept whole — no `is`/`has` prefix stripping — so the state key mirrors
|
|
82
|
+
* the prop that triggers it.
|
|
83
|
+
*/
|
|
84
|
+
function stateNameFromProp(propName: string): string {
|
|
85
|
+
return kebabCase(propName)
|
|
86
|
+
}
|
|
87
|
+
|
|
46
88
|
/**
|
|
47
89
|
* Checks whether an element's rendered class list matches a simple single-class
|
|
48
90
|
* base selector (e.g. `.root`, `.custom-states`).
|
|
@@ -29,7 +29,7 @@ import { formatDisplayName } from './utils'
|
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* Merges native and custom state blocks. Native states take precedence on any
|
|
32
|
-
* key collision (e.g. an author wrapping `isDisabled` in `
|
|
32
|
+
* key collision (e.g. an author wrapping `isDisabled` in `ElementState<>` does not
|
|
33
33
|
* shadow the inferred native `disabled` state).
|
|
34
34
|
*/
|
|
35
35
|
function mergeStates(native: States | undefined, custom: States | undefined): States | undefined {
|
|
@@ -75,7 +75,11 @@ function buildEditorElement(
|
|
|
75
75
|
const rootStates = rootElement
|
|
76
76
|
? mergeStates(
|
|
77
77
|
rootSemanticClass ? buildStatesBlock(rootElement, rootSemanticClass) : undefined,
|
|
78
|
-
buildCustomStatesBlock({
|
|
78
|
+
buildCustomStatesBlock({
|
|
79
|
+
element: rootElement,
|
|
80
|
+
propTriggers: Object.values(component.props).filter((prop) => prop.isStateTrigger),
|
|
81
|
+
classTriggers: customClassTriggers,
|
|
82
|
+
}),
|
|
79
83
|
)
|
|
80
84
|
: undefined
|
|
81
85
|
|
|
@@ -147,9 +151,11 @@ function buildElements(
|
|
|
147
151
|
const cssCustomProps = nearestCommonAncestorCustomProps.get(element.traceId) ?? {}
|
|
148
152
|
|
|
149
153
|
const semanticClass = getSemanticBlockName(element)
|
|
154
|
+
// Inner-element custom states come from custom-class triggers only; prop
|
|
155
|
+
// triggers (`ElementState<>`) are component-level and attach to the root.
|
|
150
156
|
const states = mergeStates(
|
|
151
157
|
semanticClass ? buildStatesBlock(element, semanticClass) : undefined,
|
|
152
|
-
buildCustomStatesBlock({ element, classTriggers: customClassTriggers }),
|
|
158
|
+
buildCustomStatesBlock({ element, propTriggers: [], classTriggers: customClassTriggers }),
|
|
153
159
|
)
|
|
154
160
|
|
|
155
161
|
result[element.name] = {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { compileTsFile } from '../../ts-compiler'
|
|
4
|
+
import { extractAllComponentInfo } from './components'
|
|
5
|
+
|
|
6
|
+
const propTriggeredFixture = path.resolve(
|
|
7
|
+
__dirname,
|
|
8
|
+
'../../../../example-components/src/components/DesignStates/Custom/PropTriggered/WithMatchingClass/component.tsx',
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
describe('TS extractor — ElementState<> trigger detection', () => {
|
|
12
|
+
it('flags an ElementState<>-wrapped prop and unwraps its inner type', async () => {
|
|
13
|
+
const programResult = await compileTsFile(propTriggeredFixture)
|
|
14
|
+
expect(programResult.isOk()).toBe(true)
|
|
15
|
+
const program = programResult._unsafeUnwrap()
|
|
16
|
+
|
|
17
|
+
const components = extractAllComponentInfo(program, propTriggeredFixture)
|
|
18
|
+
const component = components.find((candidate) => candidate.componentName === 'PropStates')
|
|
19
|
+
expect(component).toBeDefined()
|
|
20
|
+
|
|
21
|
+
const isLoading = component!.props.isLoading
|
|
22
|
+
expect(isLoading.isStateTrigger).toBe(true)
|
|
23
|
+
// The `ElementState<boolean>` wrapper is unwrapped — the prop behaves as a plain boolean.
|
|
24
|
+
expect(isLoading.type).toBe('boolean')
|
|
25
|
+
|
|
26
|
+
// Plain booleans are never flagged as state triggers.
|
|
27
|
+
expect(component!.props.showLabel.isStateTrigger).toBeUndefined()
|
|
28
|
+
})
|
|
29
|
+
})
|
|
@@ -208,16 +208,27 @@ function convertComponentDoc(doc: ComponentDoc, program: ts.Program, checker: ts
|
|
|
208
208
|
|
|
209
209
|
// Resolve the type deeply
|
|
210
210
|
let declaredTypeInfo: DeclaredTypeInfo | undefined
|
|
211
|
+
let stateTrigger: StateTriggerInfo | undefined
|
|
211
212
|
if (propSymbol) {
|
|
212
213
|
const decl = propSymbol.getDeclarations()?.[0]
|
|
213
214
|
if (decl) {
|
|
214
|
-
|
|
215
|
+
const declSourceFile = decl.getSourceFile()
|
|
216
|
+
declaredTypeInfo = getDeclaredTypeInfo(propSymbol, declSourceFile, checker)
|
|
217
|
+
stateTrigger = detectStateTrigger(propSymbol, declSourceFile, checker)
|
|
215
218
|
}
|
|
216
219
|
}
|
|
217
220
|
|
|
218
|
-
|
|
221
|
+
// An `ElementState<Inner>` prop should behave as its inner type everywhere except
|
|
222
|
+
// for the `isStateTrigger` flag, so unwrap the inner type for `type` and resolution
|
|
223
|
+
// (and drop the `ElementState` declared symbol so it isn't mistaken for a semantic type).
|
|
224
|
+
const typeString = stateTrigger?.innerTypeName ?? declaredTypeInfo?.name ?? propItem.type.name
|
|
219
225
|
const resolvedType = propType
|
|
220
|
-
? resolveType({
|
|
226
|
+
? resolveType({
|
|
227
|
+
type: propType,
|
|
228
|
+
checker,
|
|
229
|
+
typeString,
|
|
230
|
+
declaredSymbol: stateTrigger ? undefined : declaredTypeInfo?.symbol,
|
|
231
|
+
})
|
|
221
232
|
: { kind: 'primitive' as const, value: propItem.type.name }
|
|
222
233
|
|
|
223
234
|
// Convert default value
|
|
@@ -239,6 +250,7 @@ function convertComponentDoc(doc: ComponentDoc, program: ts.Program, checker: ts
|
|
|
239
250
|
resolvedType,
|
|
240
251
|
description: propItem.description || undefined,
|
|
241
252
|
deprecated: tags?.deprecated !== undefined ? true : undefined,
|
|
253
|
+
isStateTrigger: stateTrigger ? true : undefined,
|
|
242
254
|
}
|
|
243
255
|
}
|
|
244
256
|
|
|
@@ -477,6 +489,63 @@ interface DeclaredTypeInfo {
|
|
|
477
489
|
symbol?: ts.Symbol
|
|
478
490
|
}
|
|
479
491
|
|
|
492
|
+
interface StateTriggerInfo {
|
|
493
|
+
/** Text of the inner type argument, e.g. `'boolean'`. */
|
|
494
|
+
innerTypeName: string
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Detects when a prop is declared with the `ElementState<>` marker type from
|
|
499
|
+
* `@wix/react-component-utils` (e.g. `isLoading?: ElementState<boolean>`), which opts
|
|
500
|
+
* the prop in as the trigger for a custom design state. Returns the inner type
|
|
501
|
+
* argument so callers can treat the prop as its underlying type. Returns
|
|
502
|
+
* `undefined` for any other declaration.
|
|
503
|
+
*/
|
|
504
|
+
function detectStateTrigger(
|
|
505
|
+
prop: ts.Symbol,
|
|
506
|
+
sourceFile: ts.SourceFile,
|
|
507
|
+
checker: ts.TypeChecker,
|
|
508
|
+
): StateTriggerInfo | undefined {
|
|
509
|
+
const declaration = prop.getDeclarations()?.[0]
|
|
510
|
+
if (!declaration) return undefined
|
|
511
|
+
|
|
512
|
+
if (
|
|
513
|
+
!(ts.isPropertySignature(declaration) || ts.isPropertyDeclaration(declaration) || ts.isParameter(declaration)) ||
|
|
514
|
+
!declaration.type ||
|
|
515
|
+
!ts.isTypeReferenceNode(declaration.type) ||
|
|
516
|
+
!declaration.type.typeArguments ||
|
|
517
|
+
declaration.type.typeArguments.length !== 1
|
|
518
|
+
) {
|
|
519
|
+
return undefined
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const typeName = declaration.type.typeName
|
|
523
|
+
const identifier = ts.isIdentifier(typeName) ? typeName : typeName.right
|
|
524
|
+
if (identifier.text !== 'ElementState') return undefined
|
|
525
|
+
if (!isElementStateAliasFromUtils(identifier, checker)) return undefined
|
|
526
|
+
|
|
527
|
+
return { innerTypeName: declaration.type.typeArguments[0].getText(sourceFile) }
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Confirms an identifier referencing `ElementState` resolves to the marker type
|
|
532
|
+
* alias exported by `@wix/react-component-utils`, rather than an unrelated local
|
|
533
|
+
* type that happens to be named `ElementState`.
|
|
534
|
+
*/
|
|
535
|
+
function isElementStateAliasFromUtils(identifier: ts.Identifier, checker: ts.TypeChecker): boolean {
|
|
536
|
+
let symbol = checker.getSymbolAtLocation(identifier)
|
|
537
|
+
if (!symbol) return false
|
|
538
|
+
if ((symbol.getFlags() & ts.SymbolFlags.Alias) !== 0) {
|
|
539
|
+
symbol = checker.getAliasedSymbol(symbol)
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const declaration = symbol.getDeclarations()?.[0]
|
|
543
|
+
if (!declaration || !ts.isTypeAliasDeclaration(declaration)) return false
|
|
544
|
+
if (declaration.name.text !== 'ElementState') return false
|
|
545
|
+
|
|
546
|
+
return declaration.getSourceFile().fileName.includes('react-component-utils')
|
|
547
|
+
}
|
|
548
|
+
|
|
480
549
|
function getDeclaredTypeInfo(
|
|
481
550
|
prop: ts.Symbol,
|
|
482
551
|
sourceFile: ts.SourceFile,
|
|
@@ -51,6 +51,11 @@ export interface PropInfo {
|
|
|
51
51
|
description?: string
|
|
52
52
|
// Whether the prop is marked as deprecated via @deprecated JSDoc tag
|
|
53
53
|
deprecated?: boolean
|
|
54
|
+
// True when the prop is wrapped in the `ElementState<>` marker type from
|
|
55
|
+
// @wix/react-component-utils, declaring it as the trigger for a custom design
|
|
56
|
+
// state. `type` and `resolvedType` reflect the unwrapped inner type, so the prop
|
|
57
|
+
// still behaves as a normal data prop everywhere else.
|
|
58
|
+
isStateTrigger?: boolean
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
export interface ComponentInfo {
|