@yahoo/uds-create-config 5.19.1 → 5.21.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.
Files changed (56) hide show
  1. package/dist/configs/documentation.js +1 -1
  2. package/dist/configs/system.d.ts +607 -2
  3. package/dist/configs/system.js +8 -0
  4. package/dist/entities/documentation/Documentation.d.ts +1 -1
  5. package/dist/entities/system/Component.js +1 -2
  6. package/dist/entities/system/LinkedSystem.d.ts +45 -5
  7. package/dist/entities/system/LinkedSystem.js +55 -4
  8. package/dist/entities/system/Modifier.d.ts +5 -2
  9. package/dist/entities/system/Modifier.js +5 -2
  10. package/dist/entities/system/Motion.d.ts +578 -4
  11. package/dist/entities/system/Motion.js +233 -70
  12. package/dist/entities/system/css-motion-reach.d.ts +62 -0
  13. package/dist/entities/system/css-motion-reach.js +195 -0
  14. package/dist/entities/system/css-motion.d.ts +55 -0
  15. package/dist/entities/system/css-motion.js +445 -0
  16. package/dist/entities/system/css-timing.d.ts +40 -0
  17. package/dist/entities/system/css-timing.js +247 -0
  18. package/dist/entities/system/easings.d.ts +29 -0
  19. package/dist/entities/system/easings.js +99 -0
  20. package/dist/entities/system/motion-convert.d.ts +42 -0
  21. package/dist/entities/system/motion-convert.js +517 -0
  22. package/dist/entities/system/motion-presets.d.ts +9 -1
  23. package/dist/entities/system/motion-presets.js +27 -38
  24. package/dist/entities/system/motion-variants.js +66 -49
  25. package/dist/entities/system/reduced-motion.d.ts +20 -0
  26. package/dist/entities/system/reduced-motion.js +42 -0
  27. package/dist/framework/Config.d.ts +32 -2
  28. package/dist/framework/Config.js +117 -7
  29. package/dist/framework/class-names.d.ts +2 -1
  30. package/dist/framework/class-names.js +31 -13
  31. package/dist/framework/defineEntity.d.ts +3 -1
  32. package/dist/framework/defineEntity.js +2 -0
  33. package/dist/framework/registered.d.ts +8 -1
  34. package/dist/framework/registry.d.ts +38 -1
  35. package/dist/framework/registry.js +3 -1
  36. package/dist/framework/render-spec.js +7 -2
  37. package/dist/framework/schema-version.d.ts +1 -1
  38. package/dist/framework/schema-version.js +5 -5
  39. package/dist/framework/signature.js +4 -2
  40. package/dist/index.d.ts +2 -2
  41. package/dist/index.js +1 -1
  42. package/dist/migrations/2.0.0/v1-artifact.d.ts +606 -2
  43. package/dist/migrations/20261009000514_css_motion_states.d.ts +11 -0
  44. package/dist/migrations/20261009000514_css_motion_states.js +10 -0
  45. package/dist/migrations/20261009022722_starting_style_modifier.d.ts +9 -0
  46. package/dist/migrations/20261009022722_starting_style_modifier.js +10 -0
  47. package/dist/migrations/20261009181635_css_motion_strict.d.ts +23 -0
  48. package/dist/migrations/20261009181635_css_motion_strict.js +152 -0
  49. package/dist/migrations/20261009193004_link_override_ref.d.ts +9 -0
  50. package/dist/migrations/20261009193004_link_override_ref.js +10 -0
  51. package/dist/spec/links.d.ts +2 -0
  52. package/dist/spec/links.js +2 -0
  53. package/dist/spec/motion.d.ts +7 -0
  54. package/dist/spec/motion.js +7 -0
  55. package/dist/tsconfig.tsbuildinfo +1 -1
  56. package/package.json +10 -2
@@ -1,23 +1,29 @@
1
+ import { isRef, refSchema } from "../../framework/utils/refs.js";
1
2
  import { pathInput } from "../../framework/schemas.js";
2
3
  import { defineEntity } from "../../framework/defineEntity.js";
4
+ import { authoredValue } from "../../framework/projections.js";
5
+ import { convertMotion, convertMotionPreset, planMotionConversion } from "./motion-convert.js";
3
6
  import { renameMotionVariant } from "./motion-variants.js";
7
+ import { cssMotionVariantGroups } from "./css-motion-reach.js";
8
+ import { REDUCED_MOTION_ATTRIBUTE, REDUCED_MOTION_VALUES } from "./reduced-motion.js";
4
9
  import { z } from "zod";
5
10
  //#region src/entities/system/Motion.ts
