praxis-kit 7.3.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.
@@ -1,11 +1,72 @@
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
 
@@ -193,7 +254,7 @@ type ClassPluginFactory<TProps extends AnyRecord = EmptyRecord> = <V extends Var
193
254
  * wherever a factory's concrete plugin-props shape isn't tracked (factory generics,
194
255
  * capability wiring). */
195
256
  type AnyClassPluginFactory = ClassPluginFactory<AnyRecord> | undefined;
196
- type ExtractPluginProps<TPlugin extends AnyClassPluginFactory> = TPlugin extends ClassPluginFactory<infer T> ? string extends keyof T ? EmptyRecord : T : EmptyRecord;
257
+ type ExtractPluginProps<TPlugin extends AnyClassPluginFactory> = TPlugin extends ClassPluginFactory<infer T> ? string extends keyof T ? NoPluginProps : T : NoPluginProps;
197
258
  type PluginInstance<TPlugin extends AnyClassPluginFactory> = TPlugin extends ClassPluginFactory<infer TProps> ? ClassPlugin<TProps> : undefined;
198
259
 
199
260
  type AriaContext = {
@@ -258,6 +319,12 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
258
319
  * `@praxis-kit/diagnostics`.
259
320
  */
260
321
  readonly diagnostics?: Diagnostics | DiagnosticsMode;
322
+ /**
323
+ * ARIA/accessibility rules evaluated against the resolved tag and props on every render.
324
+ * Each rule is a function receiving the current context and returning zero or more
325
+ * violations, some of which can carry an auto-applicable fix (see `createRemoveAttributeRule`
326
+ * and friends in `praxis-kit/contract`).
327
+ */
261
328
  readonly aria?: readonly AriaRule[];
262
329
  /**
263
330
  * Rules that need `AriaPolicyEngine`'s fix-application/caching machinery
@@ -269,6 +336,11 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
269
336
  * misleading `aria` name to get the machinery it needs.
270
337
  */
271
338
  readonly rules?: readonly AriaRule[];
339
+ /**
340
+ * Declares which children are valid, by name, match predicate, and cardinality (e.g. "at
341
+ * least 1, at most 4 `Button` children"). Open by default — children matching no rule are
342
+ * still allowed unless `exclusiveChildren` is set.
343
+ */
272
344
  readonly children?: readonly ChildRuleInput[];
273
345
  /**
274
346
  * When true, only children matching a `children` rule (or text, per `allowText`)
@@ -281,19 +353,49 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
281
353
  * or any listed rule. Default: true.
282
354
  */
283
355
  readonly allowText?: boolean;
356
+ /**
357
+ * Prop transforms composed with the component's own `normalize` (from `FactoryOptions`) and
358
+ * run before it. Unlike `normalize`, these live in the enforcement bucket because they
359
+ * typically encode a built-in HTML/ARIA fact rather than component-specific behavior.
360
+ */
284
361
  readonly props?: readonly PropNormalizer[];
285
362
  /** Restricts the `as` prop to this set of tags. Violations route through diagnostics. */
286
363
  readonly allowedAs?: readonly TAllowed[];
287
364
  };
288
365
 
289
366
  type StylingOptions<V extends Readonly<VariantMap> = Readonly<EmptyRecord>, TPreset extends RecipeMap<V> = Readonly<EmptyRecord>, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory> = {
367
+ /** Class applied to every instance regardless of variant selection. */
290
368
  readonly base?: ClassName;
369
+ /**
370
+ * Named variant groups (e.g. `intent`, `size`), each mapping its possible values to a
371
+ * class string. A consumer selects a value per group as a prop (`<Button intent="primary">`).
372
+ */
291
373
  readonly variants?: V;
374
+ /** Value used for a variant group when the consumer doesn't pass one explicitly. */
292
375
  readonly defaults?: Partial<DefaultVariants<V>>;
376
+ /**
377
+ * Applies an extra class only when a specific *combination* of variant selections matches —
378
+ * for cases `variants` alone can't express (e.g. `intent: 'primary'` + `size: 'lg'` together
379
+ * need a class neither variant would add on its own).
380
+ */
293
381
  readonly compounds?: readonly CompoundVariant<V>[];
382
+ /**
383
+ * Named bundles of variant values, selectable as a single unit via the `recipe` prop (e.g.
384
+ * `<Button recipe="cta">` instead of setting `intent`/`size` individually).
385
+ */
294
386
  readonly presets?: TPreset;
387
+ /** Maps a resolved tag directly to a raw class string, independent of the variant system. */
295
388
  readonly tags?: Readonly<TagMap>;
389
+ /**
390
+ * A `ClassPluginFactory` (e.g. the Tailwind layout pipeline) that extends class resolution
391
+ * with its own owned props, layered on top of `variants`/`presets`/`tags`.
392
+ */
296
393
  readonly plugin?: TPlugin;
394
+ /**
395
+ * A cache-key → resolved-class-string lookup for every statically-known variant
396
+ * combination, skipping runtime class computation entirely when a match is found. Normally
397
+ * generated by a build-time class-extraction plugin rather than hand-authored.
398
+ */
297
399
  readonly precomputedClasses?: Readonly<Record<string, string>>;
298
400
  };
