praxis-kit 0.1.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import "clsx";
2
2
  import { Diagnostic, DiagnosticInput, Diagnostics as Diagnostics$1, DiagnosticsMode } from "../_shared/diagnostics.js";
3
- import { ReadonlyDeep, RequireAtLeastOne, Simplify } from "type-fest";
3
+ import { Except, ReadonlyDeep, RequireAtLeastOne, SetRequired, Simplify } from "type-fest";
4
4
  //#region ../../lib/foundation/src/string-map.d.ts
5
5
  /**
6
6
  * A string-keyed object whose values are of type `T`.
@@ -24,6 +24,38 @@ type EmptyRecord = Record<never, never>;
24
24
  * `{ Header, Content, Footer }`.
25
25
  */
26
26
  type SubComponentMap = Readonly<AnyRecord>;
27
+ /**
28
+ * Fallback for `ExtractPluginProps<TPlugin>` when a plugin contributes no
29
+ * props, including the no-plugin case.
30
+ *
31
+ * Structurally identical to `EmptyRecord`, but named separately so editor
32
+ * hovers remain self-descriptive.
33
+ */
34
+ type NoPluginProps = EmptyRecord;
35
+ /**
36
+ * Determines whether an object type should be treated as empty.
37
+ *
38
+ * `keyof T` ignores call and construct signatures...
39
+ */
40
+ type IsEmptyRecord<T extends object> = T extends ((...args: never[]) => unknown) ? false : T extends (new (...args: never[]) => unknown) ? false : keyof T extends never ? true : false;
41
+ /**
42
+ * Merges two object types while eliding empty operands.
43
+ *
44
+ * If either operand is {@link EmptyRecord}, the other operand is returned
45
+ * directly instead of producing intersections such as
46
+ * `Component & EmptyRecord` in editor hovers.
47
+ *
48
+ * Unlike a homomorphic mapped type (for example `Simplify<T>`), this preserves
49
+ * call and construct signatures. Many component types are callable objects,
50
+ * and mapped types silently discard those signatures.
51
+ *
52
+ * @remarks
53
+ * Instantiate `MergeRecords` directly. Introducing an intermediate alias for
54
+ * one operand (for example `type C = PolymorphicComponent<G>`) can prevent
55
+ * `IsEmptyRecord` from evaluating eagerly, which breaks assignability under
56
+ * `exactOptionalPropertyTypes`.
57
+ */
58
+ type MergeRecords<A extends object, B extends object> = IsEmptyRecord<A> extends true ? B : IsEmptyRecord<B> extends true ? A : A & B;
27
59
  //#endregion
28
60
  //#region ../../lib/primitive/src/types/intrinsic-tag.d.ts
29
61
  type IntrinsicTag = keyof HTMLElementTagNameMap;
@@ -171,6 +203,64 @@ type RecipeMap<V extends VariantMap = VariantMap> = Readonly<StringMap<VariantSe
171
203
  //#region ../../lib/primitive/src/types/variants/recipe-target.d.ts
172
204
  type RecipeTarget<TVariants extends VariantMap = VariantMap> = VariantSelection<TVariants>;
173
205
  //#endregion
206
+ //#region ../../lib/primitive/src/types/variants/polymorphic-generics.d.ts
207
+ /**
208
+ * The framework-neutral descriptor for a single praxis-kit component's contract — every render
209
+ * mechanism (tag resolution, prop merging, classes, ARIA) and every framework adapter's
210
+ * component type is built from this one shape. Deliberately just data: five type parameters and
211
+ * their corresponding properties, with no notion of JSX, call signatures, refs, or any
212
+ * framework-specific rendering concern. Each adapter (React, Vue, Svelte, Solid, Lit, Web) builds
213
+ * its own idiomatic component type on top of a `PolymorphicGenerics<...>` instantiation — see
214
+ * `PolymorphicComponent<G>` (`adapters/react/src/shared/types/polymorphic-props.ts`) for the
215
+ * React example — rather than this interface knowing anything about any of them.
216
+ *
217
+ * Use the `*Of<T>` accessor aliases below (`DefaultOf<G>`, `PropsOf<G>`, etc.) to read a single
218
+ * field back out of an already-resolved `G`, instead of indexing `G['default']` etc. directly at
219
+ * call sites — same rationale as any accessor: the property name stays an implementation detail,
220
+ * and every reader benefits together if it ever needs to change.
221
+ */
222
+ interface PolymorphicGenerics<
223
+ /**
224
+ * The element/tag this component renders as when the consumer doesn't override it via `as`
225
+ * (`AllowedOf<G>` permitting) — e.g. `'button'`, `'div'`. Defaults to the widest `ElementType`
226
+ * so a generic `PolymorphicGenerics` reference (with nothing else specified) still compiles.
227
+ */
228
+ TDefault extends ElementType = ElementType,
229
+ /**
230
+ * The props this specific component declares — its own contract, before variants are mixed
231
+ * in. Defaults to `AnyRecord` for the same "still compiles unspecified" reason as `TDefault`.
232
+ */
233
+ Props extends AnyRecord = AnyRecord,
234
+ /**
235
+ * This component's variant definitions (e.g. `{ intent: { primary: ..., ghost: ... } }`).
236
+ * Constrained to `Readonly<VariantMap>` — not the wider `AnyRecord` — specifically so `TPreset`
237
+ * below can be expressed as `RecipeMap<Variants>` and get real per-variant-key checking,
238
+ * instead of falling back to an unconstrained `RecipeMap<VariantMap>`.
239
+ */
240
+ Variants extends Readonly<VariantMap> = Readonly<VariantMap>,
241
+ /**
242
+ * Named presets (`RecipeMap<Variants>`) — bundles of variant selections a consumer activates
243
+ * by key instead of repeating the same variant combination at every call site. Tied to
244
+ * `Variants`, not `AnyRecord`, precisely so a preset can only ever select keys/values that
245
+ * `Variants` actually defines — an invalid preset is a type error, not a silent no-op.
246
+ * Defaults to `Readonly<EmptyRecord>` (no presets), which is the common case: a component can
247
+ * have variants without necessarily defining any named presets over them, and most don't.
248
+ */
249
+ TPreset extends RecipeMap<Variants> = Readonly<EmptyRecord>,
250
+ /**
251
+ * The set of elements/tags a consumer is allowed to switch to via `as`. Defaults to the widest
252
+ * `ElementType`, under which `AllowedOf<G>` imposes no restriction at all (see
253
+ * `PolymorphicControlProps.as`'s own comment in the React adapter for the concrete effect this
254
+ * has at a component's actual call site).
255
+ */
256
+ TAllowed extends ElementType = ElementType> {
257
+ default: TDefault;
258
+ props: Props;
259
+ variants: Variants;
260
+ preset: TPreset;
261
+ allowed: TAllowed;
262
+ }
263
+ //#endregion
174
264
  //#region ../../lib/primitive/src/types/variants/compound/compound-variant.d.ts
