@yahoo/uds-create-config 5.40.3 → 5.41.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/react-native-system.d.ts +970 -0
- package/dist/configs/react-native-system.js +18 -0
- package/dist/configs/system.d.ts +903 -536
- package/dist/configs/system.js +17 -1
- package/dist/entities/system/LinkedSystem.d.ts +2 -17
- package/dist/entities/system/Motion.d.ts +903 -536
- package/dist/entities/system/Motion.js +81 -101
- package/dist/entities/system/css-motion-reach.js +7 -2
- package/dist/entities/system/css-motion.js +8 -3
- package/dist/entities/system/motion-convert.d.ts +20 -14
- package/dist/entities/system/motion-convert.js +27 -58
- package/dist/entities/system/motion-presets.js +4 -3
- package/dist/entities/system/motion-types.d.ts +7 -0
- package/dist/entities/system/motion-types.js +132 -0
- package/dist/entities/system/motion-variants.js +28 -12
- package/dist/entities/system/reanimated-motion.d.ts +777 -0
- package/dist/entities/system/reanimated-motion.js +100 -0
- package/dist/framework/Config.d.ts +4 -4
- package/dist/framework/Config.js +4 -4
- package/dist/framework/class-names.js +4 -2
- package/dist/framework/registered.d.ts +1 -8
- package/dist/framework/registry.d.ts +1 -2
- package/dist/framework/schema-version.d.ts +1 -1
- package/dist/framework/schema-version.js +5 -5
- package/dist/framework/utils/field-schema.js +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/migrations/2.0.0/v1-artifact.d.ts +903 -536
- package/dist/migrations/20261015203142_shared_motion.d.ts +4 -0
- package/dist/migrations/20261015203142_shared_motion.js +8 -0
- package/dist/migrations/20261015205522_declarative_reanimated.d.ts +4 -0
- package/dist/migrations/20261015205522_declarative_reanimated.js +8 -0
- package/dist/migrations/20261015212352_native_motion.d.ts +4 -0
- package/dist/migrations/20261015212352_native_motion.js +10 -0
- package/dist/migrations/20261015212414_motion_types.d.ts +5 -0
- package/dist/migrations/20261015212414_motion_types.js +8 -0
- package/dist/migrations/20261015212722_reanimated_cubic_bezier.d.ts +4 -0
- package/dist/migrations/20261015212722_reanimated_cubic_bezier.js +8 -0
- package/dist/migrations/20261015221052_motion_json_schema.d.ts +7 -0
- package/dist/migrations/20261015221052_motion_json_schema.js +8 -0
- package/dist/migrations/20261016135620_motion_type_only.d.ts +9 -0
- package/dist/migrations/20261016135620_motion_type_only.js +9 -0
- package/dist/migrations/20261016164316_motion_compatibility.d.ts +8 -0
- package/dist/migrations/20261016164316_motion_compatibility.js +9 -0
- package/dist/migrations/motion-source-codemod.d.ts +3 -0
- package/dist/spec/motion.d.ts +3 -2
- package/dist/spec/motion.js +2 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -1,29 +1,20 @@
|
|
|
1
1
|
import { isRef, refSchema } from "../../framework/utils/refs.js";
|
|
2
|
+
import { METADATA_KEYS } from "../../framework/utils/field-schema.js";
|
|
2
3
|
import { pathInput } from "../../framework/schemas.js";
|
|
3
4
|
import { defineEntity } from "../../framework/defineEntity.js";
|
|
5
|
+
import { isNativeConfig } from "../../configs/platform.js";
|
|
4
6
|
import { authoredValue } from "../../framework/projections.js";
|
|
5
|
-
import { convertMotion
|
|
7
|
+
import { convertMotion } from "./motion-convert.js";
|
|
8
|
+
import { cssMotionValue, motionTransition, reanimatedMotion, webMotionBody } from "./motion-types.js";
|
|
6
9
|
import { renameMotionVariant } from "./motion-variants.js";
|
|
7
10
|
import { cssMotionVariantGroups } from "./css-motion-reach.js";
|
|
8
11
|
import { REDUCED_MOTION_ATTRIBUTE, REDUCED_MOTION_VALUES } from "./reduced-motion.js";
|
|
9
12
|
import { z } from "zod";
|
|
10
13
|
//#region src/entities/system/Motion.ts
|
|
11
14
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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,
|
|
17
|
-
* which runs under a `Presence` ancestor when the element unmounts (the build makes it motion's
|
|
18
|
-
* `AnimatePresence`). Each state is a set of values or the name of one of the preset's
|
|
19
|
-
* `variants`, and `layout` animates the element's own size and position as they change.
|
|
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.
|
|
24
|
-
*
|
|
25
|
-
* Each member declares its whole surface, because `strictObject` drops what it doesn't name and does
|
|
26
|
-
* it silently.
|
|
15
|
+
* Named motion implementations use `{ type, value }`. A type owns its payload and execution path;
|
|
16
|
+
* selecting another implementation is an explicit reference override, never an implicit conversion.
|
|
17
|
+
* `runtime` bodies below are the compatibility schema for existing configs and sealed sources.
|
|
27
18
|
*/
|
|
28
19
|
const values = z.record(z.string(), z.unknown());
|
|
29
20
|
/**
|
|
@@ -36,7 +27,7 @@ const gesture = z.union([
|
|
|
36
27
|
z.string(),
|
|
37
28
|
z.array(z.string())
|
|
38
29
|
]).optional();
|
|
39
|
-
const RUNTIME = "
|
|
30
|
+
const RUNTIME = "Deprecated compatibility format. New presets use type and value.";
|
|
40
31
|
const initial = z.union([
|
|
41
32
|
values,
|
|
42
33
|
z.string(),
|
|
@@ -171,30 +162,41 @@ const inlineJsMotion = JsMotion.omit({ runtime: true }).extend({ runtime: z.lite
|
|
|
171
162
|
/** The fields of a JS preset that `motion.create` reads as props, in schema order. The emitter
|
|
172
163
|
* reads this rather than restating the schema, so a field added here reaches every emitted preset. */
|
|
173
164
|
const jsMotionPropFields = Object.keys(JsMotion.shape).filter((field) => field !== "runtime" && field !== "reducedMotion");
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
165
|
+
const legacyType = z.undefined();
|
|
166
|
+
legacyType._zod.toJSONSchema = () => ({ not: {} });
|
|
167
|
+
const LegacyMotion = z.strictObject({
|
|
168
|
+
...CssMotion.shape,
|
|
169
|
+
...JsMotion.shape,
|
|
170
|
+
type: legacyType.optional().describe("Absent for a legacy preset; new presets select their implementation with type."),
|
|
171
|
+
runtime: z.enum(["css", "js"]).describe(RUNTIME)
|
|
172
|
+
}).superRefine((body, context) => {
|
|
173
|
+
const legacy = Object.fromEntries(Object.entries(body).filter(([key]) => key !== "type" && !METADATA_KEYS.includes(key)));
|
|
174
|
+
const parsed = (body.runtime === "css" ? CssMotion : JsMotion).safeParse(legacy);
|
|
175
|
+
if (!parsed.success) for (const issue of parsed.error.issues) context.addIssue({
|
|
176
|
+
code: "custom",
|
|
177
|
+
message: issue.message,
|
|
178
|
+
path: issue.path
|
|
179
|
+
});
|
|
177
180
|
});
|
|
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
|
-
};
|
|
192
181
|
const Motion = defineEntity({
|
|
193
182
|
kind: "motion",
|
|
194
183
|
label: "Motion",
|
|
195
184
|
labelPlural: "Motion presets",
|
|
196
|
-
fields: z.discriminatedUnion("
|
|
185
|
+
fields: z.discriminatedUnion("type", [
|
|
186
|
+
reanimatedMotion,
|
|
187
|
+
z.strictObject({
|
|
188
|
+
type: z.literal("css").describe("The animation implementation that reads this preset."),
|
|
189
|
+
value: cssMotionValue.describe("CSS keyframes and transition/animation longhands; interaction targets belong in component styles."),
|
|
190
|
+
reducedMotion
|
|
191
|
+
}),
|
|
192
|
+
z.strictObject({
|
|
193
|
+
type: z.literal("motion/react").describe("The animation implementation that reads this preset."),
|
|
194
|
+
value: JsMotion.omit({ runtime: true }).extend({ transition: motionTransition.optional() }).describe("Motion states, gestures, variants and serializable transitions.")
|
|
195
|
+
}),
|
|
196
|
+
LegacyMotion
|
|
197
|
+
]),
|
|
197
198
|
rebaseLinkedBody: ({ body, extend }) => {
|
|
199
|
+
if (body.type !== void 0 || extend.type !== void 0) return body;
|
|
198
200
|
const to = extend.runtime;
|
|
199
201
|
if (to !== "css" && to !== "js" || body.runtime === to) return body;
|
|
200
202
|
return convertMotion({
|
|
@@ -204,81 +206,59 @@ const Motion = defineEntity({
|
|
|
204
206
|
},
|
|
205
207
|
layerKeys: {
|
|
206
208
|
motion: {
|
|
207
|
-
schema: (declared) => z.union([
|
|
209
|
+
schema: (declared) => z.union([
|
|
210
|
+
declared ?? z.union([refSchema, z.string()]),
|
|
211
|
+
inlineJsMotion,
|
|
212
|
+
z.literal(false)
|
|
213
|
+
], { 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
214
|
reach: ({ config, value, element }) => {
|
|
209
|
-
|
|
215
|
+
if (isNativeConfig(config)) return "prop";
|
|
216
|
+
const preset = config.resolve("motion", String(authoredValue(config, "motion", value)))?.toJSON();
|
|
217
|
+
if (preset?.type === "react-native-reanimated") throw new Error("No UDS web motion integration for react-native-reanimated. Select a supported preset or disable motion.");
|
|
218
|
+
return preset && webMotionBody(preset).runtime === "css" && isRef(element) ? "prop" : "none";
|
|
210
219
|
},
|
|
211
220
|
propType: "motion"
|
|
212
221
|
},
|
|
213
222
|
reducedMotion: {
|
|
214
223
|
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 }
|
|
224
|
+
reach: ({ config }) => isNativeConfig(config) ? "prop" : { attribute: REDUCED_MOTION_ATTRIBUTE }
|
|
216
225
|
}
|
|
217
226
|
},
|
|
218
|
-
schemas: {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
})
|
|
227
|
+
schemas: {
|
|
228
|
+
/**
|
|
229
|
+
* A variant name is a string the presets that animate together match by, not a ref, so nothing
|
|
230
|
+
* cascades it. Renaming one in a single preset's `variants` leaves its own states and every inner
|
|
231
|
+
* layer that answers it naming the old one, which animates to nothing and says nothing.
|
|
232
|
+
*/
|
|
233
|
+
"rename-variant": {
|
|
234
|
+
input: z.object({
|
|
235
|
+
path: pathInput("motion"),
|
|
236
|
+
key: z.string().min(1),
|
|
237
|
+
to: z.string().min(1)
|
|
238
|
+
}),
|
|
239
|
+
readOnly: false,
|
|
240
|
+
label: "Rename variant",
|
|
241
|
+
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`.",
|
|
242
|
+
scope: "item",
|
|
243
|
+
title: (entity) => `Rename ${entity} variant`,
|
|
244
|
+
example: ({ config }) => {
|
|
245
|
+
for (const preset of config.list("motion")) {
|
|
246
|
+
const variants = webMotionBody(preset.toJSON()).variants;
|
|
247
|
+
const [key] = Object.keys(variants ?? {});
|
|
248
|
+
if (key !== void 0) return {
|
|
249
|
+
path: preset.path,
|
|
250
|
+
key,
|
|
251
|
+
to: `${key}-renamed`
|
|
252
|
+
};
|
|
253
|
+
}
|
|
266
254
|
},
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
example: convertExample,
|
|
275
|
-
handler: (input, config) => planMotionConversion({
|
|
276
|
-
config,
|
|
277
|
-
path: String(input.path),
|
|
278
|
-
to: input.to
|
|
279
|
-
})
|
|
280
|
-
}
|
|
281
|
-
}
|
|
255
|
+
handler: (input, config) => renameMotionVariant(config, {
|
|
256
|
+
path: String(input.path),
|
|
257
|
+
from: String(input.key),
|
|
258
|
+
to: String(input.to),
|
|
259
|
+
groupOf: (config, preset) => cssMotionVariantGroups(config).find((group) => group.has(preset)) ?? new Set([preset])
|
|
260
|
+
})
|
|
261
|
+
} }
|
|
282
262
|
});
|
|
283
263
|
//#endregion
|
|
284
264
|
export { JS_ONLY_FIELDS, Motion, inlineJsMotion, jsMotionPropFields };
|
|
@@ -2,6 +2,7 @@ import { isRef, kindOf, pathOf } from "../../framework/utils/refs.js";
|
|
|
2
2
|
import { isPlainObject } from "../../framework/utils/field-path.js";
|
|
3
3
|
import { createSliceMemo } from "../../framework/memo.js";
|
|
4
4
|
import { convertMotion } from "./motion-convert.js";
|
|
5
|
+
import { webMotionBody } from "./motion-types.js";
|
|
5
6
|
import { placedMotions, variantGroupsOf } from "./motion-variants.js";
|
|
6
7
|
import { requiredContentLayers } from "./required-content-layers.js";
|
|
7
8
|
//#region src/entities/system/css-motion-reach.ts
|
|
@@ -54,7 +55,7 @@ const resolvePlacements = createSliceMemo({
|
|
|
54
55
|
for (const entry of Object.values(styles)) for (const [layer, bag] of Object.entries(entry?.layers ?? {})) {
|
|
55
56
|
const preset = namedPreset(isPlainObject(bag) ? bag.motion : void 0);
|
|
56
57
|
if (preset === void 0) continue;
|
|
57
|
-
if ((config
|
|
58
|
+
if (motionBodyAt(config, preset)?.runtime !== "css" || !(layer in layers)) continue;
|
|
58
59
|
const key = `${component.path}\u0000${layer}\u0000${preset}`;
|
|
59
60
|
if (seen.has(key)) continue;
|
|
60
61
|
seen.add(key);
|
|
@@ -96,7 +97,7 @@ const resolveExits = createSliceMemo({
|
|
|
96
97
|
for (const entry of Object.values(styles)) for (const [layer, bag] of Object.entries(entry?.layers ?? {})) {
|
|
97
98
|
const preset = namedPreset(isPlainObject(bag) ? bag.motion : void 0);
|
|
98
99
|
if (preset === void 0) continue;
|
|
99
|
-
const body = config
|
|
100
|
+
const body = motionBodyAt(config, preset);
|
|
100
101
|
if (body?.runtime !== "css" || body.exit === void 0) continue;
|
|
101
102
|
unmountable ??= unmountableLayers(config, component.path);
|
|
102
103
|
if (!unmountable.has(layer)) continue;
|
|
@@ -191,5 +192,9 @@ const runtimeSet = (body) => {
|
|
|
191
192
|
function linkedMotionSwaps(config) {
|
|
192
193
|
return resolveLinkedSwaps(config);
|
|
193
194
|
}
|
|
195
|
+
function motionBodyAt(config, path) {
|
|
196
|
+
const body = config.resolve("motion", path)?.toJSON();
|
|
197
|
+
return body ? webMotionBody(body) : void 0;
|
|
198
|
+
}
|
|
194
199
|
//#endregion
|
|
195
200
|
export { cssMotionExitsOnUnmount, cssMotionPlacements, cssMotionVariantGroups, linkedMotionSwaps, swappedLinkJsMotions };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isPlainObject } from "../../framework/utils/field-path.js";
|
|
2
2
|
import { createSliceMemo } from "../../framework/memo.js";
|
|
3
3
|
import { cssEasing, easingCurve, linearEasing, springTiming } from "./css-timing.js";
|
|
4
|
+
import { webMotionBody } from "./motion-types.js";
|
|
4
5
|
import { stateVariantNames } from "./motion-variants.js";
|
|
5
6
|
import { resolvePresetValue } from "./motion-presets.js";
|
|
6
7
|
//#region src/entities/system/css-motion.ts
|
|
@@ -394,8 +395,8 @@ const resolveCssMotionPresets = createSliceMemo({
|
|
|
394
395
|
tokens.push(entry);
|
|
395
396
|
};
|
|
396
397
|
for (const preset of config.list("motion")) {
|
|
397
|
-
const body = preset.toJSON();
|
|
398
|
-
if (
|
|
398
|
+
const body = webMotionBody(preset.toJSON());
|
|
399
|
+
if (preset.toJSON().runtime !== "css") continue;
|
|
399
400
|
if (!hasStates(body)) continue;
|
|
400
401
|
lists.push(...keyframeLists({
|
|
401
402
|
preset: preset.path,
|
|
@@ -428,7 +429,7 @@ function cssMotionTransitions(config) {
|
|
|
428
429
|
*/
|
|
429
430
|
function cssMotionExitDuration({ config, preset }) {
|
|
430
431
|
const transitions = cssMotionTransitions(config)[preset];
|
|
431
|
-
const exit = (config
|
|
432
|
+
const exit = motionBodyAt(config, preset)?.exit;
|
|
432
433
|
if (!transitions || exit === void 0) return 0;
|
|
433
434
|
const timings = isPlainObject(exit) ? [transitions.states.exit ?? {}] : stateVariantNames(exit).map((name) => transitions.variants[name] ?? {});
|
|
434
435
|
return Math.max(0, ...timings.flatMap((group) => Object.values(group).map((timing) => (milliseconds(timing.duration) ?? 0) + (milliseconds(timing.delay) ?? 0))));
|
|
@@ -441,5 +442,9 @@ function cssMotionKeyframeLists(config) {
|
|
|
441
442
|
function cssMotionTokens(config) {
|
|
442
443
|
return resolveCssMotionPresets(config).tokens;
|
|
443
444
|
}
|
|
445
|
+
function motionBodyAt(config, path) {
|
|
446
|
+
const body = config.resolve("motion", path)?.toJSON();
|
|
447
|
+
return body ? webMotionBody(body) : void 0;
|
|
448
|
+
}
|
|
444
449
|
//#endregion
|
|
445
450
|
export { CSS_MOTION_STATES, cssMotionExitDuration, cssMotionKeyframeLists, cssMotionTokens, cssMotionTransitions };
|
|
@@ -1,4 +1,23 @@
|
|
|
1
1
|
//#region src/entities/system/motion-convert.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* A motion preset rewritten for the other runtime: a JS preset as the CSS preset that plays closest
|
|
4
|
+
* to it, and a CSS preset as a JS one. Retained only for existing links that set a borrowed
|
|
5
|
+
* legacy preset's runtime. Explicit type/value presets never enter this compatibility path.
|
|
6
|
+
*
|
|
7
|
+
* The states (`variants`, `initial`, `animate`, `exit`, `while*`) mean the same in both runtimes, so
|
|
8
|
+
* they carry over as they are, and what changes is the timing:
|
|
9
|
+
*
|
|
10
|
+
* - JS → CSS. A spring stays a spring (`easing: { spring: { … } }`), a tween's `duration` becomes a
|
|
11
|
+
* CSS time, its `ease` a CSS easing, and a value's own transition goes under `properties`. What CSS
|
|
12
|
+
* has no counterpart for is dropped: `layout`, `drag*` and `whileDrag`, a state's `transitionEnd`,
|
|
13
|
+
* and a transition's orchestration (`when`, `delayChildren`, `staggerChildren`). A repeat is refused,
|
|
14
|
+
* since a transition plays once.
|
|
15
|
+
* - CSS → JS. The same in reverse. An easing `motion/react` can't read (`linear()`, `steps()`, a
|
|
16
|
+
* `var()`) is refused, and so are `@keyframes` that aren't one list of values per property.
|
|
17
|
+
*
|
|
18
|
+
* What a conversion approximates is played differently than written: a list of easings, one per
|
|
19
|
+
* keyframe, and a keyframe list CSS plays only as its last value.
|
|
20
|
+
*/
|
|
2
21
|
/** One field a conversion leaves out, plays differently, or can't carry over. */
|
|
3
22
|
interface MotionConversionNote {
|
|
4
23
|
/** Where the field sits, as a dotted path: `transition.opacity.repeat`. */
|
|
@@ -25,18 +44,5 @@ declare function convertMotion({
|
|
|
25
44
|
body: Body;
|
|
26
45
|
to: MotionRuntime;
|
|
27
46
|
}): MotionConversion;
|
|
28
|
-
/** What `motion/convert-plan` answers: the conversion `motion/convert` would make, and where it
|
|
29
|
-
* would write it. */
|
|
30
|
-
interface MotionConversionPlan {
|
|
31
|
-
readonly path: string;
|
|
32
|
-
readonly to: MotionRuntime;
|
|
33
|
-
/** The linked system the preset is borrowed from, whose link the conversion is written to. */
|
|
34
|
-
readonly link?: string;
|
|
35
|
-
readonly dropped: readonly MotionConversionNote[];
|
|
36
|
-
readonly approximated: readonly MotionConversionNote[];
|
|
37
|
-
readonly refused: readonly MotionConversionNote[];
|
|
38
|
-
/** Why the conversion would be refused. `undefined` when it would go through. */
|
|
39
|
-
readonly blocked?: string;
|
|
40
|
-
}
|
|
41
47
|
//#endregion
|
|
42
|
-
export { MotionConversion, MotionConversionNote,
|
|
48
|
+
export { MotionConversion, MotionConversionNote, MotionRuntime, convertMotion };
|
|
@@ -1,10 +1,28 @@
|
|
|
1
|
-
import { reject } from "../../framework/rejection.js";
|
|
2
1
|
import { isRef } from "../../framework/utils/refs.js";
|
|
3
2
|
import { isPlainObject } from "../../framework/utils/field-path.js";
|
|
4
3
|
import { MOTION_EASING_NAMES, bezierPoints, motionOfCssKeyword } from "./easings.js";
|
|
5
4
|
import { cssEasing } from "./css-timing.js";
|
|
6
5
|
import { JS_ONLY_FIELDS } from "./Motion.js";
|
|
7
6
|
//#region src/entities/system/motion-convert.ts
|
|
7
|
+
/**
|
|
8
|
+
* A motion preset rewritten for the other runtime: a JS preset as the CSS preset that plays closest
|
|
9
|
+
* to it, and a CSS preset as a JS one. Retained only for existing links that set a borrowed
|
|
10
|
+
* legacy preset's runtime. Explicit type/value presets never enter this compatibility path.
|
|
11
|
+
*
|
|
12
|
+
* The states (`variants`, `initial`, `animate`, `exit`, `while*`) mean the same in both runtimes, so
|
|
13
|
+
* they carry over as they are, and what changes is the timing:
|
|
14
|
+
*
|
|
15
|
+
* - JS → CSS. A spring stays a spring (`easing: { spring: { … } }`), a tween's `duration` becomes a
|
|
16
|
+
* CSS time, its `ease` a CSS easing, and a value's own transition goes under `properties`. What CSS
|
|
17
|
+
* has no counterpart for is dropped: `layout`, `drag*` and `whileDrag`, a state's `transitionEnd`,
|
|
18
|
+
* and a transition's orchestration (`when`, `delayChildren`, `staggerChildren`). A repeat is refused,
|
|
19
|
+
* since a transition plays once.
|
|
20
|
+
* - CSS → JS. The same in reverse. An easing `motion/react` can't read (`linear()`, `steps()`, a
|
|
21
|
+
* `var()`) is refused, and so are `@keyframes` that aren't one list of values per property.
|
|
22
|
+
*
|
|
23
|
+
* What a conversion approximates is played differently than written: a list of easings, one per
|
|
24
|
+
* keyframe, and a keyframe list CSS plays only as its last value.
|
|
25
|
+
*/
|
|
8
26
|
/** The states both runtimes share. */
|
|
9
27
|
const STATES = [
|
|
10
28
|
"initial",
|
|
@@ -53,6 +71,13 @@ var Notes = class {
|
|
|
53
71
|
/** `body` as the `to` runtime takes it, with what the conversion left out, changed or refused. */
|
|
54
72
|
function convertMotion({ body, to }) {
|
|
55
73
|
const notes = new Notes();
|
|
74
|
+
if (body.type !== void 0) {
|
|
75
|
+
notes.refused.push({
|
|
76
|
+
at: "type",
|
|
77
|
+
reason: "Typed presets use explicit replacement refs; automatic conversion is a legacy operation."
|
|
78
|
+
});
|
|
79
|
+
return { ...notes };
|
|
80
|
+
}
|
|
56
81
|
if (body.runtime === to) return {
|
|
57
82
|
body,
|
|
58
83
|
...notes
|
|
@@ -457,61 +482,5 @@ function offsetOf(offset) {
|
|
|
457
482
|
return value >= 0 && value <= 1 ? value : void 0;
|
|
458
483
|
}
|
|
459
484
|
const camelCase = (property) => property.startsWith("--") ? property : property.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
|
|
460
|
-
/** What converting the preset at `path` would do, without doing it. */
|
|
461
|
-
function planMotionConversion({ config, path, to }) {
|
|
462
|
-
const body = config.resolve("motion", path)?.toJSON();
|
|
463
|
-
if (!body) reject("NOT_FOUND", `There is no motion preset "${path}".`);
|
|
464
|
-
const link = config.borrowedFrom("motion", path);
|
|
465
|
-
const conversion = convertMotion({
|
|
466
|
-
body,
|
|
467
|
-
to
|
|
468
|
-
});
|
|
469
|
-
return {
|
|
470
|
-
path,
|
|
471
|
-
to,
|
|
472
|
-
...link === void 0 ? {} : { link },
|
|
473
|
-
dropped: conversion.dropped,
|
|
474
|
-
approximated: conversion.approximated,
|
|
475
|
-
refused: conversion.refused,
|
|
476
|
-
...conversion.refused.length > 0 ? { blocked: refusal({
|
|
477
|
-
path,
|
|
478
|
-
to,
|
|
479
|
-
refused: conversion.refused
|
|
480
|
-
}) } : {}
|
|
481
|
-
};
|
|
482
|
-
}
|
|
483
|
-
/**
|
|
484
|
-
* The preset at `path` rewritten for `to`. A preset of this config's own is replaced by its
|
|
485
|
-
* conversion. One borrowed from a linked system takes `runtime: to` on the link, which converts the
|
|
486
|
-
* source's preset as it reads, so it keeps following what the source changes.
|
|
487
|
-
*/
|
|
488
|
-
function convertMotionPreset({ config, path, to }) {
|
|
489
|
-
const plan = planMotionConversion({
|
|
490
|
-
config,
|
|
491
|
-
path,
|
|
492
|
-
to
|
|
493
|
-
});
|
|
494
|
-
if (plan.blocked !== void 0) reject("UNSUPPORTED", plan.blocked);
|
|
495
|
-
const body = config.resolve("motion", path)?.toJSON();
|
|
496
|
-
if (body.runtime === to) return config;
|
|
497
|
-
if (plan.link !== void 0) return config.apply({
|
|
498
|
-
kind: "motion",
|
|
499
|
-
operation: "update",
|
|
500
|
-
path,
|
|
501
|
-
data: { runtime: to }
|
|
502
|
-
});
|
|
503
|
-
return config.apply({
|
|
504
|
-
kind: "motion",
|
|
505
|
-
operation: "replace",
|
|
506
|
-
path,
|
|
507
|
-
data: convertMotion({
|
|
508
|
-
body,
|
|
509
|
-
to
|
|
510
|
-
}).body
|
|
511
|
-
});
|
|
512
|
-
}
|
|
513
|
-
function refusal({ path, to, refused }) {
|
|
514
|
-
return `"${path}" can't be converted to ${to === "css" ? "CSS" : "JS"}. ${refused.map((note) => `\`${note.at}\`: ${note.reason}`).join(" ")}`;
|
|
515
|
-
}
|
|
516
485
|
//#endregion
|
|
517
|
-
export { convertMotion
|
|
486
|
+
export { convertMotion };
|
|
@@ -3,6 +3,7 @@ import { composedValueFormOf } from "./composed-values.js";
|
|
|
3
3
|
import { createSliceMemo } from "../../framework/memo.js";
|
|
4
4
|
import { tokenValueRow } from "./token-values.js";
|
|
5
5
|
import { MOTION_EASING_NAMES, bezierPoints, motionOfCssKeyword } from "./easings.js";
|
|
6
|
+
import { webMotionBody } from "./motion-types.js";
|
|
6
7
|
import { inlineMotions } from "./motion-variants.js";
|
|
7
8
|
import { isReducedMotion } from "./reduced-motion.js";
|
|
8
9
|
import { jsMotionPropFields } from "./Motion.js";
|
|
@@ -221,8 +222,8 @@ const resolveJsMotionPresets = createSliceMemo({
|
|
|
221
222
|
const inline = {};
|
|
222
223
|
const tokens = [];
|
|
223
224
|
for (const preset of config.list("motion")) {
|
|
224
|
-
const body = preset.toJSON();
|
|
225
|
-
if (body.
|
|
225
|
+
const body = webMotionBody(preset.toJSON());
|
|
226
|
+
if (body.type !== "motion/react") continue;
|
|
226
227
|
presets[preset.path] = resolveBody(config, preset.path, body, tokens);
|
|
227
228
|
}
|
|
228
229
|
for (const entry of inlineMotions(config).valid) inline[entry.id] = resolveBody(config, entry.id, entry.body, tokens);
|
|
@@ -267,7 +268,7 @@ const presetReducedMotions = createSliceMemo({
|
|
|
267
268
|
compute: (config) => {
|
|
268
269
|
const out = {};
|
|
269
270
|
for (const preset of config.list("motion")) {
|
|
270
|
-
const { reducedMotion } = preset.toJSON();
|
|
271
|
+
const { reducedMotion } = webMotionBody(preset.toJSON());
|
|
271
272
|
if (isReducedMotion(reducedMotion)) out[preset.path] = reducedMotion;
|
|
272
273
|
}
|
|
273
274
|
for (const entry of inlineMotions(config).valid) if (isReducedMotion(entry.body.reducedMotion)) out[entry.id] = entry.body.reducedMotion;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
//#region src/entities/system/motion-types.d.ts
|
|
4
|
+
/** Read a typed preset's payload without translating between implementations. */
|
|
5
|
+
declare function webMotionBody(body: Record<string, unknown>): Record<string, unknown>;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { webMotionBody };
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { refTo } from "../../framework/utils/refs.js";
|
|
2
|
+
import { isCssProperty } from "../../css/values.js";
|
|
3
|
+
import { reanimatedStyle } from "./reanimated-motion.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
//#region src/entities/system/motion-types.ts
|
|
6
|
+
const number = z.union([z.number(), refTo("token")]);
|
|
7
|
+
const positive = z.union([z.number().positive(), refTo("token")]);
|
|
8
|
+
const nonnegative = z.union([z.number().nonnegative(), refTo("token")]);
|
|
9
|
+
/** Compatibility for the initial hook-based experiment; new presets use reanimatedStyle. */
|
|
10
|
+
const legacyReanimatedValue = z.discriminatedUnion("method", [z.strictObject({
|
|
11
|
+
method: z.literal("withTiming"),
|
|
12
|
+
config: z.strictObject({ duration: nonnegative.optional() })
|
|
13
|
+
}), z.strictObject({
|
|
14
|
+
method: z.literal("withSpring"),
|
|
15
|
+
config: z.strictObject({
|
|
16
|
+
stiffness: positive.optional(),
|
|
17
|
+
damping: nonnegative.optional(),
|
|
18
|
+
mass: positive.optional(),
|
|
19
|
+
overshootClamping: z.boolean().optional(),
|
|
20
|
+
energyThreshold: positive.optional(),
|
|
21
|
+
velocity: number.optional()
|
|
22
|
+
})
|
|
23
|
+
})]);
|
|
24
|
+
const reanimatedMotion = z.strictObject({
|
|
25
|
+
type: z.literal("react-native-reanimated").describe("The animation implementation that reads this preset."),
|
|
26
|
+
value: z.union([reanimatedStyle, legacyReanimatedValue]).describe("Serializable Reanimated transitions and keyframes, or a legacy timing/spring preset."),
|
|
27
|
+
reducedMotion: z.enum([
|
|
28
|
+
"user",
|
|
29
|
+
"always",
|
|
30
|
+
"never"
|
|
31
|
+
]).optional().describe("Whether native motion follows the OS preference, is always reduced, or is never reduced.")
|
|
32
|
+
});
|
|
33
|
+
/** CSS keyframes and CSS animation longhands. No Framer gesture or transition vocabulary. */
|
|
34
|
+
const cssMotionValue = z.strictObject({
|
|
35
|
+
keyframes: z.record(z.string(), z.record(z.string().refine((key) => isCssProperty(key) || key.startsWith("--"), "Expected a CSS property; use translate instead of x/y."), z.union([
|
|
36
|
+
z.string(),
|
|
37
|
+
z.number(),
|
|
38
|
+
refTo("token")
|
|
39
|
+
]))).optional(),
|
|
40
|
+
transitionProperty: z.string().optional(),
|
|
41
|
+
transitionDuration: z.string().optional(),
|
|
42
|
+
transitionDelay: z.string().optional(),
|
|
43
|
+
transitionTimingFunction: z.string().optional(),
|
|
44
|
+
transitionBehavior: z.enum(["normal", "allow-discrete"]).optional(),
|
|
45
|
+
animationDuration: z.string().optional(),
|
|
46
|
+
animationDelay: z.string().optional(),
|
|
47
|
+
animationTimingFunction: z.string().optional(),
|
|
48
|
+
animationIterationCount: z.union([z.number(), z.literal("infinite")]).optional(),
|
|
49
|
+
animationDirection: z.enum([
|
|
50
|
+
"normal",
|
|
51
|
+
"reverse",
|
|
52
|
+
"alternate",
|
|
53
|
+
"alternate-reverse"
|
|
54
|
+
]).optional(),
|
|
55
|
+
animationFillMode: z.enum([
|
|
56
|
+
"none",
|
|
57
|
+
"forwards",
|
|
58
|
+
"backwards",
|
|
59
|
+
"both"
|
|
60
|
+
]).optional()
|
|
61
|
+
});
|
|
62
|
+
/** Read a typed preset's payload without translating between implementations. */
|
|
63
|
+
function webMotionBody(body) {
|
|
64
|
+
if (body.type === void 0) return {
|
|
65
|
+
...body,
|
|
66
|
+
type: body.runtime === "js" ? "motion/react" : body.runtime
|
|
67
|
+
};
|
|
68
|
+
return {
|
|
69
|
+
...body.value,
|
|
70
|
+
type: body.type,
|
|
71
|
+
runtime: body.type === "motion/react" ? "js" : body.type,
|
|
72
|
+
...body.reducedMotion === void 0 ? {} : { reducedMotion: body.reducedMotion }
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/** Serializable subset of Motion's transition options, with seconds for time values. */
|
|
76
|
+
const transitionOptions = z.strictObject({
|
|
77
|
+
type: z.union([z.enum([
|
|
78
|
+
"tween",
|
|
79
|
+
"spring",
|
|
80
|
+
"inertia"
|
|
81
|
+
]), z.literal(false)]).optional(),
|
|
82
|
+
duration: nonnegative.optional(),
|
|
83
|
+
delay: number.optional(),
|
|
84
|
+
ease: z.union([
|
|
85
|
+
z.string(),
|
|
86
|
+
z.tuple([
|
|
87
|
+
z.number(),
|
|
88
|
+
z.number(),
|
|
89
|
+
z.number(),
|
|
90
|
+
z.number()
|
|
91
|
+
]),
|
|
92
|
+
refTo("token"),
|
|
93
|
+
z.array(z.union([z.string(), z.tuple([
|
|
94
|
+
z.number(),
|
|
95
|
+
z.number(),
|
|
96
|
+
z.number(),
|
|
97
|
+
z.number()
|
|
98
|
+
])]))
|
|
99
|
+
]).optional(),
|
|
100
|
+
times: z.array(z.number()).optional(),
|
|
101
|
+
repeat: nonnegative.optional(),
|
|
102
|
+
repeatType: z.enum([
|
|
103
|
+
"loop",
|
|
104
|
+
"reverse",
|
|
105
|
+
"mirror"
|
|
106
|
+
]).optional(),
|
|
107
|
+
repeatDelay: nonnegative.optional(),
|
|
108
|
+
stiffness: positive.optional(),
|
|
109
|
+
damping: nonnegative.optional(),
|
|
110
|
+
mass: positive.optional(),
|
|
111
|
+
velocity: number.optional(),
|
|
112
|
+
bounce: number.optional(),
|
|
113
|
+
visualDuration: nonnegative.optional(),
|
|
114
|
+
restSpeed: nonnegative.optional(),
|
|
115
|
+
restDelta: nonnegative.optional(),
|
|
116
|
+
when: z.enum(["beforeChildren", "afterChildren"]).optional(),
|
|
117
|
+
delayChildren: number.optional(),
|
|
118
|
+
staggerChildren: number.optional(),
|
|
119
|
+
staggerDirection: z.union([z.literal(1), z.literal(-1)]).optional()
|
|
120
|
+
});
|
|
121
|
+
const motionTransition = transitionOptions.loose().superRefine((value, ctx) => {
|
|
122
|
+
for (const [key, options] of Object.entries(value)) {
|
|
123
|
+
if (key in transitionOptions.shape) continue;
|
|
124
|
+
if (!transitionOptions.safeParse(options).success) ctx.addIssue({
|
|
125
|
+
code: "custom",
|
|
126
|
+
path: [key],
|
|
127
|
+
message: "Expected per-property Motion transition options."
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
//#endregion
|
|
132
|
+
export { cssMotionValue, motionTransition, reanimatedMotion, webMotionBody };
|