@wix/zero-config-implementation 1.82.0 → 1.84.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 CHANGED
@@ -857,14 +857,12 @@ export declare function extractComponentManifest(componentPath: string, compiled
857
857
  *
858
858
  * @param componentPath - Path to the TypeScript source file
859
859
  * @param compiledEntryPath - Path to the built JS entry file of the component's package (e.g. dist/index.js)
860
- * @param options.onError - Called for each non-fatal extraction error as it occurs
861
860
  * @errors
862
861
  * - {@link NotFoundError} — Source file does not exist (phase: `compile`)
863
- * - {@link ParseError} — TypeScript config or component types could not be parsed (phase: `compile` | `extract`)
864
- * - {@link IoError} — Component loaded but render/extractors failed (phase: `render`)
862
+ * - {@link ParseError} — TypeScript config or component types could not be parsed (phase: `compile` | `css` | `extract`)
863
+ * - {@link IoError} — Module failed to load, export access threw, render failed, or CSS selector matching failed
865
864
  */
866
865
  export declare interface ExtractComponentManifestOptions extends RunExtractorsOptions {
867
- onError?: (error: ExtractionError) => void;
868
866
  }
869
867
 
870
868
  export declare function extractComponentManifestResult(componentPath: string, compiledEntryPath: string, options?: ExtractComponentManifestOptions): ResultAsync<ManifestResult, InstanceType<typeof NotFoundError> | InstanceType<typeof ParseError> | InstanceType<typeof IoError>>;
@@ -909,13 +907,6 @@ declare type ExtractErrTypes<T extends readonly Result<unknown, unknown>[]> = {
909
907
  [idx in keyof T]: T[idx] extends Result<unknown, infer E> ? E : never;
910
908
  };
911
909
 
912
- /** A non-fatal issue encountered during component extraction. */
913
- export declare interface ExtractionError {
914
- componentName: string;
915
- phase: 'render' | 'coupling' | 'css' | 'loader' | 'conversion';
916
- error: InstanceType<typeof BaseError>;
917
- }
918
-
919
910
  export declare interface ExtractionResult {
920
911
  html: string;
921
912
  store: ExtractorStore;
@@ -1590,7 +1581,6 @@ CauseArg extends Cause,
1590
1581
 
1591
1582
  export declare interface ManifestResult {
1592
1583
  component: EditorReactComponent;
1593
- errors: ExtractionError[];
1594
1584
  }
1595
1585
 
1596
1586
  declare type MemberListOf<T> = ((T extends unknown ? (t: T) => T : never) extends infer U ? (U extends unknown ? (u: U) => unknown : never) extends (v: infer V) => unknown ? V : never : never) extends (_: unknown) => infer W ? [...MemberListOf<Exclude<T, W>>, W] : [];
@@ -2395,6 +2385,8 @@ export declare interface PropInfo {
2395
2385
  resolvedType: ResolvedType;
2396
2386
  description?: string;
2397
2387
  deprecated?: boolean;
2388
+ min?: string;
2389
+ max?: string;
2398
2390
  isStateTrigger?: boolean;
2399
2391
  }
2400
2392
 
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-tAvBvLbA.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-Byvq-358.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.82.0",
7
+ "version": "1.84.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",
@@ -82,5 +82,5 @@
82
82
  ]
83
83
  }
84
84
  },
85
- "falconPackageHash": "e0c2218f13619a2e924dab970b18c75b5745958f61951a3928964230"
85
+ "falconPackageHash": "69093241c1274c1e1c73c1f04a0b7b1d9f478ab11a8829b00f8c8cc8"
86
86
  }
@@ -169,7 +169,10 @@ function handlePrimitiveType(
169
169
  }
