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.
@@ -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,15 +1,43 @@
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;
11
26
 
12
27
  type ElementType = IntrinsicTag | (string & {});
28
+ /**
29
+ * Resolves a component's default tag to its real DOM interface — `HTMLDialogElement` for
30
+ * `'dialog'`, `HTMLDetailsElement` for `'details'`, and so on — falling back to `HTMLElement`
31
+ * for custom-element tags or anything not in `HTMLElementTagNameMap`. Used to type
32
+ * `FactoryOptions.onElement`'s `element` param so component authors get direct, correctly-typed
33
+ * access to tag-specific native members (`dialogEl.showModal()`) without an unsafe cast.
34
+ *
35
+ * The fallback is `HTMLElement`, not the more generic `Element` — every tag reachable through
36
+ * `IntrinsicTag` extends it, and so does every custom element per spec, so members `HTMLElement`
37
+ * itself declares (`showPopover()`/`hidePopover()`/`togglePopover()`, the `popover` attribute)
38
+ * stay directly accessible even for tags with no dedicated entry in `HTMLElementTagNameMap`.
39
+ */
40
+ type ElementForTag<TDefault extends ElementType> = TDefault extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[TDefault] : HTMLElement;
13
41
 
14
42
  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
43
  type KnownAriaRole = (typeof KNOWN_ARIA_ROLES)[number];
@@ -235,6 +263,12 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
235
263
  * `@praxis-kit/diagnostics`.
236
264
  */
237
265
  readonly diagnostics?: Diagnostics$1 | DiagnosticsMode;
266
+ /**
267
+ * ARIA/accessibility rules evaluated against the resolved tag and props on every render.
268
+ * Each rule is a function receiving the current context and returning zero or more
269
+ * violations, some of which can carry an auto-applicable fix (see `createRemoveAttributeRule`
270
+ * and friends in `praxis-kit/contract`).
271
+ */
238
272
  readonly aria?: readonly AriaRule[];
239
273
  /**
240
274
  * Rules that need `AriaPolicyEngine`'s fix-application/caching machinery
@@ -246,6 +280,11 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
246
280
  * misleading `aria` name to get the machinery it needs.
247
281
  */
248
282
  readonly rules?: readonly AriaRule[];
283
+ /**
284
+ * Declares which children are valid, by name, match predicate, and cardinality (e.g. "at
285
+ * least 1, at most 4 `Button` children"). Open by default — children matching no rule are
286
+ * still allowed unless `exclusiveChildren` is set.
287
+ */
249
288
  readonly children?: readonly ChildRuleInput[];
250
289
  /**
251
290
  * When true, only children matching a `children` rule (or text, per `allowText`)
@@ -258,19 +297,49 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
258
297
  * or any listed rule. Default: true.
259
298
  */
260
299
  readonly allowText?: boolean;
300
+ /**
301
+ * Prop transforms composed with the component's own `normalize` (from `FactoryOptions`) and
302
+ * run before it. Unlike `normalize`, these live in the enforcement bucket because they
303
+ * typically encode a built-in HTML/ARIA fact rather than component-specific behavior.
304
+ */
261
305
  readonly props?: readonly PropNormalizer[];
262
306
  /** Restricts the `as` prop to this set of tags. Violations route through diagnostics. */
263
307
  readonly allowedAs?: readonly TAllowed[];
264
308
  };
265
309
 