6
11
  /**
7
- * Motion (`fade/in`) — an animation preset, DISCRIMINATED by `runtime` so the payload is
8
- * coupled to how it animates, not loosely optional:
9
- * - `css` → `keyframes` REQUIRED (codegen emits `@keyframes` from them); a css preset with
10
- * no keyframes — which used to validate and silently emit nothing — is now rejected.
11
- * - `js` → no keyframes; carries the interaction state the client drives, `exit` included,
12
+ * Motion (`fade/in`) — an animation preset, discriminated by `runtime`:
13
+ * - `css` → played by the stylesheet. Either `keyframes`, which codegen emits as `@keyframes`, or
14
+ * states (`variants`, `initial`, `animate`, `exit`, `whileHover`, `whileTap`, `whileFocus`) with a
15
+ * `transition` written as CSS, or both. The states mean what a JS preset's do.
16
+ * - `js` → played by `motion/react`: the interaction state the client drives, `exit` included,
12
17
  * which runs under an `AnimatePresence` ancestor when the element unmounts. Each state is a set
13
18
  * of values or the name of one of the preset's `variants`, and `layout` animates the element's
14
19
  * own size and position as they change.
15
- * Illegal cross-runtime mixes (a css preset with `animate`, a js preset with `keyframes`)
16
- * can't be represented — each member is strict.
20
+ * Each takes only its own fields. `motion/convert` rewrites a preset from one to the other, and a
21
+ * link's `{ override: { runtime: 'css' } }` converts the source's preset as it reads
22
+ * (`motion-convert.ts`). A CSS preset with neither keyframes nor a state is refused, since it would
23
+ * emit nothing.
17
24
  *
18
- * Each member declares its WHOLE surface, because `strictObject` drops what it doesn't name and does
19
- * it silently: the first version declared three js fields, so every gesture preset in a real system
20
- * lost `whileHover`/`whileTap`/`drag` on the way in and animated nothing.
25
+ * Each member declares its whole surface, because `strictObject` drops what it doesn't name and does
26
+ * it silently.
21
27
  */
22
28
  const values = z.record(z.string(), z.unknown());
