@yahoo/uds-create-config 5.19.1 → 5.20.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/documentation.js +1 -1
- package/dist/configs/system.d.ts +607 -2
- package/dist/configs/system.js +6 -0
- package/dist/entities/documentation/Documentation.d.ts +1 -1
- package/dist/entities/system/Component.js +1 -2
- package/dist/entities/system/LinkedSystem.d.ts +17 -2
- package/dist/entities/system/Modifier.d.ts +5 -2
- package/dist/entities/system/Modifier.js +5 -2
- package/dist/entities/system/Motion.d.ts +578 -4
- package/dist/entities/system/Motion.js +233 -70
- package/dist/entities/system/css-motion-reach.d.ts +62 -0
- package/dist/entities/system/css-motion-reach.js +195 -0
- package/dist/entities/system/css-motion.d.ts +55 -0
- package/dist/entities/system/css-motion.js +445 -0
- package/dist/entities/system/css-timing.d.ts +40 -0
- package/dist/entities/system/css-timing.js +247 -0
- package/dist/entities/system/easings.d.ts +29 -0
- package/dist/entities/system/easings.js +99 -0
- package/dist/entities/system/motion-convert.d.ts +42 -0
- package/dist/entities/system/motion-convert.js +517 -0
- package/dist/entities/system/motion-presets.d.ts +9 -1
- package/dist/entities/system/motion-presets.js +27 -38
- package/dist/entities/system/motion-variants.js +66 -49
- package/dist/entities/system/reduced-motion.d.ts +20 -0
- package/dist/entities/system/reduced-motion.js +42 -0
- package/dist/framework/Config.d.ts +7 -2
- package/dist/framework/Config.js +47 -5
- package/dist/framework/class-names.d.ts +2 -1
- package/dist/framework/class-names.js +31 -13
- package/dist/framework/defineEntity.d.ts +3 -1
- package/dist/framework/defineEntity.js +2 -0
- package/dist/framework/registered.d.ts +8 -1
- package/dist/framework/registry.d.ts +38 -1
- package/dist/framework/registry.js +3 -1
- package/dist/framework/render-spec.js +7 -2
- package/dist/framework/schema-version.d.ts +1 -1
- package/dist/framework/schema-version.js +5 -5
- package/dist/framework/signature.js +4 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/migrations/2.0.0/v1-artifact.d.ts +606 -2
- package/dist/migrations/20261009000514_css_motion_states.d.ts +11 -0
- package/dist/migrations/20261009000514_css_motion_states.js +10 -0
- package/dist/migrations/20261009022722_starting_style_modifier.d.ts +9 -0
- package/dist/migrations/20261009022722_starting_style_modifier.js +10 -0
- package/dist/migrations/20261009181635_css_motion_strict.d.ts +23 -0
- package/dist/migrations/20261009181635_css_motion_strict.js +152 -0
- package/dist/spec/motion.d.ts +7 -0
- package/dist/spec/motion.js +7 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -2
|
@@ -71,6 +71,11 @@ const MOTION_VARIANT_STATES = [
|
|
|
71
71
|
"whileDrag"
|
|
72
72
|
];
|
|
73
73
|
const jsBody = (config, path) => jsMotionBody(config, path);
|
|
74
|
+
/** A named CSS preset's body. */
|
|
75
|
+
const cssBody = (config, path) => {
|
|
76
|
+
const body = config.resolve("motion", path)?.toJSON();
|
|
77
|
+
return body?.runtime === "css" ? body : void 0;
|
|
78
|
+
};
|
|
74
79
|
/** The variant names one state holds: a name, a list of names, or none (values, `false`). */
|
|
75
80
|
function stateVariantNames(value) {
|
|
76
81
|
if (typeof value === "string") return [value];
|
|
@@ -123,46 +128,55 @@ const composedComponents = (config, path) => {
|
|
|
123
128
|
*/
|
|
124
129
|
const motionVariantGroups = createSliceMemo({
|
|
125
130
|
kinds: ["component", "motion"],
|
|
126
|
-
compute: (config) => {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
131
|
+
compute: (config) => variantGroupsOf({
|
|
132
|
+
config,
|
|
133
|
+
placements: placedMotions(config)
|
|
134
|
+
})
|
|
135
|
+
});
|
|
136
|
+
/**
|
|
137
|
+
* The presets that share variant names among `placements`, closed over placement and composition as
|
|
138
|
+
* {@link motionVariantGroups} describes. The JS presets' groups and the CSS presets' are each this,
|
|
139
|
+
* over their own placements.
|
|
140
|
+
*/
|
|
141
|
+
function variantGroupsOf({ config, placements }) {
|
|
142
|
+
const parent = /* @__PURE__ */ new Map();
|
|
143
|
+
const find = (node) => {
|
|
144
|
+
let root = node;
|
|
145
|
+
while (parent.has(root) && parent.get(root) !== root) root = parent.get(root);
|
|
146
|
+
parent.set(node, root);
|
|
147
|
+
return root;
|
|
148
|
+
};
|
|
149
|
+
const join = (a, b) => {
|
|
150
|
+
const ra = find(a);
|
|
151
|
+
const rb = find(b);
|
|
152
|
+
if (ra !== rb) parent.set(ra, rb);
|
|
153
|
+
};
|
|
154
|
+
const placing = /* @__PURE__ */ new Set();
|
|
155
|
+
for (const { component, preset } of placements) {
|
|
156
|
+
placing.add(component);
|
|
157
|
+
join(`c\u0000${component}`, `p\u0000${preset}`);
|
|
158
|
+
}
|
|
159
|
+
for (const component of placing) {
|
|
160
|
+
const seen = new Set([component]);
|
|
161
|
+
const queue = composedComponents(config, component);
|
|
162
|
+
while (queue.length > 0) {
|
|
163
|
+
const next = queue.shift();
|
|
164
|
+
if (seen.has(next)) continue;
|
|
165
|
+
seen.add(next);
|
|
166
|
+
if (placing.has(next)) join(`c\u0000${component}`, `c\u0000${next}`);
|
|
167
|
+
queue.push(...composedComponents(config, next));
|
|
162
168
|
}
|
|
163
|
-
return [...groups.values()];
|
|
164
169
|
}
|
|
165
|
-
|
|
170
|
+
const groups = /* @__PURE__ */ new Map();
|
|
171
|
+
for (const node of parent.keys()) {
|
|
172
|
+
if (node[0] !== "p") continue;
|
|
173
|
+
const root = find(node);
|
|
174
|
+
const group = groups.get(root) ?? /* @__PURE__ */ new Set();
|
|
175
|
+
group.add(node.slice(2));
|
|
176
|
+
groups.set(root, group);
|
|
177
|
+
}
|
|
178
|
+
return [...groups.values()];
|
|
179
|
+
}
|
|
166
180
|
/** The presets that share variant names with `preset` — itself alone when no rule places it. */
|
|
167
181
|
function motionVariantGroupOf(config, preset) {
|
|
168
182
|
return motionVariantGroups(config).find((group) => group.has(preset)) ?? new Set([preset]);
|
|
@@ -181,22 +195,25 @@ function renamedBody(body, from, to) {
|
|
|
181
195
|
}
|
|
182
196
|
const mentions = (body, name) => declaredVariants(body).includes(name) || namedVariants(body).includes(name);
|
|
183
197
|
/**
|
|
184
|
-
* Rename a variant across every preset that shares names with `path` ({@link motionVariantGroupOf}
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
* two would become one
|
|
198
|
+
* Rename a variant across every preset that shares names with `path` ({@link motionVariantGroupOf},
|
|
199
|
+
* or `groupOf` for a CSS preset, whose presets share names among themselves): the key in each
|
|
200
|
+
* `variants` that declares it, and each state that names it. Refused when `path` is no preset that
|
|
201
|
+
* declares `from`, or when a preset in the set already declares `to`, since the two would become one
|
|
202
|
+
* variant.
|
|
188
203
|
*/
|
|
189
|
-
function renameMotionVariant(config, { path, from, to }) {
|
|
190
|
-
const
|
|
191
|
-
|
|
204
|
+
function renameMotionVariant(config, { path, from, to, groupOf }) {
|
|
205
|
+
const css = cssBody(config, path) !== void 0;
|
|
206
|
+
const bodyOf = css ? cssBody : jsBody;
|
|
207
|
+
const own = bodyOf(config, path);
|
|
208
|
+
if (!own) reject("NOT_FOUND", `"${path}" is not a motion preset.`);
|
|
192
209
|
if (!declaredVariants(own).includes(from)) reject("NOT_FOUND", `"${path}" declares no variant "${from}".`);
|
|
193
210
|
if (from === to) return config;
|
|
194
|
-
const group = [...motionVariantGroupOf(config, path)];
|
|
195
|
-
const taken = group.filter((preset) => declaredVariants(
|
|
211
|
+
const group = [...css && groupOf ? groupOf(config, path) : motionVariantGroupOf(config, path)];
|
|
212
|
+
const taken = group.filter((preset) => declaredVariants(bodyOf(config, preset)).includes(to));
|
|
196
213
|
if (taken.length > 0) reject("ALREADY_EXISTS", `"${to}" is already a variant of ${taken.map((preset) => `"${preset}"`).join(", ")}, which animate${taken.length === 1 ? "s" : ""} with "${path}". Renaming "${from}" onto it would make the two one variant.`);
|
|
197
214
|
let next = config;
|
|
198
215
|
for (const preset of group) {
|
|
199
|
-
const body =
|
|
216
|
+
const body = bodyOf(next, preset);
|
|
200
217
|
if (!body || !mentions(body, from)) continue;
|
|
201
218
|
const inline = inlineMotions(next).valid.find((entry) => entry.id === preset);
|
|
202
219
|
if (!inline) {
|
|
@@ -267,4 +284,4 @@ function orphanedMotionVariants(config) {
|
|
|
267
284
|
return out;
|
|
268
285
|
}
|
|
269
286
|
//#endregion
|
|
270
|
-
export { MOTION_VARIANT_STATES, declaredVariants, inlineMotionId, inlineMotions, isInlineMotion, jsMotionBody, motionVariantGroups, orphanedMotionVariants, placedMotions, renameMotionVariant, stateVariantNames };
|
|
287
|
+
export { MOTION_VARIANT_STATES, declaredVariants, inlineMotionId, inlineMotions, isInlineMotion, jsMotionBody, motionVariantGroups, orphanedMotionVariants, placedMotions, renameMotionVariant, stateVariantNames, variantGroupsOf };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Config } from "../../framework/Config.js";
|
|
2
|
+
|
|
3
|
+
//#region src/entities/system/reduced-motion.d.ts
|
|
4
|
+
/** The attribute a layer carries its reduced motion as. */
|
|
5
|
+
declare const REDUCED_MOTION_ATTRIBUTE = "data-uds-reduced-motion";
|
|
6
|
+
/** What a layer's motion does under the viewer's reduced-motion setting. */
|
|
7
|
+
declare const REDUCED_MOTION_VALUES: readonly ["user", "always", "never"];
|
|
8
|
+
type ReducedMotion = (typeof REDUCED_MOTION_VALUES)[number];
|
|
9
|
+
/** A style rule setting a layer's reduced motion. */
|
|
10
|
+
interface ReducedMotionLayer {
|
|
11
|
+
readonly component: string;
|
|
12
|
+
/** The rule's key: its condition, `*` for the unconditional rule. */
|
|
13
|
+
readonly rule: string;
|
|
14
|
+
readonly layer: string;
|
|
15
|
+
readonly value: ReducedMotion;
|
|
16
|
+
}
|
|
17
|
+
/** Every style rule that sets a layer's reduced motion. */
|
|
18
|
+
declare function reducedMotionLayers(config: Config): readonly ReducedMotionLayer[];
|
|
19
|
+
//#endregion
|
|
20
|
+
export { REDUCED_MOTION_ATTRIBUTE, REDUCED_MOTION_VALUES, ReducedMotion, ReducedMotionLayer, reducedMotionLayers };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { isPlainObject } from "../../framework/utils/field-path.js";
|
|
2
|
+
import { createSliceMemo } from "../../framework/memo.js";
|
|
3
|
+
//#region src/entities/system/reduced-motion.ts
|
|
4
|
+
/** The style rule key a layer's reduced motion is set by. */
|
|
5
|
+
const REDUCED_MOTION_KEY = "reducedMotion";
|
|
6
|
+
/** The attribute a layer carries its reduced motion as. */
|
|
7
|
+
const REDUCED_MOTION_ATTRIBUTE = "data-uds-reduced-motion";
|
|
8
|
+
/** What a layer's motion does under the viewer's reduced-motion setting. */
|
|
9
|
+
const REDUCED_MOTION_VALUES = [
|
|
10
|
+
"user",
|
|
11
|
+
"always",
|
|
12
|
+
"never"
|
|
13
|
+
];
|
|
14
|
+
function isReducedMotion(value) {
|
|
15
|
+
return REDUCED_MOTION_VALUES.includes(value);
|
|
16
|
+
}
|
|
17
|
+
const resolveReducedMotion = createSliceMemo({
|
|
18
|
+
kinds: ["component"],
|
|
19
|
+
compute: (config) => {
|
|
20
|
+
const layers = [];
|
|
21
|
+
for (const component of config.list("component")) {
|
|
22
|
+
const { styles = {} } = component.toJSON();
|
|
23
|
+
for (const [rule, entry] of Object.entries(styles)) for (const [layer, bag] of Object.entries(entry?.layers ?? {})) {
|
|
24
|
+
if (!isPlainObject(bag) || !(REDUCED_MOTION_KEY in bag)) continue;
|
|
25
|
+
const value = bag[REDUCED_MOTION_KEY];
|
|
26
|
+
if (isReducedMotion(value)) layers.push({
|
|
27
|
+
component: component.path,
|
|
28
|
+
rule,
|
|
29
|
+
layer,
|
|
30
|
+
value
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return layers;
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
/** Every style rule that sets a layer's reduced motion. */
|
|
38
|
+
function reducedMotionLayers(config) {
|
|
39
|
+
return resolveReducedMotion(config);
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
export { REDUCED_MOTION_ATTRIBUTE, REDUCED_MOTION_VALUES, isReducedMotion, reducedMotionLayers };
|
|
@@ -9,7 +9,7 @@ import { RefGraph, TraverseOptions } from "./ref-graph.js";
|
|
|
9
9
|
import { FieldsSchema } from "./utils/field-schema.js";
|
|
10
10
|
import { SubEntityClass } from "./defineSubEntity.js";
|
|
11
11
|
import { LocalOverlay, SourceAnswer, SourceOverlay, SourceResolver, SourceState } from "./sources.js";
|
|
12
|
-
import { EntityKind } from "./registry.js";
|
|
12
|
+
import { EntityKind, LayerKey } from "./registry.js";
|
|
13
13
|
import { AuthoredEntry } from "./utils/group.js";
|
|
14
14
|
import { EntityClass } from "./defineEntity.js";
|
|
15
15
|
import { BreakPlan, LinkPlan, SourceRowState, UnlinkPlan } from "../entities/system/link-plan.js";
|
|
@@ -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 = 20261009181635;
|
|
77
77
|
interface SerializedConfig {
|
|
78
78
|
/** See {@link SERIALIZED_CONFIG_VERSION}. Always written; a stored artifact may lack it. */
|
|
79
79
|
readonly version: number;
|
|
@@ -667,6 +667,11 @@ declare class Config {
|
|
|
667
667
|
* was registered into this instance. `undefined` for a kind this config doesn't know. The single
|
|
668
668
|
* accessor every internal read routes through. */
|
|
669
669
|
entityOf(kind: string): EntityKind | undefined;
|
|
670
|
+
/** Every key a style rule's layer bag takes that a kind owns, with the kind that owns it. See
|
|
671
|
+
* {@link LayerKey}. */
|
|
672
|
+
layerKeys(): ReadonlyMap<string, LayerKey & {
|
|
673
|
+
readonly kind: string;
|
|
674
|
+
}>;
|
|
670
675
|
/**
|
|
671
676
|
* The value each of a kind's fields takes when a create leaves it out — `responsive: false` on a
|
|
672
677
|
* style property, say.
|
package/dist/framework/Config.js
CHANGED
|
@@ -55,6 +55,9 @@ function nameEdgeReaders(entities) {
|
|
|
55
55
|
for (const [kind, entry] of entities) if (entry.nameEdges) readers.set(kind, entry.nameEdges);
|
|
56
56
|
return readers;
|
|
57
57
|
}
|
|
58
|
+
/** {@link Config.layerKeys}, once per set of registered kinds, which every config built on one
|
|
59
|
+
* registration shares. */
|
|
60
|
+
const layerKeysByEntities = /* @__PURE__ */ new WeakMap();
|
|
58
61
|
/**
|
|
59
62
|
* The kinds whose items name an external source — what a `@slug` in a ref resolves against.
|
|
60
63
|
*
|
|
@@ -812,6 +815,19 @@ var Config = class Config {
|
|
|
812
815
|
entityOf(kind) {
|
|
813
816
|
return this.state.entities.get(kind);
|
|
814
817
|
}
|
|
818
|
+
/** Every key a style rule's layer bag takes that a kind owns, with the kind that owns it. See
|
|
819
|
+
* {@link LayerKey}. */
|
|
820
|
+
layerKeys() {
|
|
821
|
+
const cached = layerKeysByEntities.get(this.state.entities);
|
|
822
|
+
if (cached) return cached;
|
|
823
|
+
const out = /* @__PURE__ */ new Map();
|
|
824
|
+
for (const [kind, entity] of this.state.entities) for (const [key, declared] of Object.entries(entity.layerKeys ?? {})) out.set(key, {
|
|
825
|
+
...declared,
|
|
826
|
+
kind
|
|
827
|
+
});
|
|
828
|
+
layerKeysByEntities.set(this.state.entities, out);
|
|
829
|
+
return out;
|
|
830
|
+
}
|
|
815
831
|
/**
|
|
816
832
|
* The value each of a kind's fields takes when a create leaves it out — `responsive: false` on a
|
|
817
833
|
* style property, say.
|
|
@@ -1868,14 +1884,24 @@ var Config = class Config {
|
|
|
1868
1884
|
kind: patch.kind,
|
|
1869
1885
|
path: pathOf(extension)
|
|
1870
1886
|
}) : void 0;
|
|
1871
|
-
const written = applyPathDelta(current ? extendBody(
|
|
1887
|
+
const written = applyPathDelta(current ? extendBody(rebasedLinkedBody({
|
|
1888
|
+
entry,
|
|
1889
|
+
path: pathOf(extension),
|
|
1890
|
+
body: current.source,
|
|
1891
|
+
extend: current.overlay
|
|
1892
|
+
}), current.overlay) : resolved.toJSON(), delta, isField);
|
|
1872
1893
|
const local = { ...current ? current.overlay : extension.extend };
|
|
1873
1894
|
for (const field of touched) if (Object.hasOwn(delta, field) && delta[field] === null) delete local[field];
|
|
1874
1895
|
else if (Object.hasOwn(written, field)) local[field] = written[field];
|
|
1875
1896
|
else delete local[field];
|
|
1876
1897
|
const sourceBody = this.configFor(extension)?.resolve(patch.kind, pathOf(extension))?.toJSON();
|
|
1877
1898
|
if (!sourceBody) reject("NOT_FOUND", `Cannot edit ${patch.kind} "${patch.path}" — its source "${extension.__ref}" is unavailable`);
|
|
1878
|
-
const candidate = extendBody(
|
|
1899
|
+
const candidate = extendBody(rebasedLinkedBody({
|
|
1900
|
+
entry,
|
|
1901
|
+
path: pathOf(extension),
|
|
1902
|
+
body: sourceBody,
|
|
1903
|
+
extend: local
|
|
1904
|
+
}), local);
|
|
1879
1905
|
const createSchema = wholeBodySchema(entry, { config: this });
|
|
1880
1906
|
let merged = candidate;
|
|
1881
1907
|
if (createSchema) {
|
|
@@ -2610,12 +2636,19 @@ var Config = class Config {
|
|
|
2610
2636
|
if (!base) return void 0;
|
|
2611
2637
|
const slug = sourceOf(extension);
|
|
2612
2638
|
const owns = (kindName) => source?.ownedKinds().includes(kindName) ?? false;
|
|
2613
|
-
const
|
|
2639
|
+
const inherited = slug ? qualifyRefs({
|
|
2614
2640
|
value: base.toJSON(),
|
|
2615
2641
|
source: slug,
|
|
2616
2642
|
owns,
|
|
2617
2643
|
namesItAlready: (target) => this.borrowsExactly(target, slug)
|
|
2618
|
-
}) : base.toJSON()
|
|
2644
|
+
}) : base.toJSON();
|
|
2645
|
+
const extend = extension.extend ?? {};
|
|
2646
|
+
const merged = extendBody(rebasedLinkedBody({
|
|
2647
|
+
entry: this.entityOf(kind),
|
|
2648
|
+
path: pathOf(extension),
|
|
2649
|
+
body: inherited,
|
|
2650
|
+
extend
|
|
2651
|
+
}), extend);
|
|
2619
2652
|
return this.entityOf(kind)?.prepareLinkedBody?.({
|
|
2620
2653
|
path: pathOf(extension),
|
|
2621
2654
|
body: merged,
|
|
@@ -5425,7 +5458,7 @@ var Config = class Config {
|
|
|
5425
5458
|
* links from the provided sources. A declared `linkedKind` with no provided source throws. */
|
|
5426
5459
|
hydrateFrom(stored, options) {
|
|
5427
5460
|
const claimed = schemaVersionOf(stored);
|
|
5428
|
-
if (claimed >
|
|
5461
|
+
if (claimed > 20261009181635) throw new SchemaVersionTooNew(claimed);
|
|
5429
5462
|
const wire = upgradeSerializedConfig(stored);
|
|
5430
5463
|
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\`.`);
|
|
5431
5464
|
const leftover = wire.options;
|
|
@@ -5590,6 +5623,15 @@ function extendBody(base, delta) {
|
|
|
5590
5623
|
* differently, and why the source's body is a parameter. `extend: { values: [Token.ref('surface')] }`
|
|
5591
5624
|
* means "alongside what they already accept", which has nothing to join without their list.
|
|
5592
5625
|
*/
|
|
5626
|
+
/** A source's body as an extension reads it before its `extend` merges in: the kind's
|
|
5627
|
+
* {@link EntityKind.rebaseLinkedBody}, where it declares one. */
|
|
5628
|
+
function rebasedLinkedBody({ entry, path, body, extend }) {
|
|
5629
|
+
return entry?.rebaseLinkedBody?.({
|
|
5630
|
+
path,
|
|
5631
|
+
body,
|
|
5632
|
+
extend
|
|
5633
|
+
}) ?? body;
|
|
5634
|
+
}
|
|
5593
5635
|
function overlayBody({ overlay, theirs }) {
|
|
5594
5636
|
if (!overlay) return void 0;
|
|
5595
5637
|
return layerOverlay({
|
|
@@ -123,7 +123,8 @@ declare function motionClassName(config: Config, path: string): string;
|
|
|
123
123
|
* rule declares. Without this the value went nowhere — `motion` names no style property so it isn't
|
|
124
124
|
* a declaration, and it IS a declared prop of the layer's element so it isn't an attribute either.
|
|
125
125
|
*
|
|
126
|
-
* A JS preset is skipped: it animates in the client and has no class to wear.
|
|
126
|
+
* A JS preset is skipped: it animates in the client and has no class to wear. The classes come in the
|
|
127
|
+
* order the rules apply, so the last one that applies is the preset the layer plays.
|
|
127
128
|
*/
|
|
128
129
|
declare function componentMotionClasses(config: Config, path: string): ComponentRuleClass[];
|
|
129
130
|
/**
|
|
@@ -277,6 +277,12 @@ function componentCompositeClasses(config, path) {
|
|
|
277
277
|
}
|
|
278
278
|
return out;
|
|
279
279
|
}
|
|
280
|
+
/** The bag keys a kind owns as props of a type, as an element's declared props spell them. */
|
|
281
|
+
function ownedLayerProps(config) {
|
|
282
|
+
const out = {};
|
|
283
|
+
for (const [key, owned] of config.layerKeys()) if (owned.propType !== void 0) out[key] = { type: owned.propType };
|
|
284
|
+
return out;
|
|
285
|
+
}
|
|
280
286
|
/** The class a CSS motion preset is applied by. Namespaced, so it doesn't share a space with the
|
|
281
287
|
* style-prop utilities; the `@keyframes` it runs keep their own name. */
|
|
282
288
|
function motionClassName(config, path) {
|
|
@@ -293,25 +299,30 @@ function motionClassName(config, path) {
|
|
|
293
299
|
* rule declares. Without this the value went nowhere — `motion` names no style property so it isn't
|
|
294
300
|
* a declaration, and it IS a declared prop of the layer's element so it isn't an attribute either.
|
|
295
301
|
*
|
|
296
|
-
* A JS preset is skipped: it animates in the client and has no class to wear.
|
|
302
|
+
* A JS preset is skipped: it animates in the client and has no class to wear. The classes come in the
|
|
303
|
+
* order the rules apply, so the last one that applies is the preset the layer plays.
|
|
297
304
|
*/
|
|
298
305
|
function componentMotionClasses(config, path) {
|
|
299
306
|
const component = config.resolve("component", path);
|
|
300
307
|
if (!component) return [];
|
|
301
308
|
const { layers = {}, styles = {} } = component.toJSON();
|
|
302
309
|
const out = [];
|
|
303
|
-
|
|
310
|
+
const rules = Object.values(styles);
|
|
311
|
+
const unconditional = (rule) => !rule.when || Object.keys(rule.when).length === 0;
|
|
312
|
+
for (const rule of [...rules.filter(unconditional), ...rules.filter((rule) => !unconditional(rule))]) {
|
|
304
313
|
const when = Object.fromEntries(Object.entries(rule.when ?? {}).map(([prop, value]) => [prop, refLeaf(value)]));
|
|
305
314
|
for (const [layer, bag] of Object.entries(rule.layers ?? {})) {
|
|
306
315
|
const element = layers[layer];
|
|
307
|
-
if (
|
|
308
|
-
const declared =
|
|
316
|
+
if (element === void 0) continue;
|
|
317
|
+
const declared = {
|
|
318
|
+
...ownedLayerProps(config),
|
|
319
|
+
...isRef(element) ? resolveComponentProps(config, pathOf(element.__ref)) : {}
|
|
320
|
+
};
|
|
309
321
|
for (const [prop, value] of Object.entries(bag ?? {})) {
|
|
310
322
|
if (declared[prop]?.type !== "motion") continue;
|
|
311
323
|
const name = presetNamed(value);
|
|
312
324
|
if (name === void 0) continue;
|
|
313
|
-
|
|
314
|
-
if (preset?.runtime !== "css" || !preset.keyframes) continue;
|
|
325
|
+
if ((config.resolve("motion", name)?.toJSON())?.runtime !== "css") continue;
|
|
315
326
|
out.push({
|
|
316
327
|
layer,
|
|
317
328
|
when,
|
|
@@ -530,8 +541,19 @@ function layerRoutedProps(config, path, state = {}) {
|
|
|
530
541
|
const declared = isRef(element) ? resolveComponentProps(config, pathOf(element.__ref)) : {};
|
|
531
542
|
for (const [prop, value] of Object.entries(bag ?? {})) {
|
|
532
543
|
if (prop.startsWith("_") || isCustomPropertyKey(prop)) continue;
|
|
533
|
-
|
|
534
|
-
|
|
544
|
+
const owned = config.layerKeys().get(prop);
|
|
545
|
+
if (owned) {
|
|
546
|
+
const reach = owned.reach({
|
|
547
|
+
config,
|
|
548
|
+
value,
|
|
549
|
+
element
|
|
550
|
+
});
|
|
551
|
+
if (reach === "none") continue;
|
|
552
|
+
if (reach !== "prop") {
|
|
553
|
+
out[layer] ??= {};
|
|
554
|
+
out[layer][reach.attribute] = value;
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
535
557
|
}
|
|
536
558
|
const styleKey = stylePropertyPathFor({
|
|
537
559
|
config,
|
|
@@ -600,11 +622,7 @@ function styleRuleMotionClasses(config, path, state = {}) {
|
|
|
600
622
|
...state
|
|
601
623
|
};
|
|
602
624
|
const out = {};
|
|
603
|
-
for (const rule of componentMotionClasses(config, path))
|
|
604
|
-
if (!ruleApplies(rule.when, resolved)) continue;
|
|
605
|
-
out[rule.layer] ??= [];
|
|
606
|
-
if (!out[rule.layer].includes(rule.className)) out[rule.layer].push(rule.className);
|
|
607
|
-
}
|
|
625
|
+
for (const rule of componentMotionClasses(config, path)) if (ruleApplies(rule.when, resolved)) out[rule.layer] = [rule.className];
|
|
608
626
|
return out;
|
|
609
627
|
}
|
|
610
628
|
/**
|
|
@@ -355,7 +355,9 @@ declare function defineEntity<F extends FieldsSchema, G extends FieldsSchema = z
|
|
|
355
355
|
* an edit to one has nowhere to land.
|
|
356
356
|
*/
|
|
357
357
|
bulkOverlayWrite?: (args: BulkOverlayWrite) => Record<string, unknown>; /** Adapt a pinned source body before linked validation. */
|
|
358
|
-
prepareLinkedBody?: EntityKind['prepareLinkedBody'];
|
|
358
|
+
prepareLinkedBody?: EntityKind['prepareLinkedBody']; /** See {@link EntityKind.rebaseLinkedBody}. */
|
|
359
|
+
rebaseLinkedBody?: EntityKind['rebaseLinkedBody']; /** See {@link EntityKind.layerKeys}. */
|
|
360
|
+
layerKeys?: EntityKind['layerKeys'];
|
|
359
361
|
/**
|
|
360
362
|
* Edges this kind's data implies by name rather than by ref, and how to rewrite one — the pair that
|
|
361
363
|
* makes a record keyed by another kind's item names behave like a reference: reported by
|
|
@@ -124,6 +124,8 @@ function defineEntity(spec) {
|
|
|
124
124
|
bulkDirectives: spec.bulkDirectives,
|
|
125
125
|
bulkOverlayWrite: spec.bulkOverlayWrite,
|
|
126
126
|
prepareLinkedBody: spec.prepareLinkedBody,
|
|
127
|
+
rebaseLinkedBody: spec.rebaseLinkedBody,
|
|
128
|
+
layerKeys: spec.layerKeys,
|
|
127
129
|
nameEdges: spec.nameEdges,
|
|
128
130
|
styleBags: spec.styleBags,
|
|
129
131
|
rewriteName: spec.rewriteName,
|
|
@@ -74,6 +74,13 @@ interface RegisteredEntities {}
|
|
|
74
74
|
* arguments a locally authored component would.
|
|
75
75
|
*/
|
|
76
76
|
interface RegisteredComponentContracts {}
|
|
77
|
+
/**
|
|
78
|
+
* Qualified linked motion preset → the runtimes a link can read it as: its own, and the other one when
|
|
79
|
+
* the preset converts (`motion/convert`). The consumer's emitted `uds-env.d.ts` fills it from the
|
|
80
|
+
* installed source config, so a link that swaps a preset to a runtime it can't convert to is a type
|
|
81
|
+
* error at the swap. Unaugmented, a swap takes either runtime.
|
|
82
|
+
*/
|
|
83
|
+
interface RegisteredMotionRuntimes {}
|
|
77
84
|
/** The generated contract for one qualified linked component, or an open fallback before build. */
|
|
78
85
|
type ComponentContractOf<P extends string> = P extends keyof RegisteredComponentContracts ? RegisteredComponentContracts[P] : {
|
|
79
86
|
layers: Record<string, unknown>;
|
|
@@ -100,4 +107,4 @@ type PathOf<K extends string> = K extends keyof RegisteredPaths ? RegisteredPath
|
|
|
100
107
|
/** The value type for a style prop — its registered union, else `string`. */
|
|
101
108
|
type ValueOf<P extends string> = P extends keyof RegisteredStyleProps ? RegisteredStyleProps[P] extends string | number ? RegisteredStyleProps[P] : string : string;
|
|
102
109
|
//#endregion
|
|
103
|
-
export { ComponentContractOf, PathOf, RegisteredComponentContracts, RegisteredEntities, RegisteredModifiers, RegisteredPaths, RegisteredStyleProps, ValueOf };
|
|
110
|
+
export { ComponentContractOf, PathOf, RegisteredComponentContracts, RegisteredEntities, RegisteredModifiers, RegisteredMotionRuntimes, RegisteredPaths, RegisteredStyleProps, ValueOf };
|
|
@@ -47,8 +47,45 @@ type AuthoredValueForm = (ctx: {
|
|
|
47
47
|
readonly target: string; /** The leaves the target offers, which is what the form has to answer with. */
|
|
48
48
|
readonly leaves: readonly string[];
|
|
49
49
|
}) => z.ZodTypeAny | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* A key a style rule's layer bag takes that a kind owns, rather than a prop the element declares or a
|
|
52
|
+
* style property: `motion` names a preset, `reducedMotion` says how the layer treats the OS setting.
|
|
53
|
+
*/
|
|
54
|
+
interface LayerKey {
|
|
55
|
+
/** What the key takes. `declared` is the element's own prop of the same name, where it declares
|
|
56
|
+
* one, which the key's value can also be. */
|
|
57
|
+
readonly schema: (declared: z.ZodTypeAny | undefined) => z.ZodTypeAny;
|
|
58
|
+
/**
|
|
59
|
+
* How the value reaches the layer's element: as the element's prop of the key's name (`'prop'`),
|
|
60
|
+
* as an attribute (`{ attribute }`), or by some other route the build writes, such as a class the
|
|
61
|
+
* stylesheet emits (`'none'`).
|
|
62
|
+
*/
|
|
63
|
+
readonly reach: (args: {
|
|
64
|
+
readonly config: Config; /** The value as the rule stores it. */
|
|
65
|
+
readonly value: unknown; /** The layer's element: a component ref, or a tag. */
|
|
66
|
+
readonly element: unknown;
|
|
67
|
+
}) => 'prop' | 'none' | {
|
|
68
|
+
readonly attribute: string;
|
|
69
|
+
};
|
|
70
|
+
/** The prop type the key stands for on every element, for a reader that finds an element's props by
|
|
71
|
+
* type: `motion`'s is `'motion'`, so a layer whose element declares no `motion` prop still plays a
|
|
72
|
+
* preset a rule names. */
|
|
73
|
+
readonly propType?: string;
|
|
74
|
+
}
|
|
50
75
|
interface EntityKind {
|
|
51
76
|
readonly name: string;
|
|
77
|
+
/**
|
|
78
|
+
* A source's body as this config reads it under a linked extension, before the extension's `extend`
|
|
79
|
+
* merges in: for a kind whose extension can change what the rest of the body means, such as a motion
|
|
80
|
+
* preset whose `runtime` the extension sets.
|
|
81
|
+
*/
|
|
82
|
+
readonly rebaseLinkedBody?: (args: {
|
|
83
|
+
readonly path: string;
|
|
84
|
+
readonly body: Record<string, unknown>;
|
|
85
|
+
readonly extend: Record<string, unknown>;
|
|
86
|
+
}) => Record<string, unknown>;
|
|
87
|
+
/** The keys a style rule's layer bag takes that this kind owns, by key. See {@link LayerKey}. */
|
|
88
|
+
readonly layerKeys?: Readonly<Record<string, LayerKey>>;
|
|
52
89
|
/** Adapt a pinned source body before this kind validates a linked extension. */
|
|
53
90
|
readonly prepareLinkedBody?: (args: {
|
|
54
91
|
readonly path: string;
|
|
@@ -333,4 +370,4 @@ interface EntityKind {
|
|
|
333
370
|
* kind def a `Config` holds (`config.entityOf(kind)`); `undefined` → no ops. */
|
|
334
371
|
declare function opsForKind(entry: EntityKind | undefined, config?: Config): DerivedOp[];
|
|
335
372
|
//#endregion
|
|
336
|
-
export { AuthoredValueForm, ComputedFields, ComputedInput, EntityKind, Linkable, opsForKind };
|
|
373
|
+
export { AuthoredValueForm, ComputedFields, ComputedInput, EntityKind, LayerKey, Linkable, opsForKind };
|
|
@@ -32,7 +32,7 @@ function titleCase(kind) {
|
|
|
32
32
|
/** Derive a kind's immutable metadata from its field schemas — the value a `defineEntity`/
|
|
33
33
|
* `defineDerivedEntity` attaches to its class and a `Config.register` stores in `#entities`. */
|
|
34
34
|
function buildKind(spec) {
|
|
35
|
-
const { name, prepareLinkedBody, fields, groupFields, subEntities = {}, computed, label, labelPlural, schemas: userSchemas, derived, sourceIdentity, sourceOrigin, authoredRefs, linkable, localGroupFields, emergentGroups, singleton = false, bareMember, sourceResolution, bulkAdopts, bulkOverlay, bulkDirectives, bulkOverlayWrite, nameEdges, rewriteName, styleBags, uniqueLeaves, authoredValue, mentionFields, invariants } = spec;
|
|
35
|
+
const { name, prepareLinkedBody, rebaseLinkedBody, layerKeys, fields, groupFields, subEntities = {}, computed, label, labelPlural, schemas: userSchemas, derived, sourceIdentity, sourceOrigin, authoredRefs, linkable, localGroupFields, emergentGroups, singleton = false, bareMember, sourceResolution, bulkAdopts, bulkOverlay, bulkDirectives, bulkOverlayWrite, nameEdges, rewriteName, styleBags, uniqueLeaves, authoredValue, mentionFields, invariants } = spec;
|
|
36
36
|
const allComputed = {
|
|
37
37
|
sources: ({ member, config }) => sourceSlugs(member, config),
|
|
38
38
|
borrowedFrom: ({ config, path }) => config.borrowedFrom(name, path),
|
|
@@ -49,6 +49,8 @@ function buildKind(spec) {
|
|
|
49
49
|
return {
|
|
50
50
|
name,
|
|
51
51
|
...prepareLinkedBody ? { prepareLinkedBody } : {},
|
|
52
|
+
...rebaseLinkedBody ? { rebaseLinkedBody } : {},
|
|
53
|
+
...layerKeys ? { layerKeys } : {},
|
|
52
54
|
label: resolvedLabel,
|
|
53
55
|
entityName: spec.entityName ?? entityClassName(name),
|
|
54
56
|
labelPlural: labelPlural ?? `${resolvedLabel}s`,
|
|
@@ -5,8 +5,8 @@ import { unconditionalPreviewProps } from "./preview-store.js";
|
|
|
5
5
|
import { authoredBag, authoredValue, declaredPropDefaults, memberFromLeaf, refListOf, resolveComponentProps, stylePropertyPathFor } from "./projections.js";
|
|
6
6
|
import { slotHostLayer } from "./slot-host.js";
|
|
7
7
|
import { forwardRoutes, forwardedLayers, intoName, isValueSlot, renderBindingsOf, routesContent } from "./prop-surface.js";
|
|
8
|
-
import { inlineMotionProps, jsMotionPresets } from "../entities/system/motion-presets.js";
|
|
9
8
|
import { withRequiredContentHidden } from "../entities/system/required-content-layers.js";
|
|
9
|
+
import { inlineMotionProps, jsMotionPresets } from "../entities/system/motion-presets.js";
|
|
10
10
|
import { graftPreviewElements } from "../spec/preview-elements.js";
|
|
11
11
|
import { componentLayerClass, componentPropClasses, componentRuleClasses, layerJsMotionPresets, layerRoutedProps, splitStyleProps, styleRuleMotionClasses } from "./class-names.js";
|
|
12
12
|
//#region src/framework/render-spec.ts
|
|
@@ -1350,7 +1350,7 @@ function expandInstance(sink, path, instanceProps, content, hint, instanceSlots)
|
|
|
1350
1350
|
"data-uds-component": path,
|
|
1351
1351
|
"data-uds-layer": layer,
|
|
1352
1352
|
...forwarded[layer],
|
|
1353
|
-
...motionPreset !== void 0 ? presetProps[motionPreset] : {},
|
|
1353
|
+
...motionPreset !== void 0 ? withoutKey(presetProps[motionPreset], "reducedMotion") : {},
|
|
1354
1354
|
...Object.fromEntries((valueRoutes.get(layer) ?? []).filter(({ from }) => state[from] !== void 0).map(({ from, into }) => [into, state[from]])),
|
|
1355
1355
|
...isRoot ? rest : {},
|
|
1356
1356
|
...slot && slotValue !== void 0 ? { [intoName(slot.into)]: slotValue } : {},
|
|
@@ -1511,5 +1511,10 @@ function sameKeys(a, b) {
|
|
|
1511
1511
|
function expandComponent(config, path, props = {}, options = {}) {
|
|
1512
1512
|
return expandSpec(config, instanceSpec(path, props), options);
|
|
1513
1513
|
}
|
|
1514
|
+
/** A record without one of its keys. */
|
|
1515
|
+
function withoutKey(record, key) {
|
|
1516
|
+
const { [key]: _omitted, ...rest } = record ?? {};
|
|
1517
|
+
return rest;
|
|
1518
|
+
}
|
|
1514
1519
|
//#endregion
|
|
1515
1520
|
export { ANATOMY_MARKER, MOTION_FACTORY, anatomyElementComponent, anatomyTextProp, andListOf, boundElementType, componentSpec, deriveSpec, entryKeyOf, expandComponent, expandSpec, instanceSpec, isExpression, layerTextProp, liveStateProps, previewSpec, propInto, propsIntoLayer, readStatePointer, resolveValue, resolveVisibility, routeOf, routesOf, slotAnatomyPropOf, slotTargetsOf, specWithResolvedVisibility, spelled, statePointerProp, valueForwardsOf, visibilityStateProps, visibilityTerms, withoutTruthyTerm, wrappedElementKey };
|
|
@@ -15,7 +15,7 @@ import { Patch, SerializedConfig } from "./Config.js";
|
|
|
15
15
|
* sets this constant to its own older timestamp; the merge ref then carries a registered migration
|
|
16
16
|
* newer than the constant, and the assertion fails until the author re-mints.
|
|
17
17
|
*/
|
|
18
|
-
declare const CURRENT_SCHEMA_VERSION =
|
|
18
|
+
declare const CURRENT_SCHEMA_VERSION = 20261009181635;
|
|
19
19
|
/** What the walkers need to see of an envelope — loose on purpose, so a typed `SerializedConfig`
|
|
20
20
|
* and a raw old-shape record both flow in without casts. */
|
|
21
21
|
interface VersionStamped {
|
|
@@ -13,7 +13,7 @@ import { ConfigFormatError } from "./rejection.js";
|
|
|
13
13
|
* sets this constant to its own older timestamp; the merge ref then carries a registered migration
|
|
14
14
|
* newer than the constant, and the assertion fails until the author re-mints.
|
|
15
15
|
*/
|
|
16
|
-
const CURRENT_SCHEMA_VERSION =
|
|
16
|
+
const CURRENT_SCHEMA_VERSION = 20261009181635;
|
|
17
17
|
/** The mint that moved `options` into the `settings` kind — restated here, rather than imported
|
|
18
18
|
* from the migration that owns it, because that migration imports this module. */
|
|
19
19
|
const SETTINGS_AS_KIND_VERSION = 20260908171448;
|
|
@@ -45,7 +45,7 @@ function registerSchemaMigrations(...migrations) {
|
|
|
45
45
|
for (const migration of migrations) {
|
|
46
46
|
if (migration.version !== 1 && !isMintTimestamp(migration.version)) throw new Error(`Schema migration version ${migration.version} is not a mint timestamp — mint one with \`date +%Y%m%d%H%M%S\`, like a supabase migration.`);
|
|
47
47
|
if (REGISTERED.has(migration.version)) throw new Error(`A schema migration at version ${migration.version} is already registered.`);
|
|
48
|
-
if (migration.version >
|
|
48
|
+
if (migration.version > 20261009181635) throw new Error(`Schema migration ${migration.version} is newer than CURRENT_SCHEMA_VERSION (${CURRENT_SCHEMA_VERSION}) — a stale branch merged, or the mint forgot to bump the constant. Re-mint: set CURRENT_SCHEMA_VERSION to the newest migration version.`);
|
|
49
49
|
REGISTERED.set(migration.version, migration);
|
|
50
50
|
}
|
|
51
51
|
}
|
|
@@ -102,7 +102,7 @@ function pendingFor(version, target, options) {
|
|
|
102
102
|
* Already-current input passes through untouched — same reference, so hot paths pay nothing.
|
|
103
103
|
*/
|
|
104
104
|
function upgradeSerializedConfig(json, options) {
|
|
105
|
-
const target = options?.target ??
|
|
105
|
+
const target = options?.target ?? 20261009181635;
|
|
106
106
|
const version = detectedWireVersion(json);
|
|
107
107
|
if (version > target) throw new SchemaVersionTooNew(version, target);
|
|
108
108
|
const raw = json;
|
|
@@ -160,7 +160,7 @@ function upgradedSources(json, options) {
|
|
|
160
160
|
* meaning under the current schema and replay skips it — reported by the caller, not swallowed here.
|
|
161
161
|
*/
|
|
162
162
|
function upgradePatch(patch, writtenAt, options) {
|
|
163
|
-
const target = options?.target ??
|
|
163
|
+
const target = options?.target ?? 20261009181635;
|
|
164
164
|
if (writtenAt > target) throw new SchemaVersionTooNew(writtenAt, target);
|
|
165
165
|
let current = patch;
|
|
166
166
|
for (const migration of pendingFor(writtenAt, target, options)) {
|
|
@@ -179,7 +179,7 @@ function upgradePatch(patch, writtenAt, options) {
|
|
|
179
179
|
* stamp would cause.
|
|
180
180
|
*/
|
|
181
181
|
function upgradeDraftEntries(draft, options) {
|
|
182
|
-
const target = options?.target ??
|
|
182
|
+
const target = options?.target ?? 20261009181635;
|
|
183
183
|
const entries = [];
|
|
184
184
|
const held = [];
|
|
185
185
|
const retired = [];
|