175
265
  type RequireAtLeastOneIfNotEmpty<T> = keyof T extends never ? EmptyRecord : RequireAtLeastOne<T>;
176
266
  type CompoundVariantConditionValue<V extends VariantMap, K extends keyof V> = VariantKey<V, K> | NonEmptyArray<VariantKey<V, K>>;
@@ -242,6 +332,7 @@ type ClassPluginFactory<TProps extends AnyRecord = EmptyRecord> = <V extends Var
242
332
  * wherever a factory's concrete plugin-props shape isn't tracked (factory generics,
243
333
  * capability wiring). */
244
334
  type AnyClassPluginFactory = ClassPluginFactory<AnyRecord> | undefined;
335
+ type ExtractPluginProps<TPlugin extends AnyClassPluginFactory> = TPlugin extends ClassPluginFactory<infer T> ? string extends keyof T ? NoPluginProps : T : NoPluginProps;
245
336
  //#endregion
246
337
  //#region ../../lib/primitive/src/types/aria-rule/aria-context.d.ts
247
338
  type AriaContext = {
@@ -427,16 +518,6 @@ type StylingOptions<V extends Readonly<VariantMap> = Readonly<EmptyRecord>, TPre
427
518
  type NormalizeFn<Props extends AnyRecord = AnyRecord> = {
428
519
  normalize(props: Readonly<Props & IntrinsicProps>): Props & IntrinsicProps;
429
520
  }['normalize'];
430
- /**
431
- * The type-erased shape of {@link FactoryOptions} — every generic parameter widened to its bound.
432
- *
433
- * Use it for a value that must hold *any* factory config (a registry, a generic wrapper). It
434
- * cannot check `styling.compounds` conditions against the real variant keys/values, because it
435
- * has forgotten what they are — for that, annotate against `FactoryOptions<...>` with the concrete
436
- * generics (or `satisfies FactoryOptions<'button', Props, typeof variants>`), which keeps an
437
- * invalid compound condition a type error rather than a silent no-op.
438
- */
439
- type AnyFactoryOptions = FactoryOptions<ElementType, AnyRecord, VariantMap, RecipeMap<VariantMap>, AnyClassPluginFactory>;
440
521
  /**
441
522
  * The framework-neutral component-authoring config passed to `createContractComponent` in every
442
523
  * adapter: default tag + name, own-prop defaults, a `normalize` transform, `styling` (variants,
@@ -446,7 +527,9 @@ type AnyFactoryOptions = FactoryOptions<ElementType, AnyRecord, VariantMap, Reci
446
527
  * `satisfies FactoryOptions<TDefault, Props, typeof variants, ...>` on a config object narrows
447
528
  * `styling.compounds` conditions to the real per-variant-key shape — including resolving a
448
529
  * boolean-shaped axis (`{ true, false }`) to a real `boolean` — so a condition naming a variant or
449
- * value that does not exist is a compile error. `AnyFactoryOptions` cannot do this.
530
+ * value that does not exist is a compile error. Leaving `V` at the bare `VariantMap` instead
531
+ * (whether via `FactoryOptions`'s own default or an explicit erased instantiation) forgets that
532
+ * shape entirely, so the same invalid condition becomes a silent no-op instead.
450
533
  */
451
534
  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> = {
452
535
  /** The intrinsic tag the component renders by default. Overridable per instance via `as`. */
@@ -455,6 +538,33 @@ type FactoryOptions<TDefault extends ElementType = ElementType, Props extends An
455
538
  readonly name?: string;
456
539
  /** Values used for the component's own (non-variant) props when the consumer omits them. */
457
540
  readonly defaults?: Partial<NoInfer<Props>>;
541
+ /**
542
+ * Optional, type-only declaration of this component's complete own-prop shape — present purely
543
+ * for type recovery, never read at runtime (see `declareProps` in `@praxis-kit/adapter-utils`).
544
+ *
545
+ * `defaults` alone can only prove a prop *has a default*, not that it's the complete prop model
546
+ * a component accepts — a `defaults: { size: 'md' }` component may still take `onClick`,
547
+ * `disabled`, and other props with no default at all, none of which a `defaults`-only recovery
548
+ * can see (see `ContractPropsFrom`'s own doc comment for the general shape of this problem).
549
+ * `props` closes that gap: when present, `ContractPropsOf<C>` / `ContractProps<typeof Component>`
550
+ * recover this declared type directly and exactly, in place of the necessarily-partial,
551
+ * literal-widened recovery `defaults` alone allows.
552
+ *
553
+ * Typed as `object | undefined`, not `NoInfer<Props> | undefined` like `defaults`/`onElement` —
554
+ * deliberately decoupled from this interface's own `Props` generic, unlike those two fields.
555
+ * `defaults`/`onElement` are tied to `Props` because real runtime code reads them against a
556
+ * concretely-resolved `Props` for a real call; `props` is never read at runtime at all (see
557
+ * above), so it has no such need, and tying it to `Props extends AnyRecord` would force every
558
+ * hand-declared prop `interface`/`type` an author passes through `declareProps<Props>()` to
559
+ * structurally satisfy `Record<string, unknown>` — a real TypeScript limitation (a named type
560
+ * without an index signature never satisfies that, even via plain assignment, only a fresh
561
+ * object literal does) that would make this field far more awkward to use for its one real job:
562
+ * carrying an author's own already-precise prop type through untouched. `ContractPropsFrom`
563
+ * (`contract-model.ts`) recovers the real value here structurally, straight off `O`'s own
564
+ * literal type — independent of this field's declared type, same as every other `Contract*From`
565
+ * derivation in that file.
566
+ */
567
+ readonly props?: object | undefined;
458
568
  /**
459
569
  * A pure `(props) => props` transform run on every render, after `enforcement.props`'s
460
570
  * normalizers see the same input. Use this for component-specific prop shaping — anything
@@ -509,7 +619,9 @@ type FactoryOptions<TDefault extends ElementType = ElementType, Props extends An
509
619
  *
510
620
  * Return a cleanup function to run when the instance unmounts.
511
621
  */
512
- readonly onElement?: (element: ElementForTag<TDefault | TAllowed>, getProps: () => Readonly<Props>) => void | (() => void);
622
+ readonly onElement?: {
623
+ onElement(element: ElementForTag<TDefault | TAllowed>, getProps: () => Readonly<Props>): void | (() => void);
624
+ }['onElement'];
513
625
  };
514
626
  //#endregion
515
627
  //#region ../../lib/primitive/src/types/factory/resolved-factory-options.d.ts
@@ -546,6 +658,210 @@ type ResolvedFactoryOptions<TDefault extends ElementType = ElementType, Props ex
546
658
  readonly precomputedClasses?: Readonly<StringMap<string>>;
547
659
  };
