praxis-kit 7.3.0 → 7.5.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,15 +2,89 @@ 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>;
8
- /** A compound component's named sub-components, e.g. `{ Header, Content, Footer }`. */
20
+ /**
21
+ * A compound component's named sub-components, for example
22
+ * `{ Header, Content, Footer }`.
23
+ */
9
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;
10
71
 
11
72
  type IntrinsicTag = keyof HTMLElementTagNameMap;
12
73
 
13
74
  type ElementType = IntrinsicTag | (string & {});
75
+ /**
76
+ * Resolves a component's default tag to its real DOM interface — `HTMLDialogElement` for
77
+ * `'dialog'`, `HTMLDetailsElement` for `'details'`, and so on — falling back to `HTMLElement`
78
+ * for custom-element tags or anything not in `HTMLElementTagNameMap`. Used to type
79
+ * `FactoryOptions.onElement`'s `element` param so component authors get direct, correctly-typed
80
+ * access to tag-specific native members (`dialogEl.showModal()`) without an unsafe cast.
81
+ *
82
+ * The fallback is `HTMLElement`, not the more generic `Element` — every tag reachable through
83
+ * `IntrinsicTag` extends it, and so does every custom element per spec, so members `HTMLElement`
84
+ * itself declares (`showPopover()`/`hidePopover()`/`togglePopover()`, the `popover` attribute)
85
+ * stay directly accessible even for tags with no dedicated entry in `HTMLElementTagNameMap`.
86
+ */
87
+ type ElementForTag<TDefault extends ElementType> = TDefault extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[TDefault] : HTMLElement;
14
88
 
15
89
  declare const KNOWN_ARIA_ROLES: readonly ["alert", "alertdialog", "application", "article", "banner", "blockquote", "button", "caption", "cell", "checkbox", "code", "columnheader", "combobox", "complementary", "contentinfo", "definition", "deletion", "dialog", "document", "emphasis", "feed", "figure", "form", "generic", "grid", "gridcell", "group", "heading", "img", "insertion", "link", "list", "listbox", "listitem", "log", "main", "marquee", "math", "menu", "menubar", "menuitem", "menuitemcheckbox", "menuitemradio", "meter", "navigation", "none", "note", "option", "paragraph", "presentation", "progressbar", "radio", "radiogroup", "region", "row", "rowgroup", "rowheader", "scrollbar", "search", "searchbox", "separator", "slider", "spinbutton", "status", "strong", "subscript", "superscript", "switch", "tab", "table", "tablist", "tabpanel", "term", "textbox", "time", "timer", "toolbar", "tooltip", "tree", "treegrid", "treeitem"];
16
90
  type KnownAriaRole = (typeof KNOWN_ARIA_ROLES)[number];
@@ -189,7 +263,7 @@ type ClassPluginFactory<TProps extends AnyRecord = EmptyRecord> = <V extends Var
189
263
  * wherever a factory's concrete plugin-props shape isn't tracked (factory generics,
190
264
  * capability wiring). */
191
265
  type AnyClassPluginFactory = ClassPluginFactory<AnyRecord> | undefined;
192
- type ExtractPluginProps<TPlugin extends AnyClassPluginFactory> = TPlugin extends ClassPluginFactory<infer T> ? string extends keyof T ? EmptyRecord : T : EmptyRecord;
266
+ type ExtractPluginProps<TPlugin extends AnyClassPluginFactory> = TPlugin extends ClassPluginFactory<infer T> ? string extends keyof T ? NoPluginProps : T : NoPluginProps;
193
267
 
194
268
  type AriaContext = {
195
269
  readonly tag: IntrinsicTag;
@@ -253,6 +327,12 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
253
327
  * `@praxis-kit/diagnostics`.
254
328
  */
255
329
  readonly diagnostics?: Diagnostics | DiagnosticsMode;
330
+ /**
331
+ * ARIA/accessibility rules evaluated against the resolved tag and props on every render.
332
+ * Each rule is a function receiving the current context and returning zero or more
333
+ * violations, some of which can carry an auto-applicable fix (see `createRemoveAttributeRule`
334
+ * and friends in `praxis-kit/contract`).
335
+ */
256
336
  readonly aria?: readonly AriaRule[];
257
337
  /**
258
338
  * Rules that need `AriaPolicyEngine`'s fix-application/caching machinery
@@ -264,6 +344,11 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
264
344
  * misleading `aria` name to get the machinery it needs.
265
345
  */
266
346
  readonly rules?: readonly AriaRule[];
347
+ /**
348
+ * Declares which children are valid, by name, match predicate, and cardinality (e.g. "at
349
+ * least 1, at most 4 `Button` children"). Open by default — children matching no rule are
350
+ * still allowed unless `exclusiveChildren` is set.
351
+ */
267
352
  readonly children?: readonly ChildRuleInput[];
268
353
  /**
269
354
  * When true, only children matching a `children` rule (or text, per `allowText`)
@@ -276,19 +361,49 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
276
361
  * or any listed rule. Default: true.
277
362
  */
278
363
  readonly allowText?: boolean;
364
+ /**
365
+ * Prop transforms composed with the component's own `normalize` (from `FactoryOptions`) and
366
+ * run before it. Unlike `normalize`, these live in the enforcement bucket because they
367
+ * typically encode a built-in HTML/ARIA fact rather than component-specific behavior.
368
+ */
279
369
  readonly props?: readonly PropNormalizer[];
280
370
  /** Restricts the `as` prop to this set of tags. Violations route through diagnostics. */
281
371
  readonly allowedAs?: readonly TAllowed[];
282
372
  };