299
401
 
@@ -302,11 +404,21 @@ type NormalizeFn<Props extends AnyRecord = AnyRecord> = {
302
404
  }['normalize'];
303
405
  type AnyFactoryOptions = FactoryOptions<ElementType, AnyRecord, VariantMap, RecipeMap<VariantMap>, AnyClassPluginFactory>;
304
406
  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> = {
407
+ /** The intrinsic tag the component renders by default. Overridable per instance via `as`. */
305
408
  readonly tag?: TDefault;
409
+ /** Display name used in diagnostics, dev tools, and generated component naming. */
306
410
  readonly name?: string;
411
+ /** Values used for the component's own (non-variant) props when the consumer omits them. */
307
412
  readonly defaults?: Partial<NoInfer<Props>>;
413
+ /**
414
+ * A pure `(props) => props` transform run on every render, after `enforcement.props`'s
415
+ * normalizers see the same input. Use this for component-specific prop shaping — anything
416
+ * that depends on live instance state or the real DOM element belongs in `onElement` instead.
417
+ */
308
418
  readonly normalize?: NormalizeFn<NoInfer<Props>>;
419
+ /** Variant groups, base classes, presets, and the optional class-resolution plugin. */
309
420
  readonly styling?: StylingOptions<V, TPreset, TPlugin>;
421
+ /** ARIA rules, child-content contracts, and other runtime validation for this component. */
310
422
  readonly enforcement?: EnforcementOptions<TAllowed>;
311
423
  /**
312
424
  * Adapter-resolved diagnostics default, spread in by `resolveAdapterCommonOptions`. Not meant to
@@ -419,6 +531,57 @@ declare class ChildrenEvaluator extends InvariantBase {
419
531
 
420
532
  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
533
 
534
+ /**
535
+ * Matches option types that declare child enforcement rules.
536
+ *
537
+ * This type is used to determine whether a
538
+ * {@link ChildrenEvaluator} should be included in a built bundle.
539
+ */
540
+ type WithChildrenEnforcement = {
541
+ enforcement: {
542
+ children: readonly unknown[];
543
+ };
544
+ };
545
+ /**
546
+ * The bundle of child evaluation services produced when
547
+ * child enforcement rules are configured.
548
+ */
549
+ type ChildrenEvaluatorBundle = {
550
+ childrenEvaluator: ChildrenEvaluator;
551
+ };
552
+ /**
553
+ * Conditionally includes a {@link ChildrenEvaluator} in the
554
+ * built bundle when child enforcement rules are present.
555
+ *
556
+ * When no child enforcement rules are configured, this type
557
+ * resolves to {@link EmptyRecord}, omitting the property
558
+ * entirely rather than making it optional. Consumers can
559
+ * safely narrow using:
560
+ *
561
+ * ```ts
562
+ * if ('childrenEvaluator' in bundle) {
563
+ * // bundle.childrenEvaluator is available
564
+ * }
565
+ * ```
566
+ *
567
+ * @typeParam TOptions - The component configuration options.
568
+ */
569
+ type BuiltChildrenEvaluator<TOptions extends WithChildRules> = TOptions extends WithChildrenEnforcement ? ChildrenEvaluatorBundle : EmptyRecord;
570
+
571
+ /**
572
+ * Determines whether a prop should be stripped before forwarding to the
573
+ * rendered element.
574
+ *
575
+ * Returning `true` excludes the prop from the output; returning `false`
576
+ * keeps it. This is the inverse polarity of `shouldForwardProp`-style
577
+ * predicates (Emotion/styled-components), where `true` means include.
578
+ *
579
+ * @param key - The prop name being evaluated.
580
+ * @param variantKeys - The set of configured variant prop names.
581
+ * @returns `true` to strip the prop; `false` to forward it.
582
+ */
583
+ type FilterPredicate = (key: string, variantKeys: ReadonlySet<string>) => boolean;
584
+
422
585
  declare class SlotValidator extends InvariantBase {
423
586
  #private;
424
587
  constructor(name: string, diagnostics: Diagnostics, elementTerm: string);
@@ -427,21 +590,11 @@ declare class SlotValidator extends InvariantBase {
427
590
  assertSingleChild(count: number): void;
428
591
  }
429
592
 
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
593
  declare function defineContractComponent<O extends FactoryOptions>(options: O): <R>(factory: (options: O) => R) => R;
441
594
 
442
595
  type UnknownProps = AnyRecord;
443
596
 
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> & {
597
+ 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
598
  /**
446
599
  * Return true for any prop key that should be consumed but not forwarded to the DOM.
447
600
  * Receives `runtime.options.variantKeys` as a convenience if needed.
@@ -459,8 +612,42 @@ type BuiltRuntime<G extends PolymorphicGenerics = PolymorphicGenerics, TOptions
459
612
  onElement?: OnElementFn<PropsOf<G>>;
460
613
  };
461
614
 
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 & {
615
+ /**
616
+ * Creates a praxis-kit contract bundle for use with Svelte's `<Polymorphic>` component.
617
+ *
618
+ * Unlike the other adapters, this returns a plain bundle object rather than a component —
619
+ * Svelte components must come from `.svelte` files, a compile-time constraint — so the bundle
620
+ * is passed as the `bundle` prop:
621
+ *
622
+ * ```ts
623
+ * // button.ts
624
+ * export const buttonBundle = createContractComponent({
625
+ * tag: 'button',
626
+ * name: 'Button',
627
+ * styling: {
628
+ * base: 'btn',
629
+ * variants: { intent: { primary: 'btn--primary', ghost: 'btn--ghost' } },
630
+ * defaults: { intent: 'primary' },
631
+ * },
632
+ * })
633
+ * ```
634
+ *
635
+ * ```svelte
636
+ * <!-- Button.svelte -->
637
+ * <script lang="ts">
638
+ * import Polymorphic from 'praxis-kit/svelte/Polymorphic.svelte'
639
+ * import { buttonBundle } from './button'
640
+ * </script>
641
+ * <Polymorphic bundle={buttonBundle} intent="ghost" as="a" href="/home">Home</Polymorphic>
642
+ * ```
643
+ *
644
+ * Pass `subComponents` to attach named sub-components (`Card.Header`) — `Object.assign` works
645
+ * the same way on a plain bundle as on a component function/class, so `Card.Header` is itself
646
+ * just another bundle, passed to its own `<Polymorphic bundle={Card.Header}>`. Pass `onElement`
647
+ * to run setup once the real DOM element exists.
648
+ */
649
+ 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
650
  readonly subComponents?: TSubComponents;
464
- }): BuiltRuntime<PolymorphicGenerics<TDefault, Props & ExtractPluginProps<TPlugin>, Variants, TPreset>, TOptions> & TSubComponents;
651
+ }): MergeRecords<BuiltRuntime<PolymorphicGenerics<TDefault, MergeRecords<Props, ExtractPluginProps<TPlugin>>, Variants, TPreset>, TOptions>, TSubComponents>;
465
652
 
