praxis-kit 7.3.0 → 7.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,15 +2,89 @@ import { RequireAtLeastOne, Simplify, ReadonlyDeep, OmitIndexSignature } from 't
2
2
  import { Diagnostics, DiagnosticInput, DiagnosticsMode } from '../_shared/diagnostics.js';
3
3
  import { 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
 
13
74
  type ElementType = IntrinsicTag | (string & {});
75
+ /**
76
+ * Resolves a component's default tag to its real DOM interface — `HTMLDialogElement` for
77
+ * `'dialog'`, `HTMLDetailsElement` for `'details'`, and so on — falling back to `HTMLElement`
78
+ * for custom-element tags or anything not in `HTMLElementTagNameMap`. Used to type
79
+ * `FactoryOptions.onElement`'s `element` param so component authors get direct, correctly-typed
80
+ * access to tag-specific native members (`dialogEl.showModal()`) without an unsafe cast.
81
+ *
82
+ * The fallback is `HTMLElement`, not the more generic `Element` — every tag reachable through
83
+ * `IntrinsicTag` extends it, and so does every custom element per spec, so members `HTMLElement`
84
+ * itself declares (`showPopover()`/`hidePopover()`/`togglePopover()`, the `popover` attribute)
85
+ * stay directly accessible even for tags with no dedicated entry in `HTMLElementTagNameMap`.
86
+ */
87
+ type ElementForTag<TDefault extends ElementType> = TDefault extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[TDefault] : HTMLElement;
14
88
 
15
89
  declare const KNOWN_ARIA_ROLES: readonly ["alert", "alertdialog", "application", "article", "banner", "blockquote", "button", "caption", "cell", "checkbox", "code", "columnheader", "combobox", "complementary", "contentinfo", "definition", "deletion", "dialog", "document", "emphasis", "feed", "figure", "form", "generic", "grid", "gridcell", "group", "heading", "img", "insertion", "link", "list", "listbox", "listitem", "log", "main", "marquee", "math", "menu", "menubar", "menuitem", "menuitemcheckbox", "menuitemradio", "meter", "navigation", "none", "note", "option", "paragraph", "presentation", "progressbar", "radio", "radiogroup", "region", "row", "rowgroup", "rowheader", "scrollbar", "search", "searchbox", "separator", "slider", "spinbutton", "status", "strong", "subscript", "superscript", "switch", "tab", "table", "tablist", "tabpanel", "term", "textbox", "time", "timer", "toolbar", "tooltip", "tree", "treegrid", "treeitem"];
16
90
  type KnownAriaRole = (typeof KNOWN_ARIA_ROLES)[number];
@@ -189,7 +263,7 @@ type ClassPluginFactory<TProps extends AnyRecord = EmptyRecord> = <V extends Var
189
263
  * wherever a factory's concrete plugin-props shape isn't tracked (factory generics,
190
264
  * capability wiring). */
191
265
  type AnyClassPluginFactory = ClassPluginFactory<AnyRecord> | undefined;
192
- type ExtractPluginProps<TPlugin extends AnyClassPluginFactory> = TPlugin extends ClassPluginFactory<infer T> ? string extends keyof T ? EmptyRecord : T : EmptyRecord;
266
+ type ExtractPluginProps<TPlugin extends AnyClassPluginFactory> = TPlugin extends ClassPluginFactory<infer T> ? string extends keyof T ? NoPluginProps : T : NoPluginProps;
193
267
 
194
268
  type AriaContext = {
195
269
  readonly tag: IntrinsicTag;
@@ -253,6 +327,12 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
253
327
  * `@praxis-kit/diagnostics`.
254
328
  */
255
329
  readonly diagnostics?: Diagnostics | DiagnosticsMode;
330
+ /**
331
+ * ARIA/accessibility rules evaluated against the resolved tag and props on every render.
332
+ * Each rule is a function receiving the current context and returning zero or more
333
+ * violations, some of which can carry an auto-applicable fix (see `createRemoveAttributeRule`
334
+ * and friends in `praxis-kit/contract`).
335
+ */
256
336
  readonly aria?: readonly AriaRule[];
257
337
  /**
258
338
  * Rules that need `AriaPolicyEngine`'s fix-application/caching machinery
@@ -264,6 +344,11 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
264
344
  * misleading `aria` name to get the machinery it needs.
265
345
  */
266
346
  readonly rules?: readonly AriaRule[];
347
+ /**
348
+ * Declares which children are valid, by name, match predicate, and cardinality (e.g. "at
349
+ * least 1, at most 4 `Button` children"). Open by default — children matching no rule are
350
+ * still allowed unless `exclusiveChildren` is set.
351
+ */
267
352
  readonly children?: readonly ChildRuleInput[];
268
353
  /**
269
354
  * When true, only children matching a `children` rule (or text, per `allowText`)
@@ -276,19 +361,49 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
276
361
  * or any listed rule. Default: true.
277
362
  */
278
363
  readonly allowText?: boolean;
364
+ /**
365
+ * Prop transforms composed with the component's own `normalize` (from `FactoryOptions`) and
366
+ * run before it. Unlike `normalize`, these live in the enforcement bucket because they
367
+ * typically encode a built-in HTML/ARIA fact rather than component-specific behavior.
368
+ */
279
369
  readonly props?: readonly PropNormalizer[];
280
370
  /** Restricts the `as` prop to this set of tags. Violations route through diagnostics. */
281
371
  readonly allowedAs?: readonly TAllowed[];
282
372
  };
283
373
 
284
374
  type StylingOptions<V extends Readonly<VariantMap> = Readonly<EmptyRecord>, TPreset extends RecipeMap<V> = Readonly<EmptyRecord>, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory> = {
375
+ /** Class applied to every instance regardless of variant selection. */
285
376
  readonly base?: ClassName;
377
+ /**
378
+ * Named variant groups (e.g. `intent`, `size`), each mapping its possible values to a
379
+ * class string. A consumer selects a value per group as a prop (`<Button intent="primary">`).
380
+ */
286
381
  readonly variants?: V;
382
+ /** Value used for a variant group when the consumer doesn't pass one explicitly. */
287
383
  readonly defaults?: Partial<DefaultVariants<V>>;
384
+ /**
385
+ * Applies an extra class only when a specific *combination* of variant selections matches —
386
+ * for cases `variants` alone can't express (e.g. `intent: 'primary'` + `size: 'lg'` together
387
+ * need a class neither variant would add on its own).
388
+ */
288
389
  readonly compounds?: readonly CompoundVariant<V>[];
390
+ /**
391
+ * Named bundles of variant values, selectable as a single unit via the `recipe` prop (e.g.
392
+ * `<Button recipe="cta">` instead of setting `intent`/`size` individually).
393
+ */
289
394
  readonly presets?: TPreset;
395
+ /** Maps a resolved tag directly to a raw class string, independent of the variant system. */
290
396
  readonly tags?: Readonly<TagMap>;
397
+ /**
398
+ * A `ClassPluginFactory` (e.g. the Tailwind layout pipeline) that extends class resolution
399
+ * with its own owned props, layered on top of `variants`/`presets`/`tags`.
400
+ */
291
401
  readonly plugin?: TPlugin;
402
+ /**
403
+ * A cache-key → resolved-class-string lookup for every statically-known variant
404
+ * combination, skipping runtime class computation entirely when a match is found. Normally
405
+ * generated by a build-time class-extraction plugin rather than hand-authored.
406
+ */
292
407
  readonly precomputedClasses?: Readonly<Record<string, string>>;
293
408
  };
294
409
 
@@ -297,11 +412,21 @@ type NormalizeFn<Props extends AnyRecord = AnyRecord> = {
297
412
  }['normalize'];
298
413
  type AnyFactoryOptions = FactoryOptions<ElementType, AnyRecord, VariantMap, RecipeMap<VariantMap>, AnyClassPluginFactory>;
299
414
  type FactoryOptions<TDefault extends ElementType = ElementType, Props extends AnyRecord = EmptyRecord, V extends Readonly<VariantMap> = Readonly<EmptyRecord>, TPreset extends RecipeMap<V> = Readonly<EmptyRecord>, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory, TAllowed extends ElementType = ElementType> = {
415
+ /** The intrinsic tag the component renders by default. Overridable per instance via `as`. */
300
416
  readonly tag?: TDefault;
417
+ /** Display name used in diagnostics, dev tools, and generated component naming. */
301
418
  readonly name?: string;
419
+ /** Values used for the component's own (non-variant) props when the consumer omits them. */
302
420
  readonly defaults?: Partial<NoInfer<Props>>;
421
+ /**
422
+ * A pure `(props) => props` transform run on every render, after `enforcement.props`'s
423
+ * normalizers see the same input. Use this for component-specific prop shaping — anything
424
+ * that depends on live instance state or the real DOM element belongs in `onElement` instead.
425
+ */
303
426
  readonly normalize?: NormalizeFn<NoInfer<Props>>;
427
+ /** Variant groups, base classes, presets, and the optional class-resolution plugin. */
304
428
  readonly styling?: StylingOptions<V, TPreset, TPlugin>;
429
+ /** ARIA rules, child-content contracts, and other runtime validation for this component. */
305
430
  readonly enforcement?: EnforcementOptions<TAllowed>;
306
431
  /**
307
432
  * Adapter-resolved diagnostics default, spread in by `resolveAdapterCommonOptions`. Not meant to
@@ -325,13 +450,23 @@ type FactoryOptions<TDefault extends ElementType = ElementType, Props extends An
325
450
  * no prop-based equivalent), not for anything expressible as a plain
326
451
  * prop.
327
452
  *
453
+ * `element` is typed to the real DOM interface of every tag the rendered
454
+ * element could actually be — `TDefault` plus whatever `enforcement.allowed`
455
+ * permits via `as` (`HTMLDialogElement` for `tag: 'dialog'`,
456
+ * `HTMLDetailsElement` for `tag: 'details'`, and so on) — no cast needed to
457
+ * reach tag-specific members. A component that leaves `allowed`
458
+ * unconstrained (any tag reachable via `as`) falls back to `HTMLElement`,
459
+ * which still covers members every element shares (`showPopover()` and
460
+ * friends); restrict `enforcement.allowed` to the tags `onElement`
461
+ * actually knows how to handle to get real narrowing.
462
+ *
328
463
  * `getProps` returns the instance's *current* resolved props at call
329
464
  * time — read it from inside a listener registered once at mount, rather
330
465
  * than re-subscribing on every prop change.
331
466
  *
332
467
  * Return a cleanup function to run when the instance unmounts.
333
468
  */
334
- readonly onElement?: (element: Element, getProps: () => Readonly<Props>) => void | (() => void);
469
+ readonly onElement?: (element: ElementForTag<TDefault | TAllowed>, getProps: () => Readonly<Props>) => void | (() => void);
335
470
  };
336
471
 
337
472
  declare function defineContractComponent<O extends FactoryOptions>(options: O): <R>(factory: (options: O) => R) => R;
@@ -345,7 +480,7 @@ type UnknownProps = AnyRecord;
345
480
  type SlotComponent = ComponentType<UnknownProps>;
346
481
  type AnyVNode = VNode<any>;
347
482
 
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> & {
483
+ 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
484
  /** Component used to render the asChild slot. Defaults to the built-in Slot. */
350
485
  slotComponent?: SlotComponent;
351
486
  /**
@@ -388,8 +523,29 @@ type PolymorphicComponent<G extends PolymorphicGenerics> = {
388
523
  displayName?: string;
389
524
  };
390
525
 
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> & {
526
+ /**
527
+ * Creates a polymorphic Preact component with praxis-kit contracts applied.
528
+ *
529
+ * ```tsx
530
+ * const Button = createContractComponent({
531
+ * tag: 'button',
532
+ * name: 'Button',
533
+ * styling: {
534
+ * base: 'btn',
535
+ * variants: { intent: { primary: 'btn--primary', ghost: 'btn--ghost' } },
536
+ * defaults: { intent: 'primary' },
537
+ * },
538
+ * })
539
+ *
540
+ * <Button intent="ghost" as="a" href="/home">Home</Button>
541
+ * ```
542
+ *
543
+ * Returns a `forwardRef` component — `ref` is forwarded to the rendered host element. Pass
544
+ * `subComponents` to attach named sub-components (`Card.Header`) and `onElement` to run setup
545
+ * once the real DOM element exists.
546
+ */
547
+ 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
548
  readonly subComponents?: TSubComponents;
393
- }): PolymorphicComponent<PolymorphicGenerics<TDefault, Props & ExtractPluginProps<TPlugin>, Variants, TPreset>> & TSubComponents;
549
+ }): MergeRecords<PolymorphicComponent<PolymorphicGenerics<TDefault, MergeRecords<Props, ExtractPluginProps<TPlugin>>, Variants, TPreset>>, TSubComponents>;
394
550
 