283
373
 
284
374
  type StylingOptions<V extends Readonly<VariantMap> = Readonly<EmptyRecord>, TPreset extends RecipeMap<V> = Readonly<EmptyRecord>, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory> = {
375
+ /** Class applied to every instance regardless of variant selection. */
285
376
  readonly base?: ClassName;
377
+ /**
378
+ * Named variant groups (e.g. `intent`, `size`), each mapping its possible values to a
379
+ * class string. A consumer selects a value per group as a prop (`<Button intent="primary">`).
380
+ */
286
381
  readonly variants?: V;
382
+ /** Value used for a variant group when the consumer doesn't pass one explicitly. */
287
383
  readonly defaults?: Partial<DefaultVariants<V>>;
384
+ /**
385
+ * Applies an extra class only when a specific *combination* of variant selections matches —
386
+ * for cases `variants` alone can't express (e.g. `intent: 'primary'` + `size: 'lg'` together
387
+ * need a class neither variant would add on its own).
388
+ */
288
389
  readonly compounds?: readonly CompoundVariant<V>[];
390
+ /**
391
+ * Named bundles of variant values, selectable as a single unit via the `recipe` prop (e.g.
392
+ * `<Button recipe="cta">` instead of setting `intent`/`size` individually).
393
+ */
289
394
  readonly presets?: TPreset;
395
+ /** Maps a resolved tag directly to a raw class string, independent of the variant system. */
290
396
  readonly tags?: Readonly<TagMap>;
397
+ /**
398
+ * A `ClassPluginFactory` (e.g. the Tailwind layout pipeline) that extends class resolution
399
+ * with its own owned props, layered on top of `variants`/`presets`/`tags`.
400
+ */
291
401
  readonly plugin?: TPlugin;
402
+ /**
403
+ * A cache-key → resolved-class-string lookup for every statically-known variant
404
+ * combination, skipping runtime class computation entirely when a match is found. Normally
405
+ * generated by a build-time class-extraction plugin rather than hand-authored.
406
+ */
292
407
  readonly precomputedClasses?: Readonly<Record<string, string>>;
293
408
  };
294
409
 
@@ -297,11 +412,21 @@ type NormalizeFn<Props extends AnyRecord = AnyRecord> = {
297
412
  }['normalize'];
298
413
  type AnyFactoryOptions = FactoryOptions<ElementType, AnyRecord, VariantMap, RecipeMap<VariantMap>, AnyClassPluginFactory>;
299
414
  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> = {
415
+ /** The intrinsic tag the component renders by default. Overridable per instance via `as`. */
300
416
  readonly tag?: TDefault;
417
+ /** Display name used in diagnostics, dev tools, and generated component naming. */
301
418
  readonly name?: string;
419
+ /** Values used for the component's own (non-variant) props when the consumer omits them. */
302
420
  readonly defaults?: Partial<NoInfer<Props>>;
421
+ /**
422
+ * A pure `(props) => props` transform run on every render, after `enforcement.props`'s
423
+ * normalizers see the same input. Use this for component-specific prop shaping — anything
424
+ * that depends on live instance state or the real DOM element belongs in `onElement` instead.
425
+ */
303
426
  readonly normalize?: NormalizeFn<NoInfer<Props>>;
427
+ /** Variant groups, base classes, presets, and the optional class-resolution plugin. */
304
428
  readonly styling?: StylingOptions<V, TPreset, TPlugin>;
429
+ /** ARIA rules, child-content contracts, and other runtime validation for this component. */
305
430
  readonly enforcement?: EnforcementOptions<TAllowed>;
306
431
  /**
307
432
  * Adapter-resolved diagnostics default, spread in by `resolveAdapterCommonOptions`. Not meant to
@@ -325,13 +450,23 @@ type FactoryOptions<TDefault extends ElementType = ElementType, Props extends An
325
450
  * no prop-based equivalent), not for anything expressible as a plain
326
451
  * prop.
327
452
  *
453
+ * `element` is typed to the real DOM interface of every tag the rendered
454
+ * element could actually be — `TDefault` plus whatever `enforcement.allowed`
455
+ * permits via `as` (`HTMLDialogElement` for `tag: 'dialog'`,
456
+ * `HTMLDetailsElement` for `tag: 'details'`, and so on) — no cast needed to
457
+ * reach tag-specific members. A component that leaves `allowed`
458
+ * unconstrained (any tag reachable via `as`) falls back to `HTMLElement`,
459
+ * which still covers members every element shares (`showPopover()` and
460
+ * friends); restrict `enforcement.allowed` to the tags `onElement`
461
+ * actually knows how to handle to get real narrowing.
462
+ *
328
463
  * `getProps` returns the instance's *current* resolved props at call
329
464
  * time — read it from inside a listener registered once at mount, rather
330
465
  * than re-subscribing on every prop change.
331
466
  *
332
467
  * Return a cleanup function to run when the instance unmounts.
333
468
  */
334
- readonly onElement?: (element: Element, getProps: () => Readonly<Props>) => void | (() => void);
469
+ readonly onElement?: (element: ElementForTag<TDefault | TAllowed>, getProps: () => Readonly<Props>) => void | (() => void);
335
470
  };