266
310
  type StylingOptions<V extends Readonly<VariantMap> = Readonly<EmptyRecord>, TPreset extends RecipeMap<V> = Readonly<EmptyRecord>, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory> = {
311
+ /** Class applied to every instance regardless of variant selection. */
267
312
  readonly base?: ClassName;
313
+ /**
314
+ * Named variant groups (e.g. `intent`, `size`), each mapping its possible values to a
315
+ * class string. A consumer selects a value per group as a prop (`<Button intent="primary">`).
316
+ */
268
317
  readonly variants?: V;
318
+ /** Value used for a variant group when the consumer doesn't pass one explicitly. */
269
319
  readonly defaults?: Partial<DefaultVariants<V>>;
320
+ /**
321
+ * Applies an extra class only when a specific *combination* of variant selections matches —
322
+ * for cases `variants` alone can't express (e.g. `intent: 'primary'` + `size: 'lg'` together
323
+ * need a class neither variant would add on its own).
324
+ */
270
325
  readonly compounds?: readonly CompoundVariant<V>[];
326
+ /**
327
+ * Named bundles of variant values, selectable as a single unit via the `recipe` prop (e.g.
328
+ * `<Button recipe="cta">` instead of setting `intent`/`size` individually).
329
+ */
271
330
  readonly presets?: TPreset;
331
+ /** Maps a resolved tag directly to a raw class string, independent of the variant system. */
272
332
  readonly tags?: Readonly<TagMap>;
333
+ /**
334
+ * A `ClassPluginFactory` (e.g. the Tailwind layout pipeline) that extends class resolution
335
+ * with its own owned props, layered on top of `variants`/`presets`/`tags`.
336
+ */
273
337
  readonly plugin?: TPlugin;
338
+ /**
339
+ * A cache-key → resolved-class-string lookup for every statically-known variant
340
+ * combination, skipping runtime class computation entirely when a match is found. Normally
341
+ * generated by a build-time class-extraction plugin rather than hand-authored.
342
+ */
274
343
  readonly precomputedClasses?: Readonly<Record<string, string>>;
275
344
  };
276
345
 
@@ -279,11 +348,21 @@ type NormalizeFn<Props extends AnyRecord = AnyRecord> = {
279
348
  }['normalize'];
280
349
  type AnyFactoryOptions = FactoryOptions<ElementType, AnyRecord, VariantMap, RecipeMap<VariantMap>, AnyClassPluginFactory>;
281
350
  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> = {
351
+ /** The intrinsic tag the component renders by default. Overridable per instance via `as`. */
282
352
  readonly tag?: TDefault;
353
+ /** Display name used in diagnostics, dev tools, and generated component naming. */
283
354
  readonly name?: string;
355
+ /** Values used for the component's own (non-variant) props when the consumer omits them. */
284
356
  readonly defaults?: Partial<NoInfer<Props>>;
357
+ /**
358
+ * A pure `(props) => props` transform run on every render, after `enforcement.props`'s
359
+ * normalizers see the same input. Use this for component-specific prop shaping — anything
360
+ * that depends on live instance state or the real DOM element belongs in `onElement` instead.
361
+ */
285
362
  readonly normalize?: NormalizeFn<NoInfer<Props>>;
363
+ /** Variant groups, base classes, presets, and the optional class-resolution plugin. */
286
364
  readonly styling?: StylingOptions<V, TPreset, TPlugin>;
365
+ /** ARIA rules, child-content contracts, and other runtime validation for this component. */
287
366
  readonly enforcement?: EnforcementOptions<TAllowed>;
288
367
  /**
289
368
  * Adapter-resolved diagnostics default, spread in by `resolveAdapterCommonOptions`. Not meant to
@@ -307,13 +386,23 @@ type FactoryOptions<TDefault extends ElementType = ElementType, Props extends An
307
386
  * no prop-based equivalent), not for anything expressible as a plain
308
387
  * prop.
309
388
  *
389
+ * `element` is typed to the real DOM interface of every tag the rendered
390
+ * element could actually be — `TDefault` plus whatever `enforcement.allowed`
391
+ * permits via `as` (`HTMLDialogElement` for `tag: 'dialog'`,
392
+ * `HTMLDetailsElement` for `tag: 'details'`, and so on) — no cast needed to
393
+ * reach tag-specific members. A component that leaves `allowed`
394
+ * unconstrained (any tag reachable via `as`) falls back to `HTMLElement`,
395
+ * which still covers members every element shares (`showPopover()` and
396
+ * friends); restrict `enforcement.allowed` to the tags `onElement`
397
+ * actually knows how to handle to get real narrowing.
398
+ *
310
399
  * `getProps` returns the instance's *current* resolved props at call
311
400
  * time — read it from inside a listener registered once at mount, rather
312
401
  * than re-subscribing on every prop change.
313
402
  *
314
403
  * Return a cleanup function to run when the instance unmounts.
315
404
  */
316
- readonly onElement?: (element: Element, getProps: () => Readonly<Props>) => void | (() => void);
405
+ readonly onElement?: (element: ElementForTag<TDefault | TAllowed>, getProps: () => Readonly<Props>) => void | (() => void);
317
406
  };