170
170
  } else if (typeValue.includes('number')) {
171
171
  dataItem.dataType = DATA_TYPE.number
172
- dataItem.number = {}
172
+ dataItem.number =
173
+ propInfo.min != null || propInfo.max != null
174
+ ? { ...(propInfo.min != null && { min: propInfo.min }), ...(propInfo.max != null && { max: propInfo.max }) }
175
+ : {}
173
176
  } else if (typeValue.includes('boolean')) {
174
177
  dataItem.dataType = DATA_TYPE.booleanValue
175
178
  } else {
@@ -11,6 +11,7 @@ import type {
11
11
  } from '@wix/react-component-schema'
12
12
  import { CSS_PROPERTIES, ELEMENTS } from '@wix/react-component-schema'
13
13
  import { camelCase } from 'case-anything'
14
+ import { IoError } from '../errors'
14
15
  import { buildRefElementManifestKey } from '../extensions/ref-elements/path-utils'
15
16
  import type { RefElementMatch } from '../extensions/ref-elements/types'
16
17
  import type { ComponentInfoWithCss } from '../index'
@@ -66,8 +67,14 @@ function buildEditorElement(
66
67
  nearestCommonAncestorCustomProps: Map<string, Record<string, CssCustomPropertyItem>>,
67
68
  ): EditorElement {
68
69
  const rootElement = component.elements[0]
69
- const childElements = rootElement?.children ?? []
70
- const rootCustomProps = rootElement ? (nearestCommonAncestorCustomProps.get(rootElement.traceId) ?? {}) : {}
70
+ if (!rootElement) {
71
+ throw new IoError(
72
+ `Component "${component.componentName}" render produced no root element — null or text-only render`,
73
+ { props: { phase: 'render' } },
74
+ )
75
+ }
76
+ const childElements = rootElement.children
77
+ const rootCustomProps = nearestCommonAncestorCustomProps.get(rootElement.traceId) ?? {}
71
78
 
72
79
  const customClassTriggers = collectCustomClassTriggers(component)
73
80
 
@@ -149,9 +156,8 @@ function buildData(
149
156
  const defaultValue = prop.defaultValue?.kind !== 'unresolved' ? prop.defaultValue?.value : undefined
150
157
 
151
158
  const result = buildDataItem(prop, defaultValue, propUsages, prop.propPath)
152
- if (result.isOk()) {
153
- data[name] = result.value
154
- }
159
+ if (result.isErr()) throw result.error
160
+ data[name] = result.value
155
161
  }
156
162
 
157
163
  return data
@@ -0,0 +1,33 @@
1
+ import path from 'node:path'
2
+ import { beforeEach, expect, it, vi } from 'vitest'
3
+ import { extractComponentManifestResult } from './index'
4
+
5
+ vi.mock('./information-extractors/ts', async (importOriginal) => {
6
+ const original = await importOriginal<typeof import('./information-extractors/ts')>()
7
+ return {
8
+ ...original,
9
+ extractCssImports: vi.fn(),
10
+ }
11
+ })
12
+
13
+ const componentPath = path.resolve(
14
+ import.meta.dirname,
15
+ '../../../packages/example-components/src/components/FunctionComponent/component.tsx',
16
+ )
17
+ const compiledEntryPath = path.resolve(import.meta.dirname, '../../../packages/example-components/dist/index.js')
18
+
19
+ it('throws ParseError when extractCssImports throws', async () => {
20
+ const { extractCssImports } = await import('./information-extractors/ts')
21
+ vi.mocked(extractCssImports).mockImplementation(() => {
22
+ throw new Error('simulated extractCssImports failure')
23
+ })
24
+
25
+ const result = await extractComponentManifestResult(componentPath, compiledEntryPath)
26
+
27
+ expect(result.isErr()).toBe(true)
28
+ if (result.isErr()) {
29
+ expect(result.error.name).toBe('ParseError')
30
+ expect(result.error.message).toContain('Failed to extract CSS import paths from')
31
+ expect(result.error.message).toContain('simulated extractCssImports failure')
32
+ }
33
+ })
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { WixServicesWrapper } from '@wix/builder-services-wrapper'
2
2
  import type { EditorReactComponent } from '@wix/react-component-schema'
3
- import { Result, ResultAsync, errAsync, okAsync } from 'neverthrow'
3
+ import { Result, ResultAsync } from 'neverthrow'
4
4
  import React, { type ComponentType } from 'react'
5
5
 
6
6
  import { toEditorReactComponent } from './converters'
@@ -9,13 +9,11 @@ import { buildContextProviderModules } from './extensions/context-providers/cont
9
9
  import { buildContextAwareWrapper, loadMockProviders } from './extensions/context-providers/mock-provider'
10
10
  import { buildRefElementContext } from './extensions/ref-elements/context'
11
11
  import type { RefElementContext } from './extensions/ref-elements/types'
12
- import type { ExtractionError } from './extraction-types'
13
12
  import type { RunExtractorsOptions } from './information-extractors/react'
14
13
  import { extractCssImports, extractDefaultComponentInfo } from './information-extractors/ts'
15
14
  import type { ComponentInfo } from './information-extractors/ts/types'
16
15
  import { processComponent } from './manifest-pipeline'
17
16
  import { findComponent, findDefaultComponent, loadModuleForExtraction } from './module-loader'
18
- import type { LoadModuleFailure } from './module-loader'
19
17
  import { compileTsFile } from './ts-compiler'
20
18
 
21
19
  const defaultWrapper = (Component: ComponentType<unknown>): ComponentType<unknown> => {
@@ -30,7 +28,6 @@ const defaultWrapper = (Component: ComponentType<unknown>): ComponentType<unknow
30
28
 
31
29
  export interface ManifestResult {
32
30
  component: EditorReactComponent
33
- errors: ExtractionError[]
34
31
  }
35
32
 
36
33
  // ─────────────────────────────────────────────────────────────────────────────
@@ -42,15 +39,12 @@ export interface ManifestResult {
42
39
  *
43
40
  * @param componentPath - Path to the TypeScript source file
44
41
  * @param compiledEntryPath - Path to the built JS entry file of the component's package (e.g. dist/index.js)
45
- * @param options.onError - Called for each non-fatal extraction error as it occurs
46
42
  * @errors
47
43
  * - {@link NotFoundError} — Source file does not exist (phase: `compile`)
48
- * - {@link ParseError} — TypeScript config or component types could not be parsed (phase: `compile` | `extract`)
49
- * - {@link IoError} — Component loaded but render/extractors failed (phase: `render`)
44
+ * - {@link ParseError} — TypeScript config or component types could not be parsed (phase: `compile` | `css` | `extract`)
45
+ * - {@link IoError} — Module failed to load, export access threw, render failed, or CSS selector matching failed
50
46
  */
51
- export interface ExtractComponentManifestOptions extends RunExtractorsOptions {
52
- onError?: (error: ExtractionError) => void
53
- }
47
+ export interface ExtractComponentManifestOptions extends RunExtractorsOptions {}
54
48
 
55
49
  export function extractComponentManifestResult(
56
50
  componentPath: string,
@@ -60,12 +54,6 @@ export function extractComponentManifestResult(
60
54
  ManifestResult,
61
55
  InstanceType<typeof NotFoundError> | InstanceType<typeof ParseError> | InstanceType<typeof IoError>
62
56
  > {
63
- const errors: ExtractionError[] = []
64
- const report = (error: ExtractionError): void => {
65
- errors.push(error)
66
- options?.onError?.(error)
67
- }
68
-
69
57
  // Step 1: Compile TypeScript (fatal)
70
58
  return compileTsFile(componentPath)
71
59
  .andThen((program) => {
@@ -137,68 +125,42 @@ export function extractComponentManifestResult(
137
125
  wrapper,
138
126
  }
139
127
 
140
- // Step 5: Load the compiled package module (non-fatal) - done after TS extraction
141
- // so componentName is known when reporting failures
128
+ // Step 5: Load the compiled package module (fatal)
142
129
  return loadModuleForExtraction(compiledEntryPath, refElementContext.runtimeModules)
143
- .map(({ cleanup, moduleExports }) => ({
144
- loadComponent: (name: string) =>
145
- findComponent(moduleExports, name) ?? findDefaultComponent(moduleExports),
146
- failure: null as LoadModuleFailure | null,
147
- cleanup,
148
- }))
149
- .orElse((failure) =>
150
- okAsync({
151
- loadComponent: () => null as ComponentType<unknown> | null,
152
- cleanup: async () => {},
153
- failure,
154
- }),
155
- )
156
- .andThen(({ cleanup, failure, loadComponent }) => {
157
- if (failure) {
158
- if (failure.esmError) {
159
- report({
160
- componentName,
161
- phase: 'loader',
162
- error: new IoError('ESM import failed', { cause: failure.esmError, props: { phase: 'loader' } }),
163
- })
164
- }
165
- report({
166
- componentName,
167
- phase: 'loader',
168
- error: new IoError('CJS require failed', { cause: failure.cjsError, props: { phase: 'loader' } }),
169
- })
170
- }
130
+ .mapErr((failure) => {
131
+ const esmDetail = failure.esmError ? `ESM: ${failure.esmError.message}` : 'ESM: not attempted'
132
+ return new IoError(
133
+ `Failed to load compiled bundle at "${compiledEntryPath}". ${esmDetail}. CJS: ${failure.cjsError.message}. Ensure the package is built before extraction.`,
134
+ { cause: failure.cjsError, props: { phase: 'loader' } },
135
+ )
136
+ })
137
+ .andThen(({ cleanup, moduleExports }) => {
138
+ const loadComponent = (name: string): ComponentType<unknown> | null =>
139
+ findComponent(moduleExports, name) ?? findDefaultComponent(moduleExports)
171
140
 
172
- // Step 6: Extract CSS imports (non-fatal)
173
- let cssImportPaths: string[] = []
174
- const cssResult = Result.fromThrowable(extractCssImports, (thrown) => thrown)(program)
175
- if (cssResult.isOk()) {
176
- cssImportPaths = cssResult.value
177
- } else {
178
- const thrown = cssResult.error
179
- report({
180
- componentName,
181
- phase: 'css',
182
- error: new ParseError(
183
- `Failed to extract CSS imports: ${thrown instanceof Error ? thrown.message : String(thrown)}`,
184
- { cause: thrown instanceof Error ? thrown : undefined, props: { phase: 'css' } },
185
- ),
186
- })
141
+ const getCssImportPaths = (): string[] => {
142
+ const cssResult = Result.fromThrowable(
143
+ extractCssImports,
144
+ (thrown) =>
145
+ new ParseError(
146
+ `Failed to extract CSS import paths from "${componentPath}": ${thrown instanceof Error ? thrown.message : String(thrown)}`,
147
+ { cause: thrown instanceof Error ? thrown : undefined, props: { phase: 'css' } },
148
+ ),
149
+ )(program)
150
+ if (cssResult.isErr()) throw cssResult.error
151
+ return cssResult.value
187
152
  }
188
153
 
189
- // Step 7: Process the default-exported component
154
+ // Step 6+7: CSS extraction and component processing — both under the cleanup boundary
190
155
  return ResultAsync.fromPromise(
191
156
  processComponentWithCleanup(
192
157
  componentInfo,
193
158
  loadComponent,
194
- cssImportPaths,
195
- !!failure,
196
- report,
159
+ getCssImportPaths,
197
160
  optionsWithDefaults,
198
161
  compiledEntryPath,
199
162
  refElementContext,
200
163
  cleanup,
201
- errors,
202
164
  ),
203
165
  normalizeComponentExtractionCleanupFailure,
204
166
  )
@@ -224,22 +186,18 @@ function normalizeComponentExtractionCleanupFailure(thrown: unknown): InstanceTy
224
186
  async function processComponentWithCleanup(
225
187
  componentInfo: ComponentInfo,
226
188
  loadComponent: (name: string) => ComponentType<unknown> | null,
227
- cssImportPaths: string[],
228
- didModuleLoadFail: boolean,
229
- report: (error: ExtractionError) => void,
189
+ getCssImportPaths: () => string[],
230
190
  options: ExtractComponentManifestOptions,
231
191
  compiledEntryPath: string,
232
192
  refElementContext: RefElementContext,
233
193
  cleanup: () => Promise<void>,
234
- errors: ExtractionError[],
235
194
  ): Promise<ManifestResult> {
236
195
  try {
196
+ const cssImportPaths = getCssImportPaths()
237
197
  const processResult = processComponent(
238
198
  componentInfo,
239
199
  loadComponent,
240
200
  cssImportPaths,
241
- didModuleLoadFail,
242
- report,
243
201
  options,
244
202
  compiledEntryPath,
245
203
  refElementContext.eligiblePaths,
@@ -248,10 +206,7 @@ async function processComponentWithCleanup(
248
206
  throw processResult.error
249
207
  }
250
208
 
251
- return {
252
- component: toEditorReactComponent(processResult.component),
253
- errors,
254
- }
209
+ return { component: toEditorReactComponent(processResult.component) }
255
210
  } finally {
256
211
  await cleanup()
257
212
  }
@@ -292,7 +247,6 @@ export async function extractComponentManifest(
292
247
 
293
248
  // ── Tier 1: High-Level API ──────────────────────────────────────────────────
294
249
  // extractComponentManifest() is exported above as a named function declaration.
295
- export type { ExtractionError } from './extraction-types'
296
250
  export type { EditorReactComponent } from '@wix/react-component-schema'
297
251
  export type { ComponentInfoWithCss, ExtractedCssInfo, ProcessComponentResult } from './manifest-pipeline'
298
252
 
@@ -125,56 +125,58 @@ function parseAllProperties(
125
125
  const propertiesMap = new Map<string, CSSProperty[]>()
126
126
  const varUsagesByProperty = new Map<string, Set<string>>()
127
127
 
128
- try {
129
- const ast = parse(cssString, { parseCustomProperty: true })
130
-
131
- const valueAliases = parseValueAliases(ast)
132
-
133
- walk(ast, {
134
- visit: 'Rule',
135
- enter(this: WalkContext, rule: Rule) {
136
- try {
137
- const properties: CSSProperty[] = []
138
- for (const child of rule.block.children) {
139
- if (child.type !== 'Declaration') continue
140
- const extracted = extractProperty(child, varUsagesByProperty, valueAliases)
141
- if (extracted) {
142
- properties.push(extracted)
143
- }
128
+ const ast = parse(cssString, {
129
+ parseCustomProperty: true,
130
+ parseAtrulePrelude: false,
131
+ onParseError: (error) => {
132
+ throw error
133
+ },
134
+ })
135
+
136
+ const valueAliases = parseValueAliases(ast)
137
+
138
+ walk(ast, {
139
+ visit: 'Rule',
140
+ enter(this: WalkContext, rule: Rule) {
141
+ try {
142
+ const properties: CSSProperty[] = []
143
+ for (const child of rule.block.children) {
144
+ if (child.type !== 'Declaration') continue
145
+ const extracted = extractProperty(child, varUsagesByProperty, valueAliases)
146
+ if (extracted) {
147
+ properties.push(extracted)
144
148
  }
149
+ }
145
150
 
146
- if (properties.length === 0) return
151
+ if (properties.length === 0) return
147
152
 
148
- const selectorStrings: string[] = []
153
+ const selectorStrings: string[] = []
149
154
 
150
- if (isInsideKeyframes(this)) {
151
- const keyframeName = this.atrule?.prelude ? generate(this.atrule.prelude).trim() : ''
152
- if (!keyframeName) return
155
+ if (isInsideKeyframes(this)) {
156
+ const keyframeName = this.atrule?.prelude ? generate(this.atrule.prelude).trim() : ''
157
+ if (!keyframeName) return
153
158
 
154
- const keyframeSelector = generate(rule.prelude).trim()
155
- selectorStrings.push(`@keyframes ${keyframeName} ${keyframeSelector}`)
156
- } else {
157
- if (rule.prelude.type !== 'SelectorList') return
158
- for (const selectorNode of rule.prelude.children) {
159
- if (selectorNode.type !== 'Selector') continue
160
- const selectorString = generate(selectorNode)
161
- parsedSelectors.set(selectorString, selectorNode)
162
- selectorStrings.push(selectorString)
163
- }
159
+ const keyframeSelector = generate(rule.prelude).trim()
160
+ selectorStrings.push(`@keyframes ${keyframeName} ${keyframeSelector}`)
161
+ } else {
162
+ if (rule.prelude.type !== 'SelectorList') return
163
+ for (const selectorNode of rule.prelude.children) {
164
+ if (selectorNode.type !== 'Selector') continue
165
+ const selectorString = generate(selectorNode)
166
+ parsedSelectors.set(selectorString, selectorNode)
167
+ selectorStrings.push(selectorString)
164
168
  }
169
+ }
165
170
 
166
- for (const selectorString of selectorStrings) {
167
- const existing = propertiesMap.get(selectorString) ?? []
168
- propertiesMap.set(selectorString, [...existing, ...properties])
169
- }
170
- } catch {
171
- // Skip rules that can't be processed
171
+ for (const selectorString of selectorStrings) {
172
+ const existing = propertiesMap.get(selectorString) ?? []
173
+ propertiesMap.set(selectorString, [...existing, ...properties])
172
174
  }
173
- },
174
- })
175
- } catch (error) {
176
- console.error('CSS parsing error:', error)
177
- }
175
+ } catch {
176
+ // Skip rules that can't be processed
177
+ }
178
+ },
179
+ })
178
180
 
179
181
  return { propertiesMap, varUsagesByProperty }
180
182
  }
@@ -506,33 +508,34 @@ function extractAtruleDescriptors(block: CssNode): Map<string, string> {
506
508
  function parseRegisteredProperties(cssString: string): Map<string, RegisteredCustomProperty> {
507
509
  const registered = new Map<string, RegisteredCustomProperty>()
508
510
 
509
- try {
510
- const ast = parse(cssString)
511
-
512
- walk(ast, {
513
- visit: 'Atrule',
514
- enter(atrule: Atrule) {
515
- if (atrule.name !== 'property' || !atrule.prelude || !atrule.block) return
516
-
517
- const varName = generate(atrule.prelude).trim()
518
- if (!varName.startsWith('--')) return
519
-
520
- const descriptors = extractAtruleDescriptors(atrule.block)
521
- const resolved = cssPropertyTypeFromSyntax(descriptors.get('syntax') ?? '*')
522
- if (!resolved) return
523
- const { cssPropertyType, enumOptions } = resolved
524
- const initialValue = descriptors.get('initial-value')
525
-
526
- registered.set(varName, {
527
- cssPropertyType,
528
- ...(enumOptions && { enumOptions }),
529
- ...(initialValue !== undefined && { defaultValue: initialValue }),
530
- })
531
- },
532
- })
533
- } catch (error) {
534
- console.error('CSS @property parsing error:', error)
535
- }
511
+ const ast = parse(cssString, {
512
+ parseAtrulePrelude: false,
513
+ onParseError: (error) => {
514
+ throw error
515
+ },
516
+ })
517
+
518
+ walk(ast, {
519
+ visit: 'Atrule',
520
+ enter(atrule: Atrule) {
521
+ if (atrule.name !== 'property' || !atrule.prelude || !atrule.block) return
522
+
523
+ const varName = generate(atrule.prelude).trim()
524
+ if (!varName.startsWith('--')) return
525
+
526
+ const descriptors = extractAtruleDescriptors(atrule.block)
527
+ const resolved = cssPropertyTypeFromSyntax(descriptors.get('syntax') ?? '*')
528
+ if (!resolved) return
529
+ const { cssPropertyType, enumOptions } = resolved
530
+ const initialValue = descriptors.get('initial-value')
531
+
532
+ registered.set(varName, {
533
+ cssPropertyType,
534
+ ...(enumOptions && { enumOptions }),
535
+ ...(initialValue !== undefined && { defaultValue: initialValue }),
536
+ })
537
+ },
538
+ })
536
539
 
537
540
  return registered
538
541
  }
@@ -662,47 +665,48 @@ function analyzeStateSelector(selector: Selector): AnalyzedCompound[] {
662
665
  function parseStateClasses(cssString: string): StateClassRule[] {
663
666
  const results: StateClassRule[] = []
664
667
 
665
- try {
666
- const ast = parse(cssString)
667
-
668
- walk(ast, {
669
- visit: 'Rule',
670
- enter(rule: Rule) {
671
- if (rule.prelude.type !== 'SelectorList') return
672
-
673
- const pseudoByBase = new Map<string, NativePseudoClass>()
674
- const modifiersByBase = new Map<string, Set<string>>()
675
-
676
- for (const selectorNode of rule.prelude.children) {
677
- if (selectorNode.type !== 'Selector') continue
678
- for (const analyzed of analyzeStateSelector(selectorNode)) {
679
- if (analyzed.pseudoClass && !pseudoByBase.has(analyzed.base)) {
680
- pseudoByBase.set(analyzed.base, analyzed.pseudoClass)
681
- }
682
- if (analyzed.modifiers.length > 0) {
683
- const modifiers = modifiersByBase.get(analyzed.base) ?? new Set<string>()
684
- for (const modifier of analyzed.modifiers) modifiers.add(modifier)
685
- modifiersByBase.set(analyzed.base, modifiers)
686
- }
668
+ const ast = parse(cssString, {
669
+ parseAtrulePrelude: false,
670
+ onParseError: (error) => {
671
+ throw error
672
+ },
673
+ })
674
+
675
+ walk(ast, {
676
+ visit: 'Rule',
677
+ enter(rule: Rule) {
678
+ if (rule.prelude.type !== 'SelectorList') return
679
+
680
+ const pseudoByBase = new Map<string, NativePseudoClass>()
681
+ const modifiersByBase = new Map<string, Set<string>>()
682
+
683
+ for (const selectorNode of rule.prelude.children) {
684
+ if (selectorNode.type !== 'Selector') continue
685
+ for (const analyzed of analyzeStateSelector(selectorNode)) {
686
+ if (analyzed.pseudoClass && !pseudoByBase.has(analyzed.base)) {
687
+ pseudoByBase.set(analyzed.base, analyzed.pseudoClass)
688
+ }
689
+ if (analyzed.modifiers.length > 0) {
690
+ const modifiers = modifiersByBase.get(analyzed.base) ?? new Set<string>()
691
+ for (const modifier of analyzed.modifiers) modifiers.add(modifier)
692
+ modifiersByBase.set(analyzed.base, modifiers)
687
693
  }
688
694
  }
695
+ }
689
696
 
690
- for (const [baseSelector, modifiers] of modifiersByBase) {
691
- const basePseudo = pseudoByBase.get(baseSelector)
692
- for (const modifier of modifiers) {
693
- // A modifier is the editor-trigger class for a NATIVE state only when its
694
- // own state name matches the base's pseudo (`x--hover` ↔ `:hover`). A custom
695
- // modifier sharing a base with a native pseudo (`x--selected` next to `:hover`)
696
- // stays custom — it must not inherit the unrelated pseudo.
697
- const pseudoClass = basePseudo && stateNameFromModifier(modifier) === basePseudo ? basePseudo : undefined
698
- results.push({ baseSelector, modifier, pseudoClass })
699
- }
697
+ for (const [baseSelector, modifiers] of modifiersByBase) {
698
+ const basePseudo = pseudoByBase.get(baseSelector)
699
+ for (const modifier of modifiers) {
700
+ // A modifier is the editor-trigger class for a NATIVE state only when its
701
+ // own state name matches the base's pseudo (`x--hover` ↔ `:hover`). A custom
702
+ // modifier sharing a base with a native pseudo (`x--selected` next to `:hover`)
703
+ // stays custom — it must not inherit the unrelated pseudo.
704
+ const pseudoClass = basePseudo && stateNameFromModifier(modifier) === basePseudo ? basePseudo : undefined
705
+ results.push({ baseSelector, modifier, pseudoClass })
700
706
  }
701
- },
702
- })
703
- } catch (error) {
704
- console.error('CSS state-class parsing error:', error)
705
- }
707
+ }
708
+ },
709
+ })
706
710
 
707
711
  return results
708
712
  }
@@ -252,6 +252,8 @@ function convertComponentDoc(doc: ComponentDoc, program: ts.Program, checker: ts
252
252
  resolvedType,
253
253
  description: propItem.description || undefined,
254
254
  deprecated: tags?.deprecated !== undefined ? true : undefined,
255
+ min: parseNumericTag(tags?.min, propName, 'min'),
256
+ max: parseNumericTag(tags?.max, propName, 'max'),
255
257
  isStateTrigger: stateTrigger ? true : undefined,
256
258
  }
257
259
  }
@@ -335,6 +337,14 @@ function findPropsType(
335
337
  // Default value conversion
336
338
  // ─────────────────────────────────────────────────────────────────────────────
337
339
 
340
+ function parseNumericTag(value: string | undefined, propName: string, tag: string): string | undefined {
341
+ if (value !== undefined && Number.isNaN(Number(value))) {
342
+ console.warn(`@${tag} on prop "${propName}" is not a valid number ("${value}") — ignoring`)
343
+ return undefined
344
+ }
345
+ return value
346
+ }
347
+
338
348
  function convertDefaultValue(rdtDefault: { value: unknown } | null): DefaultValue | undefined {
339
349
  if (!rdtDefault || rdtDefault.value == null) return undefined
340
350
 
@@ -51,6 +51,9 @@ 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
+ // Numeric range constraints extracted from @min / @max JSDoc tags (decimal strings)
55
+ min?: string
56
+ max?: string
54
57
  // True when the prop is wrapped in the `ElementState<>` marker type from
55
58
  // @wix/react-component-utils, declaring it as the trigger for a custom design
56
59
  // state. `type` and `resolvedType` reflect the unwrapped inner type, so the prop