praxis-kit 7.0.0 → 7.4.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.
@@ -2,9 +2,72 @@ import { Ref, PropsWithChildren, ReactElement, ComponentType, JSX, ReactNode } f
2
2
  import { RequireAtLeastOne, Simplify, ReadonlyDeep, NonEmptyTuple } from 'type-fest';
3
3
  import { Diagnostics, DiagnosticInput, DiagnosticsMode } from './_shared/diagnostics.js';
4
4
 
5
+ /**
6
+ * A string-keyed object whose values are of type `T`.
7
+ */
5
8
  type StringMap<T = unknown> = Record<string, T>;
9
+ /**
10
+ * A string-keyed object with values of unknown type.
11
+ */
6
12
  type AnyRecord = StringMap<unknown>;
13
+ /**
14
+ * An object type with no named properties.
15
+ *
16
+ * Unlike `{}`, this excludes arbitrary properties during type operations while
17
+ * still satisfying `extends object`.
18
+ */
7
19
  type EmptyRecord = Record<never, never>;
20
+ /**
21
+ * A compound component's named sub-components, for example
22
+ * `{ Header, Content, Footer }`.
23
+ */
24
+ type SubComponentMap = Readonly<AnyRecord>;
25
+ /**
26
+ * Default `Variants` type for components that declare no variants.
27
+ *
28
+ * Structurally identical to `Readonly<EmptyRecord>`, but named separately so
29
+ * editor hovers remain self-descriptive.
30
+ */
31
+ type NoVariants = Readonly<EmptyRecord>;
32
+ /**
33
+ * Default `TPreset` type for components that declare no named presets.
34
+ *
35
+ * Structurally identical to `Readonly<EmptyRecord>`, but named separately so
36
+ * editor hovers remain self-descriptive.
37
+ */
38
+ type NoPreset = Readonly<EmptyRecord>;
39
+ /**
40
+ * Fallback for `ExtractPluginProps<TPlugin>` when a plugin contributes no
41
+ * props, including the no-plugin case.
42
+ *
43
+ * Structurally identical to `EmptyRecord`, but named separately so editor
44
+ * hovers remain self-descriptive.
45
+ */
46
+ type NoPluginProps = EmptyRecord;
47
+ /**
48
+ * Determines whether an object type should be treated as empty.
49
+ *
50
+ * `keyof T` ignores call and construct signatures...
51
+ */
52
+ type IsEmptyRecord<T extends object> = T extends (...args: never[]) => unknown ? false : T extends new (...args: never[]) => unknown ? false : keyof T extends never ? true : false;
53
+ /**
54
+ * Merges two object types while eliding empty operands.
55
+ *
56
+ * If either operand is {@link EmptyRecord}, the other operand is returned
57
+ * directly instead of producing intersections such as
58
+ * `Component & EmptyRecord` in editor hovers.
59
+ *
60
+ * Unlike a homomorphic mapped type (for example `Simplify<T>`), this preserves
61
+ * call and construct signatures. Many component types are callable objects,
62
+ * and mapped types silently discard those signatures.
63
+ *
64
+ * @remarks
65
+ * Instantiate `MergeRecords` directly. Introducing an intermediate alias for
66
+ * one operand (for example `type C = PolymorphicComponent<G>`) can prevent
67
+ * `IsEmptyRecord` from evaluating eagerly, which breaks assignability under
68
+ * `exactOptionalPropertyTypes`.
69
+ */
70
+ type MergeRecords<A extends object, B extends object> = IsEmptyRecord<A> extends true ? B : IsEmptyRecord<B> extends true ? A : A & B;
8
71
 
9
72
  type IntrinsicTag = keyof HTMLElementTagNameMap;
10
73
 
@@ -188,7 +251,7 @@ type ClassPluginFactory<TProps extends AnyRecord = EmptyRecord> = <V extends Var
188
251
  * wherever a factory's concrete plugin-props shape isn't tracked (factory generics,
189
252
  * capability wiring). */
190
253
  type AnyClassPluginFactory = ClassPluginFactory<AnyRecord> | undefined;
191
- type ExtractPluginProps<TPlugin extends AnyClassPluginFactory> = TPlugin extends ClassPluginFactory<infer T> ? string extends keyof T ? EmptyRecord : T : EmptyRecord;
254
+ type ExtractPluginProps<TPlugin extends AnyClassPluginFactory> = TPlugin extends ClassPluginFactory<infer T> ? string extends keyof T ? NoPluginProps : T : NoPluginProps;
192
255
 
