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.
@@ -98,7 +98,13 @@ declare enum DiagnosticCode {
98
98
  InternalError = "INTERNAL9000"
99
99
  }
100
100
 
101
+ /**
102
+ * A string-keyed object whose values are of type `T`.
103
+ */
101
104
  type StringMap<T = unknown> = Record<string, T>;
105
+ /**
106
+ * A string-keyed object with values of unknown type.
107
+ */
102
108
  type AnyRecord = StringMap<unknown>;
103
109
 
104
110
  declare enum Severity {
@@ -1,10 +1,25 @@
1
1
  import { Diagnostics as Diagnostics$1, DiagnosticInput, DiagnosticsMode } from '../_shared/diagnostics.js';
2
2
  import { RequireAtLeastOne, Simplify, ReadonlyDeep } from 'type-fest';
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>;
9
24
 
10
25
  type IntrinsicTag = keyof HTMLElementTagNameMap;
@@ -235,6 +250,12 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
235
250
  * `@praxis-kit/diagnostics`.
236
251
  */
237
252
  readonly diagnostics?: Diagnostics$1 | DiagnosticsMode;
253
+ /**
254
+ * ARIA/accessibility rules evaluated against the resolved tag and props on every render.
255
+ * Each rule is a function receiving the current context and returning zero or more
256
+ * violations, some of which can carry an auto-applicable fix (see `createRemoveAttributeRule`
257
+ * and friends in `praxis-kit/contract`).
258
+ */
238
259
  readonly aria?: readonly AriaRule[];
239
260
  /**
240
261
  * Rules that need `AriaPolicyEngine`'s fix-application/caching machinery
@@ -246,6 +267,11 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
246
267
  * misleading `aria` name to get the machinery it needs.
247
268
  */
248
269
  readonly rules?: readonly AriaRule[];
270
+ /**
271
+ * Declares which children are valid, by name, match predicate, and cardinality (e.g. "at
272
+ * least 1, at most 4 `Button` children"). Open by default — children matching no rule are
273
+ * still allowed unless `exclusiveChildren` is set.
274
+ */
249
275
  readonly children?: readonly ChildRuleInput[];
250
276
  /**
251
277
  * When true, only children matching a `children` rule (or text, per `allowText`)
@@ -258,19 +284,49 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
258
284
  * or any listed rule. Default: true.
259
285
  */
260
286
  readonly allowText?: boolean;
287
+ /**
288
+ * Prop transforms composed with the component's own `normalize` (from `FactoryOptions`) and
289
+ * run before it. Unlike `normalize`, these live in the enforcement bucket because they
290
+ * typically encode a built-in HTML/ARIA fact rather than component-specific behavior.
291
+ */
261
292
  readonly props?: readonly PropNormalizer[];
262
293
  /** Restricts the `as` prop to this set of tags. Violations route through diagnostics. */
263
294
  readonly allowedAs?: readonly TAllowed[];
264
295
  };
265
296
 
266
297
  type StylingOptions<V extends Readonly<VariantMap> = Readonly<EmptyRecord>, TPreset extends RecipeMap<V> = Readonly<EmptyRecord>, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory> = {
298
+ /** Class applied to every instance regardless of variant selection. */
267
299
  readonly base?: ClassName;
300
+ /**
301
+ * Named variant groups (e.g. `intent`, `size`), each mapping its possible values to a
302
+ * class string. A consumer selects a value per group as a prop (`<Button intent="primary">`).
303
+ */
268
304
  readonly variants?: V;
305
+ /** Value used for a variant group when the consumer doesn't pass one explicitly. */
269
306
  readonly defaults?: Partial<DefaultVariants<V>>;
307
+ /**
308
+ * Applies an extra class only when a specific *combination* of variant selections matches —
309
+ * for cases `variants` alone can't express (e.g. `intent: 'primary'` + `size: 'lg'` together
310
+ * need a class neither variant would add on its own).
311
+ */
270
312
  readonly compounds?: readonly CompoundVariant<V>[];
313
+ /**
314
+ * Named bundles of variant values, selectable as a single unit via the `recipe` prop (e.g.
315
+ * `<Button recipe="cta">` instead of setting `intent`/`size` individually).
316
+ */
271
317
  readonly presets?: TPreset;
318
+ /** Maps a resolved tag directly to a raw class string, independent of the variant system. */
272
319
  readonly tags?: Readonly<TagMap>;
320
+ /**
321
+ * A `ClassPluginFactory` (e.g. the Tailwind layout pipeline) that extends class resolution
322
+ * with its own owned props, layered on top of `variants`/`presets`/`tags`.
323
+ */
273
324
  readonly plugin?: TPlugin;
325
+ /**
326
+ * A cache-key → resolved-class-string lookup for every statically-known variant
327
+ * combination, skipping runtime class computation entirely when a match is found. Normally
328
+ * generated by a build-time class-extraction plugin rather than hand-authored.
329
+ */
274
330
  readonly precomputedClasses?: Readonly<Record<string, string>>;
275
331
  };
