@yahoo/uds-create-config 5.25.2 → 5.26.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.
- package/dist/configs/system.js +2 -0
- package/dist/entities/system/Component.d.ts +35 -2
- package/dist/entities/system/Component.js +224 -9
- package/dist/entities/system/Settings.js +1 -1
- package/dist/entities/system/StyleProperty.js +1 -1
- package/dist/entities/system/component-module.d.ts +17 -1
- package/dist/entities/system/component-module.js +54 -2
- package/dist/entities/system/defineComponent.d.ts +53 -13
- package/dist/entities/system/defineComponent.js +1 -0
- package/dist/entities/system/forward-route-invariants.js +1 -1
- package/dist/entities/system/icon-domain-invariants.js +1 -1
- package/dist/entities/system/root-layer-invariants.js +1 -1
- package/dist/entities/system/token-values.js +1 -1
- package/dist/framework/Config.d.ts +1 -1
- package/dist/framework/Config.js +1 -1
- package/dist/framework/class-names.d.ts +27 -1
- package/dist/framework/class-names.js +56 -3
- package/dist/framework/component-states.d.ts +40 -0
- package/dist/framework/component-states.js +53 -0
- package/dist/framework/interaction-conditions.d.ts +48 -0
- package/dist/framework/interaction-conditions.js +104 -0
- package/dist/framework/layer-styles.d.ts +5 -1
- package/dist/framework/layer-styles.js +18 -4
- package/dist/framework/projections.js +44 -2
- package/dist/framework/render-spec.js +14 -4
- package/dist/framework/schema-version.d.ts +1 -1
- package/dist/framework/schema-version.js +5 -5
- package/dist/framework/validate-spec.js +14 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +6 -4
- package/dist/migrations/20261013154037_component_states.d.ts +19 -0
- package/dist/migrations/20261013154037_component_states.js +74 -0
- package/dist/renderer/componentEntry.d.ts +7 -1
- package/dist/renderer/componentEntry.js +30 -1
- package/dist/renderer/forced-stamp.d.ts +26 -0
- package/dist/renderer/forced-stamp.js +50 -0
- package/dist/renderer/index.d.ts +3 -1
- package/dist/renderer/index.js +3 -1
- package/dist/renderer/use-forced-stamp.d.ts +11 -0
- package/dist/renderer/use-forced-stamp.js +50 -0
- package/dist/spec/specToJsx.d.ts +6 -0
- package/dist/spec/specToJsx.js +5 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -627,8 +627,35 @@ type DefaultsOf<P extends PropMap> = Partial<DeclaredProps<P>> & {
|
|
|
627
627
|
* `as` clause drops exactly the named keys and keeps the rest as declared.
|
|
628
628
|
*/
|
|
629
629
|
type Without<T, K extends PropertyKey> = string extends K ? T : { [Key in keyof T as Key extends K ? never : Key]: T[Key] };
|
|
630
|
+
/** One state a component declares in `.states()` — what its render knows and a style rule keys on.
|
|
631
|
+
* A boolean (`filled`), or a closed set of names with an optional default (`phase`). */
|
|
632
|
+
type StateDecl = {
|
|
633
|
+
readonly type: 'boolean';
|
|
634
|
+
} | {
|
|
635
|
+
readonly type: 'variant';
|
|
636
|
+
readonly value: readonly string[];
|
|
637
|
+
readonly default?: string;
|
|
638
|
+
};
|
|
639
|
+
/** A component's states, by name. */
|
|
640
|
+
type StateMap = Readonly<Record<string, StateDecl>>;
|
|
641
|
+
/** The value each state of `S` takes: a boolean's `true`/`false`, a variant's names. */
|
|
642
|
+
type StateValues<S> = { [K in keyof S]?: S[K] extends {
|
|
643
|
+
type: 'variant';
|
|
644
|
+
value: readonly (infer V)[];
|
|
645
|
+
} ? V : boolean };
|
|
646
|
+
/**
|
|
647
|
+
* How a render reports its states — `const layers = states(props, { filled: value !== '' })` — and
|
|
648
|
+
* spreads the bundles it hands back: `<Box {...layers.root}>`.
|
|
649
|
+
*
|
|
650
|
+
* A pure call, not a hook, so it can sit under a condition. It takes the render's `props` (the
|
|
651
|
+
* bundles at rest, or as Studio or a test forced them) and returns the bundles BUILT FOR those
|
|
652
|
+
* states, a fresh set: every rule naming a state, a prop a rule sets on a layer, the root's
|
|
653
|
+
* `data-<name>` attribute, a portal layer and a nested instance all follow the values given. A
|
|
654
|
+
* forced value still wins over them.
|
|
655
|
+
*/
|
|
656
|
+
type StatesHelper<S extends StateMap, L extends LayerMap = LayerMap> = (props: Record<keyof L & string, Record<string, unknown>>, values: StateValues<S>) => Record<keyof L & string, Record<string, unknown>>;
|
|
630
657
|
/** What a render receives: the generated per-layer bundle, plus the props the component declared. */
|
|
631
|
-
type RenderArgs<L extends LayerMap, P extends PropMap
|
|
658
|
+
type RenderArgs<L extends LayerMap, P extends PropMap, S extends StateMap = Record<never, never>> = {
|
|
632
659
|
readonly props: Record<keyof L & string, Record<string, unknown>>;
|
|
633
660
|
/**
|
|
634
661
|
* The ref the emitted module forwards to the render, for the element it chooses to attach it to.
|
|
@@ -637,7 +664,9 @@ type RenderArgs<L extends LayerMap, P extends PropMap> = {
|
|
|
637
664
|
* `<input ref={ref}>` in another — and which element is the render's decision.
|
|
638
665
|
*/
|
|
639
666
|
readonly ref?: Ref<any>;
|
|
640
|
-
} &
|
|
667
|
+
} & ([keyof S] extends [never] ? Record<never, never> : {
|
|
668
|
+
readonly states: StatesHelper<S, L>;
|
|
669
|
+
}) & DeclaredProps<P, L, unknown, 'render'> & Record<string, unknown>;
|
|
641
670
|
interface ComponentBuilder<L extends LayerMap, P extends PropMap,
|
|
642
671
|
/** Props the RENDER takes that the config doesn't describe — React handlers, mostly (`onPress`).
|
|
643
672
|
* They're part of the component's surface but not of its data, which is why they're declared at
|
|
@@ -650,7 +679,7 @@ D = Record<never, never>,
|
|
|
650
679
|
* `.forwards()` so `ComponentPropsOf` can union each forwarded layer's element props — `root` is
|
|
651
680
|
* already covered structurally by {@link ElementOf}, so for a root-only forward this changes no
|
|
652
681
|
* inferred type, which is what makes replacing `inherits` a no-op at every call site. */
|
|
653
|
-
F extends string = never
|
|
682
|
+
F extends string = never, /** The states `.states()` declared — what the render knows and reports, never a caller's prop. */S extends StateMap = Record<never, never>> extends Authored {
|
|
654
683
|
/**
|
|
655
684
|
* The phantom call signature — the authored value is never rendered (the build substitutes the
|
|
656
685
|
* emitted module), but a call site is checked against it. Generic over the element a call site
|
|
@@ -671,7 +700,7 @@ F extends string = never> extends Authored {
|
|
|
671
700
|
/** The authored body, readable so codegen can pair it with the render found in the source. */
|
|
672
701
|
readonly body: Record<string, unknown>;
|
|
673
702
|
readonly path?: string;
|
|
674
|
-
layers<const L2 extends LayerMap>(layers: L2): ComponentBuilder<L2, P, E, D, F>;
|
|
703
|
+
layers<const L2 extends LayerMap>(layers: L2): ComponentBuilder<L2, P, E, D, F, S>;
|
|
675
704
|
/**
|
|
676
705
|
* This component's props.
|
|
677
706
|
*
|
|
@@ -680,8 +709,19 @@ F extends string = never> extends Authored {
|
|
|
680
709
|
* provide. The callback and its `layer`/`bind` helpers are gone — they only ever split a path string
|
|
681
710
|
* that the parameter's own type now checks.
|
|
682
711
|
*/
|
|
683
|
-
props<const P2 extends PropMap<LayerPath<L>>>(props: P2 & NoInfer<{ [K in keyof P2]: Exact<P2[K], AuthoredProp<LayerPath<L>>> }>): ComponentBuilder<L, P2, E, D, F>;
|
|
684
|
-
|
|
712
|
+
props<const P2 extends PropMap<LayerPath<L>>>(props: P2 & NoInfer<{ [K in keyof P2]: Exact<P2[K], AuthoredProp<LayerPath<L>>> }>): ComponentBuilder<L, P2, E, D, F, S>;
|
|
713
|
+
/**
|
|
714
|
+
* What the render knows that a style rule keys on — the field has a value, the image loaded, the
|
|
715
|
+
* indicator is sliding: `{ filled: { type: 'boolean' } }`, `{ phase: { type: 'variant', value:
|
|
716
|
+
* ['idle', 'sliding'], default: 'idle' } }`.
|
|
717
|
+
*
|
|
718
|
+
* Not props, so not part of the component's API: the render passes its `props` and the values it
|
|
719
|
+
* knows to `const layers = states(props, { filled })` and spreads those layers in `props`' place;
|
|
720
|
+
* a rule naming a state in `when` follows the values given. A value a caller controls is a prop;
|
|
721
|
+
* the render reports the state from it.
|
|
722
|
+
*/
|
|
723
|
+
states<const S2 extends StateMap>(states: S2): ComponentBuilder<L, P, E, D, F, S2>;
|
|
724
|
+
styles(rules: readonly StyleRuleInput<L>[]): ComponentBuilder<L, P, E, D, F, S>;
|
|
685
725
|
/**
|
|
686
726
|
* What this component applies with nothing set.
|
|
687
727
|
*
|
|
@@ -692,7 +732,7 @@ F extends string = never> extends Authored {
|
|
|
692
732
|
* `const`, so an `as` keeps its literal type: without it `{ as: 'input' }` widens to `string` and
|
|
693
733
|
* there's no tag left to resolve the element's props against.
|
|
694
734
|
*/
|
|
695
|
-
defaultProps<const D2 extends DefaultsOf<P>>(defaults: D2): ComponentBuilder<L, P, E, D2, F>;
|
|
735
|
+
defaultProps<const D2 extends DefaultsOf<P>>(defaults: D2): ComponentBuilder<L, P, E, D2, F, S>;
|
|
696
736
|
/**
|
|
697
737
|
* The layers whose element props this component ALSO exposes, keyed by layer.
|
|
698
738
|
*
|
|
@@ -704,7 +744,7 @@ F extends string = never> extends Authored {
|
|
|
704
744
|
* source FROM stored config — every keyed collection there is one call with a record literal, and a
|
|
705
745
|
* second authored form would make generated source drift from what a person writes.
|
|
706
746
|
*/
|
|
707
|
-
forwards<const F2 extends Readonly<Partial<Record<keyof L & string, Forwarded>>>>(forwards: F2): ComponentBuilder<L, P, E, D, F | (keyof F2 & string)>;
|
|
747
|
+
forwards<const F2 extends Readonly<Partial<Record<keyof L & string, Forwarded>>>>(forwards: F2): ComponentBuilder<L, P, E, D, F | (keyof F2 & string), S>;
|
|
708
748
|
/**
|
|
709
749
|
* What a person is told about this component: the label a picker shows, and what it's for.
|
|
710
750
|
* `guidance` is for a model composing with it, in the imperative voice of a rule; the guidance
|
|
@@ -717,7 +757,7 @@ F extends string = never> extends Authored {
|
|
|
717
757
|
guidance?: string;
|
|
718
758
|
hidden?: boolean;
|
|
719
759
|
deprecated?: boolean;
|
|
720
|
-
}): ComponentBuilder<L, P, E, D, F>;
|
|
760
|
+
}): ComponentBuilder<L, P, E, D, F, S>;
|
|
721
761
|
/**
|
|
722
762
|
* The anatomy: which elements this component renders, in what order and nesting.
|
|
723
763
|
*
|
|
@@ -726,7 +766,7 @@ F extends string = never> extends Authored {
|
|
|
726
766
|
* it from the source and copies it verbatim, so what it returns is irrelevant; declaring it is
|
|
727
767
|
* what puts it in the file.
|
|
728
768
|
*/
|
|
729
|
-
render<E2 = E>(fn: (args: Without<RenderArgs<L, P>, keyof E2> & E2) => unknown): ComponentBuilder<L, P, E2, D, F>;
|
|
769
|
+
render<E2 = E>(fn: (args: Without<RenderArgs<L, P, S>, keyof E2> & E2) => unknown): ComponentBuilder<L, P, E2, D, F, S>;
|
|
730
770
|
/**
|
|
731
771
|
* An optional browser-canvas anatomy for a native component.
|
|
732
772
|
*
|
|
@@ -734,7 +774,7 @@ F extends string = never> extends Authored {
|
|
|
734
774
|
* Use this only when the phone anatomy is a poor fit for a browser canvas (a full-screen safe-area
|
|
735
775
|
* wrapper, for example). Like `render`, it is source-read build input and stores no config data.
|
|
736
776
|
*/
|
|
737
|
-
renderCanvas<E2 extends E = E>(fn: (args: Without<RenderArgs<L, P>, keyof E2> & E2) => unknown): ComponentBuilder<L, P, E, D, F>;
|
|
777
|
+
renderCanvas<E2 extends E = E>(fn: (args: Without<RenderArgs<L, P, S>, keyof E2> & E2) => unknown): ComponentBuilder<L, P, E, D, F, S>;
|
|
738
778
|
/**
|
|
739
779
|
* What a preview of this component STARTS from — the props a single tile is rendered with.
|
|
740
780
|
*
|
|
@@ -755,7 +795,7 @@ F extends string = never> extends Authored {
|
|
|
755
795
|
* so a preview can say a boolean is worth a column the way a rule would.
|
|
756
796
|
*/
|
|
757
797
|
when?: readonly PreviewCondition<P>[];
|
|
758
|
-
}): ComponentBuilder<L, P, E, D, F>;
|
|
798
|
+
}): ComponentBuilder<L, P, E, D, F, S>;
|
|
759
799
|
toStored(): Record<string, unknown>;
|
|
760
800
|
assignPath(path: string): void;
|
|
761
801
|
}
|
|
@@ -787,4 +827,4 @@ interface DefineComponent {
|
|
|
787
827
|
declare function definePropsFirst<const P extends PropMap<LayerPath>>(props: P & NoInfer<{ [K in keyof P]: Exact<P[K], AuthoredProp<LayerPath>> }>): ComponentBuilder<LayerMap, P>;
|
|
788
828
|
declare const defineComponent: DefineComponent;
|
|
789
829
|
//#endregion
|
|
790
|
-
export { AsInput, AuthoredComponent, AuthoredProp, ComponentBuilder, ComponentPropsOf, DefaultsOf, GlyphComponent, GlyphValue, LayerElement, LayerMap, PhantomCallProps, PhantomReadProps, PropCondition, PropDecl, PropMap, RegisteredMembers, RenderArgs, SlotCodeForm, SlotDecl, StyleRuleInput, defineComponent };
|
|
830
|
+
export { AsInput, AuthoredComponent, AuthoredProp, ComponentBuilder, ComponentPropsOf, DefaultsOf, GlyphComponent, GlyphValue, LayerElement, LayerMap, PhantomCallProps, PhantomReadProps, PropCondition, PropDecl, PropMap, RegisteredMembers, RenderArgs, SlotCodeForm, SlotDecl, StateMap, StyleRuleInput, defineComponent };
|
|
@@ -15,6 +15,7 @@ function build(body) {
|
|
|
15
15
|
path: void 0,
|
|
16
16
|
layers: (layers) => next({ layers: withEmittedRefs(layers) }),
|
|
17
17
|
props: (props) => next({ props }),
|
|
18
|
+
states: (states) => next({ states }),
|
|
18
19
|
styles: (rules) => next({ styles: rules }),
|
|
19
20
|
defaultProps: (defaults) => next({ defaultProps: defaults }),
|
|
20
21
|
forwards: (forwards) => next({ forwards }),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isRef, pathOf } from "../../framework/utils/refs.js";
|
|
2
|
-
import { isPlainRecord } from "./style-bag.js";
|
|
3
2
|
import { createSliceMemo } from "../../framework/memo.js";
|
|
3
|
+
import { isPlainRecord } from "./style-bag.js";
|
|
4
4
|
import { stylePropEntries } from "../../framework/projections.js";
|
|
5
5
|
import { forwardRoutes, intoName, routedPropIn, routesContent } from "../../framework/prop-surface.js";
|
|
6
6
|
//#region src/entities/system/forward-route-invariants.ts
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isRef, kindOf, memberOf, pathOf } from "../../framework/utils/refs.js";
|
|
2
|
+
import { createSliceMemo } from "../../framework/memo.js";
|
|
2
3
|
import { spellValues } from "../../framework/value-domain.js";
|
|
3
4
|
import { collectBag, isPlainRecord } from "./style-bag.js";
|
|
4
|
-
import { createSliceMemo } from "../../framework/memo.js";
|
|
5
5
|
import { glyphComponentOf, glyphLibrariesOf, memberLeaves, propValueDomain, refListOf, resolveComponentProps } from "../../framework/projections.js";
|
|
6
6
|
//#region src/entities/system/icon-domain-invariants.ts
|
|
7
7
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { isPlainRecord } from "./style-bag.js";
|
|
2
1
|
import { createSliceMemo } from "../../framework/memo.js";
|
|
2
|
+
import { isPlainRecord } from "./style-bag.js";
|
|
3
3
|
//#region src/entities/system/root-layer-invariants.ts
|
|
4
4
|
/**
|
|
5
5
|
* A component's layers include one named `root`. The name is what the anatomy's edge list starts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { collectRefs, kindOf, pathOf, ref, splitRef } from "../../framework/utils/refs.js";
|
|
2
|
-
import { parseRootFontSize } from "../../css/root-font-size.js";
|
|
3
2
|
import { createSliceMemo } from "../../framework/memo.js";
|
|
3
|
+
import { parseRootFontSize } from "../../css/root-font-size.js";
|
|
4
4
|
import { CSS_DEFAULTS, Settings } from "./Settings.js";
|
|
5
5
|
import { cssVar, declaresCssVariable, resolveTokenValue } from "../../framework/projections.js";
|
|
6
6
|
//#region src/entities/system/token-values.ts
|
|
@@ -73,7 +73,7 @@ interface RecordedSource {
|
|
|
73
73
|
* The dense-counter `2` a short-lived scheme stamped before the chain existed reads as the mint
|
|
74
74
|
* that replaced it (see `detectedWireVersion`).
|
|
75
75
|
*/
|
|
76
|
-
declare const SERIALIZED_CONFIG_VERSION =
|
|
76
|
+
declare const SERIALIZED_CONFIG_VERSION = 20261013154037;
|
|
77
77
|
interface SerializedConfig {
|
|
78
78
|
/** See {@link SERIALIZED_CONFIG_VERSION}. Always written; a stored artifact may lack it. */
|
|
79
79
|
readonly version: number;
|
package/dist/framework/Config.js
CHANGED
|
@@ -5749,7 +5749,7 @@ var Config = class Config {
|
|
|
5749
5749
|
* links from the provided sources. A declared `linkedKind` with no provided source throws. */
|
|
5750
5750
|
hydrateFrom(stored, options) {
|
|
5751
5751
|
const claimed = schemaVersionOf(stored);
|
|
5752
|
-
if (claimed >
|
|
5752
|
+
if (claimed > 20261013154037) throw new SchemaVersionTooNew(claimed);
|
|
5753
5753
|
const wire = upgradeSerializedConfig(stored);
|
|
5754
5754
|
if (!Array.isArray(wire.ownedKinds) || typeof wire.items !== "object" || wire.items === null) throw new ConfigFormatError(`Config "${stored.name ?? "system-config"}" cannot be hydrated: this is not a serialized config-v2 config (no "ownedKinds"/"items"). A pre-cutover build artifact converts through the registered cutover migration — import the config type's module (e.g. \`configs/system\`) before hydrating, or port the repo once with \`uds migrate\`.`);
|
|
5755
5755
|
const leftover = wire.options;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Config } from "./Config.js";
|
|
2
|
+
import { ComponentState, UDS_FORCE_STATES, componentStateDefaults, componentStates } from "./component-states.js";
|
|
2
3
|
|
|
3
4
|
//#region src/framework/class-names.d.ts
|
|
4
5
|
/** `HStack` → `hstack`, `AvatarItem` → `avatar-item`. A dash only between a lower/digit and an
|
|
@@ -13,6 +14,17 @@ declare function kebabComponent(name: string): string;
|
|
|
13
14
|
* nothing when one side skips the kebab-casing.
|
|
14
15
|
*/
|
|
15
16
|
declare function forceStateAttribute(modifier: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* The selector a forced state's stylesheet twin matches: the force attribute naming `owner`, the
|
|
19
|
+
* component whose rule it is (`[data-create-force-hover~="Button"]`).
|
|
20
|
+
*
|
|
21
|
+
* The attribute's value is the list of components forced on that element, by path. A component
|
|
22
|
+
* whose root IS another component's instance shares that element, so a bare `[attr]` lit the inner
|
|
23
|
+
* component's hover in the outer one's Hover column; naming the owner keeps each column to the
|
|
24
|
+
* component it belongs to. Without an owner (a global style, a shared utility) any forced element
|
|
25
|
+
* matches.
|
|
26
|
+
*/
|
|
27
|
+
declare function forceStateSelector(modifier: string, owner?: string): string;
|
|
16
28
|
/**
|
|
17
29
|
* The attribute that forces one mode group on an element and its subtree:
|
|
18
30
|
* `data-create-force-mode-<group>="<option>"`. The group is lowercased and a nested group's slashes
|
|
@@ -75,6 +87,20 @@ declare function componentLayerClass(config: Config, path: string, layer: string
|
|
|
75
87
|
* the cheaper problem.
|
|
76
88
|
*/
|
|
77
89
|
declare function ruleCondition(when: Record<string, unknown> | null | undefined): string;
|
|
90
|
+
/**
|
|
91
|
+
* The attribute a state rides on the root — `filled` → `data-filled`, `isLoaded` → `data-is-loaded`.
|
|
92
|
+
* A boolean is present when true and absent otherwise; a variant holds its value. The emitted module
|
|
93
|
+
* spells it with the same transform, since a stylesheet keyed on one spelling matches nothing the
|
|
94
|
+
* render wrote in another.
|
|
95
|
+
*/
|
|
96
|
+
declare function componentStateAttribute(name: string): string;
|
|
97
|
+
/**
|
|
98
|
+
* The attributes a root carries for its states' values — `{ filled: true }` → `{ 'data-filled': '' }`.
|
|
99
|
+
* A state given no value takes its declared default; one that is false or unset carries nothing.
|
|
100
|
+
* What a projection that has the values up front (the canvas) writes, and what the emitted module's
|
|
101
|
+
* `states` helper writes from the render's.
|
|
102
|
+
*/
|
|
103
|
+
declare function componentStateAttributes(states: readonly ComponentState[], values: Readonly<Record<string, unknown>>): Record<string, string>;
|
|
78
104
|
/** One layer's conditional class — the rule it comes from, and the props that have to hold. */
|
|
79
105
|
interface ComponentRuleClass {
|
|
80
106
|
/** The layer the class lands on. */
|
|
@@ -362,4 +388,4 @@ declare function modifierUtilitiesUsed(config: Config): Set<string>;
|
|
|
362
388
|
*/
|
|
363
389
|
declare function splitVariants(cls: string): string[];
|
|
364
390
|
//#endregion
|
|
365
|
-
export { ComponentRuleClass, PickedComposite, SplitStyleProps, arbitraryClassName, arbitraryNeedsClass, classLeaf, classSelector, componentClassBase, componentCompositeClasses, componentLayerClass, componentMotionClasses, componentPropClasses, componentPropComposites, componentRuleClasses, forceModeAttribute, forceModeAttributeOf, forceModeProp, forceModePropGroup, forceModePropValue, forceModeProps, forceStateAttribute, inlineRuleComposites, isForceModeProp, kebabComponent, layerJsMotionPresets, layerRoutedProps, modifierUtilitiesUsed, motionClassName, ruleCondition, splitStyleProps, splitVariants, stylePropClassBase, stylePropClassName, stylePropClasses, stylePropOffScaleLiteral, stylePropOwnClasses, styleRuleMotionClasses };
|
|
391
|
+
export { ComponentRuleClass, PickedComposite, SplitStyleProps, arbitraryClassName, arbitraryNeedsClass, classLeaf, classSelector, componentClassBase, componentCompositeClasses, componentLayerClass, componentMotionClasses, componentPropClasses, componentPropComposites, componentRuleClasses, componentStateAttribute, componentStateAttributes, forceModeAttribute, forceModeAttributeOf, forceModeProp, forceModePropGroup, forceModePropValue, forceModeProps, forceStateAttribute, forceStateSelector, inlineRuleComposites, isForceModeProp, kebabComponent, layerJsMotionPresets, layerRoutedProps, modifierUtilitiesUsed, motionClassName, ruleCondition, splitStyleProps, splitVariants, stylePropClassBase, stylePropClassName, stylePropClasses, stylePropOffScaleLiteral, stylePropOwnClasses, styleRuleMotionClasses };
|
|
@@ -2,6 +2,7 @@ import { isRef, kindOf, memberOf, pathOf, refLeaf, refOfKey } from "./utils/refs
|
|
|
2
2
|
import { forwardsOf, rootLayerOf } from "../entities/system/element.js";
|
|
3
3
|
import { createSliceMemo } from "./memo.js";
|
|
4
4
|
import { classPrefixOf, prefixed, varPrefixOf } from "../entities/system/Settings.js";
|
|
5
|
+
import "./component-states.js";
|
|
5
6
|
import { storedPreview } from "./preview-store.js";
|
|
6
7
|
import { authoredValue, className, isCustomPropertyKey, negatedCssValue, renderStyleValue, resolveComponentProps, stylePropEntries, stylePropLeafForToken, stylePropNumberLeaf, stylePropNumberUnit, stylePropValueLeaves, stylePropertyPathFor } from "./projections.js";
|
|
7
8
|
import { inlineMotionId, isInlineMotion, jsMotionBody } from "../entities/system/motion-variants.js";
|
|
@@ -45,6 +46,31 @@ function forceStateAttribute(modifier) {
|
|
|
45
46
|
return `${FORCE_ATTRIBUTE_PREFIX}${kebabComponent(name.slice(name.lastIndexOf("/") + 1))}`;
|
|
46
47
|
}
|
|
47
48
|
/**
|
|
49
|
+
* The selector a forced state's stylesheet twin matches: the force attribute naming `owner`, the
|
|
50
|
+
* component whose rule it is (`[data-create-force-hover~="Button"]`).
|
|
51
|
+
*
|
|
52
|
+
* The attribute's value is the list of components forced on that element, by path. A component
|
|
53
|
+
* whose root IS another component's instance shares that element, so a bare `[attr]` lit the inner
|
|
54
|
+
* component's hover in the outer one's Hover column; naming the owner keeps each column to the
|
|
55
|
+
* component it belongs to. Without an owner (a global style, a shared utility) any forced element
|
|
56
|
+
* matches.
|
|
57
|
+
*/
|
|
58
|
+
function forceStateSelector(modifier, owner) {
|
|
59
|
+
const attribute = forceStateAttribute(modifier);
|
|
60
|
+
return owner === void 0 ? `[${attribute}]` : `[${attribute}~=${JSON.stringify(owner)}]`;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The force attributes a pin bag asks for: one per pseudo-state pinned on (`{ _active: true }` →
|
|
64
|
+
* `data-create-force-active`), each naming `owner`, the component it's forced on (see
|
|
65
|
+
* {@link forceStateSelector}). A component state in the same bag (`dragging`) is the render's, not
|
|
66
|
+
* the stylesheet's, so it's left out.
|
|
67
|
+
*/
|
|
68
|
+
function forcedModifierAttributes(pins, owner = "") {
|
|
69
|
+
const attrs = {};
|
|
70
|
+
for (const [key, value] of Object.entries(pins ?? {})) if (key.startsWith("_") && !key.includes("@") && (value === true || value === "true")) attrs[forceStateAttribute(key)] = owner;
|
|
71
|
+
return attrs;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
48
74
|
* The attribute that forces one mode group on an element and its subtree:
|
|
49
75
|
* `data-create-force-mode-<group>="<option>"`. The group is lowercased and a nested group's slashes
|
|
50
76
|
* become dashes, since an attribute name can hold neither. The emitter admits this attribute beside
|
|
@@ -137,6 +163,31 @@ function ruleCondition(when) {
|
|
|
137
163
|
return Object.entries(when ?? {}).map(([prop, value]) => `${prop}-${String(value).replaceAll("/", "-").replaceAll(".", "_").replace(/\s+/g, "-")}`).join("-");
|
|
138
164
|
}
|
|
139
165
|
/**
|
|
166
|
+
* The attribute a state rides on the root — `filled` → `data-filled`, `isLoaded` → `data-is-loaded`.
|
|
167
|
+
* A boolean is present when true and absent otherwise; a variant holds its value. The emitted module
|
|
168
|
+
* spells it with the same transform, since a stylesheet keyed on one spelling matches nothing the
|
|
169
|
+
* render wrote in another.
|
|
170
|
+
*/
|
|
171
|
+
function componentStateAttribute(name) {
|
|
172
|
+
return `data-${kebabComponent(name)}`;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* The attributes a root carries for its states' values — `{ filled: true }` → `{ 'data-filled': '' }`.
|
|
176
|
+
* A state given no value takes its declared default; one that is false or unset carries nothing.
|
|
177
|
+
* What a projection that has the values up front (the canvas) writes, and what the emitted module's
|
|
178
|
+
* `states` helper writes from the render's.
|
|
179
|
+
*/
|
|
180
|
+
function componentStateAttributes(states, values) {
|
|
181
|
+
const out = {};
|
|
182
|
+
for (const state of states) {
|
|
183
|
+
const value = values[state.name] ?? state.default;
|
|
184
|
+
if (value === void 0 || value === null || value === false) continue;
|
|
185
|
+
if (value === "false" && state.type === "boolean") continue;
|
|
186
|
+
out[componentStateAttribute(state.name)] = state.type === "boolean" ? "" : String(value);
|
|
187
|
+
}
|
|
188
|
+
return out;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
140
191
|
* Every conditional class a component's style rules produce.
|
|
141
192
|
*
|
|
142
193
|
* Unconditional rules are absent by design: they land on the layer's own zero-specificity selector
|
|
@@ -171,15 +222,17 @@ function computeRuleClasses(config, path) {
|
|
|
171
222
|
const base = componentClassBase(config, path);
|
|
172
223
|
const out = [];
|
|
173
224
|
for (const rule of Object.values(styles)) {
|
|
174
|
-
const when = Object.fromEntries(Object.entries(rule.when ?? {}).map(([prop, value]) => [prop, refLeaf(value)]));
|
|
225
|
+
const when = Object.fromEntries(Object.entries(rule.when ?? {}).filter(([prop]) => !prop.startsWith("_")).map(([prop, value]) => [prop, refLeaf(value)]));
|
|
175
226
|
const condition = ruleCondition(when);
|
|
176
227
|
if (!condition) continue;
|
|
177
228
|
for (const layer of Object.keys(rule.layers ?? {})) {
|
|
178
229
|
if (!(layer in layers)) continue;
|
|
230
|
+
const className = `${base}-${layer}-${condition}`;
|
|
231
|
+
if (out.some((entry) => entry.className === className)) continue;
|
|
179
232
|
out.push({
|
|
180
233
|
layer,
|
|
181
234
|
when,
|
|
182
|
-
className
|
|
235
|
+
className
|
|
183
236
|
});
|
|
184
237
|
}
|
|
185
238
|
}
|
|
@@ -1024,4 +1077,4 @@ function splitVariants(cls) {
|
|
|
1024
1077
|
return segments;
|
|
1025
1078
|
}
|
|
1026
1079
|
//#endregion
|
|
1027
|
-
export { arbitraryClassName, arbitraryNeedsClass, classLeaf, classSelector, componentClassBase, componentCompositeClasses, componentLayerClass, componentMotionClasses, componentPropClasses, componentPropComposites, componentRuleClasses, forceModeAttribute, forceModeAttributeOf, forceModeProp, forceModePropGroup, forceModePropValue, forceModeProps, forceStateAttribute, inlineRuleComposites, isForceModeProp, kebabComponent, layerJsMotionPresets, layerRoutedProps, modifierUtilitiesUsed, motionClassName, ruleCondition, splitStyleProps, splitVariants, stylePropClassBase, stylePropClassName, stylePropClasses, stylePropOffScaleLiteral, stylePropOwnClasses, styleRuleMotionClasses };
|
|
1080
|
+
export { arbitraryClassName, arbitraryNeedsClass, classLeaf, classSelector, componentClassBase, componentCompositeClasses, componentLayerClass, componentMotionClasses, componentPropClasses, componentPropComposites, componentRuleClasses, componentStateAttribute, componentStateAttributes, forceModeAttribute, forceModeAttributeOf, forceModeProp, forceModePropGroup, forceModePropValue, forceModeProps, forceStateAttribute, forceStateSelector, forcedModifierAttributes, inlineRuleComposites, isForceModeProp, kebabComponent, layerJsMotionPresets, layerRoutedProps, modifierUtilitiesUsed, motionClassName, ruleCondition, splitStyleProps, splitVariants, stylePropClassBase, stylePropClassName, stylePropClasses, stylePropOffScaleLiteral, stylePropOwnClasses, styleRuleMotionClasses };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Config } from "./Config.js";
|
|
2
|
+
|
|
3
|
+
//#region src/framework/component-states.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* One STATE a component declares (its `states`): what the render knows ("the field has a value",
|
|
6
|
+
* "the image loaded") and a style rule keys on. Not a prop — a caller never passes one.
|
|
7
|
+
*
|
|
8
|
+
* The render reports its states with the `states(…)` call it is handed, in its body before it reads a
|
|
9
|
+
* layer bundle; the module builds each bundle when the render first reads it, so a rule naming a
|
|
10
|
+
* state is tested like any prop, against the state's value: forced, else reported, else its rest
|
|
11
|
+
* value ({@link componentStateDefaults}). The root also carries each state as a `data-<name>`
|
|
12
|
+
* attribute ({@link componentStateAttribute}). Studio and tests force a state through the module's
|
|
13
|
+
* internal channel ({@link UDS_FORCE_STATES}), which wins over the render's value.
|
|
14
|
+
*/
|
|
15
|
+
interface ComponentState {
|
|
16
|
+
readonly name: string;
|
|
17
|
+
readonly type: 'boolean' | 'variant';
|
|
18
|
+
/** A variant state's names, in declared order. */
|
|
19
|
+
readonly values?: readonly string[];
|
|
20
|
+
/** The value the root carries when neither the render nor a forced value gives one — a variant
|
|
21
|
+
* state's declared default. A boolean's rest is `false`, which is no attribute. */
|
|
22
|
+
readonly default?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The one key a forced state reaches an emitted module under: `{ [UDS_FORCE_STATES]: { filled: true } }`.
|
|
26
|
+
* Internal on purpose: Studio's grid and canvas and a test force a state through it, and no caller's
|
|
27
|
+
* typed props mention it, so forcing a state is never part of a component's API.
|
|
28
|
+
*/
|
|
29
|
+
declare const UDS_FORCE_STATES = "__udsForceStates";
|
|
30
|
+
/** The states a component declares, in declared order — see {@link ComponentState}. Its OWN: a
|
|
31
|
+
* state belongs to the render that sets it, so one a layer's component declares is that layer's. */
|
|
32
|
+
declare function componentStates(config: Config, path: string): readonly ComponentState[];
|
|
33
|
+
/**
|
|
34
|
+
* What each of a component's states reads at rest — a boolean's `false`, a variant's declared
|
|
35
|
+
* default — which every surface lays a reported or forced value over before testing a rule. A
|
|
36
|
+
* variant state with no default has no rest value, so a rule naming one of its values waits for one.
|
|
37
|
+
*/
|
|
38
|
+
declare function componentStateDefaults(config: Config, path: string): Record<string, string | boolean>;
|
|
39
|
+
//#endregion
|
|
40
|
+
export { ComponentState, UDS_FORCE_STATES, componentStateDefaults, componentStates };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createSliceMemo } from "./memo.js";
|
|
2
|
+
//#region src/framework/component-states.ts
|
|
3
|
+
/**
|
|
4
|
+
* The one key a forced state reaches an emitted module under: `{ [UDS_FORCE_STATES]: { filled: true } }`.
|
|
5
|
+
* Internal on purpose: Studio's grid and canvas and a test force a state through it, and no caller's
|
|
6
|
+
* typed props mention it, so forcing a state is never part of a component's API.
|
|
7
|
+
*/
|
|
8
|
+
const UDS_FORCE_STATES = "__udsForceStates";
|
|
9
|
+
/** The states a component declares, in declared order — see {@link ComponentState}. Its OWN: a
|
|
10
|
+
* state belongs to the render that sets it, so one a layer's component declares is that layer's. */
|
|
11
|
+
function componentStates(config, path) {
|
|
12
|
+
const cache = statesCache(config);
|
|
13
|
+
const hit = cache.get(path);
|
|
14
|
+
if (hit) return hit;
|
|
15
|
+
const states = computeStates(config, path);
|
|
16
|
+
cache.set(path, states);
|
|
17
|
+
return states;
|
|
18
|
+
}
|
|
19
|
+
/** Per component slice, the states of each component asked for so far — an expansion asks once per
|
|
20
|
+
* root it paints. */
|
|
21
|
+
const statesCache = createSliceMemo({
|
|
22
|
+
kinds: ["component"],
|
|
23
|
+
compute: (_config) => /* @__PURE__ */ new Map()
|
|
24
|
+
});
|
|
25
|
+
function computeStates(config, path) {
|
|
26
|
+
const body = config.resolve("component", path)?.toJSON();
|
|
27
|
+
if (!body?.states || typeof body.states !== "object") return [];
|
|
28
|
+
const out = [];
|
|
29
|
+
for (const [name, decl] of Object.entries(body.states)) if (decl?.type === "boolean") out.push({
|
|
30
|
+
name,
|
|
31
|
+
type: "boolean"
|
|
32
|
+
});
|
|
33
|
+
else if (decl?.type === "variant" && Array.isArray(decl.value)) out.push({
|
|
34
|
+
name,
|
|
35
|
+
type: "variant",
|
|
36
|
+
values: decl.value.map(String),
|
|
37
|
+
...typeof decl.default === "string" ? { default: decl.default } : {}
|
|
38
|
+
});
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* What each of a component's states reads at rest — a boolean's `false`, a variant's declared
|
|
43
|
+
* default — which every surface lays a reported or forced value over before testing a rule. A
|
|
44
|
+
* variant state with no default has no rest value, so a rule naming one of its values waits for one.
|
|
45
|
+
*/
|
|
46
|
+
function componentStateDefaults(config, path) {
|
|
47
|
+
const out = {};
|
|
48
|
+
for (const state of componentStates(config, path)) if (state.type === "boolean") out[state.name] = false;
|
|
49
|
+
else if (state.default !== void 0) out[state.name] = state.default;
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
export { UDS_FORCE_STATES, componentStateDefaults, componentStates };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Config } from "./Config.js";
|
|
2
|
+
|
|
3
|
+
//#region src/framework/interaction-conditions.d.ts
|
|
4
|
+
/** One interaction a rule keys on: the bag key, the modifier it resolves to, and whose it is. */
|
|
5
|
+
interface InteractionCondition {
|
|
6
|
+
/** As written in `when`: `_hover`. */
|
|
7
|
+
readonly key: string;
|
|
8
|
+
/** The modifier's path: `state/hover`. */
|
|
9
|
+
readonly modifier: string;
|
|
10
|
+
/** The layer whose interaction it is — the root for `true`. */
|
|
11
|
+
readonly layer: string;
|
|
12
|
+
}
|
|
13
|
+
/** Whether `modifier` (a path or leaf) can be a rule's interaction condition. */
|
|
14
|
+
declare function isInteractionModifier(config: Config, modifier: string): boolean;
|
|
15
|
+
/** The `when` key an interaction modifier is written under: its leaf, `_hover`. */
|
|
16
|
+
declare function interactionKey(modifier: string): string;
|
|
17
|
+
/** Every interaction modifier the config declares, by path. */
|
|
18
|
+
declare function interactionModifiers(config: Config): ReadonlySet<string>;
|
|
19
|
+
/**
|
|
20
|
+
* A rule's condition split in two: the props and states the render tests, and the interactions the
|
|
21
|
+
* stylesheet (or, for what CSS can't do, the module's own event tracking) holds it under.
|
|
22
|
+
*/
|
|
23
|
+
declare function whenParts({
|
|
24
|
+
config,
|
|
25
|
+
when,
|
|
26
|
+
layers
|
|
27
|
+
}: {
|
|
28
|
+
config: Config;
|
|
29
|
+
when: Readonly<Record<string, unknown>> | null | undefined; /** The component's layers, for the root an interaction's `true` names. */
|
|
30
|
+
layers?: Readonly<Record<string, unknown>>;
|
|
31
|
+
}): {
|
|
32
|
+
props: Record<string, unknown>;
|
|
33
|
+
interactions: InteractionCondition[];
|
|
34
|
+
};
|
|
35
|
+
/** Whether `when` names any interaction — what tells the readers a rule holds only under one. */
|
|
36
|
+
declare function hasInteraction(config: Config, when: Readonly<Record<string, unknown>> | null | undefined): boolean;
|
|
37
|
+
/** One interaction a component's rules key on, with the selector that tells whether it holds. */
|
|
38
|
+
interface ComponentInteraction extends InteractionCondition {
|
|
39
|
+
/** The modifier's own selector, without its leading `&` (`:hover`): what `element.matches` asks. */
|
|
40
|
+
readonly selector: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Every interaction a component's rules key on, once per key and layer — what a surface that renders
|
|
44
|
+
* the component from its config, with no module to track them, watches on the live element.
|
|
45
|
+
*/
|
|
46
|
+
declare function componentInteractions(config: Config, path: string): ComponentInteraction[];
|
|
47
|
+
//#endregion
|
|
48
|
+
export { ComponentInteraction, InteractionCondition, componentInteractions, hasInteraction, interactionKey, interactionModifiers, isInteractionModifier, whenParts };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { modifierCategory } from "../entities/system/modifier-axis.js";
|
|
2
|
+
import { rootLayerOf } from "../entities/system/element.js";
|
|
3
|
+
import { createSliceMemo } from "./memo.js";
|
|
4
|
+
//#region src/framework/interaction-conditions.ts
|
|
5
|
+
/**
|
|
6
|
+
* A style rule's interaction conditions: `when: { _hover: true }`, "while the component is hovered".
|
|
7
|
+
*
|
|
8
|
+
* A rule's `when` names props and states, which the render knows. An interaction is the browser's —
|
|
9
|
+
* the pointer is over an element, it is pressed, it has focus — and a rule may key on one the same
|
|
10
|
+
* way, so the rule's whole reach (styles, an instance's props, content) applies while it holds. The
|
|
11
|
+
* value names whose interaction it is: `true` is the component's own root, and a layer name is that
|
|
12
|
+
* layer (`_hover: 'closeButton'`, "while the close button is hovered").
|
|
13
|
+
*
|
|
14
|
+
* Only an interaction modifier qualifies: a selector modifier that isn't a mode and names a state of
|
|
15
|
+
* the element itself (`:hover`, `[aria-checked="true"]`), never a pseudo-element (`::placeholder`)
|
|
16
|
+
* or a relation to another element (`[data-group]:hover &`).
|
|
17
|
+
*/
|
|
18
|
+
/** Whether `modifier` (a path or leaf) can be a rule's interaction condition. */
|
|
19
|
+
function isInteractionModifier(config, modifier) {
|
|
20
|
+
return interactionModifiers(config).has(config.resolve("modifier", modifier)?.path ?? "");
|
|
21
|
+
}
|
|
22
|
+
/** The `when` key an interaction modifier is written under: its leaf, `_hover`. */
|
|
23
|
+
function interactionKey(modifier) {
|
|
24
|
+
return `_${modifier.slice(modifier.lastIndexOf("/") + 1)}`;
|
|
25
|
+
}
|
|
26
|
+
/** Every interaction modifier the config declares, by path. */
|
|
27
|
+
function interactionModifiers(config) {
|
|
28
|
+
return interactionMemo(config);
|
|
29
|
+
}
|
|
30
|
+
const interactionMemo = createSliceMemo({
|
|
31
|
+
kinds: ["modifier"],
|
|
32
|
+
compute: (config) => {
|
|
33
|
+
const paths = /* @__PURE__ */ new Set();
|
|
34
|
+
for (const modifier of config.list("modifier")) {
|
|
35
|
+
const { type, value } = modifier.toJSON() ?? {};
|
|
36
|
+
if (type !== "selector" || typeof value !== "string") continue;
|
|
37
|
+
if (modifierCategory(config, modifier.path) === "mode") continue;
|
|
38
|
+
const own = value.trim().replace(/^&/, "");
|
|
39
|
+
if (own.includes("::") || own.includes("&") || /\s/.test(own)) continue;
|
|
40
|
+
paths.add(modifier.path);
|
|
41
|
+
}
|
|
42
|
+
return paths;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
/**
|
|
46
|
+
* A rule's condition split in two: the props and states the render tests, and the interactions the
|
|
47
|
+
* stylesheet (or, for what CSS can't do, the module's own event tracking) holds it under.
|
|
48
|
+
*/
|
|
49
|
+
function whenParts({ config, when, layers }) {
|
|
50
|
+
const props = {};
|
|
51
|
+
const interactions = [];
|
|
52
|
+
const root = layers ? rootLayerOf(layers)[0] : "root";
|
|
53
|
+
for (const [key, value] of Object.entries(when ?? {})) {
|
|
54
|
+
const modifier = key.startsWith("_") ? config.resolve("modifier", key.slice(1)) : void 0;
|
|
55
|
+
if (modifier && interactionModifiers(config).has(modifier.path)) {
|
|
56
|
+
if (value === false || value === "false" || value == null) continue;
|
|
57
|
+
interactions.push({
|
|
58
|
+
key,
|
|
59
|
+
modifier: modifier.path,
|
|
60
|
+
layer: value === true || value === "true" ? root : String(value)
|
|
61
|
+
});
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
props[key] = value;
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
props,
|
|
68
|
+
interactions
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** Whether `when` names any interaction — what tells the readers a rule holds only under one. */
|
|
72
|
+
function hasInteraction(config, when) {
|
|
73
|
+
return whenParts({
|
|
74
|
+
config,
|
|
75
|
+
when
|
|
76
|
+
}).interactions.length > 0;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Every interaction a component's rules key on, once per key and layer — what a surface that renders
|
|
80
|
+
* the component from its config, with no module to track them, watches on the live element.
|
|
81
|
+
*/
|
|
82
|
+
function componentInteractions(config, path) {
|
|
83
|
+
const body = config.resolve("component", path)?.toJSON();
|
|
84
|
+
const out = [];
|
|
85
|
+
for (const rule of Object.values(body?.styles ?? {})) {
|
|
86
|
+
const { interactions } = whenParts({
|
|
87
|
+
config,
|
|
88
|
+
when: rule.when,
|
|
89
|
+
layers: body?.layers
|
|
90
|
+
});
|
|
91
|
+
for (const interaction of interactions) {
|
|
92
|
+
if (out.some((seen) => seen.key === interaction.key && seen.layer === interaction.layer)) continue;
|
|
93
|
+
const value = (config.resolve("modifier", interaction.modifier)?.toJSON())?.value;
|
|
94
|
+
if (typeof value !== "string") continue;
|
|
95
|
+
out.push({
|
|
96
|
+
...interaction,
|
|
97
|
+
selector: value.trim().replace(/^&/, "")
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
//#endregion
|
|
104
|
+
export { componentInteractions, hasInteraction, interactionKey, interactionModifiers, isInteractionModifier, whenParts };
|
|
@@ -2,7 +2,11 @@ import { Config } from "./Config.js";
|
|
|
2
2
|
|
|
3
3
|
//#region src/framework/layer-styles.d.ts
|
|
4
4
|
/** Whether every prop a rule names holds the value it asks for. Compared as strings, because a
|
|
5
|
-
* condition is canonicalized into the rule's key and a variant value is a string there.
|
|
5
|
+
* condition is canonicalized into the rule's key and a variant value is a string there.
|
|
6
|
+
*
|
|
7
|
+
* An interaction (`_hover`, see `interaction-conditions`) holds only when the state says it does —
|
|
8
|
+
* `{ _hover: true }`, a surface forcing it — whichever layer the rule names: no prop bag is ever
|
|
9
|
+
* hovered, so at rest the rule is out. */
|
|
6
10
|
declare function ruleApplies(when: Record<string, unknown> | null | undefined, state: Record<string, unknown>): boolean;
|
|
7
11
|
interface LayerStyleOptions {
|
|
8
12
|
/**
|