193
256
  type AriaContext = {
194
257
  readonly tag: IntrinsicTag;
@@ -252,6 +315,12 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
252
315
  * `@praxis-kit/diagnostics`.
253
316
  */
254
317
  readonly diagnostics?: Diagnostics | DiagnosticsMode;
318
+ /**
319
+ * ARIA/accessibility rules evaluated against the resolved tag and props on every render.
320
+ * Each rule is a function receiving the current context and returning zero or more
321
+ * violations, some of which can carry an auto-applicable fix (see `createRemoveAttributeRule`
322
+ * and friends in `praxis-kit/contract`).
323
+ */
255
324
  readonly aria?: readonly AriaRule[];
256
325
  /**
257
326
  * Rules that need `AriaPolicyEngine`'s fix-application/caching machinery
@@ -263,6 +332,11 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
263
332
  * misleading `aria` name to get the machinery it needs.
264
333
  */
265
334
  readonly rules?: readonly AriaRule[];
335
+ /**
336
+ * Declares which children are valid, by name, match predicate, and cardinality (e.g. "at
337
+ * least 1, at most 4 `Button` children"). Open by default — children matching no rule are
338
+ * still allowed unless `exclusiveChildren` is set.
339
+ */
266
340
  readonly children?: readonly ChildRuleInput[];
267
341
  /**
268
342
  * When true, only children matching a `children` rule (or text, per `allowText`)
@@ -275,19 +349,49 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
275
349
  * or any listed rule. Default: true.
276
350
  */
277
351
  readonly allowText?: boolean;
352
+ /**
353
+ * Prop transforms composed with the component's own `normalize` (from `FactoryOptions`) and
354
+ * run before it. Unlike `normalize`, these live in the enforcement bucket because they
355
+ * typically encode a built-in HTML/ARIA fact rather than component-specific behavior.
356
+ */
278
357
  readonly props?: readonly PropNormalizer[];
279
358
  /** Restricts the `as` prop to this set of tags. Violations route through diagnostics. */
280
359
  readonly allowedAs?: readonly TAllowed[];
281
360
  };
282
361
 
283
362
  type StylingOptions<V extends Readonly<VariantMap> = Readonly<EmptyRecord>, TPreset extends RecipeMap<V> = Readonly<EmptyRecord>, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory> = {
363
+ /** Class applied to every instance regardless of variant selection. */
284
364
  readonly base?: ClassName;
365
+ /**
366
+ * Named variant groups (e.g. `intent`, `size`), each mapping its possible values to a
367
+ * class string. A consumer selects a value per group as a prop (`<Button intent="primary">`).
368
+ */
285
369
  readonly variants?: V;
370
+ /** Value used for a variant group when the consumer doesn't pass one explicitly. */
286
371
  readonly defaults?: Partial<DefaultVariants<V>>;
372
+ /**
373
+ * Applies an extra class only when a specific *combination* of variant selections matches —
374
+ * for cases `variants` alone can't express (e.g. `intent: 'primary'` + `size: 'lg'` together
375
+ * need a class neither variant would add on its own).
376
+ */
287
377
  readonly compounds?: readonly CompoundVariant<V>[];
378
+ /**
379
+ * Named bundles of variant values, selectable as a single unit via the `recipe` prop (e.g.
380
+ * `<Button recipe="cta">` instead of setting `intent`/`size` individually).
381
+ */
288
382
  readonly presets?: TPreset;
383
+ /** Maps a resolved tag directly to a raw class string, independent of the variant system. */
289
384
  readonly tags?: Readonly<TagMap>;
385
+ /**
386
+ * A `ClassPluginFactory` (e.g. the Tailwind layout pipeline) that extends class resolution
387
+ * with its own owned props, layered on top of `variants`/`presets`/`tags`.
388
+ */
290
389
  readonly plugin?: TPlugin;
390
+ /**
391
+ * A cache-key → resolved-class-string lookup for every statically-known variant
392
+ * combination, skipping runtime class computation entirely when a match is found. Normally
393
+ * generated by a build-time class-extraction plugin rather than hand-authored.
394
+ */
291
395
  readonly precomputedClasses?: Readonly<Record<string, string>>;
292
396
  };
293
397
 
@@ -296,17 +400,51 @@ type NormalizeFn<Props extends AnyRecord = AnyRecord> = {
296
400
  }['normalize'];
297
401
  type AnyFactoryOptions = FactoryOptions<ElementType, AnyRecord, VariantMap, RecipeMap<VariantMap>, AnyClassPluginFactory>;