466
653
  export { type AnyFactoryOptions, type BuiltRuntime, type ElementType, type EmptyRecord, type FilterPredicate, type PolymorphicGenerics, type SvelteFactoryOptions, type UnknownProps, type WithChildRules, createContractComponent, defineContractComponent };
@@ -17,8 +17,20 @@ import { RequireAtLeastOne, Simplify, ValueOf } from 'type-fest';
17
17
  */
18
18
  declare const layoutKeys: readonly ["flex", "inline-flex", "grid", "inline-grid", "block", "inline-block", "inline", "hidden", "contents", "flow-root", "list-item", "table", "inline-table", "table-caption", "table-cell", "table-column", "table-column-group", "table-footer-group", "table-header-group", "table-row-group", "table-row"];
19
19
 
20
+ /**
21
+ * A string-keyed object whose values are of type `T`.
22
+ */
20
23
  type StringMap<T = unknown> = Record<string, T>;
24
+ /**
25
+ * A string-keyed object with values of unknown type.
26
+ */
21
27
  type AnyRecord = StringMap<unknown>;
28
+ /**
29
+ * An object type with no named properties.
30
+ *
31
+ * Unlike `{}`, this excludes arbitrary properties during type operations while
32
+ * still satisfying `extends object`.
33
+ */
22
34
  type EmptyRecord = Record<never, never>;
23
35
 
24
36
  type IntrinsicTag = keyof HTMLElementTagNameMap;
@@ -2,7 +2,13 @@ import { Plugin } from 'vite';
2
2
  import { Except, Simplify } from 'type-fest';
3
3
  import ts from 'typescript';
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>;
7
13
 