276
332
 
@@ -279,11 +335,21 @@ type NormalizeFn<Props extends AnyRecord = AnyRecord> = {
279
335
  }['normalize'];
280
336
  type AnyFactoryOptions = FactoryOptions<ElementType, AnyRecord, VariantMap, RecipeMap<VariantMap>, AnyClassPluginFactory>;
281
337
  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> = {
338
+ /** The intrinsic tag the component renders by default. Overridable per instance via `as`. */
282
339
  readonly tag?: TDefault;
340
+ /** Display name used in diagnostics, dev tools, and generated component naming. */
283
341
  readonly name?: string;
342
+ /** Values used for the component's own (non-variant) props when the consumer omits them. */
284
343
  readonly defaults?: Partial<NoInfer<Props>>;
344
+ /**
345
+ * A pure `(props) => props` transform run on every render, after `enforcement.props`'s
346
+ * normalizers see the same input. Use this for component-specific prop shaping — anything
347
+ * that depends on live instance state or the real DOM element belongs in `onElement` instead.
348
+ */
285
349
  readonly normalize?: NormalizeFn<NoInfer<Props>>;
350
+ /** Variant groups, base classes, presets, and the optional class-resolution plugin. */
286
351
  readonly styling?: StylingOptions<V, TPreset, TPlugin>;
352
+ /** ARIA rules, child-content contracts, and other runtime validation for this component. */
287
353
  readonly enforcement?: EnforcementOptions<TAllowed>;
288
354
  /**
289
355
  * Adapter-resolved diagnostics default, spread in by `resolveAdapterCommonOptions`. Not meant to
@@ -1,4 +1,10 @@
1
+ /**
2
+ * A string-keyed object whose values are of type `T`.
3
+ */
1
4
  type StringMap<T = unknown> = Record<string, T>;
5
+ /**
6
+ * A string-keyed object with values of unknown type.
7
+ */
2
8
  type AnyRecord = StringMap<unknown>;
3
9
 
4
10
  /**
@@ -1,7 +1,13 @@
1
1
  import { ReadonlyDeep } from 'type-fest';
2
2
  import { DiagnosticInput } 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>;
6
12
 
7
13
  type IntrinsicTag = keyof HTMLElementTagNameMap;
@@ -2,11 +2,72 @@ import { RequireAtLeastOne, Simplify, ReadonlyDeep } from 'type-fest';
2
2
  import { Diagnostics, DiagnosticInput, DiagnosticsMode } from '../_shared/diagnostics.js';
3
3
  import { LitElement } from 'lit';
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
 
@@ -172,7 +233,7 @@ type ClassPluginFactory<TProps extends AnyRecord = EmptyRecord> = <V extends Var
172
233
  * wherever a factory's concrete plugin-props shape isn't tracked (factory generics,
173
234
  * capability wiring). */
174
235
  type AnyClassPluginFactory = ClassPluginFactory<AnyRecord> | undefined;
175
- type ExtractPluginProps<TPlugin extends AnyClassPluginFactory> = TPlugin extends ClassPluginFactory<infer T> ? string extends keyof T ? EmptyRecord : T : EmptyRecord;
236
+ type ExtractPluginProps<TPlugin extends AnyClassPluginFactory> = TPlugin extends ClassPluginFactory<infer T> ? string extends keyof T ? NoPluginProps : T : NoPluginProps;
176
237
 
177
238
  type AriaContext = {
178
239
  readonly tag: IntrinsicTag;
@@ -236,6 +297,12 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
236
297
  * `@praxis-kit/diagnostics`.
237
298
  */
238
299
  readonly diagnostics?: Diagnostics | DiagnosticsMode;
300
+ /**
301
+ * ARIA/accessibility rules evaluated against the resolved tag and props on every render.
302
+ * Each rule is a function receiving the current context and returning zero or more
303
+ * violations, some of which can carry an auto-applicable fix (see `createRemoveAttributeRule`
304
+ * and friends in `praxis-kit/contract`).
305
+ */
239
306
  readonly aria?: readonly AriaRule[];
240
307
  /**
241
308
  * Rules that need `AriaPolicyEngine`'s fix-application/caching machinery
@@ -247,6 +314,11 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
247
314
  * misleading `aria` name to get the machinery it needs.
248
315
  */
249
316
  readonly rules?: readonly AriaRule[];
317
+ /**
318
+ * Declares which children are valid, by name, match predicate, and cardinality (e.g. "at
319
+ * least 1, at most 4 `Button` children"). Open by default — children matching no rule are
320
+ * still allowed unless `exclusiveChildren` is set.
321
+ */
250
322
  readonly children?: readonly ChildRuleInput[];
251
323
  /**
252
324
  * When true, only children matching a `children` rule (or text, per `allowText`)
@@ -259,19 +331,49 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
259
331
  * or any listed rule. Default: true.
260
332
  */
261
333
  readonly allowText?: boolean;
334
+ /**
335
+ * Prop transforms composed with the component's own `normalize` (from `FactoryOptions`) and
336
+ * run before it. Unlike `normalize`, these live in the enforcement bucket because they
337
+ * typically encode a built-in HTML/ARIA fact rather than component-specific behavior.
338
+ */
262
339
  readonly props?: readonly PropNormalizer[];
263
340
  /** Restricts the `as` prop to this set of tags. Violations route through diagnostics. */
264
341
  readonly allowedAs?: readonly TAllowed[];
265
342
  };
266
343
 
267
344
  type StylingOptions<V extends Readonly<VariantMap> = Readonly<EmptyRecord>, TPreset extends RecipeMap<V> = Readonly<EmptyRecord>, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory> = {
345
+ /** Class applied to every instance regardless of variant selection. */
268
346
  readonly base?: ClassName;
347
+ /**
348
+ * Named variant groups (e.g. `intent`, `size`), each mapping its possible values to a
349
+ * class string. A consumer selects a value per group as a prop (`<Button intent="primary">`).
350
+ */
269
351
  readonly variants?: V;
352
+ /** Value used for a variant group when the consumer doesn't pass one explicitly. */
270
353
  readonly defaults?: Partial<DefaultVariants<V>>;
354
+ /**
355
+ * Applies an extra class only when a specific *combination* of variant selections matches —
356
+ * for cases `variants` alone can't express (e.g. `intent: 'primary'` + `size: 'lg'` together
357
+ * need a class neither variant would add on its own).
358
+ */
271
359
  readonly compounds?: readonly CompoundVariant<V>[];
360
+ /**
361
+ * Named bundles of variant values, selectable as a single unit via the `recipe` prop (e.g.
362
+ * `<Button recipe="cta">` instead of setting `intent`/`size` individually).
363
+ */
272
364
  readonly presets?: TPreset;
365
+ /** Maps a resolved tag directly to a raw class string, independent of the variant system. */
273
366
  readonly tags?: Readonly<TagMap>;
367
+ /**
368
+ * A `ClassPluginFactory` (e.g. the Tailwind layout pipeline) that extends class resolution
369
+ * with its own owned props, layered on top of `variants`/`presets`/`tags`.
370
+ */
274
371
  readonly plugin?: TPlugin;
372
+ /**
373
+ * A cache-key → resolved-class-string lookup for every statically-known variant
374
+ * combination, skipping runtime class computation entirely when a match is found. Normally
375
+ * generated by a build-time class-extraction plugin rather than hand-authored.
376
+ */
275
377
  readonly precomputedClasses?: Readonly<Record<string, string>>;
276
378
  };
277
379
 
@@ -280,11 +382,21 @@ type NormalizeFn<Props extends AnyRecord = AnyRecord> = {
280
382
  }['normalize'];
281
383
  type AnyFactoryOptions = FactoryOptions<ElementType, AnyRecord, VariantMap, RecipeMap<VariantMap>, AnyClassPluginFactory>;
282
384
  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> = {
385
+ /** The intrinsic tag the component renders by default. Overridable per instance via `as`. */
283
386
  readonly tag?: TDefault;
387
+ /** Display name used in diagnostics, dev tools, and generated component naming. */
284
388
  readonly name?: string;
389
+ /** Values used for the component's own (non-variant) props when the consumer omits them. */
285
390
  readonly defaults?: Partial<NoInfer<Props>>;
391
+ /**
392
+ * A pure `(props) => props` transform run on every render, after `enforcement.props`'s
393
+ * normalizers see the same input. Use this for component-specific prop shaping — anything
394
+ * that depends on live instance state or the real DOM element belongs in `onElement` instead.
395
+ */
286
396
  readonly normalize?: NormalizeFn<NoInfer<Props>>;
397
+ /** Variant groups, base classes, presets, and the optional class-resolution plugin. */
287
398
  readonly styling?: StylingOptions<V, TPreset, TPlugin>;
399
+ /** ARIA rules, child-content contracts, and other runtime validation for this component. */
288
400
  readonly enforcement?: EnforcementOptions<TAllowed>;
289
401
  /**
290
402
  * Adapter-resolved diagnostics default, spread in by `resolveAdapterCommonOptions`. Not meant to
@@ -317,6 +429,18 @@ type FactoryOptions<TDefault extends ElementType = ElementType, Props extends An
317
429
  readonly onElement?: (element: Element, getProps: () => Readonly<Props>) => void | (() => void);
318
430
  };
319
431
 
432
+ /**
433
+ * Determines whether a prop should be stripped before forwarding to the
434
+ * rendered element.
435
+ *
436
+ * Returning `true` excludes the prop from the output; returning `false`
437
+ * keeps it. This is the inverse polarity of `shouldForwardProp`-style
438
+ * predicates (Emotion/styled-components), where `true` means include.
439
+ *
440
+ * @param key - The prop name being evaluated.
441
+ * @param variantKeys - The set of configured variant prop names.
442
+ * @returns `true` to strip the prop; `false` to forward it.
443
+ */
320
444
  type FilterPredicate = (key: string, variantKeys: ReadonlySet<string>) => boolean;
321
445
 
322
446
  declare function defineContractComponent<O extends FactoryOptions>(options: O): <R>(factory: (options: O) => R) => R;
@@ -332,7 +456,7 @@ declare function defineContractComponent<O extends FactoryOptions>(options: O):
332
456
  * Note: this adapter targets Light DOM composition only. Shadow DOM slot
333
457
  * protocol is intentionally out of scope.
334
458
  */
335
- type LitFactoryOptions<TDefault extends ElementType = ElementType, TProps extends AnyRecord = EmptyRecord, TVariants extends Readonly<VariantMap> = Readonly<EmptyRecord>, TPreset extends RecipeMap<TVariants> = Readonly<EmptyRecord>, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory> = FactoryOptions<TDefault, TProps, TVariants, TPreset, TPlugin> & {
459
+ type LitFactoryOptions<TDefault extends ElementType = ElementType, TProps extends AnyRecord = EmptyRecord, TVariants extends Readonly<VariantMap> = NoVariants, TPreset extends RecipeMap<TVariants> = NoPreset, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory> = FactoryOptions<TDefault, TProps, TVariants, TPreset, TPlugin> & {
336
460
  readonly filterProps?: FilterPredicate;
337
461
  };
338
462
 
@@ -344,14 +468,14 @@ type UnknownProps = AnyRecord;
344
468
  * (which would trigger TS4094 in declaration emit). Variant key instance
345
469
  * properties are typed via the TVariants parameter.
346
470
  */
347
- type LitContractComponent<TVariants extends Readonly<VariantMap> = Readonly<EmptyRecord>, TPluginProps extends AnyRecord = EmptyRecord> = {
348
- new (): LitElement & {
471
+ type LitContractComponent<TVariants extends Readonly<VariantMap> = NoVariants, TPluginProps extends AnyRecord = EmptyRecord> = {
472
+ new (): MergeRecords<LitElement & {
349
473
  as: string | undefined;
350
474
  recipe: string | undefined;
351
475
  praxisClass: string | undefined;
352
476
  } & {
353
477
  [K in Extract<keyof TVariants, string>]?: string | null;
354
- } & TPluginProps;
478
+ }, TPluginProps>;
355
479
  };
356
480
 
357
481
  /**
@@ -374,9 +498,9 @@ type LitContractComponent<TVariants extends Readonly<VariantMap> = Readonly<Empt
374
498
  * customElements.define('praxis-button', Button)
375
499
  * ```
376
500
  */
377
- declare function createContractComponent<TDefault extends ElementType, TProps extends UnknownProps = EmptyRecord, TVariants extends Readonly<VariantMap> = Readonly<EmptyRecord>, TPreset extends RecipeMap<TVariants> = Readonly<EmptyRecord>, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory, TSubComponents extends Readonly<AnyRecord> = EmptyRecord>(options: LitFactoryOptions<TDefault, TProps, TVariants, TPreset, TPlugin> & {
501
+ declare function createContractComponent<TDefault extends ElementType, TProps extends UnknownProps = EmptyRecord, TVariants extends Readonly<VariantMap> = NoVariants, TPreset extends RecipeMap<TVariants> = NoPreset, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory, TSubComponents extends Readonly<AnyRecord> = EmptyRecord>(options: LitFactoryOptions<TDefault, TProps, TVariants, TPreset, TPlugin> & {
378
502
  readonly subComponents?: TSubComponents;
379
- }): LitContractComponent<TVariants, ExtractPluginProps<TPlugin>> & TSubComponents;
503
+ }): MergeRecords<LitContractComponent<TVariants, ExtractPluginProps<TPlugin>>, TSubComponents>;
380
504
 
381
505
  /**
382
506
  * Renders a praxis-kit Lit component to an HTML string without requiring a DOM.
@@ -2,11 +2,72 @@ import { RequireAtLeastOne, Simplify, ReadonlyDeep, OmitIndexSignature } from 't
2
2
  import { Diagnostics, DiagnosticInput, DiagnosticsMode } from '../_shared/diagnostics.js';
3
3
  import { ComponentChildren, VNode, ComponentType, JSX, Ref } from 'preact';
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
 
@@ -189,7 +250,7 @@ type ClassPluginFactory<TProps extends AnyRecord = EmptyRecord> = <V extends Var
189
250
  * wherever a factory's concrete plugin-props shape isn't tracked (factory generics,
190
251
  * capability wiring). */
191
252
  type AnyClassPluginFactory = ClassPluginFactory<AnyRecord> | undefined;
192
- 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;
193
254
 
194
255
  type AriaContext = {
195
256
  readonly tag: IntrinsicTag;
@@ -253,6 +314,12 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
253
314
  * `@praxis-kit/diagnostics`.
254
315
  */
255
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
+ */
256
323
  readonly aria?: readonly AriaRule[];
257
324
  /**
258
325
  * Rules that need `AriaPolicyEngine`'s fix-application/caching machinery
@@ -264,6 +331,11 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
264
331
  * misleading `aria` name to get the machinery it needs.
265
332
  */
266
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
+ */
267
339
  readonly children?: readonly ChildRuleInput[];
268
340
  /**
269
341
  * When true, only children matching a `children` rule (or text, per `allowText`)
@@ -276,19 +348,49 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
276
348
  * or any listed rule. Default: true.
277
349
  */
278
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
+ */
279
356
  readonly props?: readonly PropNormalizer[];
280
357
  /** Restricts the `as` prop to this set of tags. Violations route through diagnostics. */
281
358
  readonly allowedAs?: readonly TAllowed[];
282
359
  };
283
360
 
284
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. */
285
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
+ */
286
368
  readonly variants?: V;
369
+ /** Value used for a variant group when the consumer doesn't pass one explicitly. */
287
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
+ */
288
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
+ */
289
381
  readonly presets?: TPreset;
382
+ /** Maps a resolved tag directly to a raw class string, independent of the variant system. */
290
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
+ */
291
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
+ */
292
394
  readonly precomputedClasses?: Readonly<Record<string, string>>;
293
395
  };
294
396
 
@@ -297,11 +399,21 @@ type NormalizeFn<Props extends AnyRecord = AnyRecord> = {
297
399
  }['normalize'];
298
400
  type AnyFactoryOptions = FactoryOptions<ElementType, AnyRecord, VariantMap, RecipeMap<VariantMap>, AnyClassPluginFactory>;
299
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`. */
300
403
  readonly tag?: TDefault;
404
+ /** Display name used in diagnostics, dev tools, and generated component naming. */
301
405
  readonly name?: string;
406
+ /** Values used for the component's own (non-variant) props when the consumer omits them. */
302
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
+ */
303
413
  readonly normalize?: NormalizeFn<NoInfer<Props>>;
414
+ /** Variant groups, base classes, presets, and the optional class-resolution plugin. */
304
415
  readonly styling?: StylingOptions<V, TPreset, TPlugin>;
416
+ /** ARIA rules, child-content contracts, and other runtime validation for this component. */
305
417
  readonly enforcement?: EnforcementOptions<TAllowed>;
306
418
  /**
307
419
  * Adapter-resolved diagnostics default, spread in by `resolveAdapterCommonOptions`. Not meant to
@@ -345,7 +457,7 @@ type UnknownProps = AnyRecord;
345
457
  type SlotComponent = ComponentType<UnknownProps>;
346
458
  type AnyVNode = VNode<any>;
347
459
 
348
- type PreactFactoryOptions<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> & {
460
+ type PreactFactoryOptions<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> & {
349
461
  /** Component used to render the asChild slot. Defaults to the built-in Slot. */
350
462
  slotComponent?: SlotComponent;
351
463
  /**
@@ -388,8 +500,29 @@ type PolymorphicComponent<G extends PolymorphicGenerics> = {
388
500
  displayName?: string;
389
501
  };
390
502
 
391
- 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: PreactFactoryOptions<TDefault, Props, Variants, TPreset, TPlugin> & {
503
+ /**
504
+ * Creates a polymorphic Preact component with praxis-kit contracts applied.
505
+ *
506
+ * ```tsx
507
+ * const Button = createContractComponent({
508
+ * tag: 'button',
509
+ * name: 'Button',
510
+ * styling: {
511
+ * base: 'btn',
512
+ * variants: { intent: { primary: 'btn--primary', ghost: 'btn--ghost' } },
513
+ * defaults: { intent: 'primary' },
514
+ * },
515
+ * })
516
+ *
517
+ * <Button intent="ghost" as="a" href="/home">Home</Button>
518
+ * ```
519
+ *
520
+ * Returns a `forwardRef` component — `ref` is forwarded to the rendered host element. Pass
521
+ * `subComponents` to attach named sub-components (`Card.Header`) and `onElement` to run setup
522
+ * once the real DOM element exists.
523
+ */
524
+ 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: PreactFactoryOptions<TDefault, Props, Variants, TPreset, TPlugin> & {
392
525
  readonly subComponents?: TSubComponents;
393
- }): PolymorphicComponent<PolymorphicGenerics<TDefault, Props & ExtractPluginProps<TPlugin>, Variants, TPreset>> & TSubComponents;
526
+ }): MergeRecords<PolymorphicComponent<PolymorphicGenerics<TDefault, MergeRecords<Props, ExtractPluginProps<TPlugin>>, Variants, TPreset>>, TSubComponents>;
394
527
 
395
528
  export { type AnyFactoryOptions, type ElementRef, type ElementType, type EmptyRecord, type PolymorphicComponent, type PolymorphicGenerics, type PolymorphicProps, type PolymorphicWithAsChild, type PreactFactoryOptions, Slottable, createContractComponent, defineContractComponent };