548
660
  //#endregion
661
+ //#region ../../lib/primitive/src/types/factory/contract-input.d.ts
662
+ /**
663
+ * The author-facing input constraint for `defineContract` (`@praxis-kit/adapter-utils`) —
664
+ * `FactoryOptions` with `tag` and `name` promoted from optional to required, and `diagnostics`
665
+ * removed entirely.
666
+ *
667
+ * `tag`/`name` are required here, not just conventionally recommended, because nothing in
668
+ * `FactoryOptions` itself enforces either: today `{}` satisfies `FactoryOptions`, and an absent
669
+ * `tag` silently resolves to `'div'` inside `resolveFactoryOptions` — a contract author who forgets
670
+ * `tag` gets no signal at all. Requiring both here, at the one boundary every contract passes
671
+ * through before construction, closes that gap without touching `FactoryOptions` itself (which
672
+ * stays permissive, since it's also the type any type-erased context still needs to describe).
673
+ *
674
+ * `diagnostics` is omitted, not just left optional: per `FactoryOptions.diagnostics`'s own doc
675
+ * comment, it's adapter-resolved only ("spread in by `resolveAdapterCommonOptions`") — a contract
676
+ * author overrides diagnostics behavior through `enforcement.diagnostics` instead, never this
677
+ * field directly, so there's nothing for an author to supply here in the first place.
678
+ *
679
+ * `Props` is `ContractInput`'s first type parameter (matching `FactoryOptions`'s own field-order
680
+ * intuition, even though `FactoryOptions` itself puts `TDefault` first) purely for readability as a
681
+ * standalone type annotation — `defineContract` itself constrains its own `const O` against the
682
+ * bare, all-defaulted `ContractInput` (see that function's own doc comment), never
683
+ * `ContractInput<Props>` with `Props` given explicitly.
684
+ *
685
+ * Every other parameter's default widens to that parameter's own *bound* (`Readonly<VariantMap>`,
686
+ * `RecipeMap<V>`, `AnyClassPluginFactory`, `ElementType`), not `FactoryOptions`'s narrower
687
+ * `EmptyRecord`-style defaults — deliberate and load-bearing: a real author's contract (real
688
+ * variants, a real preset, a real plugin) must structurally satisfy whatever bound this type
689
+ * presents, and a narrow bound would only accept an empty-variants, no-preset, no-plugin contract.
690
+ * See `DECISIONS.md`'s `defineContract` entry for the concrete bug this bound choice fixes.
691
+ */
692
+ type ContractInput<Props extends AnyRecord = AnyRecord, TDefault extends ElementType = ElementType, V extends Readonly<VariantMap> = Readonly<VariantMap>, TPreset extends RecipeMap<V> = RecipeMap<V>, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory, TAllowed extends ElementType = ElementType> = SetRequired<Except<FactoryOptions<TDefault, Props, V, TPreset, TPlugin, TAllowed>, 'diagnostics'>, 'tag' | 'name'>;
693
+ //#endregion
694
+ //#region ../../lib/primitive/src/types/factory/contract-model.d.ts
695
+ /**
696
+ * The pipeline this file implements:
697
+ *
698
+ * ```text
699
+ * O (a raw contract literal)
700
+ * → Contract*From<O> one derivation per dimension, straight off O's own shape
701
+ * → ContractDimensions<O> the six derivations, assembled
702
+ * → ContractModel<...> the same six values, as a required-field carrier
703
+ * → ContractModelOf<C> resolves an already-`defineContract`-ed C's real model,
704
+ * or reconstructs one from a raw C via ContractModelFrom
705
+ * ```
706
+ *
707
+ * See `DECISIONS.md`'s `defineContract` entry for the TypeScript limitations that shaped the
708
+ * derivations below.
709
+ */
710
+ /**
711
+ * Canonical type-level representation of a contract's dimensions — the contract-level counterpart
712
+ * to `PolymorphicGenerics`. Every field is required, so a field's presence is never ambiguous the
713
+ * way it is on an optional `FactoryOptions` field.
714
+ */
715
+ interface ContractModel<TDefault extends ElementType = ElementType, Props extends AnyRecord = AnyRecord, V extends Readonly<VariantMap> = Readonly<VariantMap>, TPreset extends RecipeMap<VariantMap> = RecipeMap<VariantMap>, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory, TAllowed extends ElementType = ElementType> {
716
+ readonly tag: TDefault;
717
+ readonly props: Props;
718
+ readonly variants: V;
719
+ readonly preset: TPreset;
720
+ readonly plugin: TPlugin;
721
+ readonly allowed: TAllowed;
722
+ }
723
+ /**
724
+ * The phantom-marker shape read back via `T extends HasContractModel<infer M> ? M : ...` — the
725
+ * contract-level counterpart to `lib/contract-props`'s `HasGenerics<G>`. Type-only: never assigned
726
+ * at runtime.
727
+ *
728
+ * `__model` is **required**, not optional like `HasGenerics<G>`'s `__generics?` — an optional field
729
+ * would make `C extends HasContractModel<infer M>` trivially succeed for any `C`, defeating the
730
+ * one thing this marker exists to answer: did `C` really go through `defineContract`?
731
+ */
732
+ interface HasContractModel<M extends ContractModel = ContractModel> {
733
+ readonly __model: M;
734
+ }
735
+ type ContractTagFrom<O> = O extends {
736
+ tag: infer TDefault extends ElementType;
737
+ } ? TDefault : ElementType;
738
+ /** `'img'` → `string`, `1` → `number`, `true` → `boolean` — undoes `defineContract`'s `const O`
739
+ * literal narrowing so a `defaults` value doesn't become the only value a caller may pass. */
740
+ type WidenLiteral<T> = T extends string ? string : T extends number ? number : T extends boolean ? boolean : T;
741
+ type WidenShallow<T> = { [K in keyof T]: WidenLiteral<T[K]>; };
742
+ /** What the author explicitly declared, via `props: declareProps<Props>()`
743
+ * (`@praxis-kit/adapter-utils`) — used exactly as declared, no `Partial`/widening/`data-*`
744
+ * stripping (see `DefaultPropsFrom` for why those exist there but not here). */
745
+ type DeclaredPropsFrom<O> = O extends {
746
+ props: infer Props extends object | undefined;
747
+ } ? [NonNullable<Props>] extends [never] ? never : NonNullable<Props> & AnyRecord : never;
748
+ /** What can be inferred from `defaults` — partial at best, since a default only proves a prop
749
+ * *has* one, not that it's the complete prop set. `Partial`: a defaulted prop is optional to the
750
+ * caller by definition. `data-*` keys are dropped — every adapter has its own passthrough for
751
+ * those already (finding #43). */
752
+ type DefaultPropsFrom<O> = O extends {
753
+ defaults: infer Props extends AnyRecord;
754
+ } ? Partial<WidenShallow<Omit<Props, Extract<keyof Props, `data-${string}`>>>> : never;
755
+ /** Declared beats defaulted beats nothing. `[X] extends [never]`, not bare `X extends never` —
756
+ * tuple-wrapped so the check doesn't distribute if `X` is ever a union containing `never`. */
757
+ type ContractPropsFrom<O> = [DeclaredPropsFrom<O>] extends [never] ? [DefaultPropsFrom<O>] extends [never] ? EmptyRecord : DefaultPropsFrom<O> : DeclaredPropsFrom<O>;
758
+ type ContractVariantsFrom<O> = O extends {
759
+ styling: {
760
+ variants: infer V extends Readonly<VariantMap>;
761
+ };
762
+ } ? V : Readonly<EmptyRecord>;
763
+ type ContractPresetFrom<O> = O extends {
764
+ styling: {
765
+ presets: infer TPreset extends RecipeMap<VariantMap>;
766
+ };
767
+ } ? TPreset : Readonly<EmptyRecord>;
768
+ type ContractPluginFrom<O> = O extends {
769
+ styling: {
770
+ plugin: infer TPlugin extends AnyClassPluginFactory;
771
+ };
772
+ } ? TPlugin : AnyClassPluginFactory;
773
+ type ContractAllowedFrom<O> = O extends {
774
+ enforcement: {
775
+ allowedAs: readonly (infer TAllowed extends ElementType)[];
776
+ };
777
+ } ? TAllowed : ElementType;
778
+ /** The six derivations above, assembled — the direct input to `ContractModel`. */
779
+ type ContractDimensions<O> = {
780
+ readonly tag: ContractTagFrom<O>;
781
+ readonly props: ContractPropsFrom<O>;
782
+ readonly variants: ContractVariantsFrom<O>;
783
+ readonly preset: ContractPresetFrom<O>;
784
+ readonly plugin: ContractPluginFrom<O>;
785
+ readonly allowed: ContractAllowedFrom<O>;
786
+ };
787
+ /**
788
+ * Builds a `ContractModel` from a raw literal `O` — the fallback path `ContractModelOf<C>` uses
789
+ * when `C` never went through `defineContract` (no `__model` marker to read directly), and what
790
+ * `defineContract` itself attaches as that marker for a `C` that did.
791
+ */
792
+ type ContractModelFrom<O> = ContractDimensions<O> extends (infer D extends ContractDimensions<O>) ? ContractModel<D['tag'], D['props'], D['variants'], D['preset'], D['plugin'], D['allowed']> : never;
793
+ /**
794
+ * Resolves "does `C` carry a real `ContractModel` already, or do we need to build one from its raw
795
+ * shape" — every `Contract*Of` accessor (`contract-of.ts`) is a one-line projection off this.
796
+ */
797
+ type ContractModelOf<C> = C extends HasContractModel<infer M> ? M : ContractModelFrom<C>;
798
+ //#endregion
799
+ //#region ../../lib/primitive/src/types/factory/defined-contract.d.ts
800
+ /**
801
+ * `defineContract`'s return type — `O` itself (the pure-identity return value, see that function's
802
+ * own doc comment for why no defaults are injected), intersected with the `ContractModel` phantom
803
+ * marker `defineContract` established from `O` at its own call site. Per the `defineContract`
804
+ * proposal (`DECISIONS.md`): "the semantic distinction that matters is `FactoryOptions` describes
805
+ * *acceptable* structure, `DefinedContract<O>` represents a *concrete, established* contract" —
806
+ * `M` is exactly that establishment, made concrete and inspectable rather than left implicit in
807
+ * `O`'s own nested optional fields.
808
+ */
809
+ type DefinedContract<O extends ContractInput, M extends ContractModel = ContractModel> = O & HasContractModel<M>;
810
+ //#endregion
811
+ //#region ../../lib/primitive/src/types/factory/contract-of.d.ts
812
+ /**
813
+ * `FactoryOptions`-level accessor family, mirroring `polymorphic-generics.ts`'s `*Of<T>`
814
+ * convention (`DefaultOf<G>`, `PropsOf<G>`, etc.) but applied one layer up, to a contract itself
815
+ * rather than to the `PolymorphicGenerics` an adapter derives from it.
816
+ *
817
+ * Named with a `Contract` prefix specifically to avoid colliding with `polymorphic-generics.ts`'s
818
+ * own `PropsOf`/`VariantsOf`/`RecipeOf`/`AllowedOf`/`DefaultOf` — both families are re-exported
819
+ * from `@praxis-kit/core`, so a name clash would be a real conflict, not a style nit.
820
+ *
821
+ * Each accessor is a trivial projection off `ContractModelOf<C>` (`contract-model.ts`) — the one
822
+ * place that resolves "does `C` carry a real `ContractModel` (via `defineContract`) or does one
823
+ * need reconstructing from `C`'s raw shape," so every accessor shares one implementation of that
824
+ * resolution rather than repeating it. See `contract-model.ts`'s own doc comments for the
825
+ * `ContractXFrom<O>` derivation each of these ultimately reads through, and `DECISIONS.md`'s
826
+ * `defineContract` entry for the design history behind the required-pattern-match technique.
827
+ */
828
+ type ContractTagOf<C extends FactoryOptions> = ContractModelOf<C>['tag'];
829
+ /** See this file's own doc comment. Best-effort when `C` has no `ContractModel` marker — see
830
+ * `ContractPropsFrom`'s own doc comment for why. */
831
+ type ContractPropsOf<C extends FactoryOptions> = ContractModelOf<C>['props'];
832
+ /** See this file's own doc comment. */
833
+ type ContractVariantsOf<C extends FactoryOptions> = ContractModelOf<C>['variants'];
834
+ /** See this file's own doc comment. */
835
+ type ContractPresetOf<C extends FactoryOptions> = ContractModelOf<C>['preset'];
836
+ /** See this file's own doc comment. */
837
+ type ContractPluginOf<C extends FactoryOptions> = ContractModelOf<C>['plugin'];
838
+ /** See this file's own doc comment. */
839
+ type ContractAllowedOf<C extends FactoryOptions> = ContractModelOf<C>['allowed'];
840
+ //#endregion
841
+ //#region ../../lib/primitive/src/types/factory/contract-generics.d.ts
842
+ /**
843
+ * The canonical projection from a defined contract `C` to the `PolymorphicGenerics` shape every
844
+ * adapter's prop types are built from — the one place "G" gets computed, so it can no longer
845
+ * silently drift per adapter the way today's hand-assembled `PolymorphicGenerics<...>` instantiation
846
+ * at each `createContractComponent` call site can (see `ContractGenericsWithAllowedOf` below for
847
+ * the one confirmed instance of that drift).
848
+ *
849
+ * Folds the class-resolution plugin's own contributed props (`ExtractPluginProps<TPlugin>`) into
850
+ * `props` here, once, rather than leaving each adapter's `ContractProps` to re-derive that merge
851
+ * via its own distributive conditional type on every read — the root cause of the ~22-member
852
+ * layout-union bug PR #95 patched per-adapter (see `DECISIONS.md`'s `defineContract` entry).
853
+ */
854
+ type ContractGenericsOf<C extends FactoryOptions> = PolymorphicGenerics<ContractTagOf<C>, MergeRecords<ContractPropsOf<C>, ExtractPluginProps<ContractPluginOf<C>>>, ContractVariantsOf<C>, ContractPresetOf<C>>;
855
+ /**
856
+ * Same projection as `ContractGenericsOf`, additionally threading `TAllowed` through to
857
+ * `PolymorphicGenerics`'s own `TAllowed` parameter. Only React's `createContractComponent`
858
+ * currently uses this variant — every other adapter uses the plain `ContractGenericsOf` above,
859
+ * matching their current behavior of not narrowing `as` via `enforcement.allowedAs` at the type
860
+ * level (the ARIA/tag-resolution engine still enforces `allowedAs` identically at runtime for all
861
+ * adapters regardless of which projection their types use).
862
+ */
863
+ type ContractGenericsWithAllowedOf<C extends FactoryOptions> = PolymorphicGenerics<ContractTagOf<C>, MergeRecords<ContractPropsOf<C>, ExtractPluginProps<ContractPluginOf<C>>>, ContractVariantsOf<C>, ContractPresetOf<C>, ContractAllowedOf<C>>;
864
+ //#endregion
549
865
  //#region ../../lib/contract/src/types/aria/invalid-result-input.d.ts