8
14
  declare enum DiagnosticCategory {
@@ -3,11 +3,72 @@ import { Diagnostics, DiagnosticInput, DiagnosticsMode } from '../_shared/diagno
3
3
  import * as vue from 'vue';
4
4
  import { AllowedComponentProps } from 'vue';
5
5
 
6
+ /**
7
+ * A string-keyed object whose values are of type `T`.
8
+ */
6
9
  type StringMap<T = unknown> = Record<string, T>;
10
+ /**
11
+ * A string-keyed object with values of unknown type.
12
+ */
7
13
  type AnyRecord = StringMap<unknown>;
14
+ /**
15
+ * An object type with no named properties.
16
+ *
17
+ * Unlike `{}`, this excludes arbitrary properties during type operations while
18
+ * still satisfying `extends object`.
19
+ */
8
20
  type EmptyRecord = Record<never, never>;
9
- /** A compound component's named sub-components, e.g. `{ Header, Content, Footer }`. */
21
+ /**
22
+ * A compound component's named sub-components, for example
23
+ * `{ Header, Content, Footer }`.
24
+ */
10
25
  type SubComponentMap = Readonly<AnyRecord>;
26
+ /**
27
+ * Default `Variants` type for components that declare no variants.
28
+ *
29
+ * Structurally identical to `Readonly<EmptyRecord>`, but named separately so
30
+ * editor hovers remain self-descriptive.
31
+ */
32
+ type NoVariants = Readonly<EmptyRecord>;
33
+ /**
34
+ * Default `TPreset` type for components that declare no named presets.
35
+ *
36
+ * Structurally identical to `Readonly<EmptyRecord>`, but named separately so
37
+ * editor hovers remain self-descriptive.
38
+ */
39
+ type NoPreset = Readonly<EmptyRecord>;
40
+ /**
41
+ * Fallback for `ExtractPluginProps<TPlugin>` when a plugin contributes no
42
+ * props, including the no-plugin case.
43
+ *
44
+ * Structurally identical to `EmptyRecord`, but named separately so editor
45
+ * hovers remain self-descriptive.
46
+ */
47
+ type NoPluginProps = EmptyRecord;
48
+ /**
49
+ * Determines whether an object type should be treated as empty.
50
+ *
51
+ * `keyof T` ignores call and construct signatures...
52
+ */
53
+ type IsEmptyRecord<T extends object> = T extends (...args: never[]) => unknown ? false : T extends new (...args: never[]) => unknown ? false : keyof T extends never ? true : false;
54
+ /**
55
+ * Merges two object types while eliding empty operands.
56
+ *
57
+ * If either operand is {@link EmptyRecord}, the other operand is returned
58
+ * directly instead of producing intersections such as
59
+ * `Component & EmptyRecord` in editor hovers.
60
+ *
61
+ * Unlike a homomorphic mapped type (for example `Simplify<T>`), this preserves
62
+ * call and construct signatures. Many component types are callable objects,
63
+ * and mapped types silently discard those signatures.
64
+ *
65
+ * @remarks
66
+ * Instantiate `MergeRecords` directly. Introducing an intermediate alias for
67
+ * one operand (for example `type C = PolymorphicComponent<G>`) can prevent
68
+ * `IsEmptyRecord` from evaluating eagerly, which breaks assignability under
69
+ * `exactOptionalPropertyTypes`.
70
+ */
71
+ type MergeRecords<A extends object, B extends object> = IsEmptyRecord<A> extends true ? B : IsEmptyRecord<B> extends true ? A : A & B;
11
72
 
12
73
  type IntrinsicTag = keyof HTMLElementTagNameMap;
13
74
 
@@ -190,7 +251,7 @@ type ClassPluginFactory<TProps extends AnyRecord = EmptyRecord> = <V extends Var
190
251
  * wherever a factory's concrete plugin-props shape isn't tracked (factory generics,
191
252
  * capability wiring). */
192
253
  type AnyClassPluginFactory = ClassPluginFactory<AnyRecord> | undefined;
193
- 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;
194
255
 
195
256
  type AriaContext = {
196
257
  readonly tag: IntrinsicTag;
@@ -254,6 +315,12 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
254
315
  * `@praxis-kit/diagnostics`.
255
316
  */
256
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
+ */
257
324
  readonly aria?: readonly AriaRule[];
258
325
  /**
259
326
  * Rules that need `AriaPolicyEngine`'s fix-application/caching machinery
@@ -265,6 +332,11 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
265
332
  * misleading `aria` name to get the machinery it needs.
266
333
  */
267
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
+ */
268
340
  readonly children?: readonly ChildRuleInput[];
269
341
  /**
270
342
  * When true, only children matching a `children` rule (or text, per `allowText`)
@@ -277,19 +349,49 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
277
349
  * or any listed rule. Default: true.
278
350
  */
279
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
+ */
280
357
  readonly props?: readonly PropNormalizer[];
281
358
  /** Restricts the `as` prop to this set of tags. Violations route through diagnostics. */
282
359
  readonly allowedAs?: readonly TAllowed[];
283
360
  };
284
361
 
285
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. */
286
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
+ */
287
369
  readonly variants?: V;
370
+ /** Value used for a variant group when the consumer doesn't pass one explicitly. */
288
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
+ */
289
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
+ */
290
382
  readonly presets?: TPreset;
383
+ /** Maps a resolved tag directly to a raw class string, independent of the variant system. */
291
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
+ */
292
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
+ */
293
395
  readonly precomputedClasses?: Readonly<Record<string, string>>;
294
396
  };