336
471
 
337
472
  declare function defineContractComponent<O extends FactoryOptions>(options: O): <R>(factory: (options: O) => R) => R;
@@ -340,7 +475,7 @@ type UnknownProps = AnyRecord;
340
475
  type SolidElement = JSX.Element;
341
476
  type SlotRenderFn = (props: UnknownProps) => SolidElement;
342
477
 
343
- 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> & {
478
+ 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> & {
344
479
  /**
345
480
  * Return true for any prop key that should be consumed but not forwarded to the DOM.
346
481
  * Receives `runtime.options.variantKeys` as a convenience if needed.
@@ -383,8 +518,28 @@ type PolymorphicComponent<G extends PolymorphicGenerics> = {
383
518
  displayName?: string;
384
519
  };
385
520
 
386
- 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, TSubComponents extends Readonly<AnyRecord> = EmptyRecord>(options: SolidFactoryOptions<TDefault, Props, Variants, TPreset, TPlugin> & {
521
+ /**
522
+ * Creates a polymorphic Solid component with praxis-kit contracts applied.
523
+ *
524
+ * ```tsx
525
+ * const Button = createContractComponent({
526
+ * tag: 'button',
527
+ * name: 'Button',
528
+ * styling: {
529
+ * base: 'btn',
530
+ * variants: { intent: { primary: 'btn--primary', ghost: 'btn--ghost' } },
531
+ * defaults: { intent: 'primary' },
532
+ * },
533
+ * })
534
+ *
535
+ * <Button intent="ghost" as="a" href="/home">Home</Button>
536
+ * ```
537
+ *
538
+ * `ref` is forwarded as an ordinary Solid ref callback. Pass `subComponents` to attach named
539
+ * sub-components (`Card.Header`) and `onElement` to run setup once the real DOM element exists.
540
+ */
541
+ 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> & {
387
542
  readonly subComponents?: TSubComponents;
388
- }): PolymorphicComponent<PolymorphicGenerics<TDefault, Props & ExtractPluginProps<TPlugin>, Variants, TPreset>> & TSubComponents;
543
+ }): MergeRecords<PolymorphicComponent<PolymorphicGenerics<TDefault, MergeRecords<Props, ExtractPluginProps<TPlugin>>, Variants, TPreset>>, TSubComponents>;
389
544
 
390
545
  export { type AnyFactoryOptions, type ElementRef, type ElementType, type EmptyRecord, type PolymorphicComponent, type PolymorphicGenerics, type PolymorphicProps, type SolidFactoryOptions, createContractComponent, defineContractComponent };
@@ -3749,7 +3749,10 @@ function createContractComponent(options) {
3749
3749
  const consumerRef = props.ref;
3750
3750
  return (el) => {
3751
3751
  if (typeof consumerRef === "function") consumerRef(el);
3752
- const cleanup = onElement(el, () => props);
3752
+ const cleanup = onElement(
3753
+ el,
3754
+ () => props
3755
+ );
3753
3756
  if (cleanup) onCleanup(cleanup);
3754
3757
  };
3755
3758
  }