23
29
  /**
@@ -31,32 +37,20 @@ const gesture = z.union([
31
37
  z.array(z.string())
32
38
  ]).optional();
33
39
  const RUNTIME = "Which runtime plays the preset, and so which fields apply.";
34
- const CssMotion = z.strictObject({
35
- runtime: z.literal("css").describe(RUNTIME),
36
- keyframes: z.record(z.string(), z.record(z.string(), z.unknown())).describe("The CSS keyframes, keyed by offset (`from`, `50%`, `to`)."),
37
- duration: z.string().optional().describe("The CSS `animation-duration`, such as `'4s'`."),
38
- delay: z.string().optional().describe("The CSS `animation-delay`."),
39
- timingFunction: z.string().optional().describe("The CSS `animation-timing-function`."),
40
- iterationCount: z.union([z.string(), z.number()]).optional().describe("The CSS `animation-iteration-count`, or `'infinite'`."),
41
- direction: z.string().optional().describe("The CSS `animation-direction`."),
42
- fillMode: z.string().optional().describe("The CSS `animation-fill-mode`.")
43
- });
44
- const JsMotion = z.strictObject({
45
- runtime: z.literal("js").describe(RUNTIME),
46
- initial: z.union([
47
- values,
48
- z.string(),
49
- z.array(z.string()),
50
- z.literal(false)
51
- ]).optional().describe("The starting values, as `motion/react` reads them, or `false` to start at `animate` without playing on mount."),
52
- animate: gesture.describe("The values to animate to."),
53
- exit: gesture.describe("The values to animate to as the element leaves."),
54
- transition: values.optional().describe("How the animation moves, such as a spring."),
55
- whileHover: gesture.describe("Values held while the element is hovered."),
56
- whileTap: gesture.describe("Values held while the element is pressed."),
57
- whileFocus: gesture.describe("Values held while the element has focus."),
40
+ const initial = z.union([
41
+ values,
42
+ z.string(),
43
+ z.array(z.string()),
44
+ z.literal(false)
45
+ ]).optional().describe("The starting values, as `motion/react` reads them, or `false` to start at `animate` without playing on mount.");
46
+ const animate = gesture.describe("The values to animate to.");
47
+ const exit = gesture.describe("The values to animate to as the element leaves.");
48
+ const whileHover = gesture.describe("Values held while the element is hovered.");
49
+ const whileTap = gesture.describe("Values held while the element is pressed.");
50
+ const whileFocus = gesture.describe("Values held while the element has focus.");
51
+ const variants = z.record(z.string(), values).optional().describe("Named states, each a set of values, that `initial`, `animate`, `exit` and the gestures name.");
52
+ const jsOnly = {
58
53
  whileDrag: gesture.describe("Values held while the element is dragged."),
59
- variants: z.record(z.string(), values).optional().describe("Named states, each a set of values, that `initial`, `animate`, `exit` and the gestures name."),
60
54
  layout: z.union([
61
55
  z.boolean(),
62
56
  z.literal("position"),
@@ -66,6 +60,106 @@ const JsMotion = z.strictObject({
66
60
  drag: z.union([z.boolean(), z.string()]).optional().describe("Whether, and along which axis, the element can be dragged."),
67
61
  dragConstraints: z.record(z.string(), z.number()).optional().describe("The box a drag stays within."),
68
62
  dragElastic: z.number().optional().describe("How far past its constraints a drag can stretch.")
63
+ };
64
+ /** The fields only a JS preset has. `layout` measures the element across a render and `drag*`
65
+ * follows the pointer, and CSS has neither, so a conversion to CSS drops them. */
66
+ const JS_ONLY_FIELDS = Object.keys(jsOnly);
67
+ /**
68
+ * A CSS time: `'150ms'`, `'0.2s'`, or a time token. A bare number is refused, since `motion/react`
69
+ * reads one as seconds and CSS has no unitless time.
70
+ */
71
+ const cssTime = z.union([z.string(), refSchema]);
72
+ /** A spring's number, or a token that holds one. */
73
+ const springNumber = z.union([z.number(), refSchema]);
74
+ const springEasing = z.strictObject({ spring: z.strictObject({
75
+ stiffness: springNumber.optional().describe("The spring's stiffness."),
76
+ damping: springNumber.optional().describe("The spring's damping."),
77
+ mass: springNumber.optional().describe("The spring's mass."),
78
+ bounce: springNumber.optional().describe("The spring's bounce, with `duration` or `visualDuration`."),
79
+ duration: cssTime.optional().describe("With `bounce`, how long the spring takes to settle."),
80
+ visualDuration: cssTime.optional().describe("With `bounce`, how long the spring takes to reach its target visually.")
81
+ }) }).describe("A spring, which the stylesheet plays as a `linear()` curve sampled from it. Its parameters stay as written, so a spring token's value reaches it and a conversion to JS keeps it a spring.");
82
+ const cssTiming = z.strictObject({
83
+ duration: cssTime.optional().describe("How long a change takes: `'150ms'`, `'0.2s'`, or a time token. A spring easing sets its own unless this is given."),
84
+ delay: cssTime.optional().describe("How long a change waits before it starts."),
85
+ easing: z.union([
86
+ z.string(),
87
+ refSchema,
88
+ springEasing
89
+ ]).optional().describe("The CSS easing (`'ease-out'`, `'cubic-bezier(…)'`), an easing token, or a spring (`{ spring: { stiffness, damping } }`)."),
90
+ times: z.array(z.number()).optional().describe("Where each keyframe of a value written as a list sits, from 0 to 1, as `motion/react`'s `times`.")
91
+ });
92
+ /**
93
+ * How a CSS preset's states move: one timing for every value, and a value's own under `properties`
94
+ * (`properties: { opacity: { … } }`). A state's own `transition` replaces the preset's for the values
95
+ * it sets.
96
+ */
97
+ const cssTransition = cssTiming.extend({ properties: z.record(z.string(), cssTiming).optional().describe("A value's own timing, by the value's name (`opacity`, `scale`).") }).describe("How the states move.");
98
+ /**
99
+ * A CSS preset's state: values, with an optional `transition` of its own in the CSS form. A JS
100
+ * state's `transitionEnd` sets values once its animation ends, which a stylesheet can't, so a CSS
101
+ * state refuses it.
102
+ */
103
+ const cssValues = z.looseObject({ transition: cssTransition.optional() }).refine((state) => !("transitionEnd" in state), {
104
+ message: "A CSS state has no `transitionEnd`: a stylesheet can't set values once a transition ends.",
105
+ path: ["transitionEnd"]
106
+ });
107
+ const cssGesture = z.union([
108
+ cssValues,
109
+ z.string(),
110
+ z.array(z.string())
111
+ ]).optional();
112
+ const KEYFRAMES_OR_STATE = [
113
+ "keyframes",
114
+ "initial",
115
+ "animate",
116
+ "exit",
117
+ "whileHover",
118
+ "whileTap",
119
+ "whileFocus",
120
+ "variants"
121
+ ];
122
+ const reducedMotion = z.enum(REDUCED_MOTION_VALUES).optional().describe("How the preset treats the viewer's reduced-motion setting: `'user'` follows the OS, `'always'` reduces, `'never'` plays in full. A layer's own `reducedMotion` in a style rule wins over it.");
123
+ const CssMotion = z.strictObject({
124
+ runtime: z.literal("css").describe(RUNTIME),
125
+ keyframes: z.record(z.string(), z.record(z.string(), z.unknown())).optional().describe("The CSS keyframes, keyed by offset (`from`, `50%`, `to`)."),
126
+ duration: z.string().optional().describe("The keyframes' CSS `animation-duration`, such as `'4s'`."),
127
+ delay: z.string().optional().describe("The keyframes' CSS `animation-delay`."),
128
+ timingFunction: z.string().optional().describe("The keyframes' CSS `animation-timing-function`."),
129
+ iterationCount: z.union([z.string(), z.number()]).optional().describe("The keyframes' CSS `animation-iteration-count`, or `'infinite'`."),
130
+ direction: z.string().optional().describe("The keyframes' CSS `animation-direction`."),
131
+ fillMode: z.string().optional().describe("The keyframes' CSS `animation-fill-mode`."),
132
+ initial: z.union([
133
+ cssValues,
134
+ z.string(),
135
+ z.array(z.string()),
136
+ z.literal(false)
137
+ ]).optional().describe(initial.description ?? ""),
138
+ animate: cssGesture.describe(animate.description ?? ""),
139
+ exit: cssGesture.describe(exit.description ?? ""),
140
+ transition: cssTransition.optional(),
141
+ whileHover: cssGesture.describe(whileHover.description ?? ""),
142
+ whileTap: cssGesture.describe(whileTap.description ?? ""),
143
+ whileFocus: cssGesture.describe(whileFocus.description ?? ""),
144
+ variants: z.record(z.string(), cssValues).optional().describe(variants.description ?? ""),
145
+ reducedMotion
146
+ }).refine((body) => KEYFRAMES_OR_STATE.some((field) => body[field] !== void 0), { message: "A CSS preset needs `keyframes`, or a state to animate (`animate`, `variants`, `whileHover` and the rest)." });
147
+ const JsMotion = z.strictObject({
148
+ runtime: z.literal("js").describe(RUNTIME),
149
+ initial,
150
+ animate,
151
+ exit,
152
+ transition: values.optional().describe("How the animation moves, such as a spring."),
153
+ whileHover,
154
+ whileTap,
155
+ whileFocus,
156
+ whileDrag: jsOnly.whileDrag,
157
+ variants,
158
+ layout: jsOnly.layout,
159
+ drag: jsOnly.drag,
160
+ dragConstraints: jsOnly.dragConstraints,
161
+ dragElastic: jsOnly.dragElastic,
162
+ reducedMotion
69
163
  });