295
397
 
@@ -298,11 +400,21 @@ type NormalizeFn<Props extends AnyRecord = AnyRecord> = {
298
400
  }['normalize'];
299
401
  type AnyFactoryOptions = FactoryOptions<ElementType, AnyRecord, VariantMap, RecipeMap<VariantMap>, AnyClassPluginFactory>;
300
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`. */
301
404
  readonly tag?: TDefault;
405
+ /** Display name used in diagnostics, dev tools, and generated component naming. */
302
406
  readonly name?: string;
407
+ /** Values used for the component's own (non-variant) props when the consumer omits them. */
303
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
+ */
304
414
  readonly normalize?: NormalizeFn<NoInfer<Props>>;
415
+ /** Variant groups, base classes, presets, and the optional class-resolution plugin. */
305
416
  readonly styling?: StylingOptions<V, TPreset, TPlugin>;
417
+ /** ARIA rules, child-content contracts, and other runtime validation for this component. */
306
418
  readonly enforcement?: EnforcementOptions<TAllowed>;
307
419
  /**
308
420
  * Adapter-resolved diagnostics default, spread in by `resolveAdapterCommonOptions`. Not meant to
@@ -350,7 +462,7 @@ declare const Slottable: vue.DefineComponent<{}, () => vue.VNode<vue.RendererNod
350
462
 
351
463
  type UnknownProps = AnyRecord;
352
464
 
353
- type VueFactoryOptions<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> & {
465
+ type VueFactoryOptions<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> & {
354
466
  /**
355
467
  * Return true for any prop key that should be consumed but not forwarded to
356
468
  * the DOM. Variant keys are always stripped automatically.
@@ -396,8 +508,31 @@ type PolymorphicComponent<G extends PolymorphicGenerics> = {
396
508
  displayName?: string;
397
509
  };
398
510
 
399
- 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: VueFactoryOptions<TDefault, Props, Variants, TPreset, TPlugin> & {
511
+ /**
512
+ * Creates a polymorphic Vue component with praxis-kit contracts applied.
513
+ *
514
+ * ```ts
515
+ * const Button = createContractComponent({
516
+ * tag: 'button',
517
+ * name: 'Button',
518
+ * styling: {
519
+ * base: 'btn',
520
+ * variants: { intent: { primary: 'btn--primary', ghost: 'btn--ghost' } },
521
+ * defaults: { intent: 'primary' },
522
+ * },
523
+ * })
524
+ * ```
525
+ *
526
+ * ```vue
527
+ * <Button intent="ghost" as="a" href="/home">Home</Button>
528
+ * ```
529
+ *
530
+ * Pass `subComponents` to attach named sub-components (`Card.Header`) and `onElement` to run
531
+ * setup once the real DOM element exists — both purely additive on top of the generated
532
+ * component.
533
+ */
534
+ 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: VueFactoryOptions<TDefault, Props, Variants, TPreset, TPlugin> & {
400
535
  readonly subComponents?: TSubComponents;
401
- }): PolymorphicComponent<PolymorphicGenerics<TDefault, Props & ExtractPluginProps<TPlugin>, Variants, TPreset>> & TSubComponents;
536
+ }): MergeRecords<PolymorphicComponent<PolymorphicGenerics<TDefault, MergeRecords<Props, ExtractPluginProps<TPlugin>>, Variants, TPreset>>, TSubComponents>;
402
537
 
403
538
  export { type AnyFactoryOptions, type ElementType, type EmptyRecord, type PolymorphicComponent, type PolymorphicGenerics, type PolymorphicProps, type PolymorphicWithAsChild, Slottable, type SlottableProps, type VueFactoryOptions, createContractComponent, defineContractComponent };