@@ -1,3 +1,16 @@
1
+ <!--
2
+ @component
3
+ Renders a `createContractComponent` bundle. Every praxis-kit component in the Svelte adapter
4
+ is a bundle passed to this component via the `bundle` prop:
5
+
6
+ ```svelte
7
+ <Polymorphic {bundle} intent="ghost" as="a" href="/home">Home</Polymorphic>
8
+ ```
9
+
10
+ Resolves the tag (`as` or the bundle's default), variant classes, filtered props, and ARIA
11
+ attributes, then renders the result via `<svelte:element>` — or, with `asChild`, renders
12
+ `children` as a snippet receiving the resolved props instead of a host element.
13
+ -->
1
14
  <script module lang="ts">
2
15
  declare const process: { env: { NODE_ENV: string } }
3
16
  </script>
@@ -1,15 +1,89 @@
1
1
  import { RequireAtLeastOne, Simplify, ReadonlyDeep } from 'type-fest';
2
2
  import { Diagnostics, DiagnosticInput, DiagnosticsMode } from '../_shared/diagnostics.js';
3
3
 
4
+ /**
5
+ * A string-keyed object whose values are of type `T`.
6
+ */
4
7
  type StringMap<T = unknown> = Record<string, T>;
8
+ /**
9
+ * A string-keyed object with values of unknown type.
10
+ */
5
11
  type AnyRecord = StringMap<unknown>;
12
+ /**
13
+ * An object type with no named properties.
14
+ *
15
+ * Unlike `{}`, this excludes arbitrary properties during type operations while
16
+ * still satisfying `extends object`.
17
+ */
6
18
  type EmptyRecord = Record<never, never>;
7
- /** A compound component's named sub-components, e.g. `{ Header, Content, Footer }`. */
19
+ /**
20
+ * A compound component's named sub-components, for example
21
+ * `{ Header, Content, Footer }`.
22
+ */
8
23
  type SubComponentMap = Readonly<AnyRecord>;
24
+ /**
25
+ * Default `Variants` type for components that declare no variants.
26
+ *
27
+ * Structurally identical to `Readonly<EmptyRecord>`, but named separately so
28
+ * editor hovers remain self-descriptive.
29
+ */
30
+ type NoVariants = Readonly<EmptyRecord>;
31
+ /**
32
+ * Default `TPreset` type for components that declare no named presets.
33
+ *
34
+ * Structurally identical to `Readonly<EmptyRecord>`, but named separately so
35
+ * editor hovers remain self-descriptive.
36
+ */
37
+ type NoPreset = Readonly<EmptyRecord>;
38
+ /**
39
+ * Fallback for `ExtractPluginProps<TPlugin>` when a plugin contributes no
40
+ * props, including the no-plugin case.
41
+ *
42
+ * Structurally identical to `EmptyRecord`, but named separately so editor
43
+ * hovers remain self-descriptive.
44
+ */
45
+ type NoPluginProps = EmptyRecord;
46
+ /**
47
+ * Determines whether an object type should be treated as empty.
48
+ *
49
+ * `keyof T` ignores call and construct signatures...
50
+ */
51
+ type IsEmptyRecord<T extends object> = T extends (...args: never[]) => unknown ? false : T extends new (...args: never[]) => unknown ? false : keyof T extends never ? true : false;
52
+ /**
53
+ * Merges two object types while eliding empty operands.
54
+ *
55
+ * If either operand is {@link EmptyRecord}, the other operand is returned
56
+ * directly instead of producing intersections such as
57
+ * `Component & EmptyRecord` in editor hovers.
58
+ *
59
+ * Unlike a homomorphic mapped type (for example `Simplify<T>`), this preserves
60
+ * call and construct signatures. Many component types are callable objects,
61
+ * and mapped types silently discard those signatures.
62
+ *
63
+ * @remarks
64
+ * Instantiate `MergeRecords` directly. Introducing an intermediate alias for
65
+ * one operand (for example `type C = PolymorphicComponent<G>`) can prevent
66
+ * `IsEmptyRecord` from evaluating eagerly, which breaks assignability under
67
+ * `exactOptionalPropertyTypes`.
68
+ */
69
+ type MergeRecords<A extends object, B extends object> = IsEmptyRecord<A> extends true ? B : IsEmptyRecord<B> extends true ? A : A & B;
9
70
 
10
71
  type IntrinsicTag = keyof HTMLElementTagNameMap;
11
72
 
12
73
  type ElementType = IntrinsicTag | (string & {});
74
+ /**
75
+ * Resolves a component's default tag to its real DOM interface — `HTMLDialogElement` for
76
+ * `'dialog'`, `HTMLDetailsElement` for `'details'`, and so on — falling back to `HTMLElement`
77
+ * for custom-element tags or anything not in `HTMLElementTagNameMap`. Used to type
78
+ * `FactoryOptions.onElement`'s `element` param so component authors get direct, correctly-typed
79
+ * access to tag-specific native members (`dialogEl.showModal()`) without an unsafe cast.
80
+ *
81
+ * The fallback is `HTMLElement`, not the more generic `Element` — every tag reachable through
82
+ * `IntrinsicTag` extends it, and so does every custom element per spec, so members `HTMLElement`
83
+ * itself declares (`showPopover()`/`hidePopover()`/`togglePopover()`, the `popover` attribute)
84
+ * stay directly accessible even for tags with no dedicated entry in `HTMLElementTagNameMap`.
85
+ */
86
+ type ElementForTag<TDefault extends ElementType> = TDefault extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[TDefault] : HTMLElement;
13
87
 
14
88
  declare const KNOWN_ARIA_ROLES: readonly ["alert", "alertdialog", "application", "article", "banner", "blockquote", "button", "caption", "cell", "checkbox", "code", "columnheader", "combobox", "complementary", "contentinfo", "definition", "deletion", "dialog", "document", "emphasis", "feed", "figure", "form", "generic", "grid", "gridcell", "group", "heading", "img", "insertion", "link", "list", "listbox", "listitem", "log", "main", "marquee", "math", "menu", "menubar", "menuitem", "menuitemcheckbox", "menuitemradio", "meter", "navigation", "none", "note", "option", "paragraph", "presentation", "progressbar", "radio", "radiogroup", "region", "row", "rowgroup", "rowheader", "scrollbar", "search", "searchbox", "separator", "slider", "spinbutton", "status", "strong", "subscript", "superscript", "switch", "tab", "table", "tablist", "tabpanel", "term", "textbox", "time", "timer", "toolbar", "tooltip", "tree", "treegrid", "treeitem"];
15
89
  type KnownAriaRole = (typeof KNOWN_ARIA_ROLES)[number];
@@ -133,6 +207,8 @@ interface PolymorphicGenerics<TDefault extends ElementType = ElementType, Props
133
207
  preset: TPreset;
134
208
  allowed: TAllowed;
135
209
  }