70
164
  /**
71
165
  * A JS preset written in place, as a style rule's `motion` on a layer rather than a `Motion.ref`: the
@@ -76,46 +170,115 @@ const JsMotion = z.strictObject({
76
170
  const inlineJsMotion = JsMotion.omit({ runtime: true }).extend({ runtime: z.literal("js").optional().describe(RUNTIME) }).describe("A JS motion preset written in place: `initial`, `animate`, `while*`, `variants`, `transition` and the rest, as `Motion.item` takes them with `runtime: 'js'`.");
77
171
  /** The fields of a JS preset that `motion.create` reads as props, in schema order. The emitter
78
172
  * reads this rather than restating the schema, so a field added here reaches every emitted preset. */
79
- const jsMotionPropFields = Object.keys(JsMotion.shape).filter((field) => field !== "runtime");
173
+ const jsMotionPropFields = Object.keys(JsMotion.shape).filter((field) => field !== "runtime" && field !== "reducedMotion");
174
+ const convertInput = z.object({
175
+ path: pathInput("motion"),
176
+ to: z.enum(["css", "js"]).describe("The runtime to convert the preset to.")
177
+ });
178
+ /** A real preset converted to the runtime it isn't, so the example runs; none when there is none. */
179
+ const convertExample = ({ config }) => {
180
+ for (const preset of config.list("motion")) {
181
+ const body = preset.toJSON();
182
+ const to = body.runtime === "js" ? "css" : "js";
183
+ if (convertMotion({
184
+ body,
185
+ to
186
+ }).refused.length === 0) return {
187
+ path: preset.path,
188
+ to
189
+ };
190
+ }
191
+ };
80
192
  const Motion = defineEntity({
81
193
  kind: "motion",
82
194
  label: "Motion",
83
195
  labelPlural: "Motion presets",
84
196
  fields: z.discriminatedUnion("runtime", [CssMotion, JsMotion]),
85
- schemas: {
86
- /**
87
- * A variant name is a string the presets that animate together match by, not a ref, so nothing
88
- * cascades it. Renaming one in a single preset's `variants` leaves its own states and every inner
89
- * layer that answers it naming the old one, which animates to nothing and says nothing.
90
- */
91
- "rename-variant": {
92
- input: z.object({
93
- path: pathInput("motion"),
94
- key: z.string().min(1),
95
- to: z.string().min(1)
96
- }),
97
- readOnly: false,
98
- label: "Rename variant",
99
- description: "Rename one of a JS preset's `variants` (`key` → `to`) wherever the name is used: the preset's own `variants` and states (`initial`, `animate`, `exit`, `while*`), and every preset that animates with it, since a root's gestures name the variants its inner layers' presets declare. \"Animates with\" is the presets a component's style rules put on its layers, joined across a component that renders another. Refused when a preset in that set already declares `to`.",
100
- scope: "item",
101
- title: (entity) => `Rename ${entity} variant`,
102
- example: ({ config }) => {
103
- for (const preset of config.list("motion")) {
104
- const variants = preset.toJSON().variants;
105
- const [key] = Object.keys(variants ?? {});
106
- if (key !== void 0) return {
107
- path: preset.path,
108
- key,
109
- to: `${key}-renamed`
110
- };
111
- }
197
+ rebaseLinkedBody: ({ body, extend }) => {
198
+ const to = extend.runtime;
199
+ if (to !== "css" && to !== "js" || body.runtime === to) return body;
200
+ return convertMotion({
201
+ body,
202
+ to
203
+ }).body ?? body;
204
+ },
205
+ layerKeys: {
206
+ motion: {
207
+ schema: (declared) => z.union([declared ?? z.union([refSchema, z.string()]), inlineJsMotion], { error: "a layer's `motion` is a preset (`Motion.ref('<path>')`) or a JS preset written in place — `{ \"whileHover\": { \"scale\": 1.05 }, \"transition\": { … } }`, the fields `Motion.item` takes with `runtime: 'js'`. A CSS preset is named with `Motion.item`." }),
208
+ reach: ({ config, value, element }) => {
209
+ return (config.resolve("motion", String(authoredValue(config, "motion", value)))?.toJSON())?.runtime === "css" && isRef(element) ? "prop" : "none";
210
+ },
211
+ propType: "motion"
212
+ },
213
+ reducedMotion: {
214
+ schema: () => z.enum(REDUCED_MOTION_VALUES).describe("How the layer's motion, and the motion inside it, treats the viewer's reduced-motion setting: `'user'` follows the OS, `'always'` reduces, `'never'` plays in full."),
215
+ reach: () => ({ attribute: REDUCED_MOTION_ATTRIBUTE })
216
+ }
217
+ },
218
+ schemas: {
219
+ /**
220
+ * A variant name is a string the presets that animate together match by, not a ref, so nothing
221
+ * cascades it. Renaming one in a single preset's `variants` leaves its own states and every inner
222
+ * layer that answers it naming the old one, which animates to nothing and says nothing.
223
+ */
224
+ "rename-variant": {
225
+ input: z.object({
226
+ path: pathInput("motion"),
227
+ key: z.string().min(1),
228
+ to: z.string().min(1)
229
+ }),
230
+ readOnly: false,
231
+ label: "Rename variant",
232
+ description: "Rename one of a preset's `variants` (`key` → `to`) wherever the name is used: the preset's own `variants` and states (`initial`, `animate`, `exit`, `while*`), and every preset that animates with it, since a root's gestures name the variants its inner layers' presets declare. \"Animates with\" is the presets of the same runtime a component's style rules put on its layers, joined across a component that renders another. Refused when a preset in that set already declares `to`.",
233
+ scope: "item",
234
+ title: (entity) => `Rename ${entity} variant`,
235
+ example: ({ config }) => {
236
+ for (const preset of config.list("motion")) {
237
+ const variants = preset.toJSON().variants;
238
+ const [key] = Object.keys(variants ?? {});
239
+ if (key !== void 0) return {
240
+ path: preset.path,
241
+ key,
242
+ to: `${key}-renamed`
243
+ };
244
+ }
245
+ },
246
+ handler: (input, config) => renameMotionVariant(config, {
247
+ path: String(input.path),
248
+ from: String(input.key),
249
+ to: String(input.to),
250
+ groupOf: (config, preset) => cssMotionVariantGroups(config).find((group) => group.has(preset)) ?? new Set([preset])
251
+ })
252
+ },
253
+ convert: {
254
+ input: convertInput,
255
+ readOnly: false,
256
+ label: "Convert runtime",
257
+ description: "Rewrite a preset for the other runtime. `to: 'css'` keeps its states and writes its transition as CSS timing, a spring as `{ spring: { … } }`, and drops what CSS has no counterpart for: `layout`, `drag*`, `whileDrag`, a state's `transitionEnd`, and a transition's orchestration (`when`, `delayChildren`, `staggerChildren`). `to: 'js'` does the reverse. Refused when the preset holds something the other runtime can't represent, such as a repeat for CSS or a `linear()` easing for JS. A preset borrowed from a linked system takes `runtime` on the link instead, so it keeps following the source. `convert-plan` lists what would be dropped, approximated or refused.",
258
+ scope: "item",
259
+ title: (entity) => `Convert ${entity}`,
260
+ example: convertExample,
261
+ handler: (input, config) => convertMotionPreset({
262
+ config,
263
+ path: String(input.path),
264
+ to: input.to
265
+ })
112
266
  },
113
- handler: (input, config) => renameMotionVariant(config, {
114
- path: String(input.path),
115
- from: String(input.key),
116
- to: String(input.to)
117
- })
118
- } }
267
+ "convert-plan": {
268
+ input: convertInput,
269
+ readOnly: true,
270
+ label: "Plan a conversion",
271
+ description: "What `convert` with the same input would drop, approximate or refuse, field by field, and whether it writes to a link. Writes nothing.",
272
+ scope: "item",
273
+ title: (entity) => `Plan converting ${entity}`,
274
+ example: convertExample,
275
+ handler: (input, config) => planMotionConversion({
276
+ config,
277
+ path: String(input.path),
278
+ to: input.to
279
+ })
280
+ }
281
+ }
119
282
  });
120
283
  //#endregion
121
- export { Motion, inlineJsMotion, jsMotionPropFields };
284
+ export { JS_ONLY_FIELDS, Motion, inlineJsMotion, jsMotionPropFields };
@@ -0,0 +1,62 @@
1
+ import { Config } from "../../framework/Config.js";
2
+ import { MotionConversionNote, MotionRuntime } from "./motion-convert.js";
3
+
4
+ //#region src/entities/system/css-motion-reach.d.ts
5
+ /** A CSS preset a style rule puts on a layer. */
6
+ interface CssMotionPlacement {
7
+ readonly component: string;
8
+ readonly layer: string;
9
+ readonly preset: string;
10
+ /**
11
+ * Whether the layer's element can wear the preset's class: a plain element or a component. A
12
+ * package's export is handed nothing.
13
+ */
14
+ readonly wearable: boolean;
15
+ }
16
+ /** A CSS preset with an `exit` on a layer that can leave the DOM, where CSS can't play it. */
17
+ interface CssMotionExitOnUnmount {
18
+ readonly component: string;
19
+ readonly layer: string;
20
+ readonly preset: string;
21
+ }
22
+ /** A component of a link that swaps presets to CSS, still animating a layer through a JS preset. */
23
+ interface SwappedLinkJsMotion {
24
+ /** The link's slug. */
25
+ readonly link: string;
26
+ readonly component: string;
27
+ readonly layer: string;
28
+ readonly preset: string;
29
+ }
30
+ /** A link that sets a borrowed preset's `runtime`, with what converting the source's preset does. */
31
+ interface LinkedMotionSwap {
32
+ /** The link's slug. */
33
+ readonly link: string;
34
+ readonly preset: string;
35
+ readonly to: MotionRuntime;
36
+ readonly dropped: readonly MotionConversionNote[];
37
+ readonly approximated: readonly MotionConversionNote[];
38
+ /** What the conversion can't represent. A swap with any is refused, so the preset isn't borrowed. */
39
+ readonly refused: readonly MotionConversionNote[];
40
+ }
41
+ /** Every CSS preset a style rule puts on a layer, and whether the layer's element can wear it. */
42
+ declare function cssMotionPlacements(config: Config): readonly CssMotionPlacement[];
43
+ /**
44
+ * The CSS presets that share variant names: the ones a component's style rules put on its layers,
45
+ * joined across a component that renders another, as the JS presets' groups are. A preset passes the
46
+ * variants its states name only to presets in its own group.
47
+ */
48
+ declare const cssMotionVariantGroups: (config: Config) => readonly ReadonlySet<string>[];
49
+ /** Every CSS preset with an `exit` that a style rule puts on a layer that can unmount. */
50
+ declare function cssMotionExitsOnUnmount(config: Config): readonly CssMotionExitOnUnmount[];
51
+ /**
52
+ * Every layer of a linked component that animates through a JS preset, on a link that swaps presets
53
+ * to CSS. A swap meant to leave `motion/react` out of the bundle keeps it for these.
54
+ */
55
+ declare function swappedLinkJsMotions(config: Config): readonly SwappedLinkJsMotion[];
56
+ /**
57
+ * Every borrowed preset a link swaps to the other runtime (`options.motion[path].override.runtime`),
58
+ * with what converting the source's preset drops, approximates or refuses, for the build to report.
59
+ */
60
+ declare function linkedMotionSwaps(config: Config): readonly LinkedMotionSwap[];
61
+ //#endregion
62
+ export { CssMotionExitOnUnmount, CssMotionPlacement, LinkedMotionSwap, SwappedLinkJsMotion, cssMotionExitsOnUnmount, cssMotionPlacements, cssMotionVariantGroups, linkedMotionSwaps, swappedLinkJsMotions };
@@ -0,0 +1,195 @@
1
+ import { isRef, kindOf, pathOf } from "../../framework/utils/refs.js";
2
+ import { isPlainObject } from "../../framework/utils/field-path.js";
3
+ import { createSliceMemo } from "../../framework/memo.js";
4
+ import { convertMotion } from "./motion-convert.js";
5
+ import { placedMotions, variantGroupsOf } from "./motion-variants.js";
6
+ import { requiredContentLayers } from "./required-content-layers.js";
7
+ //#region src/entities/system/css-motion-reach.ts
8
+ /**
9
+ * The layers a component renders only under a condition: an anatomy node with a `visible`, or one
10
+ * inside such a node, and a layer whose required content may be missing.
11
+ */
12
+ function unmountableLayers(config, path) {
13
+ const { anatomy = {} } = config.resolve("component", path)?.toJSON() ?? {};
14
+ const parent = /* @__PURE__ */ new Map();
15
+ for (const [key, node] of Object.entries(anatomy)) {
16
+ const children = isPlainObject(node) ? node.children : void 0;
17
+ for (const child of Array.isArray(children) ? children : []) if (typeof child === "string") parent.set(child, key);
18
+ }
19
+ const gated = (key) => {
20
+ for (let at = key; at !== void 0; at = parent.get(at)) {
21
+ const node = anatomy[at];
22
+ if (isPlainObject(node) && node.visible !== void 0) return true;
23
+ }
24
+ return false;
25
+ };
26
+ const out = new Set(requiredContentLayers(config, path).keys());
27
+ for (const [key, node] of Object.entries(anatomy)) {
28
+ const element = isPlainObject(node) ? node.element : void 0;
29
+ if (isRef(element) && kindOf(element) === "layer" && gated(key)) out.add(pathOf(element));
30
+ }
31
+ return out;
32
+ }
33
+ /** The preset a layer bag's `motion` names, by path. */
34
+ function namedPreset(value) {
35
+ if (isRef(value)) return pathOf(value);
36
+ return typeof value === "string" ? value : void 0;
37
+ }
38
+ /**
39
+ * Whether a layer's element wears a CSS preset's class: a plain element through its class list, and a
40
+ * component through the `motion` value its layer is handed, which the component's style-prop runtime
41
+ * makes the class whether or not it declares a `motion` prop. A package's export is handed nothing.
42
+ */
43
+ function wears(element) {
44
+ if (typeof element === "string") return true;
45
+ return isRef(element) && kindOf(element) === "component";
46
+ }
47
+ const resolvePlacements = createSliceMemo({
48
+ kinds: ["component", "motion"],
49
+ compute: (config) => {
50
+ const out = [];
51
+ const seen = /* @__PURE__ */ new Set();
52
+ for (const component of config.list("component")) {
53
+ const { layers = {}, styles = {} } = component.toJSON();
54
+ for (const entry of Object.values(styles)) for (const [layer, bag] of Object.entries(entry?.layers ?? {})) {
55
+ const preset = namedPreset(isPlainObject(bag) ? bag.motion : void 0);
56
+ if (preset === void 0) continue;
57
+ if ((config.resolve("motion", preset)?.toJSON())?.runtime !== "css" || !(layer in layers)) continue;
58
+ const key = `${component.path}\u0000${layer}\u0000${preset}`;
59
+ if (seen.has(key)) continue;
60
+ seen.add(key);
61
+ out.push({
62
+ component: component.path,
63
+ layer,
64
+ preset,
65
+ wearable: wears(layers[layer])
66
+ });
67
+ }
68
+ }
69
+ return out;
70
+ }
71
+ });
72
+ /** Every CSS preset a style rule puts on a layer, and whether the layer's element can wear it. */
73
+ function cssMotionPlacements(config) {
74
+ return resolvePlacements(config);
75
+ }
76
+ /**
77
+ * The CSS presets that share variant names: the ones a component's style rules put on its layers,
78
+ * joined across a component that renders another, as the JS presets' groups are. A preset passes the
79
+ * variants its states name only to presets in its own group.
80
+ */
81
+ const cssMotionVariantGroups = createSliceMemo({
82
+ kinds: ["component", "motion"],
83
+ compute: (config) => variantGroupsOf({
84
+ config,
85
+ placements: cssMotionPlacements(config)
86
+ })
87
+ });
88
+ const resolveExits = createSliceMemo({
89
+ kinds: ["component", "motion"],
90
+ compute: (config) => {
91
+ const out = [];
92
+ const seen = /* @__PURE__ */ new Set();
93
+ for (const component of config.list("component")) {
94
+ const { styles = {} } = component.toJSON();
95
+ let unmountable;
96
+ for (const entry of Object.values(styles)) for (const [layer, bag] of Object.entries(entry?.layers ?? {})) {
97
+ const preset = namedPreset(isPlainObject(bag) ? bag.motion : void 0);
98
+ if (preset === void 0) continue;
99
+ const body = config.resolve("motion", preset)?.toJSON();
100
+ if (body?.runtime !== "css" || body.exit === void 0) continue;
101
+ unmountable ??= unmountableLayers(config, component.path);
102
+ if (!unmountable.has(layer)) continue;
103
+ const key = `${component.path}\u0000${layer}\u0000${preset}`;
104
+ if (seen.has(key)) continue;
105
+ seen.add(key);
106
+ out.push({
107
+ component: component.path,
108
+ layer,
109
+ preset
110
+ });
111
+ }
112
+ }
113
+ return out;
114
+ }
115
+ });
116
+ /** Every CSS preset with an `exit` that a style rule puts on a layer that can unmount. */
117
+ function cssMotionExitsOnUnmount(config) {
118
+ return resolveExits(config);
119
+ }
120
+ /** The links that swap at least one borrowed JS preset to CSS. */
121
+ function swappingLinks(config) {
122
+ const out = /* @__PURE__ */ new Set();
123
+ for (const preset of config.list("motion")) {
124
+ const overlay = config.localOverlay("motion", preset.path);
125
+ if (overlay?.fields.runtime === "css" && overlay.source?.runtime === "js") out.add(overlay.slug);
126
+ }
127
+ return out;
128
+ }
129
+ const resolveSwapped = createSliceMemo({
130
+ kinds: [
131
+ "component",
132
+ "motion",
133
+ "linked-system"
134
+ ],
135
+ compute: (config) => {
136
+ const links = swappingLinks(config);
137
+ if (links.size === 0) return [];
138
+ return placedMotions(config).flatMap((entry) => {
139
+ const link = config.borrowedFrom("component", entry.component);
140
+ if (link === void 0 || !links.has(link)) return [];
141
+ return [{
142
+ link,
143
+ ...entry
144
+ }];
145
+ });
146
+ }
147
+ });
148
+ /**
149
+ * Every layer of a linked component that animates through a JS preset, on a link that swaps presets
150
+ * to CSS. A swap meant to leave `motion/react` out of the bundle keeps it for these.
151
+ */
152
+ function swappedLinkJsMotions(config) {
153
+ return resolveSwapped(config);
154
+ }
155
+ const resolveLinkedSwaps = createSliceMemo({
156
+ kinds: ["linked-system", "motion"],
157
+ compute: (config) => {
158
+ const out = [];
159
+ for (const link of config.list("linked-system")) {
160
+ const { options } = link.toJSON();
161
+ for (const [preset, entry] of Object.entries(options?.motion ?? {})) {
162
+ if (!isPlainObject(entry)) continue;
163
+ const to = runtimeSet(entry.override) ?? runtimeSet(entry.extend);
164
+ const body = config.sourceFor(link.path)?.resolve("motion", preset)?.toJSON();
165
+ if (to === void 0 || !body || body.runtime === to) continue;
166
+ const { dropped, approximated, refused } = convertMotion({
167
+ body,
168
+ to
169
+ });
170
+ out.push({
171
+ link: link.path,
172
+ preset,
173
+ to,
174
+ dropped,
175
+ approximated,
176
+ refused
177
+ });
178
+ }
179
+ }
180
+ return out;
181
+ }
182
+ });
183
+ const runtimeSet = (body) => {
184
+ const runtime = isPlainObject(body) ? body.runtime : void 0;
185
+ return runtime === "css" || runtime === "js" ? runtime : void 0;
186
+ };
187
+ /**
188
+ * Every borrowed preset a link swaps to the other runtime (`options.motion[path].override.runtime`),
189
+ * with what converting the source's preset drops, approximates or refuses, for the build to report.
190
+ */
191
+ function linkedMotionSwaps(config) {
192
+ return resolveLinkedSwaps(config);
193
+ }
194
+ //#endregion
195
+ export { cssMotionExitsOnUnmount, cssMotionPlacements, cssMotionVariantGroups, linkedMotionSwaps, swappedLinkJsMotions };
@@ -0,0 +1,55 @@
1
+ import { Config } from "../../framework/Config.js";
2
+ import { MotionPresetToken } from "./motion-presets.js";
3
+
4
+ //#region src/entities/system/css-motion.d.ts
5
+ /** A CSS `transition` timing for one value. */
6
+ interface CssMotionTiming {
7
+ readonly duration: string;
8
+ readonly easing: string;
9
+ readonly delay?: string;
10
+ }
11
+ /** The states a CSS preset changes to. `initial` is where it starts, so nothing transitions to it. */
12
+ declare const CSS_MOTION_STATES: readonly ["animate", "exit", "whileHover", "whileTap", "whileFocus"];
13
+ type CssMotionState = (typeof CSS_MOTION_STATES)[number];
14
+ /** Timings by the value's name as the preset writes it (`scale`, `opacity`). */
15
+ type CssMotionTimings = Readonly<Record<string, CssMotionTiming>>;
16
+ /** One CSS preset's timings, by the destination they apply to. */
17
+ interface CssMotionTransitions {
18
+ /**
19
+ * The timing of every value any destination sets, under the preset's own `transition`: what the
20
+ * element moves on as it returns to its style without the preset's states, such as off hover with
21
+ * no `animate`.
22
+ */
23
+ readonly values: CssMotionTimings;
24
+ /** For each variant, the timing of each value it sets. */
25
+ readonly variants: Readonly<Record<string, CssMotionTimings>>;
26
+ /** For each state written as values rather than a variant name, the timing of each value. */
27
+ readonly states: Readonly<Partial<Record<CssMotionState, CssMotionTimings>>>;
28
+ }
29
+ /** A value a CSS preset's state animates through a list of keyframes, which a transition plays as
30
+ * its last. A list that holds and then moves plays as a delay instead, so it is not one. */
31
+ interface CssMotionKeyframeList {
32
+ readonly preset: string;
33
+ /** Where the list sits, as a dotted path: `variants.show.scale`. */
34
+ readonly at: string;
35
+ }
36
+ /** Every CSS preset that animates states, by path, with the timing of each value per destination. */
37
+ declare function cssMotionTransitions(config: Config): Readonly<Record<string, CssMotionTransitions>>;
38
+ /**
39
+ * How long a CSS preset's `exit` takes, in milliseconds: the longest of its values' durations and
40
+ * delays as the element changes to its exit state. `0` for a preset with no exit, and a timing that is
41
+ * no plain time (a `var()`) counts as none.
42
+ */
43
+ declare function cssMotionExitDuration({
44
+ config,
45
+ preset
46
+ }: {
47
+ config: Config;
48
+ preset: string;
49
+ }): number;
50
+ /** Every value a CSS preset's states write as a keyframe list, for the build to report. */
51
+ declare function cssMotionKeyframeLists(config: Config): readonly CssMotionKeyframeList[];
52
+ /** The tokens CSS presets' transitions name that come to no value, and the easings CSS can't spell. */
53
+ declare function cssMotionTokens(config: Config): readonly MotionPresetToken[];
54
+ //#endregion
55
+ export { CSS_MOTION_STATES, CssMotionKeyframeList, CssMotionState, CssMotionTiming, CssMotionTimings, CssMotionTransitions, cssMotionExitDuration, cssMotionKeyframeLists, cssMotionTokens, cssMotionTransitions };