318
407
 
319
408
  /** Shared input shape for `invalidWithFix`/`invalidWithoutFix`. */
@@ -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,15 +2,89 @@ 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
 
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];
@@ -172,7 +246,7 @@ type ClassPluginFactory<TProps extends AnyRecord = EmptyRecord> = <V extends Var
172
246
  * wherever a factory's concrete plugin-props shape isn't tracked (factory generics,
173
247
  * capability wiring). */
174
248
  type AnyClassPluginFactory = ClassPluginFactory<AnyRecord> | undefined;
175
- type ExtractPluginProps<TPlugin extends AnyClassPluginFactory> = TPlugin extends ClassPluginFactory<infer T> ? string extends keyof T ? EmptyRecord : T : EmptyRecord;
249
+ type ExtractPluginProps<TPlugin extends AnyClassPluginFactory> = TPlugin extends ClassPluginFactory<infer T> ? string extends keyof T ? NoPluginProps : T : NoPluginProps;
176
250
 
177
251
  type AriaContext = {
178
252
  readonly tag: IntrinsicTag;
@@ -236,6 +310,12 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
236
310
  * `@praxis-kit/diagnostics`.
237
311
  */
238
312
  readonly diagnostics?: Diagnostics | DiagnosticsMode;
313
+ /**
314
+ * ARIA/accessibility rules evaluated against the resolved tag and props on every render.
315
+ * Each rule is a function receiving the current context and returning zero or more
316
+ * violations, some of which can carry an auto-applicable fix (see `createRemoveAttributeRule`
317
+ * and friends in `praxis-kit/contract`).
318
+ */
239
319
  readonly aria?: readonly AriaRule[];
240
320
  /**
241
321
  * Rules that need `AriaPolicyEngine`'s fix-application/caching machinery
@@ -247,6 +327,11 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
247
327
  * misleading `aria` name to get the machinery it needs.
248
328
  */
249
329
  readonly rules?: readonly AriaRule[];
330
+ /**
331
+ * Declares which children are valid, by name, match predicate, and cardinality (e.g. "at
332
+ * least 1, at most 4 `Button` children"). Open by default — children matching no rule are
333
+ * still allowed unless `exclusiveChildren` is set.
334
+ */
250
335
  readonly children?: readonly ChildRuleInput[];
251
336
  /**
252
337
  * When true, only children matching a `children` rule (or text, per `allowText`)
@@ -259,19 +344,49 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
259
344
  * or any listed rule. Default: true.
260
345
  */
261
346
  readonly allowText?: boolean;
347
+ /**
348
+ * Prop transforms composed with the component's own `normalize` (from `FactoryOptions`) and
349
+ * run before it. Unlike `normalize`, these live in the enforcement bucket because they
350
+ * typically encode a built-in HTML/ARIA fact rather than component-specific behavior.
351
+ */
262
352
  readonly props?: readonly PropNormalizer[];
263
353
  /** Restricts the `as` prop to this set of tags. Violations route through diagnostics. */
264
354
  readonly allowedAs?: readonly TAllowed[];
265
355
  };
266
356
 