298
402
  type FactoryOptions<TDefault extends ElementType = ElementType, Props extends AnyRecord = EmptyRecord, V extends Readonly<VariantMap> = Readonly<EmptyRecord>, TPreset extends RecipeMap<V> = Readonly<EmptyRecord>, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory, TAllowed extends ElementType = ElementType> = {
403
+ /** The intrinsic tag the component renders by default. Overridable per instance via `as`. */
299
404
  readonly tag?: TDefault;
405
+ /** Display name used in diagnostics, dev tools, and generated component naming. */
300
406
  readonly name?: string;
407
+ /** Values used for the component's own (non-variant) props when the consumer omits them. */
301
408
  readonly defaults?: Partial<NoInfer<Props>>;
409
+ /**
410
+ * A pure `(props) => props` transform run on every render, after `enforcement.props`'s
411
+ * normalizers see the same input. Use this for component-specific prop shaping — anything
412
+ * that depends on live instance state or the real DOM element belongs in `onElement` instead.
413
+ */
302
414
  readonly normalize?: NormalizeFn<NoInfer<Props>>;
415
+ /** Variant groups, base classes, presets, and the optional class-resolution plugin. */
303
416
  readonly styling?: StylingOptions<V, TPreset, TPlugin>;
417
+ /** ARIA rules, child-content contracts, and other runtime validation for this component. */
304
418
  readonly enforcement?: EnforcementOptions<TAllowed>;
305
419
  /**
306
420
  * Adapter-resolved diagnostics default, spread in by `resolveAdapterCommonOptions`. Not meant to
307
421
  * be set directly by component authors — use `enforcement.diagnostics` to override per component.
308
422
  */
309
423
  readonly diagnostics?: Diagnostics;
424
+ /**
425
+ * Sub-components to attach to the generated root component, producing a
426
+ * compound component API (for example, `Card.Header`, `Card.Content`,
427
+ * and `Card.Footer`). Purely additive — has no effect on
428
+ * `enforcement.children`; author child rules explicitly if the component
429
+ * needs to validate its children.
430
+ */
431
+ readonly subComponents?: SubComponentMap;
432
+ /**
433
+ * Called once per instance, when the real underlying DOM element first
434
+ * exists, in every adapter — via that adapter's own native mount
435
+ * lifecycle, never through the props/attribute pipeline. Use this for
436
+ * wiring that needs the actual element (native imperative methods like
437
+ * `dialogEl.showModal()`, native events like `close`/`cancel` that have
438
+ * no prop-based equivalent), not for anything expressible as a plain
439
+ * prop.
440
+ *
441
+ * `getProps` returns the instance's *current* resolved props at call
442
+ * time — read it from inside a listener registered once at mount, rather
443
+ * than re-subscribing on every prop change.
444
+ *
445
+ * Return a cleanup function to run when the instance unmounts.
446
+ */
447
+ readonly onElement?: (element: Element, getProps: () => Readonly<Props>) => void | (() => void);
310
448
  };
311
449
 
312
450
  type MetadataMap = AnyRecord;