210
+ type AllowedOf<T extends PolymorphicGenerics> = T['allowed'];
211
+ type DefaultOf<T extends PolymorphicGenerics> = T['default'];
136
212
  type PropsOf<T extends PolymorphicGenerics> = T['props'];
137
213
 
138
214
  type RequireAtLeastOneIfNotEmpty<T> = keyof T extends never ? EmptyRecord : RequireAtLeastOne<T>;
@@ -193,7 +269,7 @@ type ClassPluginFactory<TProps extends AnyRecord = EmptyRecord> = <V extends Var
193
269
  * wherever a factory's concrete plugin-props shape isn't tracked (factory generics,
194
270
  * capability wiring). */
195
271
  type AnyClassPluginFactory = ClassPluginFactory<AnyRecord> | undefined;
196
- type ExtractPluginProps<TPlugin extends AnyClassPluginFactory> = TPlugin extends ClassPluginFactory<infer T> ? string extends keyof T ? EmptyRecord : T : EmptyRecord;
272
+ type ExtractPluginProps<TPlugin extends AnyClassPluginFactory> = TPlugin extends ClassPluginFactory<infer T> ? string extends keyof T ? NoPluginProps : T : NoPluginProps;
197
273
  type PluginInstance<TPlugin extends AnyClassPluginFactory> = TPlugin extends ClassPluginFactory<infer TProps> ? ClassPlugin<TProps> : undefined;
198
274
 
199
275
  type AriaContext = {
@@ -258,6 +334,12 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
258
334
  * `@praxis-kit/diagnostics`.
259
335
  */
260
336
  readonly diagnostics?: Diagnostics | DiagnosticsMode;
337
+ /**
338
+ * ARIA/accessibility rules evaluated against the resolved tag and props on every render.
339
+ * Each rule is a function receiving the current context and returning zero or more
340
+ * violations, some of which can carry an auto-applicable fix (see `createRemoveAttributeRule`
341
+ * and friends in `praxis-kit/contract`).
342
+ */
261
343
  readonly aria?: readonly AriaRule[];
262
344
  /**
263
345
  * Rules that need `AriaPolicyEngine`'s fix-application/caching machinery
@@ -269,6 +351,11 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
269
351
  * misleading `aria` name to get the machinery it needs.
270
352
  */
271
353
  readonly rules?: readonly AriaRule[];
354
+ /**
355
+ * Declares which children are valid, by name, match predicate, and cardinality (e.g. "at
356
+ * least 1, at most 4 `Button` children"). Open by default — children matching no rule are
357
+ * still allowed unless `exclusiveChildren` is set.
358
+ */
272
359
  readonly children?: readonly ChildRuleInput[];
273
360
  /**
274
361
  * When true, only children matching a `children` rule (or text, per `allowText`)
@@ -281,19 +368,49 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
281
368
  * or any listed rule. Default: true.
282
369
  */
283
370
  readonly allowText?: boolean;
371
+ /**
372
+ * Prop transforms composed with the component's own `normalize` (from `FactoryOptions`) and
373
+ * run before it. Unlike `normalize`, these live in the enforcement bucket because they
374
+ * typically encode a built-in HTML/ARIA fact rather than component-specific behavior.
375
+ */
284
376
  readonly props?: readonly PropNormalizer[];
285
377
  /** Restricts the `as` prop to this set of tags. Violations route through diagnostics. */
286
378
  readonly allowedAs?: readonly TAllowed[];
287
379
  };