550
866
  /** Shared input shape for `invalidWithFix`/`invalidWithoutFix`. */
551
867
  type InvalidResultInput = {
@@ -674,4 +990,70 @@ interface Diagnostics {
674
990
  report(diagnostic: Diagnostic): Diagnostic;
675
991
  }
676
992
  //#endregion
677
- export type { AnyFactoryOptions, AriaContext, AriaFix, AriaFixResult, AriaPhase, AriaResult, AriaRule, Diagnostics, EnforcementOptions, FactoryOptions, FixKind, IntrinsicProps, AriaInvalidResult as InvalidResult, AriaInvalidWithFix as InvalidWithFix, AriaInvalidWithoutFix as InvalidWithoutFix, NormalizeFn, PropNormalizer, RemoveAttributeFixKind, ResolvedFactoryOptions, Severity, StateNormalizerConfig, StylingOptions, ValidResult };
993
+ //#region ../../lib/adapter-utils/src/runtime/define-contract.d.ts
994
+ /**
995
+ * Takes a concrete contract input and establishes its canonical `ContractModel` from it — the
996
+ * contract-definition boundary every adapter's `createContractComponent` builds on.
997
+ *
998
+ * Deliberately a typed identity function at runtime, not a normalizer — no defaults are injected,
999
+ * no fields are added or removed. `O` is inferred once, from the literal argument, and
1000
+ * `ContractModelFrom<O>` (`contract-model.ts`) does the rest — the model's six dimensions are its
1001
+ * properties, not something this function's own signature has to think about individually.
1002
+ * `ContractInput`'s bound requires `tag` and `name`, each a non-empty string; `tag`/`name`
1003
+ * additionally reject the empty-string literal specifically, via a self-referential constraint on
1004
+ * `O` itself, since a field type alone can't express "reject this one specific literal."
1005
+ *
1006
+ * ```ts
1007
+ * export const boxContract = defineContract({ tag: 'div', name: 'Box' })
1008
+ * export const buttonContract = defineContract({
1009
+ * tag: 'button',
1010
+ * name: 'Button',
1011
+ * styling: { variants: { intent: { primary: 'btn--primary' } } },
1012
+ * })
1013
+ * ```
1014
+ */
1015
+ export declare function defineContract<const O extends ContractInput & {
1016
+ readonly tag: O['tag'] extends '' ? never : ElementType;
1017
+ readonly name: O['name'] extends '' ? never : string;
1018
+ }>(options: O): DefinedContract<O, ContractModelFrom<O>>;
1019
+ //#endregion
1020
+ //#region ../../lib/adapter-utils/src/runtime/declare-props.d.ts
1021
+ /**
1022
+ * Type-only prop declaration helper for a `defineContract` config's `props` field
1023
+ * (`FactoryOptions.props`, `lib/primitive`) — never called for its return value (always
1024
+ * `undefined` at runtime), only for its type. Exists so an author can declare a component's
1025
+ * complete prop shape inline, in the same object literal `defineContract`'s single `const O`
1026
+ * parameter already infers from, without a separate generic argument at the
1027
+ * `defineContract`/`createContractComponent` call site itself — the fix for the gap
1028
+ * `ContractPropsFrom` (`lib/primitive`) documents: `defaults` alone can only prove a prop *has
1029
+ * a default*, not that it's the complete prop model.
1030
+ *
1031
+ * ```ts
1032
+ * interface ButtonProps {
1033
+ * onClick?: () => void
1034
+ * disabled?: boolean
1035
+ * }
1036
+ *
1037
+ * const buttonContract = defineContract({
1038
+ * tag: 'button',
1039
+ * name: 'Button',
1040
+ * props: declareProps<ButtonProps>(),
1041
+ * defaults: { type: 'button' },
1042
+ * })
1043
+ * ```
1044
+ *
1045
+ * Optional — most contracts still don't need this. Only reach for it when `defaults` doesn't
1046
+ * already capture every prop the component accepts.
1047
+ *
1048
+ * Bounded by `object`, not `AnyRecord` (`Record<string, unknown>`) like `Props` is everywhere else
1049
+ * in this pipeline — deliberately. A hand-declared `interface`/`type` prop shape (the overwhelmingly
1050
+ * common case an author reaches for here) has no index signature, and TypeScript's generic
1051
+ * *constraint* satisfaction (unlike plain value assignment) requires one to satisfy
1052
+ * `Record<string, unknown>` — a real, well-known TypeScript limitation, not a design choice this
1053
+ * file is working around loosely. `object` has no such requirement and accepts the same real
1054
+ * interfaces plain assignment already does; `ContractPropsFrom` (`lib/primitive`) matches this
1055
+ * field back out with the identical `object`-bounded pattern for the same reason.
1056
+ */
1057
+ export declare function declareProps<Props extends object>(): Props | undefined;
1058
+ //#endregion
1059
+ export type { AriaContext, AriaFix, AriaFixResult, AriaPhase, AriaResult, AriaRule, ContractAllowedOf, ContractDimensions, ContractGenericsOf, ContractGenericsWithAllowedOf, ContractInput, ContractModel, ContractModelFrom, ContractModelOf, ContractPluginOf, ContractPresetOf, ContractPropsOf, ContractTagOf, ContractVariantsOf, DefinedContract, Diagnostics, EnforcementOptions, FactoryOptions, FixKind, HasContractModel, IntrinsicProps, AriaInvalidResult as InvalidResult, AriaInvalidWithFix as InvalidWithFix, AriaInvalidWithoutFix as InvalidWithoutFix, NormalizeFn, PropNormalizer, RemoveAttributeFixKind, ResolvedFactoryOptions, Severity, StateNormalizerConfig, StylingOptions, ValidResult };
@@ -208,6 +208,71 @@ const selectedProps = makeStateNormalizer({
208
208
  falseState: "synthesize"
209
209
  });
210
210
  //#endregion
211
+ //#region ../../lib/adapter-utils/src/runtime/define-contract.ts
212
+ /**
213
+ * Takes a concrete contract input and establishes its canonical `ContractModel` from it — the
214
+ * contract-definition boundary every adapter's `createContractComponent` builds on.
215
+ *
216
+ * Deliberately a typed identity function at runtime, not a normalizer — no defaults are injected,
217
+ * no fields are added or removed. `O` is inferred once, from the literal argument, and
218
+ * `ContractModelFrom<O>` (`contract-model.ts`) does the rest — the model's six dimensions are its
219
+ * properties, not something this function's own signature has to think about individually.
220
+ * `ContractInput`'s bound requires `tag` and `name`, each a non-empty string; `tag`/`name`
221
+ * additionally reject the empty-string literal specifically, via a self-referential constraint on
222
+ * `O` itself, since a field type alone can't express "reject this one specific literal."
223
+ *
224
+ * ```ts
225
+ * export const boxContract = defineContract({ tag: 'div', name: 'Box' })
226
+ * export const buttonContract = defineContract({
227
+ * tag: 'button',
228
+ * name: 'Button',
229
+ * styling: { variants: { intent: { primary: 'btn--primary' } } },
230
+ * })
231
+ * ```
232
+ */
233
+ function defineContract(options) {
234
+ return options;
235
+ }
236
+ //#endregion
237
+ //#region ../../lib/adapter-utils/src/runtime/declare-props.ts
238
+ /**
239
+ * Type-only prop declaration helper for a `defineContract` config's `props` field
240
+ * (`FactoryOptions.props`, `lib/primitive`) — never called for its return value (always
241
+ * `undefined` at runtime), only for its type. Exists so an author can declare a component's
242
+ * complete prop shape inline, in the same object literal `defineContract`'s single `const O`
243
+ * parameter already infers from, without a separate generic argument at the
244
+ * `defineContract`/`createContractComponent` call site itself — the fix for the gap
245
+ * `ContractPropsFrom` (`lib/primitive`) documents: `defaults` alone can only prove a prop *has
246
+ * a default*, not that it's the complete prop model.
247
+ *
248
+ * ```ts
249
+ * interface ButtonProps {
250
+ * onClick?: () => void
251
+ * disabled?: boolean
252
+ * }
253
+ *
254
+ * const buttonContract = defineContract({
255
+ * tag: 'button',
256
+ * name: 'Button',
257
+ * props: declareProps<ButtonProps>(),
258
+ * defaults: { type: 'button' },
259
+ * })
260
+ * ```
261
+ *
262
+ * Optional — most contracts still don't need this. Only reach for it when `defaults` doesn't
263
+ * already capture every prop the component accepts.
264
+ *
265
+ * Bounded by `object`, not `AnyRecord` (`Record<string, unknown>`) like `Props` is everywhere else
266
+ * in this pipeline — deliberately. A hand-declared `interface`/`type` prop shape (the overwhelmingly
267
+ * common case an author reaches for here) has no index signature, and TypeScript's generic
268
+ * *constraint* satisfaction (unlike plain value assignment) requires one to satisfy
269
+ * `Record<string, unknown>` — a real, well-known TypeScript limitation, not a design choice this
270
+ * file is working around loosely. `object` has no such requirement and accepts the same real
271
+ * interfaces plain assignment already does; `ContractPropsFrom` (`lib/primitive`) matches this
272
+ * field back out with the identical `object`-bounded pattern for the same reason.
273
+ */
274
+ function declareProps() {}
275
+ //#endregion
211
276
  //#region ../../lib/contract/src/aria/factories.ts
212
277
  /**
213
278
  * Builds a correctly-literal-typed `fixable: false` `AriaResult`. Exists so a rule author can
@@ -338,4 +403,4 @@ function mergeContracts(...contracts) {
338
403
  };
339
404
  }
340
405
  //#endregion
341
- export { activeContract, activeProps, createRemoveAttributeRule, disabledContract, disabledProps, expandedContract, expandedProps, invalidContract, invalidProps, invalidWithFix, invalidWithoutFix, loadingContract, loadingProps, makeStateNormalizer, mergeContracts, pressedContract, pressedProps, readonlyContract, readonlyProps, removeAttributeFix, selectedContract, selectedProps };
406
+ export { activeContract, activeProps, createRemoveAttributeRule, declareProps, defineContract, disabledContract, disabledProps, expandedContract, expandedProps, invalidContract, invalidProps, invalidWithFix, invalidWithoutFix, loadingContract, loadingProps, makeStateNormalizer, mergeContracts, pressedContract, pressedProps, readonlyContract, readonlyProps, removeAttributeFix, selectedContract, selectedProps };
@@ -138,10 +138,16 @@ const iterate = Object.freeze({
138
138
  function isString(value) {
139
139
  return typeof value === "string";
140
140
  }
141
+ function isNumber(value) {
142
+ return typeof value === "number";
143
+ }
141
144
  function isObject(value, excludeArrays = false) {
142
145
  if (value === null || typeof value !== "object") return false;
143
146
  return excludeArrays ? !Array.isArray(value) : true;
144
147
  }
148
+ function isUndefined(value) {
149
+ return value === void 0;
150
+ }
145
151
  //#endregion
146
152
  //#region ../../plugins/eslint/src/utils/ast.ts
147
153
  /** Narrows any node-shaped value to one whose `value` property is a string — the common check
@@ -158,7 +164,7 @@ function asArrayExpression(node) {
158
164
  function asNumericLiteral(node) {
159
165
  if (node?.type === "Literal") {
160
166
  const { value } = node;
161
- if (typeof value === "number") return value;
167
+ if (isNumber(value)) return value;
162
168
  }
163
169
  if (node?.type === "UnaryExpression") {
164
170
  const { operator, argument } = node;
@@ -195,7 +201,7 @@ function isFactoryCall(node, calleeNames) {
195
201
  }
196
202
  function extractVariantValues(node) {
197
203
  const valuesObj = asObjectExpression(node);
198
- if (!valuesObj) return void 0;
204
+ if (isUndefined(valuesObj)) return void 0;
199
205
  const values = /* @__PURE__ */ new Set();
200
206
  iterate.forEach(valuesObj.properties, (prop) => {
201
207
  if (prop.type !== "Property") return;
@@ -206,14 +212,14 @@ function extractVariantValues(node) {
206
212
  }
207
213
  function extractVariantMap(variantsNode) {
208
214
  const variantsObj = asObjectExpression(variantsNode);
209
- if (!variantsObj) return void 0;
215
+ if (isUndefined(variantsObj)) return void 0;
210
216
  const map = /* @__PURE__ */ new Map();
211
217
  iterate.forEach(variantsObj.properties, (prop) => {
212
218
  if (prop.type !== "Property") return;
213
219
  const key = getPropertyKey(prop);
214
- if (!key) return;
220
+ if (isUndefined(key)) return;
215
221
  const values = extractVariantValues(prop.value);
216
- if (!values) return;
222
+ if (isUndefined(values)) return;
217
223
  map.set(key, values);
218
224
  });
219
225
  return map;