@@ -507,7 +645,7 @@ interface CompiledArtifact {
507
645
  * Extends FactoryOptions with React-specific configuration.
508
646
  * slotComponent is intentionally not in core — it is a React rendering concern.
509
647
  */
510
- type ReactFactoryOptions<TDefault extends ElementType, Props extends UnknownProps, Variants extends Readonly<VariantMap>, TPreset extends RecipeMap<Variants> = Readonly<EmptyRecord>, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory, TAllowed extends ElementType = ElementType> = FactoryOptions<TDefault, Props, Variants, TPreset, TPlugin, TAllowed> & {
648
+ type ReactFactoryOptions<TDefault extends ElementType, Props extends UnknownProps, Variants extends Readonly<VariantMap>, TPreset extends RecipeMap<Variants> = NoPreset, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory, TAllowed extends ElementType = ElementType> = FactoryOptions<TDefault, Props, Variants, TPreset, TPlugin, TAllowed> & {
511
649
  /** Component used to render the asChild slot. Defaults to the built-in Slot. */
512
650
  slotComponent?: SlotComponent;
513
651
  /**
@@ -521,4 +659,4 @@ type ReactFactoryOptions<TDefault extends ElementType, Props extends UnknownProp
521
659
  artifact?: CompiledArtifact;
522
660
  };
523
661
 
524
- export { type AnyClassPluginFactory as A, type ElementType as E, type FactoryOptions as F, type PolymorphicComponent as P, type RecipeMap as R, Slottable as S, type UnknownProps as U, type VariantMap as V, type EmptyRecord as a, type ReactFactoryOptions as b, type PolymorphicGenerics as c, type ExtractPluginProps as d, type AnyFactoryOptions as e, type ElementRef as f, type PolymorphicProps as g, type PolymorphicWithAsChild as h, type PolymorphicWithRender as i, type RenderCallbackProps as j, type SlottableProps as k, defineContractComponent as l, mergeRefs as m };
662
+ export { type AnyClassPluginFactory as A, type ElementType as E, type FactoryOptions as F, type MergeRecords as M, type NoVariants as N, type PolymorphicComponent as P, type RecipeMap as R, Slottable as S, type UnknownProps as U, type VariantMap as V, type EmptyRecord as a, type NoPreset as b, type AnyRecord as c, type ReactFactoryOptions as d, type PolymorphicGenerics as e, type ExtractPluginProps as f, type AnyFactoryOptions as g, type ElementRef as h, type PolymorphicProps as i, type PolymorphicWithAsChild as j, type PolymorphicWithRender as k, type RenderCallbackProps as l, type SlottableProps as m, mergeRefs as n, defineContractComponent as o };
@@ -2,9 +2,72 @@ import { RequireAtLeastOne, Simplify, ReadonlyDeep, OmitIndexSignature } from 't
2
2
  import { Diagnostics, DiagnosticInput, DiagnosticsMode } from '../_shared/diagnostics.js';
3
3
  import { JSX } from 'solid-js';
4
4
 
5
+ /**
6
+ * A string-keyed object whose values are of type `T`.
7
+ */
5
8
  type StringMap<T = unknown> = Record<string, T>;
9
+ /**
10
+ * A string-keyed object with values of unknown type.
11
+ */
6
12
  type AnyRecord = StringMap<unknown>;
13
+ /**
14
+ * An object type with no named properties.
15
+ *
16
+ * Unlike `{}`, this excludes arbitrary properties during type operations while
17
+ * still satisfying `extends object`.
18
+ */
7
19
  type EmptyRecord = Record<never, never>;
20
+ /**
21
+ * A compound component's named sub-components, for example
22
+ * `{ Header, Content, Footer }`.
23
+ */
24
+ type SubComponentMap = Readonly<AnyRecord>;
25
+ /**
26
+ * Default `Variants` type for components that declare no variants.
27
+ *
28
+ * Structurally identical to `Readonly<EmptyRecord>`, but named separately so
29
+ * editor hovers remain self-descriptive.
30
+ */
31
+ type NoVariants = Readonly<EmptyRecord>;
32
+ /**
33
+ * Default `TPreset` type for components that declare no named presets.
34
+ *
35
+ * Structurally identical to `Readonly<EmptyRecord>`, but named separately so
36
+ * editor hovers remain self-descriptive.
37
+ */
38
+ type NoPreset = Readonly<EmptyRecord>;
39
+ /**
40
+ * Fallback for `ExtractPluginProps<TPlugin>` when a plugin contributes no
41
+ * props, including the no-plugin case.
42
+ *
43
+ * Structurally identical to `EmptyRecord`, but named separately so editor
44
+ * hovers remain self-descriptive.
45
+ */
46
+ type NoPluginProps = EmptyRecord;
47
+ /**
48
+ * Determines whether an object type should be treated as empty.
49
+ *
50
+ * `keyof T` ignores call and construct signatures...
51
+ */
52
+ type IsEmptyRecord<T extends object> = T extends (...args: never[]) => unknown ? false : T extends new (...args: never[]) => unknown ? false : keyof T extends never ? true : false;
53
+ /**
54
+ * Merges two object types while eliding empty operands.
55
+ *
56
+ * If either operand is {@link EmptyRecord}, the other operand is returned
57
+ * directly instead of producing intersections such as
58
+ * `Component & EmptyRecord` in editor hovers.
59
+ *
60
+ * Unlike a homomorphic mapped type (for example `Simplify<T>`), this preserves
61
+ * call and construct signatures. Many component types are callable objects,
62
+ * and mapped types silently discard those signatures.
63
+ *
64
+ * @remarks
65
+ * Instantiate `MergeRecords` directly. Introducing an intermediate alias for
66
+ * one operand (for example `type C = PolymorphicComponent<G>`) can prevent
67
+ * `IsEmptyRecord` from evaluating eagerly, which breaks assignability under
68
+ * `exactOptionalPropertyTypes`.
69
+ */
70
+ type MergeRecords<A extends object, B extends object> = IsEmptyRecord<A> extends true ? B : IsEmptyRecord<B> extends true ? A : A & B;
8
71
 
9
72
  type IntrinsicTag = keyof HTMLElementTagNameMap;
10
73
 
@@ -187,7 +250,7 @@ type ClassPluginFactory<TProps extends AnyRecord = EmptyRecord> = <V extends Var
187
250
  * wherever a factory's concrete plugin-props shape isn't tracked (factory generics,
188
251
  * capability wiring). */
189
252
  type AnyClassPluginFactory = ClassPluginFactory<AnyRecord> | undefined;
190
- type ExtractPluginProps<TPlugin extends AnyClassPluginFactory> = TPlugin extends ClassPluginFactory<infer T> ? string extends keyof T ? EmptyRecord : T : EmptyRecord;
253
+ type ExtractPluginProps<TPlugin extends AnyClassPluginFactory> = TPlugin extends ClassPluginFactory<infer T> ? string extends keyof T ? NoPluginProps : T : NoPluginProps;
191
254
 
192
255
  type AriaContext = {
193
256
  readonly tag: IntrinsicTag;
@@ -251,6 +314,12 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
251
314
  * `@praxis-kit/diagnostics`.
252
315
  */
253
316
  readonly diagnostics?: Diagnostics | DiagnosticsMode;
317
+ /**
318
+ * ARIA/accessibility rules evaluated against the resolved tag and props on every render.
319
+ * Each rule is a function receiving the current context and returning zero or more
320
+ * violations, some of which can carry an auto-applicable fix (see `createRemoveAttributeRule`
321
+ * and friends in `praxis-kit/contract`).
322
+ */
254
323
  readonly aria?: readonly AriaRule[];
255
324
  /**
256
325
  * Rules that need `AriaPolicyEngine`'s fix-application/caching machinery
@@ -262,6 +331,11 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
262
331
  * misleading `aria` name to get the machinery it needs.
263
332
  */
264
333
  readonly rules?: readonly AriaRule[];
334
+ /**
335
+ * Declares which children are valid, by name, match predicate, and cardinality (e.g. "at
336
+ * least 1, at most 4 `Button` children"). Open by default — children matching no rule are
337
+ * still allowed unless `exclusiveChildren` is set.
338
+ */
265
339
  readonly children?: readonly ChildRuleInput[];
266
340
  /**
267
341
  * When true, only children matching a `children` rule (or text, per `allowText`)
@@ -274,19 +348,49 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
274
348
  * or any listed rule. Default: true.
275
349
  */
276
350
  readonly allowText?: boolean;
351
+ /**
352
+ * Prop transforms composed with the component's own `normalize` (from `FactoryOptions`) and
353
+ * run before it. Unlike `normalize`, these live in the enforcement bucket because they
354
+ * typically encode a built-in HTML/ARIA fact rather than component-specific behavior.
355
+ */
277
356
  readonly props?: readonly PropNormalizer[];
278
357
  /** Restricts the `as` prop to this set of tags. Violations route through diagnostics. */
279
358
  readonly allowedAs?: readonly TAllowed[];
280
359
  };
281
360
 
282
361
  type StylingOptions<V extends Readonly<VariantMap> = Readonly<EmptyRecord>, TPreset extends RecipeMap<V> = Readonly<EmptyRecord>, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory> = {
362
+ /** Class applied to every instance regardless of variant selection. */
283
363
  readonly base?: ClassName;
364
+ /**
365
+ * Named variant groups (e.g. `intent`, `size`), each mapping its possible values to a
366
+ * class string. A consumer selects a value per group as a prop (`<Button intent="primary">`).
367
+ */
284
368
  readonly variants?: V;
369
+ /** Value used for a variant group when the consumer doesn't pass one explicitly. */
285
370
  readonly defaults?: Partial<DefaultVariants<V>>;
371
+ /**
372
+ * Applies an extra class only when a specific *combination* of variant selections matches —
373
+ * for cases `variants` alone can't express (e.g. `intent: 'primary'` + `size: 'lg'` together
374
+ * need a class neither variant would add on its own).
375
+ */
286
376
  readonly compounds?: readonly CompoundVariant<V>[];
377
+ /**
378
+ * Named bundles of variant values, selectable as a single unit via the `recipe` prop (e.g.
379
+ * `<Button recipe="cta">` instead of setting `intent`/`size` individually).
380
+ */
287
381
  readonly presets?: TPreset;
382
+ /** Maps a resolved tag directly to a raw class string, independent of the variant system. */
288
383
  readonly tags?: Readonly<TagMap>;
384
+ /**
385
+ * A `ClassPluginFactory` (e.g. the Tailwind layout pipeline) that extends class resolution
386
+ * with its own owned props, layered on top of `variants`/`presets`/`tags`.
387
+ */
289
388
  readonly plugin?: TPlugin;
389
+ /**
390
+ * A cache-key → resolved-class-string lookup for every statically-known variant
391
+ * combination, skipping runtime class computation entirely when a match is found. Normally
392
+ * generated by a build-time class-extraction plugin rather than hand-authored.
393
+ */
290
394
  readonly precomputedClasses?: Readonly<Record<string, string>>;
291
395
  };
292
396
 
@@ -295,17 +399,51 @@ type NormalizeFn<Props extends AnyRecord = AnyRecord> = {
295
399
  }['normalize'];
296
400
  type AnyFactoryOptions = FactoryOptions<ElementType, AnyRecord, VariantMap, RecipeMap<VariantMap>, AnyClassPluginFactory>;
297
401
  type FactoryOptions<TDefault extends ElementType = ElementType, Props extends AnyRecord = EmptyRecord, V extends Readonly<VariantMap> = Readonly<EmptyRecord>, TPreset extends RecipeMap<V> = Readonly<EmptyRecord>, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory, TAllowed extends ElementType = ElementType> = {
402
+ /** The intrinsic tag the component renders by default. Overridable per instance via `as`. */
298
403
  readonly tag?: TDefault;
404
+ /** Display name used in diagnostics, dev tools, and generated component naming. */
299
405
  readonly name?: string;
406
+ /** Values used for the component's own (non-variant) props when the consumer omits them. */
300
407
  readonly defaults?: Partial<NoInfer<Props>>;
408
+ /**
409
+ * A pure `(props) => props` transform run on every render, after `enforcement.props`'s
410
+ * normalizers see the same input. Use this for component-specific prop shaping — anything
411
+ * that depends on live instance state or the real DOM element belongs in `onElement` instead.
412
+ */
301
413
  readonly normalize?: NormalizeFn<NoInfer<Props>>;
414
+ /** Variant groups, base classes, presets, and the optional class-resolution plugin. */
302
415
  readonly styling?: StylingOptions<V, TPreset, TPlugin>;
416
+ /** ARIA rules, child-content contracts, and other runtime validation for this component. */
303
417
  readonly enforcement?: EnforcementOptions<TAllowed>;
304
418
  /**
305
419
  * Adapter-resolved diagnostics default, spread in by `resolveAdapterCommonOptions`. Not meant to
306
420
  * be set directly by component authors — use `enforcement.diagnostics` to override per component.
307
421
  */
308
422
  readonly diagnostics?: Diagnostics;
423
+ /**
424
+ * Sub-components to attach to the generated root component, producing a
425
+ * compound component API (for example, `Card.Header`, `Card.Content`,
426
+ * and `Card.Footer`). Purely additive — has no effect on
427
+ * `enforcement.children`; author child rules explicitly if the component
428
+ * needs to validate its children.
429
+ */
430
+ readonly subComponents?: SubComponentMap;
431
+ /**
432
+ * Called once per instance, when the real underlying DOM element first
433
+ * exists, in every adapter — via that adapter's own native mount
434
+ * lifecycle, never through the props/attribute pipeline. Use this for
435
+ * wiring that needs the actual element (native imperative methods like
436
+ * `dialogEl.showModal()`, native events like `close`/`cancel` that have
437
+ * no prop-based equivalent), not for anything expressible as a plain
438
+ * prop.
439
+ *
440
+ * `getProps` returns the instance's *current* resolved props at call
441
+ * time — read it from inside a listener registered once at mount, rather
442
+ * than re-subscribing on every prop change.
443
+ *
444
+ * Return a cleanup function to run when the instance unmounts.
445
+ */
446
+ readonly onElement?: (element: Element, getProps: () => Readonly<Props>) => void | (() => void);
309
447
  };
310
448
 
311
449
  declare function defineContractComponent<O extends FactoryOptions>(options: O): <R>(factory: (options: O) => R) => R;
@@ -314,7 +452,7 @@ type UnknownProps = AnyRecord;
314
452
  type SolidElement = JSX.Element;
315
453
  type SlotRenderFn = (props: UnknownProps) => SolidElement;
316
454
 
317
- type SolidFactoryOptions<TDefault extends ElementType, Props extends UnknownProps, Variants extends Readonly<VariantMap>, TPreset extends RecipeMap<Variants> = Readonly<EmptyRecord>, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory> = FactoryOptions<TDefault, Props, Variants, TPreset, TPlugin> & {
455
+ type SolidFactoryOptions<TDefault extends ElementType, Props extends UnknownProps, Variants extends Readonly<VariantMap>, TPreset extends RecipeMap<Variants> = NoPreset, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory> = FactoryOptions<TDefault, Props, Variants, TPreset, TPlugin> & {
318
456
  /**
319
457
  * Return true for any prop key that should be consumed but not forwarded to the DOM.
320
458
  * Receives `runtime.options.variantKeys` as a convenience if needed.
@@ -357,6 +495,28 @@ type PolymorphicComponent<G extends PolymorphicGenerics> = {
357
495
  displayName?: string;
358
496
  };
359
497
 
360
- declare function createContractComponent<TDefault extends ElementType, Props extends UnknownProps = EmptyRecord, Variants extends Readonly<VariantMap> = Readonly<EmptyRecord>, TPreset extends RecipeMap<Variants> = Readonly<EmptyRecord>, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory>(options: SolidFactoryOptions<TDefault, Props, Variants, TPreset, TPlugin>): PolymorphicComponent<PolymorphicGenerics<TDefault, Props & ExtractPluginProps<TPlugin>, Variants, TPreset>>;
498
+ /**
499
+ * Creates a polymorphic Solid component with praxis-kit contracts applied.
500
+ *
501
+ * ```tsx
502
+ * const Button = createContractComponent({
503
+ * tag: 'button',
504
+ * name: 'Button',
505
+ * styling: {
506
+ * base: 'btn',
507
+ * variants: { intent: { primary: 'btn--primary', ghost: 'btn--ghost' } },
508
+ * defaults: { intent: 'primary' },
509
+ * },
510
+ * })
511
+ *
512
+ * <Button intent="ghost" as="a" href="/home">Home</Button>
513
+ * ```
514
+ *
515
+ * `ref` is forwarded as an ordinary Solid ref callback. Pass `subComponents` to attach named
516
+ * sub-components (`Card.Header`) and `onElement` to run setup once the real DOM element exists.
517
+ */
518
+ declare function createContractComponent<TDefault extends ElementType, Props extends UnknownProps = EmptyRecord, Variants extends Readonly<VariantMap> = NoVariants, TPreset extends RecipeMap<Variants> = NoPreset, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory, TSubComponents extends Readonly<AnyRecord> = EmptyRecord>(options: SolidFactoryOptions<TDefault, Props, Variants, TPreset, TPlugin> & {
519
+ readonly subComponents?: TSubComponents;
520
+ }): MergeRecords<PolymorphicComponent<PolymorphicGenerics<TDefault, MergeRecords<Props, ExtractPluginProps<TPlugin>>, Variants, TPreset>>, TSubComponents>;
361
521
 
362
522
  export { type AnyFactoryOptions, type ElementRef, type ElementType, type EmptyRecord, type PolymorphicComponent, type PolymorphicGenerics, type PolymorphicProps, type SolidFactoryOptions, createContractComponent, defineContractComponent };
@@ -1,3 +1,27 @@
1
+ // ../../lib/adapter-utils/src/invariant.ts
2
+ function panic(message) {
3
+ throw new Error(message);
4
+ }
5
+ function invariant(condition, message) {
6
+ if (!condition) panic(message);
7
+ }
8
+
9
+ // ../../lib/adapter-utils/src/runtime/apply-display-name.ts
10
+ function applyDisplayName(component, name) {
11
+ Object.assign(component, { displayName: name ?? "PolymorphicComponent" });
12
+ }
13
+
14
+ // ../../lib/adapter-utils/src/runtime/define-component.ts
15
+ function defineContractComponent(options) {
16
+ return (factory) => factory(options);
17
+ }
18
+
19
+ // ../../lib/adapter-utils/src/runtime/assemble-compound-component.ts
20
+ function assembleCompoundComponent(root, subComponents) {
21
+ if (!subComponents) return root;
22
+ return Object.assign(root, subComponents);
23
+ }
24
+
1
25
  // ../../lib/primitive/src/tag/resolve-tag.ts
2
26
  function makeResolveTag(defaultTag) {
3
27
  return function tag(as) {
@@ -40,10 +64,13 @@ function isString(value) {
40
64
  function isNumber(value) {
41
65
  return typeof value === "number";
42
66
  }
67
+ function isFunction(value) {
68
+ return typeof value === "function";
69
+ }
43
70
 
44
71
  // ../../lib/primitive/src/rule/is-dynamic-rule.ts
45
72
  function isDynamicRule(rule) {
46
- return isObject(rule, true) && rule[RULE_BRAND] === true;
73
+ return isObject(rule, true) && Reflect.get(rule, RULE_BRAND) === true;
47
74
  }
48
75
 
49
76
  // ../../lib/primitive/src/rule/resolve-rule.ts
@@ -673,17 +700,17 @@ var COMPONENT_DEFAULT_TAG = /* @__PURE__ */ Symbol.for("praxis.component-default
673
700
  // ../../lib/primitive/src/guards/children/is-tag.ts
674
701
  function getAsProp(child) {
675
702
  if (!isObject(child) || !("props" in child)) return void 0;
676
- const props = child.props;
703
+ const { props } = child;
677
704
  if (!isObject(props)) return void 0;
678
- const as = props.as;
705
+ const as = Reflect.get(props, "as");
679
706
  return isString(as) && as !== "" ? as : void 0;
680
707
  }
681
708
  function getTag(child) {
682
709
  if (!isObject(child) || !("type" in child)) return void 0;
683
- const t = child.type;
710
+ const { type: t } = child;
684
711
  if (isString(t)) return t;
685
712
  if (typeof t === "function" || isObject(t)) {
686
- const defaultTag = t[COMPONENT_DEFAULT_TAG];
713
+ const defaultTag = Reflect.get(t, COMPONENT_DEFAULT_TAG);
687
714
  if (!isString(defaultTag)) return void 0;
688
715
  return getAsProp(child) ?? defaultTag;
689
716
  }
@@ -703,14 +730,34 @@ function isTag(...args) {
703
730
  return tag !== void 0 && set2.has(tag);
704
731
  }
705
732
 
706
- // ../../lib/adapter-utils/src/runtime/apply-display-name.ts
707
- function applyDisplayName(component, name) {
708
- Object.assign(component, { displayName: name ?? "PolymorphicComponent" });
733
+ // ../../lib/adapter-utils/src/runtime/finalize-component.ts
734
+ function finalizeComponent(component, defaultTag, subComponents) {
735
+ if (typeof defaultTag === "string") {
736
+ Object.assign(component, { [COMPONENT_DEFAULT_TAG]: defaultTag });
737
+ }
738
+ return assembleCompoundComponent(component, subComponents);
709
739
  }
710
740
 
711
- // ../../lib/adapter-utils/src/runtime/define-component.ts
712
- function defineContractComponent(options) {
713
- return (factory) => factory(options);
741
+ // ../../lib/adapter-utils/src/runtime/is-factory-options-like.ts
742
+ var FACTORY_OPTIONS_FIELD_VALIDATORS = {
743
+ tag: (v) => v === void 0 || isString(v),
744
+ name: (v) => v === void 0 || isString(v),
745
+ defaults: (v) => v === void 0 || isObject(v),
746
+ normalize: (v) => v === void 0 || isFunction(v),
747
+ styling: (v) => v === void 0 || isObject(v),
748
+ enforcement: (v) => v === void 0 || isObject(v),
749
+ diagnostics: (v) => v === void 0 || isObject(v),
750
+ subComponents: (v) => v === void 0 || isObject(v),
751
+ onElement: (v) => v === void 0 || isFunction(v)
752
+ };
753
+ function isFactoryOptionsLike(options, extraFieldValidators) {
754
+ if (!isObject(options)) return false;
755
+ const validators = { ...FACTORY_OPTIONS_FIELD_VALIDATORS, ...extraFieldValidators };
756
+ for (const [key, value] of Object.entries(options)) {
757
+ const validate = validators[key];
758
+ if (!validate || !validate(value)) return false;
759
+ }
760
+ return true;
714
761
  }
715
762
 
716
763
  // ../../lib/contract/src/aria/aria-role-policy.ts
@@ -2863,7 +2910,7 @@ function getChildProp(child, key) {
2863
2910
  if (!isVNodeLike(child)) return void 0;
2864
2911
  const { props } = child;
2865
2912
  if (!isObject(props)) return void 0;
2866
- return props[key];
2913
+ return Reflect.get(props, key);
2867
2914
  }
2868
2915
 
2869
2916
  // ../core/src/html/contracts/aria/landmarks.ts
@@ -3512,6 +3559,9 @@ function composeFilter(ownedKeys, filterProps) {
3512
3559
  return (key, variantKeys) => defaultFilter(key, variantKeys) || filterProps(key, variantKeys);
3513
3560
  }
3514
3561
 
3562
+ // ../../adapters/solid/src/create-contract-component.ts
3563
+ import { mergeProps as mergeProps2, onCleanup } from "solid-js";
3564
+
3515
3565
  // ../../adapters/solid/src/slot/slot-validator.ts
3516
3566
  var SlotValidator = class extends InvariantBase {
3517
3567
  #name;
@@ -3559,6 +3609,13 @@ function buildRuntime(options) {
3559
3609
  };
3560
3610
  }
3561
3611
 
3612
+ // ../../adapters/solid/src/is-polymorphic-component.ts
3613
+ function isPolymorphicComponent(value) {
3614
+ if (!isFunction(value)) return false;
3615
+ if (!("displayName" in value)) return true;
3616
+ return value.displayName === void 0 || isString(value.displayName);
3617
+ }
3618
+
3562
3619
  // ../../adapters/solid/src/render.tsx
3563
3620
  import { createComponent as _$createComponent } from "solid-js/web";
3564
3621
  import { mergeProps as _$mergeProps } from "solid-js/web";
@@ -3673,20 +3730,46 @@ function render({
3673
3730
  }, () => domProps()));
3674
3731
  }
3675
3732
 
3733
+ // ../../adapters/solid/src/to-solid-factory-options.ts
3734
+ var SOLID_FIELD_VALIDATORS = {
3735
+ filterProps: (v) => v === void 0 || isFunction(v)
3736
+ };
3737
+ function isSolidFactoryOptions(options) {
3738
+ return isFactoryOptionsLike(options, SOLID_FIELD_VALIDATORS);
3739
+ }
3740
+
3676
3741
  // ../../adapters/solid/src/create-contract-component.ts
3677
3742
  function createContractComponent(options) {
3743
+ invariant(isSolidFactoryOptions(options), "options is not a valid SolidFactoryOptions object");
3678
3744
  const bundle = buildRuntime(options);
3745
+ const { onElement } = options;
3679
3746
  const Component = (props) => {
3747
+ const propsWithRef = onElement ? mergeProps2(props, {
3748
+ get ref() {
3749
+ const consumerRef = props.ref;
3750
+ return (el) => {
3751
+ if (typeof consumerRef === "function") consumerRef(el);
3752
+ const cleanup = onElement(el, () => props);
3753
+ if (cleanup) onCleanup(cleanup);
3754
+ };
3755
+ }
3756
+ }) : props;
3680
3757
  return render({
3681
3758
  ...bundle,
3682
- props
3759
+ props: propsWithRef
3683
3760
  });
3684
3761
  };
3685
3762
  applyDisplayName(Component, options.name);
3686
- if (typeof bundle.runtime.options.defaultTag === "string") {
3687
- Object.assign(Component, { [COMPONENT_DEFAULT_TAG]: bundle.runtime.options.defaultTag });
3688
- }
3689
- return Component;
3763
+ const assembled = finalizeComponent(
3764
+ Component,
3765
+ bundle.runtime.options.defaultTag,
3766
+ options.subComponents
3767
+ );
3768
+ invariant(
3769
+ isPolymorphicComponent(assembled),
3770
+ "Generated component failed to satisfy the PolymorphicComponent shape"
3771
+ );
3772
+ return assembled;
3690
3773
  }
3691
3774
  export {
3692
3775
  createContractComponent,