288
380
 
289
381
  type StylingOptions<V extends Readonly<VariantMap> = Readonly<EmptyRecord>, TPreset extends RecipeMap<V> = Readonly<EmptyRecord>, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory> = {
382
+ /** Class applied to every instance regardless of variant selection. */
290
383
  readonly base?: ClassName;
384
+ /**
385
+ * Named variant groups (e.g. `intent`, `size`), each mapping its possible values to a
386
+ * class string. A consumer selects a value per group as a prop (`<Button intent="primary">`).
387
+ */
291
388
  readonly variants?: V;
389
+ /** Value used for a variant group when the consumer doesn't pass one explicitly. */
292
390
  readonly defaults?: Partial<DefaultVariants<V>>;
391
+ /**
392
+ * Applies an extra class only when a specific *combination* of variant selections matches —
393
+ * for cases `variants` alone can't express (e.g. `intent: 'primary'` + `size: 'lg'` together
394
+ * need a class neither variant would add on its own).
395
+ */
293
396
  readonly compounds?: readonly CompoundVariant<V>[];
397
+ /**
398
+ * Named bundles of variant values, selectable as a single unit via the `recipe` prop (e.g.
399
+ * `<Button recipe="cta">` instead of setting `intent`/`size` individually).
400
+ */
294
401
  readonly presets?: TPreset;
402
+ /** Maps a resolved tag directly to a raw class string, independent of the variant system. */
295
403
  readonly tags?: Readonly<TagMap>;
404
+ /**
405
+ * A `ClassPluginFactory` (e.g. the Tailwind layout pipeline) that extends class resolution
406
+ * with its own owned props, layered on top of `variants`/`presets`/`tags`.
407
+ */
296
408
  readonly plugin?: TPlugin;
409
+ /**
410
+ * A cache-key → resolved-class-string lookup for every statically-known variant
411
+ * combination, skipping runtime class computation entirely when a match is found. Normally
412
+ * generated by a build-time class-extraction plugin rather than hand-authored.
413
+ */
297
414
  readonly precomputedClasses?: Readonly<Record<string, string>>;
298
415
  };
299
416
 
@@ -302,11 +419,21 @@ type NormalizeFn<Props extends AnyRecord = AnyRecord> = {
302
419
  }['normalize'];
303
420
  type AnyFactoryOptions = FactoryOptions<ElementType, AnyRecord, VariantMap, RecipeMap<VariantMap>, AnyClassPluginFactory>;
304
421
  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> = {
422
+ /** The intrinsic tag the component renders by default. Overridable per instance via `as`. */
305
423
  readonly tag?: TDefault;
424
+ /** Display name used in diagnostics, dev tools, and generated component naming. */
306
425
  readonly name?: string;
426
+ /** Values used for the component's own (non-variant) props when the consumer omits them. */
307
427
  readonly defaults?: Partial<NoInfer<Props>>;
428
+ /**
429
+ * A pure `(props) => props` transform run on every render, after `enforcement.props`'s
430
+ * normalizers see the same input. Use this for component-specific prop shaping — anything
431
+ * that depends on live instance state or the real DOM element belongs in `onElement` instead.
432
+ */
308
433
  readonly normalize?: NormalizeFn<NoInfer<Props>>;
434
+ /** Variant groups, base classes, presets, and the optional class-resolution plugin. */
309
435
  readonly styling?: StylingOptions<V, TPreset, TPlugin>;
436
+ /** ARIA rules, child-content contracts, and other runtime validation for this component. */
310
437
  readonly enforcement?: EnforcementOptions<TAllowed>;
311
438
  /**
312
439
  * Adapter-resolved diagnostics default, spread in by `resolveAdapterCommonOptions`. Not meant to
@@ -330,13 +457,23 @@ type FactoryOptions<TDefault extends ElementType = ElementType, Props extends An
330
457
  * no prop-based equivalent), not for anything expressible as a plain
331
458
  * prop.
332
459
  *
460
+ * `element` is typed to the real DOM interface of every tag the rendered
461
+ * element could actually be — `TDefault` plus whatever `enforcement.allowed`
462
+ * permits via `as` (`HTMLDialogElement` for `tag: 'dialog'`,
463
+ * `HTMLDetailsElement` for `tag: 'details'`, and so on) — no cast needed to
464
+ * reach tag-specific members. A component that leaves `allowed`
465
+ * unconstrained (any tag reachable via `as`) falls back to `HTMLElement`,
466
+ * which still covers members every element shares (`showPopover()` and
467
+ * friends); restrict `enforcement.allowed` to the tags `onElement`
468
+ * actually knows how to handle to get real narrowing.
469
+ *
333
470
  * `getProps` returns the instance's *current* resolved props at call
334
471
  * time — read it from inside a listener registered once at mount, rather
335
472
  * than re-subscribing on every prop change.
336
473
  *
337
474
  * Return a cleanup function to run when the instance unmounts.
338
475
  */
339
- readonly onElement?: (element: Element, getProps: () => Readonly<Props>) => void | (() => void);
476
+ readonly onElement?: (element: ElementForTag<TDefault | TAllowed>, getProps: () => Readonly<Props>) => void | (() => void);
340
477
  };