267
357
  type StylingOptions<V extends Readonly<VariantMap> = Readonly<EmptyRecord>, TPreset extends RecipeMap<V> = Readonly<EmptyRecord>, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory> = {
358
+ /** Class applied to every instance regardless of variant selection. */
268
359
  readonly base?: ClassName;
360
+ /**
361
+ * Named variant groups (e.g. `intent`, `size`), each mapping its possible values to a
362
+ * class string. A consumer selects a value per group as a prop (`<Button intent="primary">`).
363
+ */
269
364
  readonly variants?: V;
365
+ /** Value used for a variant group when the consumer doesn't pass one explicitly. */
270
366
  readonly defaults?: Partial<DefaultVariants<V>>;
367
+ /**
368
+ * Applies an extra class only when a specific *combination* of variant selections matches —
369
+ * for cases `variants` alone can't express (e.g. `intent: 'primary'` + `size: 'lg'` together
370
+ * need a class neither variant would add on its own).
371
+ */
271
372
  readonly compounds?: readonly CompoundVariant<V>[];
373
+ /**
374
+ * Named bundles of variant values, selectable as a single unit via the `recipe` prop (e.g.
375
+ * `<Button recipe="cta">` instead of setting `intent`/`size` individually).
376
+ */
272
377
  readonly presets?: TPreset;
378
+ /** Maps a resolved tag directly to a raw class string, independent of the variant system. */
273
379
  readonly tags?: Readonly<TagMap>;
380
+ /**
381
+ * A `ClassPluginFactory` (e.g. the Tailwind layout pipeline) that extends class resolution
382
+ * with its own owned props, layered on top of `variants`/`presets`/`tags`.
383
+ */
274
384
  readonly plugin?: TPlugin;
385
+ /**
386
+ * A cache-key → resolved-class-string lookup for every statically-known variant
387
+ * combination, skipping runtime class computation entirely when a match is found. Normally
388
+ * generated by a build-time class-extraction plugin rather than hand-authored.
389
+ */
275
390
  readonly precomputedClasses?: Readonly<Record<string, string>>;
276
391
  };
277
392
 
@@ -280,11 +395,21 @@ type NormalizeFn<Props extends AnyRecord = AnyRecord> = {
280
395
  }['normalize'];
281
396
  type AnyFactoryOptions = FactoryOptions<ElementType, AnyRecord, VariantMap, RecipeMap<VariantMap>, AnyClassPluginFactory>;
282
397
  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> = {
398
+ /** The intrinsic tag the component renders by default. Overridable per instance via `as`. */
283
399
  readonly tag?: TDefault;
400
+ /** Display name used in diagnostics, dev tools, and generated component naming. */
284
401
  readonly name?: string;
402
+ /** Values used for the component's own (non-variant) props when the consumer omits them. */
285
403
  readonly defaults?: Partial<NoInfer<Props>>;
404
+ /**
405
+ * A pure `(props) => props` transform run on every render, after `enforcement.props`'s
406
+ * normalizers see the same input. Use this for component-specific prop shaping — anything
407
+ * that depends on live instance state or the real DOM element belongs in `onElement` instead.
408
+ */
286
409
  readonly normalize?: NormalizeFn<NoInfer<Props>>;
410
+ /** Variant groups, base classes, presets, and the optional class-resolution plugin. */
287
411
  readonly styling?: StylingOptions<V, TPreset, TPlugin>;
412
+ /** ARIA rules, child-content contracts, and other runtime validation for this component. */
288
413
  readonly enforcement?: EnforcementOptions<TAllowed>;
289
414
  /**
290
415
  * Adapter-resolved diagnostics default, spread in by `resolveAdapterCommonOptions`. Not meant to
@@ -308,15 +433,37 @@ type FactoryOptions<TDefault extends ElementType = ElementType, Props extends An
308
433
  * no prop-based equivalent), not for anything expressible as a plain
309
434
  * prop.
310
435
  *
436
+ * `element` is typed to the real DOM interface of every tag the rendered
437
+ * element could actually be — `TDefault` plus whatever `enforcement.allowed`
438
+ * permits via `as` (`HTMLDialogElement` for `tag: 'dialog'`,
439
+ * `HTMLDetailsElement` for `tag: 'details'`, and so on) — no cast needed to
440
+ * reach tag-specific members. A component that leaves `allowed`
441
+ * unconstrained (any tag reachable via `as`) falls back to `HTMLElement`,
442
+ * which still covers members every element shares (`showPopover()` and
443
+ * friends); restrict `enforcement.allowed` to the tags `onElement`
444
+ * actually knows how to handle to get real narrowing.
445
+ *
311
446
  * `getProps` returns the instance's *current* resolved props at call
312
447
  * time — read it from inside a listener registered once at mount, rather
313
448
  * than re-subscribing on every prop change.
314
449
  *
315
450
  * Return a cleanup function to run when the instance unmounts.
316
451
  */
317
- readonly onElement?: (element: Element, getProps: () => Readonly<Props>) => void | (() => void);
452
+ readonly onElement?: (element: ElementForTag<TDefault | TAllowed>, getProps: () => Readonly<Props>) => void | (() => void);
318
453
  };
