@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
@@ -0,0 +1,445 @@
1
+ import { isPlainObject } from "../../framework/utils/field-path.js";
2
+ import { createSliceMemo } from "../../framework/memo.js";
3
+ import { cssEasing, easingCurve, linearEasing, springTiming } from "./css-timing.js";
4
+ import { stateVariantNames } from "./motion-variants.js";
5
+ import { resolvePresetValue } from "./motion-presets.js";
6
+ //#region src/entities/system/css-motion.ts
7
+ /** The states a CSS preset changes to. `initial` is where it starts, so nothing transitions to it. */
8
+ const CSS_MOTION_STATES = [
9
+ "animate",
10
+ "exit",
11
+ "whileHover",
12
+ "whileTap",
13
+ "whileFocus"
14
+ ];
15
+ /** `motion/react`'s transform values, which move on a spring by default. */
16
+ const TRANSFORM_VALUES = new Set([
17
+ "transformPerspective",
18
+ "x",
19
+ "y",
20
+ "z",
21
+ "translateX",
22
+ "translateY",
23
+ "translateZ",
24
+ "scale",
25
+ "scaleX",
26
+ "scaleY",
27
+ "rotate",
28
+ "rotateX",
29
+ "rotateY",
30
+ "rotateZ",
31
+ "skew",
32
+ "skewX",
33
+ "skewY"
34
+ ]);
35
+ /** Keys of a transition that don't say how a value moves, so one holding only these takes the default. */
36
+ const ORCHESTRATION_KEYS = new Set([
37
+ "when",
38
+ "delay",
39
+ "delayChildren",
40
+ "staggerChildren",
41
+ "staggerDirection",
42
+ "repeat",
43
+ "repeatType",
44
+ "repeatDelay",
45
+ "from",
46
+ "elapsed"
47
+ ]);
48
+ /** Keys of a state's values that are not values. */
49
+ const STATE_META_KEYS = new Set(["transition", "transitionEnd"]);
50
+ const CSS_TIME = /^(-?(?:\d+(?:\.\d*)?|\.\d+))(ms|s)$/;
51
+ /** A time in milliseconds: a number is seconds, as `motion/react` reads it, and a CSS time is read as
52
+ * written. `undefined` for a string that is no plain time, such as a `var()`. */
53
+ function milliseconds(value) {
54
+ if (typeof value === "number") return value * 1e3;
55
+ if (typeof value !== "string") return void 0;
56
+ const time = CSS_TIME.exec(value.trim());
57
+ if (!time) return void 0;
58
+ return time[2] === "ms" ? Number(time[1]) : Number(time[1]) * 1e3;
59
+ }
60
+ /** A time as CSS: whole milliseconds, or the string as written when it is no plain time. */
61
+ function cssTime(value) {
62
+ const ms = milliseconds(value);
63
+ if (ms !== void 0) return `${Math.round(ms)}ms`;
64
+ return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
65
+ }
66
+ const numberOf = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
67
+ /** `motion/react`'s default for a value with no transition of its own (`getDefaultTransition`). */
68
+ function defaultTransition({ key, value }) {
69
+ if (Array.isArray(value) && value.length > 2) return {
70
+ type: "keyframes",
71
+ duration: .8
72
+ };
73
+ if (TRANSFORM_VALUES.has(key)) {
74
+ if (!key.startsWith("scale")) return {
75
+ type: "spring",
76
+ stiffness: 500,
77
+ damping: 25
78
+ };
79
+ const target = Array.isArray(value) ? value.at(-1) : value;
80
+ return {
81
+ type: "spring",
82
+ stiffness: 550,
83
+ damping: Number(target) === 0 ? 2 * Math.sqrt(550) : 30
84
+ };
85
+ }
86
+ return {
87
+ type: "keyframes",
88
+ ease: [
89
+ .25,
90
+ .1,
91
+ .35,
92
+ 1
93
+ ],
94
+ duration: .3
95
+ };
96
+ }
97
+ /**
98
+ * The transition one value moves on, as `motion/react`'s `getValueTransition` picks it, and the path
99
+ * it was written at.
100
+ */
101
+ function valueTransition({ transition, at, key, value }) {
102
+ let picked = transition;
103
+ let pickedAt = at;
104
+ for (const field of [key, "default"]) {
105
+ const own = transition?.[field];
106
+ if (!isPlainObject(own)) continue;
107
+ picked = own;
108
+ pickedAt = `${at}.${field}`;
109
+ break;
110
+ }
111
+ if (Object.keys(picked ?? {}).some((field) => !ORCHESTRATION_KEYS.has(field))) return {
112
+ transition: picked,
113
+ at: pickedAt
114
+ };
115
+ return {
116
+ transition: {
117
+ ...defaultTransition({
118
+ key,
119
+ value
120
+ }),
121
+ ...picked?.delay !== void 0 ? { delay: picked.delay } : {}
122
+ },
123
+ at: pickedAt
124
+ };
125
+ }
126
+ /** One value's resolved transition as CSS, reporting an easing it can't spell. */
127
+ function timingOf({ transition, at, report }) {
128
+ const delay = cssTime(transition.delay);
129
+ const withDelay = (timing) => delay !== void 0 && delay !== "0ms" ? {
130
+ ...timing,
131
+ delay
132
+ } : timing;
133
+ if (transition.type === "spring") {
134
+ const spring = springTiming({
135
+ stiffness: numberOf(transition.stiffness),
136
+ damping: numberOf(transition.damping),
137
+ mass: numberOf(transition.mass),
138
+ bounce: numberOf(transition.bounce),
139
+ duration: numberOf(transition.duration),
140
+ visualDuration: numberOf(transition.visualDuration)
141
+ });
142
+ return withDelay({
143
+ duration: cssTime(transition.playFor) ?? `${spring.duration}ms`,
144
+ easing: spring.easing
145
+ });
146
+ }
147
+ const duration = cssTime(transition.duration ?? .3) ?? "300ms";
148
+ const durationMs = milliseconds(duration) ?? 300;
149
+ let easing;
150
+ if (typeof transition.easing === "string") easing = transition.easing.trim();
151
+ else if (transition.ease !== void 0) {
152
+ easing = cssEasing({
153
+ ease: transition.ease,
154
+ duration: durationMs
155
+ });
156
+ if (easing === void 0) report({
157
+ at: `${at}.ease`,
158
+ problem: "easing"
159
+ });
160
+ }
161
+ return withDelay({
162
+ duration,
163
+ easing: easing ?? "cubic-bezier(0, 0, 0.58, 1)"
164
+ });
165
+ }
166
+ /** The timing of each value a destination sets. */
167
+ function timingsFor({ target, transition, at, report }) {
168
+ const out = {};
169
+ for (const [key, value] of Object.entries(target)) {
170
+ if (STATE_META_KEYS.has(key)) continue;
171
+ const picked = valueTransition({
172
+ transition,
173
+ at,
174
+ key,
175
+ value
176
+ });
177
+ const timing = timingOf({
178
+ ...picked,
179
+ report
180
+ });
181
+ const hold = heldFor({
182
+ value,
183
+ times: picked.transition.times
184
+ });
185
+ if (hold === void 0) out[key] = timing;
186
+ else if (ACCELERATED_VALUES.has(key)) out[key] = heldWhole({
187
+ timing,
188
+ hold
189
+ });
190
+ else out[key] = held({
191
+ timing,
192
+ hold
193
+ });
194
+ }
195
+ return out;
196
+ }
197
+ /**
198
+ * The share of a keyframe list's time it holds its first value before it moves, for a list that
199
+ * holds and then moves to its last (`[0.7, 0.7, 1]`), which a transition plays as a delay. Its
200
+ * `times` say where the move starts; without them the keyframes are evenly spaced. `undefined` for
201
+ * any other value.
202
+ */
203
+ function heldFor({ value, times }) {
204
+ if (!holdsThenMoves(value)) return void 0;
205
+ const start = value.length - 2;
206
+ if (Array.isArray(times) && times.length === value.length && typeof times[start] === "number") return times[start];
207
+ return start / (value.length - 1);
208
+ }
209
+ /** Whether a value is a keyframe list that holds its first value and then moves to its last. */
210
+ function holdsThenMoves(value) {
211
+ if (!Array.isArray(value) || value.length < 3) return false;
212
+ const first = JSON.stringify(value[0]);
213
+ return value.slice(1, -1).every((step) => JSON.stringify(step) === first);
214
+ }
215
+ /**
216
+ * The values `motion/react` hands to the browser's own animations (`acceleratedValues`), which ease a
217
+ * keyframe list as a whole: the keyframes sit at their `times`, and the easing runs over all of them.
218
+ * Every other value it animates itself, easing each keyframe to the next on its own.
219
+ */
220
+ const ACCELERATED_VALUES = new Set([
221
+ "opacity",
222
+ "clipPath",
223
+ "filter",
224
+ "transform"
225
+ ]);
226
+ /**
227
+ * A hold eased as a whole, as the browser plays an accelerated value's keyframes: the full duration,
228
+ * on a curve that stays at the start until the easing passes the hold and then moves in a line to
229
+ * the end. An easing that is no curve, such as a `linear()` from a spring, waits out the hold instead.
230
+ */
231
+ function heldWhole({ timing, hold }) {
232
+ const curve = easingCurve(timing.easing);
233
+ const duration = milliseconds(timing.duration);
234
+ if (!curve || duration === void 0) return held({
235
+ timing,
236
+ hold
237
+ });
238
+ return {
239
+ ...timing,
240
+ easing: linearEasing({
241
+ curve: (progress) => Math.max(0, (curve(progress) - hold) / (1 - hold)),
242
+ duration
243
+ })
244
+ };
245
+ }
246
+ /** A timing that waits out the hold: its delay grows by the held share and its duration shrinks. */
247
+ function held({ timing, hold }) {
248
+ const duration = milliseconds(timing.duration);
249
+ if (duration === void 0) return timing;
250
+ const delay = (milliseconds(timing.delay) ?? 0) + duration * hold;
251
+ return {
252
+ ...timing,
253
+ duration: `${Math.round(duration * (1 - hold))}ms`,
254
+ delay: `${Math.round(delay)}ms`
255
+ };
256
+ }
257
+ /** Whether a CSS preset animates states, rather than only playing keyframes. */
258
+ function hasStates(body) {
259
+ return body.variants !== void 0 || body.initial !== void 0 || CSS_MOTION_STATES.some((state) => body[state] !== void 0);
260
+ }
261
+ /** A spring's time as the spring reads it, in seconds: a token's value has been read to seconds
262
+ * already, and a CSS time is converted. */
263
+ function springSeconds(value) {
264
+ if (typeof value === "number") return value;
265
+ const ms = milliseconds(value);
266
+ return ms === void 0 ? void 0 : ms / 1e3;
267
+ }
268
+ /**
269
+ * A CSS timing as the reader below takes it, which is how `motion/react` writes one: a spring easing
270
+ * as `type: 'spring'` with its fields, and a value's own timing under its name. A `duration` beside a
271
+ * spring is how long the curve plays, `playFor`, since a spring's own `duration` is its settle time.
272
+ */
273
+ function readable(timing) {
274
+ const { properties, easing, duration, ...rest } = timing;
275
+ const out = { ...rest };
276
+ if (isPlainObject(easing) && isPlainObject(easing.spring)) {
277
+ const spring = easing.spring;
278
+ out.type = "spring";
279
+ for (const key of [
280
+ "stiffness",
281
+ "damping",
282
+ "mass",
283
+ "bounce"
284
+ ]) if (spring[key] !== void 0) out[key] = spring[key];
285
+ for (const key of ["duration", "visualDuration"]) {
286
+ const seconds = springSeconds(spring[key]);
287
+ if (seconds !== void 0) out[key] = seconds;
288
+ }
289
+ if (duration !== void 0) out.playFor = duration;
290
+ } else {
291
+ if (easing !== void 0) out.easing = easing;
292
+ if (duration !== void 0) out.duration = duration;
293
+ }
294
+ for (const [key, value] of Object.entries(isPlainObject(properties) ? properties : {})) if (isPlainObject(value)) out[key] = readable(value);
295
+ return out;
296
+ }
297
+ /** A preset's own transition, or a destination's, with its tokens read to values. */
298
+ function resolvedTransition({ config, value, at, report }) {
299
+ if (!isPlainObject(value)) return void 0;
300
+ const resolved = resolvePresetValue({
301
+ config,
302
+ value,
303
+ at,
304
+ report
305
+ });
306
+ return isPlainObject(resolved?.value) ? readable(resolved.value) : void 0;
307
+ }
308
+ function transitionsOf({ config, body, report }) {
309
+ const shared = resolvedTransition({
310
+ config,
311
+ value: body.transition,
312
+ at: "transition",
313
+ report
314
+ });
315
+ const destination = ({ target, at }) => {
316
+ const own = resolvedTransition({
317
+ config,
318
+ value: target.transition,
319
+ at: `${at}.transition`,
320
+ report
321
+ });
322
+ return timingsFor({
323
+ target,
324
+ transition: own ?? shared,
325
+ at: own ? `${at}.transition` : "transition",
326
+ report
327
+ });
328
+ };
329
+ const every = {};
330
+ const variants = {};
331
+ for (const [name, target] of Object.entries(isPlainObject(body.variants) ? body.variants : {})) {
332
+ if (!isPlainObject(target)) continue;
333
+ Object.assign(every, target);
334
+ variants[name] = destination({
335
+ target,
336
+ at: `variants.${name}`
337
+ });
338
+ }
339
+ const states = {};
340
+ for (const state of CSS_MOTION_STATES) {
341
+ const target = body[state];
342
+ if (!isPlainObject(target)) continue;
343
+ Object.assign(every, target);
344
+ states[state] = destination({
345
+ target,
346
+ at: state
347
+ });
348
+ }
349
+ return {
350
+ values: timingsFor({
351
+ target: every,
352
+ transition: shared,
353
+ at: "transition",
354
+ report
355
+ }),
356
+ variants,
357
+ states
358
+ };
359
+ }
360
+ /** Every value in a preset's states written as a keyframe list. */
361
+ function keyframeLists({ preset, body }) {
362
+ const targets = [...Object.entries(isPlainObject(body.variants) ? body.variants : {}).map(([name, target]) => [`variants.${name}`, target]), ...CSS_MOTION_STATES.map((state) => [state, body[state]])];
363
+ const out = [];
364
+ for (const [at, target] of targets) {
365
+ if (!isPlainObject(target)) continue;
366
+ for (const [key, value] of Object.entries(target)) if (Array.isArray(value) && value.length > 1 && !holdsThenMoves(value)) out.push({
367
+ preset,
368
+ at: `${at}.${key}`
369
+ });
370
+ }
371
+ return out;
372
+ }
373
+ const resolveCssMotionPresets = createSliceMemo({
374
+ kinds: [
375
+ "motion",
376
+ "token",
377
+ "settings",
378
+ "font"
379
+ ],
380
+ compute: (config) => {
381
+ const transitions = {};
382
+ const lists = [];
383
+ const tokens = [];
384
+ const reported = /* @__PURE__ */ new Set();
385
+ const report = (entry) => {
386
+ const key = JSON.stringify([
387
+ entry.preset,
388
+ entry.at,
389
+ entry.token,
390
+ entry.problem
391
+ ]);
392
+ if (reported.has(key)) return;
393
+ reported.add(key);
394
+ tokens.push(entry);
395
+ };
396
+ for (const preset of config.list("motion")) {
397
+ const body = preset.toJSON();
398
+ if (body.runtime !== "css") continue;
399
+ if (!hasStates(body)) continue;
400
+ lists.push(...keyframeLists({
401
+ preset: preset.path,
402
+ body
403
+ }));
404
+ transitions[preset.path] = transitionsOf({
405
+ config,
406
+ body,
407
+ report: (problem) => report({
408
+ preset: preset.path,
409
+ ...problem
410
+ })
411
+ });
412
+ }
413
+ return {
414
+ transitions,
415
+ keyframeLists: lists,
416
+ tokens
417
+ };
418
+ }
419
+ });
420
+ /** Every CSS preset that animates states, by path, with the timing of each value per destination. */
421
+ function cssMotionTransitions(config) {
422
+ return resolveCssMotionPresets(config).transitions;
423
+ }
424
+ /**
425
+ * How long a CSS preset's `exit` takes, in milliseconds: the longest of its values' durations and
426
+ * delays as the element changes to its exit state. `0` for a preset with no exit, and a timing that is
427
+ * no plain time (a `var()`) counts as none.
428
+ */
429
+ function cssMotionExitDuration({ config, preset }) {
430
+ const transitions = cssMotionTransitions(config)[preset];
431
+ const exit = (config.resolve("motion", preset)?.toJSON())?.exit;
432
+ if (!transitions || exit === void 0) return 0;
433
+ const timings = isPlainObject(exit) ? [transitions.states.exit ?? {}] : stateVariantNames(exit).map((name) => transitions.variants[name] ?? {});
434
+ return Math.max(0, ...timings.flatMap((group) => Object.values(group).map((timing) => (milliseconds(timing.duration) ?? 0) + (milliseconds(timing.delay) ?? 0))));
435
+ }
436
+ /** Every value a CSS preset's states write as a keyframe list, for the build to report. */
437
+ function cssMotionKeyframeLists(config) {
438
+ return resolveCssMotionPresets(config).keyframeLists;
439
+ }
440
+ /** The tokens CSS presets' transitions name that come to no value, and the easings CSS can't spell. */
441
+ function cssMotionTokens(config) {
442
+ return resolveCssMotionPresets(config).tokens;
443
+ }
444
+ //#endregion
445
+ export { CSS_MOTION_STATES, cssMotionExitDuration, cssMotionKeyframeLists, cssMotionTokens, cssMotionTransitions };
@@ -0,0 +1,40 @@
1
+ //#region src/entities/system/css-timing.d.ts
2
+ /**
3
+ * A `motion/react` transition as the CSS timing that plays closest to it: a spring as a duration and
4
+ * a `linear()` easing sampled from the same equations `motion/react` runs, and a tween's named or
5
+ * bezier easing as its CSS spelling.
6
+ *
7
+ * The spring is a port of `motion-dom`'s generator (`animation/generators/spring.mjs`), with the
8
+ * rest thresholds it uses for a value that moves less than 5: a CSS transition plays one curve over
9
+ * a property's whole change, so the curve runs over progress from 0 to 1. Its duration is found and
10
+ * its curve sampled as `spring().toString()` does.
11
+ */
12
+ /** The spring fields `motion/react` reads. Times are in seconds, as a transition writes them. */
13
+ interface SpringOptions {
14
+ readonly stiffness?: number;
15
+ readonly damping?: number;
16
+ readonly mass?: number;
17
+ readonly bounce?: number;
18
+ readonly duration?: number;
19
+ readonly visualDuration?: number;
20
+ }
21
+ /** A CSS timing: the duration in milliseconds and the easing function. */
22
+ interface SpringTiming {
23
+ readonly duration: number;
24
+ readonly easing: string;
25
+ }
26
+ /** A spring as the CSS duration and `linear()` easing that play it. */
27
+ declare function springTiming(options: SpringOptions): SpringTiming;
28
+ /**
29
+ * A tween's `ease` as CSS: a name `motion/react` gives, a curve of four numbers, or a CSS easing as it
30
+ * stands. `undefined` for anything else, such as a list of easings, one per keyframe.
31
+ */
32
+ declare function cssEasing({
33
+ ease,
34
+ duration
35
+ }: {
36
+ ease: unknown; /** The tween's duration in milliseconds, which sets how many points a sampled easing gets. */
37
+ duration: number;
38
+ }): string | undefined;
39
+ //#endregion
40
+ export { SpringOptions, SpringTiming, cssEasing, springTiming };