341
478
 
342
479
  type ResolvedFactoryOptions<TDefault extends ElementType = ElementType, Props extends AnyRecord = EmptyRecord, V extends Readonly<VariantMap> = Readonly<EmptyRecord>, TPreset extends RecipeMap<V> = Readonly<EmptyRecord>> = {
@@ -419,6 +556,57 @@ declare class ChildrenEvaluator extends InvariantBase {
419
556
 
420
557
  declare function createPolymorphic2<TDefault extends ElementType, Props extends AnyRecord, Variants extends Readonly<VariantMap>, TPreset extends RecipeMap<Variants> = Readonly<EmptyRecord>, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory>(options?: FactoryOptions<TDefault, Props, Variants, TPreset, TPlugin>): PolymorphicRuntime<TDefault, Props, Variants, Extract<keyof TPreset, string>, TPreset, PluginInstance<TPlugin>>;
421
558
 
559
+ /**
560
+ * Matches option types that declare child enforcement rules.
561
+ *
562
+ * This type is used to determine whether a
563
+ * {@link ChildrenEvaluator} should be included in a built bundle.
564
+ */
565
+ type WithChildrenEnforcement = {
566
+ enforcement: {
567
+ children: readonly unknown[];
568
+ };
569
+ };
570
+ /**
571
+ * The bundle of child evaluation services produced when
572
+ * child enforcement rules are configured.
573
+ */
574
+ type ChildrenEvaluatorBundle = {
575
+ childrenEvaluator: ChildrenEvaluator;
576
+ };
577
+ /**
578
+ * Conditionally includes a {@link ChildrenEvaluator} in the
579
+ * built bundle when child enforcement rules are present.
580
+ *
581
+ * When no child enforcement rules are configured, this type
582
+ * resolves to {@link EmptyRecord}, omitting the property
583
+ * entirely rather than making it optional. Consumers can
584
+ * safely narrow using:
585
+ *
586
+ * ```ts
587
+ * if ('childrenEvaluator' in bundle) {
588
+ * // bundle.childrenEvaluator is available
589
+ * }
590
+ * ```
591
+ *
592
+ * @typeParam TOptions - The component configuration options.
593
+ */
594
+ type BuiltChildrenEvaluator<TOptions extends WithChildRules> = TOptions extends WithChildrenEnforcement ? ChildrenEvaluatorBundle : EmptyRecord;
595
+
596
+ /**
597
+ * Determines whether a prop should be stripped before forwarding to the
598
+ * rendered element.
599
+ *
600
+ * Returning `true` excludes the prop from the output; returning `false`
601
+ * keeps it. This is the inverse polarity of `shouldForwardProp`-style
602
+ * predicates (Emotion/styled-components), where `true` means include.
603
+ *
604
+ * @param key - The prop name being evaluated.
605
+ * @param variantKeys - The set of configured variant prop names.
606
+ * @returns `true` to strip the prop; `false` to forward it.
607
+ */
608
+ type FilterPredicate = (key: string, variantKeys: ReadonlySet<string>) => boolean;
609
+
422
610
  declare class SlotValidator extends InvariantBase {
423
611
  #private;
424
612
  constructor(name: string, diagnostics: Diagnostics, elementTerm: string);
@@ -427,21 +615,11 @@ declare class SlotValidator extends InvariantBase {
427
615
  assertSingleChild(count: number): void;
428
616
  }
429
617
 
430
- type FilterPredicate = (key: string, variantKeys: ReadonlySet<string>) => boolean;
431
-
432
- type BuiltChildrenEvaluator<TOptions extends WithChildRules> = TOptions extends {
433
- enforcement: {
434
- children: readonly unknown[];
435
- };
436
- } ? {
437
- childrenEvaluator: ChildrenEvaluator;
438
- } : EmptyRecord;
439
-
440
618
  declare function defineContractComponent<O extends FactoryOptions>(options: O): <R>(factory: (options: O) => R) => R;
441
619
 
442
620
  type UnknownProps = AnyRecord;
443
621
 
444
- type SvelteFactoryOptions<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> & {
622
+ type SvelteFactoryOptions<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> & {
445
623
  /**
446
624
  * Return true for any prop key that should be consumed but not forwarded to the DOM.
447
625
  * Receives `runtime.options.variantKeys` as a convenience if needed.
@@ -451,16 +629,50 @@ type SvelteFactoryOptions<TDefault extends ElementType, Props extends UnknownPro
451
629
 
452
630
  type TypedRuntime<G extends PolymorphicGenerics> = ReturnType<typeof createPolymorphic2<DefaultOf<G>, PropsOf<G>, VariantsOf<G>, RecipeOf<G>>>;
453
631
 
454
- type OnElementFn<Props = unknown> = (element: Element, getProps: () => Readonly<Props>) => void | (() => void);
632
+ type OnElementFn<G extends PolymorphicGenerics = PolymorphicGenerics> = (element: ElementForTag<DefaultOf<G> | AllowedOf<G>>, getProps: () => Readonly<PropsOf<G>>) => void | (() => void);
455
633
  type BuiltRuntime<G extends PolymorphicGenerics = PolymorphicGenerics, TOptions extends WithChildRules = WithChildRules> = BuiltChildrenEvaluator<TOptions> & {
456
634
  runtime: TypedRuntime<G>;
457
635
  filterProps: FilterPredicate;
458
636
  slotValidator: SlotValidator;
459
- onElement?: OnElementFn<PropsOf<G>>;
637
+ onElement?: OnElementFn<G>;
460
638
  };
461
639
 
462
- 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, TSubComponents extends Readonly<AnyRecord> = EmptyRecord, TOptions extends WithChildRules = SvelteFactoryOptions<TDefault, Props & ExtractPluginProps<TPlugin>, Variants, TPreset>>(options: SvelteFactoryOptions<TDefault, Props, Variants, TPreset, TPlugin> & TOptions & {
640
+ /**
641
+ * Creates a praxis-kit contract bundle for use with Svelte's `<Polymorphic>` component.
642
+ *
643
+ * Unlike the other adapters, this returns a plain bundle object rather than a component —
644
+ * Svelte components must come from `.svelte` files, a compile-time constraint — so the bundle
645
+ * is passed as the `bundle` prop:
646
+ *
647
+ * ```ts
648
+ * // button.ts
649
+ * export const buttonBundle = createContractComponent({
650
+ * tag: 'button',
651
+ * name: 'Button',
652
+ * styling: {
653
+ * base: 'btn',
654
+ * variants: { intent: { primary: 'btn--primary', ghost: 'btn--ghost' } },
655
+ * defaults: { intent: 'primary' },
656
+ * },
657
+ * })
658
+ * ```
659
+ *
660
+ * ```svelte
661
+ * <!-- Button.svelte -->
662
+ * <script lang="ts">
663
+ * import Polymorphic from 'praxis-kit/svelte/Polymorphic.svelte'
664
+ * import { buttonBundle } from './button'
665
+ * </script>
666
+ * <Polymorphic bundle={buttonBundle} intent="ghost" as="a" href="/home">Home</Polymorphic>
667
+ * ```
668
+ *
669
+ * Pass `subComponents` to attach named sub-components (`Card.Header`) — `Object.assign` works
670
+ * the same way on a plain bundle as on a component function/class, so `Card.Header` is itself
671
+ * just another bundle, passed to its own `<Polymorphic bundle={Card.Header}>`. Pass `onElement`
672
+ * to run setup once the real DOM element exists.
673
+ */
674
+ 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, TOptions extends WithChildRules = SvelteFactoryOptions<TDefault, MergeRecords<Props, ExtractPluginProps<TPlugin>>, Variants, TPreset>>(options: SvelteFactoryOptions<TDefault, Props, Variants, TPreset, TPlugin> & TOptions & {
463
675
  readonly subComponents?: TSubComponents;
464
- }): BuiltRuntime<PolymorphicGenerics<TDefault, Props & ExtractPluginProps<TPlugin>, Variants, TPreset>, TOptions> & TSubComponents;
676
+ }): MergeRecords<BuiltRuntime<PolymorphicGenerics<TDefault, MergeRecords<Props, ExtractPluginProps<TPlugin>>, Variants, TPreset>, TOptions>, TSubComponents>;
465
677
 
466
678
  export { type AnyFactoryOptions, type BuiltRuntime, type ElementType, type EmptyRecord, type FilterPredicate, type PolymorphicGenerics, type SvelteFactoryOptions, type UnknownProps, type WithChildRules, createContractComponent, defineContractComponent };