395
551
  export { type AnyFactoryOptions, type ElementRef, type ElementType, type EmptyRecord, type PolymorphicComponent, type PolymorphicGenerics, type PolymorphicProps, type PolymorphicWithAsChild, type PreactFactoryOptions, Slottable, createContractComponent, defineContractComponent };
@@ -3946,7 +3946,11 @@ function createContractComponent(options) {
3946
3946
  const onElementRef = useCallback((el) => {
3947
3947
  if (!onElement) return;
3948
3948
  if (el) {
3949
- cleanupRef.current = onElement(el, () => propsRef.current) ?? void 0;
3949
+ cleanupRef.current?.();
3950
+ cleanupRef.current = onElement(
3951
+ el,
3952
+ () => propsRef.current
3953
+ ) ?? void 0;
3950
3954
  } else {
3951
3955
  cleanupRef.current?.();
3952
3956
  cleanupRef.current = void 0;
@@ -1,5 +1,5 @@
1
- import { U as UnknownProps, E as ElementType, a as EmptyRecord, V as VariantMap, R as RecipeMap, A as AnyClassPluginFactory, b as AnyRecord, c as ReactFactoryOptions, P as PolymorphicComponent, d as PolymorphicGenerics, e as ExtractPluginProps } from '../react-options-DLDsA4Tn.js';
2
- export { f as AnyFactoryOptions, g as ElementRef, F as FactoryOptions, h as PolymorphicProps, i as PolymorphicWithAsChild, j as PolymorphicWithRender, k as RenderCallbackProps, S as Slottable, l as SlottableProps, m as composeRefs, n as defineContractComponent, m as mergeRefs } from '../react-options-DLDsA4Tn.js';
1
+ import { U as UnknownProps, E as ElementType, a as EmptyRecord, V as VariantMap, N as NoVariants, R as RecipeMap, b as NoPreset, A as AnyClassPluginFactory, c as AnyRecord, d as ReactFactoryOptions, M as MergeRecords, P as PolymorphicComponent, e as PolymorphicGenerics, f as ExtractPluginProps } from '../react-options-5GbZ8Tbv.js';
2
+ export { g as AnyFactoryOptions, h as ElementRef, F as FactoryOptions, i as PolymorphicProps, j as PolymorphicWithAsChild, k as PolymorphicWithRender, l as RenderCallbackProps, S as Slottable, m as SlottableProps, n as composeRefs, o as defineContractComponent, n as mergeRefs } from '../react-options-5GbZ8Tbv.js';
3
3
  import * as react from 'react';
4
4
  import { ReactElement, Ref } from 'react';
5
5
  import 'type-fest';
@@ -22,9 +22,30 @@ type CloneInput = {
22
22
  ref: NormalizedRef;
23
23
  };
24
24
 
25
- 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, TAllowed extends ElementType = ElementType, TSubComponents extends Readonly<AnyRecord> = EmptyRecord>(options: ReactFactoryOptions<TDefault, Props, Variants, TPreset, TPlugin, TAllowed> & {
25
+ /**
26
+ * Creates a polymorphic React 19 component with praxis-kit contracts applied.
27
+ *
28
+ * ```tsx
29
+ * const Button = createContractComponent({
30
+ * tag: 'button',
31
+ * name: 'Button',
32
+ * styling: {
33
+ * base: 'btn',
34
+ * variants: { intent: { primary: 'btn--primary', ghost: 'btn--ghost' } },
35
+ * defaults: { intent: 'primary' },
36
+ * },
37
+ * })
38
+ *
39
+ * <Button intent="ghost" as="a" href="/home">Home</Button>
40
+ * ```
41
+ *
42
+ * `ref` is accepted as a plain prop (React 19) and forwarded to the rendered host element or,
43
+ * with `asChild`, to the consumer's own element. Pass `subComponents` to attach named
44
+ * sub-components (`Card.Header`) and `onElement` to run setup once the real DOM element exists.
45
+ */
46
+ declare function createContractComponent<TDefault extends ElementType, Props extends UnknownProps = EmptyRecord, Variants extends Readonly<VariantMap> = NoVariants, TPreset extends RecipeMap<Variants> = NoPreset, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory, TAllowed extends ElementType = ElementType, TSubComponents extends Readonly<AnyRecord> = EmptyRecord>(options: ReactFactoryOptions<TDefault, Props, Variants, TPreset, TPlugin, TAllowed> & {
26
47
  readonly subComponents?: TSubComponents;
27
- }): PolymorphicComponent<PolymorphicGenerics<TDefault, Props & ExtractPluginProps<TPlugin>, Variants, TPreset, TAllowed>> & TSubComponents;
48
+ }): MergeRecords<PolymorphicComponent<PolymorphicGenerics<TDefault, MergeRecords<Props, ExtractPluginProps<TPlugin>>, Variants, TPreset, TAllowed>>, TSubComponents>;
28
49
 
29
50
  type SlotProps = {
30
51
  ref?: Ref<unknown> | null;
@@ -15,7 +15,7 @@ import {
15
15
  makeCloneSlotChild,
16
16
  mergeRefs,
17
17
  render
18
- } from "../chunk-EIKPSL26.js";
18
+ } from "../chunk-SMHJEFNE.js";
19
19
 
20
20
  // ../../adapters/react/src/current/create-contract-component.ts
21
21
  import { useCallback, useRef } from "react";
@@ -59,7 +59,12 @@ function createContractComponent(options) {
59
59
  const onElementRef = useCallback((el) => {
60
60
  if (!onElement) return;
61
61
  if (el) {
62
- cleanupRef.current = onElement(el, () => propsRef.current) ?? void 0;
62
+ cleanupRef.current?.();
63
+ cleanupRef.current = void 0;
64
+ cleanupRef.current = onElement(
65
+ el,
66
+ () => propsRef.current
67
+ ) ?? void 0;
63
68
  } else {
64
69
  cleanupRef.current?.();
65
70
  cleanupRef.current = void 0;
@@ -1,10 +1,32 @@
1
- import { E as ElementType, U as UnknownProps, a as EmptyRecord, V as VariantMap, R as RecipeMap, A as AnyClassPluginFactory, c as ReactFactoryOptions, P as PolymorphicComponent, d as PolymorphicGenerics, e as ExtractPluginProps } from '../react-options-DLDsA4Tn.js';
2
- export { f as AnyFactoryOptions, g as ElementRef, F as FactoryOptions, h as PolymorphicProps, i as PolymorphicWithAsChild, j as PolymorphicWithRender, k as RenderCallbackProps, S as Slottable, l as SlottableProps, n as defineContractComponent, m as mergeRefs } from '../react-options-DLDsA4Tn.js';
1
+ import { E as ElementType, U as UnknownProps, a as EmptyRecord, V as VariantMap, N as NoVariants, R as RecipeMap, b as NoPreset, A as AnyClassPluginFactory, d as ReactFactoryOptions, P as PolymorphicComponent, e as PolymorphicGenerics, M as MergeRecords, f as ExtractPluginProps } from '../react-options-5GbZ8Tbv.js';
2
+ export { g as AnyFactoryOptions, h as ElementRef, F as FactoryOptions, i as PolymorphicProps, j as PolymorphicWithAsChild, k as PolymorphicWithRender, l as RenderCallbackProps, S as Slottable, m as SlottableProps, o as defineContractComponent, n as mergeRefs } from '../react-options-5GbZ8Tbv.js';
3
3
  import * as react from 'react';
4
4
  import 'type-fest';
5
5
  import '../_shared/diagnostics.js';
6
6
 
7
- 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, TAllowed extends ElementType = ElementType>(options: ReactFactoryOptions<TDefault, Props, Variants, TPreset, TPlugin, TAllowed>): PolymorphicComponent<PolymorphicGenerics<TDefault, Props & ExtractPluginProps<TPlugin>, Variants, TPreset, TAllowed>>;
7
+ /**
8
+ * Creates a polymorphic React component with praxis-kit contracts applied, for React 18 and
9
+ * earlier (use `praxis-kit/react` instead on React 19, which accepts `ref` as a plain prop).
10
+ *
11
+ * ```tsx
12
+ * const Button = createContractComponent({
13
+ * tag: 'button',
14
+ * name: 'Button',
15
+ * styling: {
16
+ * base: 'btn',
17
+ * variants: { intent: { primary: 'btn--primary', ghost: 'btn--ghost' } },
18
+ * defaults: { intent: 'primary' },
19
+ * },
20
+ * })
21
+ *
22
+ * <Button intent="ghost" as="a" href="/home">Home</Button>
23
+ * ```
24
+ *
25
+ * Returns a `forwardRef` component — `ref` is forwarded to the rendered host element the same
26
+ * way it works in `praxis-kit/react`. Pass `onElement` to run setup once the real DOM element
27
+ * exists; this adapter doesn't support `subComponents`.
28
+ */
29
+ declare function createContractComponent<TDefault extends ElementType, Props extends UnknownProps = EmptyRecord, Variants extends Readonly<VariantMap> = NoVariants, TPreset extends RecipeMap<Variants> = NoPreset, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory, TAllowed extends ElementType = ElementType>(options: ReactFactoryOptions<TDefault, Props, Variants, TPreset, TPlugin, TAllowed>): PolymorphicComponent<PolymorphicGenerics<TDefault, MergeRecords<Props, ExtractPluginProps<TPlugin>>, Variants, TPreset, TAllowed>>;
8
30
 
9
31
  type SlotProps = {
10
32
  [key: string]: unknown;
@@ -13,7 +13,7 @@ import {
13
13
  makeCloneSlotChild,
14
14
  mergeRefs,
15
15
  render
16
- } from "../chunk-EIKPSL26.js";
16
+ } from "../chunk-SMHJEFNE.js";
17
17
 
18
18
  // ../../adapters/react/src/legacy/create-contract-component.ts
19
19
  import { forwardRef as forwardRef2, useCallback, useRef } from "react";
@@ -57,7 +57,11 @@ function createContractComponent(options) {
57
57
  const onElementRef = useCallback((el) => {
58
58
  if (!onElement) return;
59
59
  if (el) {
60
- cleanupRef.current = onElement(el, () => propsRef.current) ?? void 0;
60
+ cleanupRef.current?.();
61
+ cleanupRef.current = onElement(
62
+ el,
63
+ () => propsRef.current
64
+ ) ?? void 0;
61
65
  } else {
62
66
  cleanupRef.current?.();
63
67
  cleanupRef.current = void 0;
@@ -2,15 +2,89 @@ import { Ref, PropsWithChildren, ReactElement, ComponentType, JSX, ReactNode } f
2
2
  import { RequireAtLeastOne, Simplify, ReadonlyDeep, NonEmptyTuple } from 'type-fest';
3
3
  import { Diagnostics, DiagnosticInput, DiagnosticsMode } from './_shared/diagnostics.js';
4
4
 
5
+ /**
6
+ * A string-keyed object whose values are of type `T`.
7
+ */
5
8
  type StringMap<T = unknown> = Record<string, T>;
9
+ /**
10
+ * A string-keyed object with values of unknown type.
11
+ */
6
12
  type AnyRecord = StringMap<unknown>;
13
+ /**
14
+ * An object type with no named properties.
15
+ *
16
+ * Unlike `{}`, this excludes arbitrary properties during type operations while
17
+ * still satisfying `extends object`.
18
+ */
7
19
  type EmptyRecord = Record<never, never>;
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];
@@ -190,7 +264,7 @@ type ClassPluginFactory<TProps extends AnyRecord = EmptyRecord> = <V extends Var
190
264
  * wherever a factory's concrete plugin-props shape isn't tracked (factory generics,
191
265
  * capability wiring). */
192
266
  type AnyClassPluginFactory = ClassPluginFactory<AnyRecord> | undefined;
193
- type ExtractPluginProps<TPlugin extends AnyClassPluginFactory> = TPlugin extends ClassPluginFactory<infer T> ? string extends keyof T ? EmptyRecord : T : EmptyRecord;
267
+ type ExtractPluginProps<TPlugin extends AnyClassPluginFactory> = TPlugin extends ClassPluginFactory<infer T> ? string extends keyof T ? NoPluginProps : T : NoPluginProps;
194
268
 
195
269
  type AriaContext = {
196
270
  readonly tag: IntrinsicTag;
@@ -254,6 +328,12 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
254
328
  * `@praxis-kit/diagnostics`.
255
329
  */
256
330
  readonly diagnostics?: Diagnostics | DiagnosticsMode;
331
+ /**
332
+ * ARIA/accessibility rules evaluated against the resolved tag and props on every render.
333
+ * Each rule is a function receiving the current context and returning zero or more
334
+ * violations, some of which can carry an auto-applicable fix (see `createRemoveAttributeRule`
335
+ * and friends in `praxis-kit/contract`).
336
+ */
257
337
  readonly aria?: readonly AriaRule[];
258
338
  /**
259
339
  * Rules that need `AriaPolicyEngine`'s fix-application/caching machinery
@@ -265,6 +345,11 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
265
345
  * misleading `aria` name to get the machinery it needs.
266
346
  */
267
347
  readonly rules?: readonly AriaRule[];
348
+ /**
349
+ * Declares which children are valid, by name, match predicate, and cardinality (e.g. "at
350
+ * least 1, at most 4 `Button` children"). Open by default — children matching no rule are
351
+ * still allowed unless `exclusiveChildren` is set.
352
+ */
268
353
  readonly children?: readonly ChildRuleInput[];
269
354
  /**
270
355
  * When true, only children matching a `children` rule (or text, per `allowText`)
@@ -277,19 +362,49 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
277
362
  * or any listed rule. Default: true.
278
363
  */
279
364
  readonly allowText?: boolean;
365
+ /**
366
+ * Prop transforms composed with the component's own `normalize` (from `FactoryOptions`) and
367
+ * run before it. Unlike `normalize`, these live in the enforcement bucket because they
368
+ * typically encode a built-in HTML/ARIA fact rather than component-specific behavior.
369
+ */
280
370
  readonly props?: readonly PropNormalizer[];
281
371
  /** Restricts the `as` prop to this set of tags. Violations route through diagnostics. */
282
372
  readonly allowedAs?: readonly TAllowed[];
283
373
  };
284
374
 
285
375
  type StylingOptions<V extends Readonly<VariantMap> = Readonly<EmptyRecord>, TPreset extends RecipeMap<V> = Readonly<EmptyRecord>, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory> = {
376
+ /** Class applied to every instance regardless of variant selection. */
286
377
  readonly base?: ClassName;
378
+ /**
379
+ * Named variant groups (e.g. `intent`, `size`), each mapping its possible values to a
380
+ * class string. A consumer selects a value per group as a prop (`<Button intent="primary">`).
381
+ */
287
382
  readonly variants?: V;
383
+ /** Value used for a variant group when the consumer doesn't pass one explicitly. */
288
384
  readonly defaults?: Partial<DefaultVariants<V>>;
385
+ /**
386
+ * Applies an extra class only when a specific *combination* of variant selections matches —
387
+ * for cases `variants` alone can't express (e.g. `intent: 'primary'` + `size: 'lg'` together
388
+ * need a class neither variant would add on its own).
389
+ */
289
390
  readonly compounds?: readonly CompoundVariant<V>[];
391
+ /**
392
+ * Named bundles of variant values, selectable as a single unit via the `recipe` prop (e.g.
393
+ * `<Button recipe="cta">` instead of setting `intent`/`size` individually).
394
+ */
290
395
  readonly presets?: TPreset;
396
+ /** Maps a resolved tag directly to a raw class string, independent of the variant system. */
291
397
  readonly tags?: Readonly<TagMap>;
398
+ /**
399
+ * A `ClassPluginFactory` (e.g. the Tailwind layout pipeline) that extends class resolution
400
+ * with its own owned props, layered on top of `variants`/`presets`/`tags`.
401
+ */
292
402
  readonly plugin?: TPlugin;
403
+ /**
404
+ * A cache-key → resolved-class-string lookup for every statically-known variant
405
+ * combination, skipping runtime class computation entirely when a match is found. Normally
406
+ * generated by a build-time class-extraction plugin rather than hand-authored.
407
+ */
293
408
  readonly precomputedClasses?: Readonly<Record<string, string>>;
294
409
  };
295
410
 
@@ -298,11 +413,21 @@ type NormalizeFn<Props extends AnyRecord = AnyRecord> = {
298
413
  }['normalize'];
299
414
  type AnyFactoryOptions = FactoryOptions<ElementType, AnyRecord, VariantMap, RecipeMap<VariantMap>, AnyClassPluginFactory>;
300
415
  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> = {
416
+ /** The intrinsic tag the component renders by default. Overridable per instance via `as`. */
301
417
  readonly tag?: TDefault;
418
+ /** Display name used in diagnostics, dev tools, and generated component naming. */
302
419
  readonly name?: string;
420
+ /** Values used for the component's own (non-variant) props when the consumer omits them. */
303
421
  readonly defaults?: Partial<NoInfer<Props>>;
422
+ /**
423
+ * A pure `(props) => props` transform run on every render, after `enforcement.props`'s
424
+ * normalizers see the same input. Use this for component-specific prop shaping — anything
425
+ * that depends on live instance state or the real DOM element belongs in `onElement` instead.
426
+ */
304
427
  readonly normalize?: NormalizeFn<NoInfer<Props>>;
428
+ /** Variant groups, base classes, presets, and the optional class-resolution plugin. */
305
429
  readonly styling?: StylingOptions<V, TPreset, TPlugin>;
430
+ /** ARIA rules, child-content contracts, and other runtime validation for this component. */
306
431
  readonly enforcement?: EnforcementOptions<TAllowed>;
307
432
  /**
308
433
  * Adapter-resolved diagnostics default, spread in by `resolveAdapterCommonOptions`. Not meant to
@@ -326,13 +451,23 @@ type FactoryOptions<TDefault extends ElementType = ElementType, Props extends An
326
451
  * no prop-based equivalent), not for anything expressible as a plain
327
452
  * prop.
328
453
  *
454
+ * `element` is typed to the real DOM interface of every tag the rendered
455
+ * element could actually be — `TDefault` plus whatever `enforcement.allowed`
456
+ * permits via `as` (`HTMLDialogElement` for `tag: 'dialog'`,
457
+ * `HTMLDetailsElement` for `tag: 'details'`, and so on) — no cast needed to
458
+ * reach tag-specific members. A component that leaves `allowed`
459
+ * unconstrained (any tag reachable via `as`) falls back to `HTMLElement`,
460
+ * which still covers members every element shares (`showPopover()` and
461
+ * friends); restrict `enforcement.allowed` to the tags `onElement`
462
+ * actually knows how to handle to get real narrowing.
463
+ *
329
464
  * `getProps` returns the instance's *current* resolved props at call
330
465
  * time — read it from inside a listener registered once at mount, rather
331
466
  * than re-subscribing on every prop change.
332
467
  *
333
468
  * Return a cleanup function to run when the instance unmounts.
334
469
  */
335
- readonly onElement?: (element: Element, getProps: () => Readonly<Props>) => void | (() => void);
470
+ readonly onElement?: (element: ElementForTag<TDefault | TAllowed>, getProps: () => Readonly<Props>) => void | (() => void);
336
471
  };
337
472
 
338
473
  type MetadataMap = AnyRecord;
@@ -533,7 +668,7 @@ interface CompiledArtifact {
533
668
  * Extends FactoryOptions with React-specific configuration.
534
669
  * slotComponent is intentionally not in core — it is a React rendering concern.
535
670
  */
536
- type ReactFactoryOptions<TDefault extends ElementType, Props extends UnknownProps, Variants extends Readonly<VariantMap>, TPreset extends RecipeMap<Variants> = Readonly<EmptyRecord>, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory, TAllowed extends ElementType = ElementType> = FactoryOptions<TDefault, Props, Variants, TPreset, TPlugin, TAllowed> & {
671
+ type ReactFactoryOptions<TDefault extends ElementType, Props extends UnknownProps, Variants extends Readonly<VariantMap>, TPreset extends RecipeMap<Variants> = NoPreset, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory, TAllowed extends ElementType = ElementType> = FactoryOptions<TDefault, Props, Variants, TPreset, TPlugin, TAllowed> & {
537
672
  /** Component used to render the asChild slot. Defaults to the built-in Slot. */
538
673
  slotComponent?: SlotComponent;
539
674
  /**
@@ -547,4 +682,4 @@ type ReactFactoryOptions<TDefault extends ElementType, Props extends UnknownProp
547
682
  artifact?: CompiledArtifact;
548
683
  };
549
684
 
550
- export { type AnyClassPluginFactory as A, type ElementType as E, type FactoryOptions as F, type PolymorphicComponent as P, type RecipeMap as R, Slottable as S, type UnknownProps as U, type VariantMap as V, type EmptyRecord as a, type AnyRecord as b, type ReactFactoryOptions as c, type PolymorphicGenerics as d, type ExtractPluginProps as e, type AnyFactoryOptions as f, type ElementRef as g, type PolymorphicProps as h, type PolymorphicWithAsChild as i, type PolymorphicWithRender as j, type RenderCallbackProps as k, type SlottableProps as l, mergeRefs as m, defineContractComponent as n };
685
+ export { type AnyClassPluginFactory as A, type ElementType as E, type FactoryOptions as F, type MergeRecords as M, type NoVariants as N, type PolymorphicComponent as P, type RecipeMap as R, Slottable as S, type UnknownProps as U, type VariantMap as V, type EmptyRecord as a, type NoPreset as b, type AnyRecord as c, type ReactFactoryOptions as d, type PolymorphicGenerics as e, type ExtractPluginProps as f, type AnyFactoryOptions as g, type ElementRef as h, type PolymorphicProps as i, type PolymorphicWithAsChild as j, type PolymorphicWithRender as k, type RenderCallbackProps as l, type SlottableProps as m, mergeRefs as n, defineContractComponent as o };