319
454
 
455
+ /**
456
+ * Determines whether a prop should be stripped before forwarding to the
457
+ * rendered element.
458
+ *
459
+ * Returning `true` excludes the prop from the output; returning `false`
460
+ * keeps it. This is the inverse polarity of `shouldForwardProp`-style
461
+ * predicates (Emotion/styled-components), where `true` means include.
462
+ *
463
+ * @param key - The prop name being evaluated.
464
+ * @param variantKeys - The set of configured variant prop names.
465
+ * @returns `true` to strip the prop; `false` to forward it.
466
+ */
320
467
  type FilterPredicate = (key: string, variantKeys: ReadonlySet<string>) => boolean;
321
468
 
322
469
  declare function defineContractComponent<O extends FactoryOptions>(options: O): <R>(factory: (options: O) => R) => R;
@@ -332,7 +479,7 @@ declare function defineContractComponent<O extends FactoryOptions>(options: O):
332
479
  * Note: this adapter targets Light DOM composition only. Shadow DOM slot
333
480
  * protocol is intentionally out of scope.
334
481
  */
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> & {
482
+ 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
483
  readonly filterProps?: FilterPredicate;
337
484
  };
338
485
 
@@ -344,14 +491,14 @@ type UnknownProps = AnyRecord;
344
491
  * (which would trigger TS4094 in declaration emit). Variant key instance
345
492
  * properties are typed via the TVariants parameter.
346
493
  */
347
- type LitContractComponent<TVariants extends Readonly<VariantMap> = Readonly<EmptyRecord>, TPluginProps extends AnyRecord = EmptyRecord> = {
348
- new (): LitElement & {
494
+ type LitContractComponent<TVariants extends Readonly<VariantMap> = NoVariants, TPluginProps extends AnyRecord = EmptyRecord> = {
495
+ new (): MergeRecords<LitElement & {
349
496
  as: string | undefined;
350
497
  recipe: string | undefined;
351
498
  praxisClass: string | undefined;
352
499
  } & {
353
500
  [K in Extract<keyof TVariants, string>]?: string | null;
354
- } & TPluginProps;
501
+ }, TPluginProps>;
355
502
  };
356
503
 
357
504
  /**
@@ -374,9 +521,9 @@ type LitContractComponent<TVariants extends Readonly<VariantMap> = Readonly<Empt
374
521
  * customElements.define('praxis-button', Button)
375
522
  * ```
376
523
  */
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> & {
524
+ 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
525
  readonly subComponents?: TSubComponents;
379
- }): LitContractComponent<TVariants, ExtractPluginProps<TPlugin>> & TSubComponents;
526
+ }): MergeRecords<LitContractComponent<TVariants, ExtractPluginProps<TPlugin>>, TSubComponents>;
380
527
 
381
528
  /**
382
529
  * Renders a praxis-kit Lit component to an HTML string without requiring a DOM.