@lumencast/compiler 0.1.0 → 0.2.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/src/compile.ts CHANGED
@@ -1,273 +1,325 @@
1
- // LSML 1.0 → flat RenderBundle compiler.
2
- //
3
- // LSML lets authors write idiomatic primitives with inline `bind: { value: "path" }`,
4
- // CSS-style `style.fontSize`, `repeat.template`, `animate` directives. The runtime
5
- // expects a flat shape: per-node `bindings` map, primitive-specific prop names
6
- // (Solar lineage: `text.size`, `text.colour`), and `repeat` whose template is its
7
- // only child.
8
- //
9
- // This compiler bridges the two formats. It does NOT execute the bundle — it
10
- // produces a JSON the runtime then renders.
11
-
12
- import type { RenderBundle, RenderNode } from "@lumencast/runtime";
13
- import type {
14
- LSMLAnimateDirective,
15
- LSMLBundle,
16
- LSMLNode,
17
- LSMLRepeat,
18
- LSMLText,
19
- } from "./lsml-types.js";
20
-
21
- export interface CompileOptions {
22
- /** When true, throws on any unrecognized LSML extension. Default false (warn-only). */
23
- strict?: boolean;
24
- /** Optional warn collector receives each warning string. */
25
- onWarn?: (message: string) => void;
26
- }
27
-
28
- export function compileBundle(lsml: LSMLBundle, options: CompileOptions = {}): RenderBundle {
29
- if (lsml.lsml !== "1.0") {
30
- throw new Error(`compiler: only LSML 1.0 is supported, got ${lsml.lsml}`);
31
- }
32
- return {
33
- scene_version: lsml.scene_version,
34
- root: compileNode(lsml.layout, options),
35
- ...(lsml.operator_inputs
36
- ? {
37
- operator_inputs: lsml.operator_inputs.map((oi) => ({
38
- path: oi.path,
39
- label: oi.label,
40
- type: oi.type as never,
41
- writable_by: oi.writable_by,
42
- ...(oi.group !== undefined ? { group: oi.group } : {}),
43
- ...(oi.constraints ?? {}),
44
- })),
45
- }
46
- : {}),
47
- ...(lsml.external_adapters
48
- ? {
49
- external_adapters: lsml.external_adapters as RenderBundle["external_adapters"],
50
- }
51
- : {}),
52
- };
53
- }
54
-
55
- function compileNode(node: LSMLNode, opts: CompileOptions): RenderNode {
56
- if (node.kind === "repeat") {
57
- return compileRepeat(node, opts);
58
- }
59
-
60
- const props: Record<string, unknown> = {};
61
- const bindings: Record<string, string> = {};
62
-
63
- // Common: bind.value/src → bindings
64
- if (node.bind?.value !== undefined) bindings["value"] = node.bind.value;
65
- if (node.bind?.src !== undefined) bindings["src"] = node.bind.src;
66
- if (node.bindStyle) {
67
- for (const [k, v] of Object.entries(node.bindStyle)) bindings[k] = v;
68
- }
69
-
70
- switch (node.kind) {
71
- case "stack":
72
- if (node.direction !== undefined) props["direction"] = node.direction;
73
- if (node.gap !== undefined) props["gap"] = node.gap;
74
- if (node.align !== undefined) props["align"] = mapAlign(node.align);
75
- if (node.justify !== undefined) props["justify"] = mapJustify(node.justify);
76
- if (node.padding !== undefined) props["padding"] = node.padding;
77
- if (node.rtl !== undefined) props["rtl"] = node.rtl;
78
- break;
79
-
80
- case "grid":
81
- if (node.columns !== undefined) props["columns"] = node.columns;
82
- if (node.rows !== undefined) props["rows"] = node.rows;
83
- if (node.gap !== undefined) props["gap"] = node.gap;
84
- if (node.padding !== undefined) props["padding"] = node.padding;
85
- break;
86
-
87
- case "frame":
88
- if (node.size !== undefined) {
89
- props["width"] = node.size.w;
90
- props["height"] = node.size.h;
91
- }
92
- if (node.position !== undefined) {
93
- props["x"] = node.position.x;
94
- props["y"] = node.position.y;
95
- }
96
- if (node.background !== undefined) props["background"] = node.background;
97
- break;
98
-
99
- case "text":
100
- mapTextStyle(node, props);
101
- if (node.format !== undefined) props["format"] = node.format;
102
- if (node.maxLines !== undefined) props["maxLines"] = node.maxLines;
103
- break;
104
-
105
- case "image":
106
- props["alt"] = node.alt;
107
- props["width"] = node.size.w;
108
- props["height"] = node.size.h;
109
- if (node.fit !== undefined) props["fit"] = node.fit;
110
- break;
111
-
112
- case "shape":
113
- props["geometry"] = node.geometry;
114
- if (node.size !== undefined) {
115
- props["width"] = node.size.w;
116
- props["height"] = node.size.h;
117
- }
118
- if (node.pathData !== undefined) props["pathData"] = node.pathData;
119
- if (node.fill !== undefined) props["fill"] = node.fill;
120
- if (node.stroke !== undefined) props["stroke"] = node.stroke;
121
- if (node.cornerRadius !== undefined) props["cornerRadius"] = node.cornerRadius;
122
- if (node.ariaLabel !== undefined) props["ariaLabel"] = node.ariaLabel;
123
- break;
124
-
125
- case "media":
126
- props["kind_hint"] = node.kind_hint;
127
- if (node.controls !== undefined) props["controls"] = node.controls;
128
- if (node.autoplay !== undefined) props["autoplay"] = node.autoplay;
129
- if (node.muted !== undefined) props["muted"] = node.muted;
130
- if (node.loop !== undefined) props["loop"] = node.loop;
131
- if (node.size !== undefined) {
132
- props["width"] = node.size.w;
133
- props["height"] = node.size.h;
134
- }
135
- break;
136
- }
137
-
138
- const children = node.children?.map((c) => compileNode(c, opts));
139
-
140
- const out: RenderNode = { kind: node.kind };
141
- if (node.id !== undefined) out.id = node.id;
142
- if (Object.keys(props).length > 0) out.props = props;
143
- if (Object.keys(bindings).length > 0) out.bindings = bindings;
144
- if (children && children.length > 0) out.children = children;
145
-
146
- // Animate directive transitions on the listed prop keys.
147
- if (node.animate) {
148
- const tx = compileAnimate(node.animate);
149
- if (tx) {
150
- const transitions: Record<string, ReturnType<typeof compileAnimate>> = {};
151
- if (node.animate.opacity !== undefined) transitions["opacity"] = tx;
152
- if (node.animate.transform?.scale !== undefined) transitions["scale"] = tx;
153
- if (node.animate.transform?.rotate !== undefined) transitions["rotate"] = tx;
154
- if (node.animate.transform?.translate !== undefined) {
155
- transitions["x"] = tx;
156
- transitions["y"] = tx;
157
- }
158
- // Type assertion: RenderNode.transitions matches Transition shape; the
159
- // cast keeps the compiler self-contained without re-importing the runtime
160
- // Transition type.
161
- if (Object.keys(transitions).length > 0) {
162
- out.transitions = transitions as RenderNode["transitions"];
163
- }
164
- }
165
- }
166
-
167
- return out;
168
- }
169
-
170
- function compileRepeat(node: LSMLRepeat, opts: CompileOptions): RenderNode {
171
- if (!node.bind?.items) {
172
- throw new Error(`compiler: repeat node "${node.id ?? "<anon>"}" missing bind.items`);
173
- }
174
- const compiledTemplate = compileNode(node.template, opts);
175
- const out: RenderNode = {
176
- kind: "repeat",
177
- bindings: { items: node.bind.items },
178
- children: [compiledTemplate],
179
- };
180
- if (node.id !== undefined) out.id = node.id;
181
- return out;
182
- }
183
-
184
- function mapTextStyle(node: LSMLText, props: Record<string, unknown>): void {
185
- if (!node.style) return;
186
- const s = node.style;
187
- // Solar's Text primitive consumes size/weight/colour (UK), not CSS-style names.
188
- if (s.fontSize !== undefined) props["size"] = s.fontSize;
189
- if (s.fontWeight !== undefined) props["weight"] = s.fontWeight;
190
- if (s.color !== undefined) props["colour"] = s.color;
191
- if (s.textAlign !== undefined) props["align"] = mapTextAlign(s.textAlign);
192
- if (s.lineHeight !== undefined) props["lineHeight"] = s.lineHeight;
193
- }
194
-
195
- function mapAlign(a: NonNullable<Extract<LSMLNode, { kind: "stack" }>["align"]>): string {
196
- // LSML uses CSS-grid vocabulary; Solar's Stack consumes flexbox vocabulary.
197
- switch (a) {
198
- case "start":
199
- return "flex-start";
200
- case "center":
201
- return "center";
202
- case "end":
203
- return "flex-end";
204
- case "stretch":
205
- return "stretch";
206
- }
207
- }
208
-
209
- function mapJustify(j: NonNullable<Extract<LSMLNode, { kind: "stack" }>["justify"]>): string {
210
- switch (j) {
211
- case "start":
212
- return "flex-start";
213
- case "center":
214
- return "center";
215
- case "end":
216
- return "flex-end";
217
- case "space-between":
218
- return "space-between";
219
- case "space-around":
220
- return "space-around";
221
- }
222
- }
223
-
224
- function mapTextAlign(a: NonNullable<NonNullable<LSMLText["style"]>["textAlign"]>): string {
225
- switch (a) {
226
- case "start":
227
- return "left";
228
- case "end":
229
- return "right";
230
- default:
231
- return a;
232
- }
233
- }
234
-
235
- function compileAnimate(a: LSMLAnimateDirective):
236
- | {
237
- kind: "tween";
238
- duration_ms: number;
239
- ease?: "linear" | "cubic-in" | "cubic-out" | "cubic-in-out";
240
- }
241
- | { kind: "spring"; stiffness?: number; damping?: number }
242
- | undefined {
243
- const t = a.transition;
244
- if (!t) return undefined;
245
- if (t.easing === "spring") {
246
- const out: { kind: "spring"; stiffness?: number; damping?: number } = { kind: "spring" };
247
- if (t.stiffness !== undefined) out.stiffness = t.stiffness;
248
- if (t.damping !== undefined) out.damping = t.damping;
249
- return out;
250
- }
251
- return {
252
- kind: "tween",
253
- duration_ms: t.duration ?? 200,
254
- ease: mapEase(t.easing),
255
- };
256
- }
257
-
258
- function mapEase(
259
- e: "linear" | "ease-in" | "ease-out" | "ease-in-out" | "spring" | undefined,
260
- ): "linear" | "cubic-in" | "cubic-out" | "cubic-in-out" | undefined {
261
- switch (e) {
262
- case "linear":
263
- return "linear";
264
- case "ease-in":
265
- return "cubic-in";
266
- case "ease-out":
267
- return "cubic-out";
268
- case "ease-in-out":
269
- return "cubic-in-out";
270
- default:
271
- return undefined;
272
- }
273
- }
1
+ // LSML 1.0 / 1.1 → flat RenderBundle compiler.
2
+ //
3
+ // LSML lets authors write idiomatic primitives with inline `bind: { value: "path" }`,
4
+ // CSS-style `style.fontSize`, `repeat.template`, `animate` directives. The runtime
5
+ // expects a flat shape: per-node `bindings` map, primitive-specific prop names
6
+ // (Solar lineage: `text.size`, `text.colour`), and `repeat` whose template is its
7
+ // only child.
8
+ //
9
+ // This compiler bridges the two formats. It does NOT execute the bundle — it
10
+ // produces a JSON the runtime then renders.
11
+ //
12
+ // Version support :
13
+ // - 1.0 (LSML-1.md) : the original 9-primitive catalog.
14
+ // - 1.1 (LSML-1.md §17 / §5.4 / §4.9) : additive over 1.0 — `instance`
15
+ // primitive, universal props (`visible` / `sizing` / `opacity` /
16
+ // `rotation`) on every primitive, `bindUniversal` field, multi-fill
17
+ // `fills[]` on shapes, stacked `backgrounds[]` on frames, profile
18
+ // declarations, `$schema` field.
19
+ //
20
+ // Unsupported 1.1 features compile to best-effort output (the renderer
21
+ // surfaces `BUNDLE_INCOMPATIBLE` per LSML §15.1 if it can't honour them).
22
+ // Bundles tagged 2.x are rejected major bumps require explicit support.
23
+
24
+ import type { RenderBundle, RenderNode } from "@lumencast/runtime";
25
+ import type {
26
+ LSMLAnimateDirective,
27
+ LSMLBundle,
28
+ LSMLNode,
29
+ LSMLRepeat,
30
+ LSMLText,
31
+ } from "./lsml-types.js";
32
+
33
+ export interface CompileOptions {
34
+ /** When true, throws on any unrecognized LSML extension. Default false (warn-only). */
35
+ strict?: boolean;
36
+ /** Optional warn collector — receives each warning string. */
37
+ onWarn?: (message: string) => void;
38
+ }
39
+
40
+ const SUPPORTED_VERSIONS = new Set(["1.0", "1.1"] as const);
41
+
42
+ export function compileBundle(lsml: LSMLBundle, options: CompileOptions = {}): RenderBundle {
43
+ if (!SUPPORTED_VERSIONS.has(lsml.lsml as "1.0" | "1.1")) {
44
+ throw new Error(
45
+ `compiler: LSML version "${lsml.lsml}" is not supported (supported: ${[...SUPPORTED_VERSIONS].join(", ")})`,
46
+ );
47
+ }
48
+ return {
49
+ scene_version: lsml.scene_version,
50
+ root: compileNode(lsml.layout, options),
51
+ ...(lsml.operator_inputs
52
+ ? {
53
+ operator_inputs: lsml.operator_inputs.map((oi) => ({
54
+ path: oi.path,
55
+ label: oi.label,
56
+ type: oi.type as never,
57
+ writable_by: oi.writable_by,
58
+ ...(oi.group !== undefined ? { group: oi.group } : {}),
59
+ ...(oi.constraints ?? {}),
60
+ })),
61
+ }
62
+ : {}),
63
+ ...(lsml.external_adapters
64
+ ? {
65
+ external_adapters: lsml.external_adapters as RenderBundle["external_adapters"],
66
+ }
67
+ : {}),
68
+ };
69
+ }
70
+
71
+ function compileNode(node: LSMLNode, opts: CompileOptions): RenderNode {
72
+ if (node.kind === "repeat") {
73
+ return compileRepeat(node, opts);
74
+ }
75
+
76
+ const props: Record<string, unknown> = {};
77
+ const bindings: Record<string, string> = {};
78
+
79
+ // Common: bind.value/src → bindings
80
+ if (node.bind?.value !== undefined) bindings["value"] = node.bind.value;
81
+ if (node.bind?.src !== undefined) bindings["src"] = node.bind.src;
82
+ if (node.bindStyle) {
83
+ for (const [k, v] of Object.entries(node.bindStyle)) bindings[k] = v;
84
+ }
85
+
86
+ switch (node.kind) {
87
+ case "stack":
88
+ if (node.direction !== undefined) props["direction"] = node.direction;
89
+ if (node.gap !== undefined) props["gap"] = node.gap;
90
+ if (node.align !== undefined) props["align"] = mapAlign(node.align);
91
+ if (node.justify !== undefined) props["justify"] = mapJustify(node.justify);
92
+ if (node.padding !== undefined) props["padding"] = node.padding;
93
+ if (node.rtl !== undefined) props["rtl"] = node.rtl;
94
+ break;
95
+
96
+ case "grid":
97
+ if (node.columns !== undefined) props["columns"] = node.columns;
98
+ if (node.rows !== undefined) props["rows"] = node.rows;
99
+ if (node.gap !== undefined) props["gap"] = node.gap;
100
+ if (node.padding !== undefined) props["padding"] = node.padding;
101
+ break;
102
+
103
+ case "frame":
104
+ if (node.size !== undefined) {
105
+ props["width"] = node.size.w;
106
+ props["height"] = node.size.h;
107
+ }
108
+ if (node.position !== undefined) {
109
+ props["x"] = node.position.x;
110
+ props["y"] = node.position.y;
111
+ }
112
+ if (node.background !== undefined) props["background"] = node.background;
113
+ break;
114
+
115
+ case "text":
116
+ mapTextStyle(node, props);
117
+ if (node.format !== undefined) props["format"] = node.format;
118
+ if (node.maxLines !== undefined) props["maxLines"] = node.maxLines;
119
+ break;
120
+
121
+ case "image":
122
+ props["alt"] = node.alt;
123
+ props["width"] = node.size.w;
124
+ props["height"] = node.size.h;
125
+ if (node.fit !== undefined) props["fit"] = node.fit;
126
+ break;
127
+
128
+ case "shape":
129
+ props["geometry"] = node.geometry;
130
+ if (node.size !== undefined) {
131
+ props["width"] = node.size.w;
132
+ props["height"] = node.size.h;
133
+ }
134
+ if (node.pathData !== undefined) props["pathData"] = node.pathData;
135
+ if (node.fill !== undefined) props["fill"] = node.fill;
136
+ if (node.stroke !== undefined) props["stroke"] = node.stroke;
137
+ if (node.cornerRadius !== undefined) props["cornerRadius"] = node.cornerRadius;
138
+ if (node.ariaLabel !== undefined) props["ariaLabel"] = node.ariaLabel;
139
+ break;
140
+
141
+ case "media":
142
+ props["kind_hint"] = node.kind_hint;
143
+ if (node.controls !== undefined) props["controls"] = node.controls;
144
+ if (node.autoplay !== undefined) props["autoplay"] = node.autoplay;
145
+ if (node.muted !== undefined) props["muted"] = node.muted;
146
+ if (node.loop !== undefined) props["loop"] = node.loop;
147
+ if (node.size !== undefined) {
148
+ props["width"] = node.size.w;
149
+ props["height"] = node.size.h;
150
+ }
151
+ break;
152
+
153
+ case "instance":
154
+ // 1.1+ — sub-scene mount (LSML §4.9). The runtime resolves
155
+ // `scene_id` + `scene_version` to a separate bundle and renders
156
+ // it inline ; the compiler just forwards the reference.
157
+ props["scene_id"] = node.scene_id;
158
+ props["scene_version"] = node.scene_version;
159
+ if (node.size !== undefined) {
160
+ props["width"] = node.size.w;
161
+ props["height"] = node.size.h;
162
+ }
163
+ if (node.fit !== undefined) props["fit"] = node.fit;
164
+ if (node.params !== undefined) props["params"] = node.params;
165
+ if (node.bindParams) {
166
+ for (const [k, v] of Object.entries(node.bindParams)) {
167
+ bindings[`params.${k}`] = v;
168
+ }
169
+ }
170
+ break;
171
+ }
172
+
173
+ // Universal props (LSML §5.4 — 1.1+). Forwarded to the renderer when
174
+ // present on the source node. Defaults are spec-side, not compiler-side
175
+ // (the runtime applies them per primitive).
176
+ if (node.visible !== undefined) props["visible"] = node.visible;
177
+ if (node.opacity !== undefined) props["opacity"] = node.opacity;
178
+ if (node.rotation !== undefined) props["rotation"] = node.rotation;
179
+ if (node.sizing !== undefined) props["sizing"] = node.sizing;
180
+ if (node.position !== undefined && props["x"] === undefined && props["y"] === undefined) {
181
+ // Frame's case above already sets x/y from `position` ; the universal
182
+ // §5.4 prop takes effect on every other primitive.
183
+ props["x"] = node.position.x;
184
+ props["y"] = node.position.y;
185
+ }
186
+ if (node.bindUniversal) {
187
+ for (const [k, v] of Object.entries(node.bindUniversal)) bindings[k] = v;
188
+ }
189
+
190
+ const children = node.children?.map((c) => compileNode(c, opts));
191
+
192
+ const out: RenderNode = { kind: node.kind };
193
+ if (node.id !== undefined) out.id = node.id;
194
+ if (Object.keys(props).length > 0) out.props = props;
195
+ if (Object.keys(bindings).length > 0) out.bindings = bindings;
196
+ if (children && children.length > 0) out.children = children;
197
+
198
+ // Animate directive → transitions on the listed prop keys.
199
+ if (node.animate) {
200
+ const tx = compileAnimate(node.animate);
201
+ if (tx) {
202
+ const transitions: Record<string, ReturnType<typeof compileAnimate>> = {};
203
+ if (node.animate.opacity !== undefined) transitions["opacity"] = tx;
204
+ if (node.animate.transform?.scale !== undefined) transitions["scale"] = tx;
205
+ if (node.animate.transform?.rotate !== undefined) transitions["rotate"] = tx;
206
+ if (node.animate.transform?.translate !== undefined) {
207
+ transitions["x"] = tx;
208
+ transitions["y"] = tx;
209
+ }
210
+ // Type assertion: RenderNode.transitions matches Transition shape; the
211
+ // cast keeps the compiler self-contained without re-importing the runtime
212
+ // Transition type.
213
+ if (Object.keys(transitions).length > 0) {
214
+ out.transitions = transitions as RenderNode["transitions"];
215
+ }
216
+ }
217
+ }
218
+
219
+ return out;
220
+ }
221
+
222
+ function compileRepeat(node: LSMLRepeat, opts: CompileOptions): RenderNode {
223
+ if (!node.bind?.items) {
224
+ throw new Error(`compiler: repeat node "${node.id ?? "<anon>"}" missing bind.items`);
225
+ }
226
+ const compiledTemplate = compileNode(node.template, opts);
227
+ const out: RenderNode = {
228
+ kind: "repeat",
229
+ bindings: { items: node.bind.items },
230
+ children: [compiledTemplate],
231
+ };
232
+ if (node.id !== undefined) out.id = node.id;
233
+ return out;
234
+ }
235
+
236
+ function mapTextStyle(node: LSMLText, props: Record<string, unknown>): void {
237
+ if (!node.style) return;
238
+ const s = node.style;
239
+ // Solar's Text primitive consumes size/weight/colour (UK), not CSS-style names.
240
+ if (s.fontSize !== undefined) props["size"] = s.fontSize;
241
+ if (s.fontWeight !== undefined) props["weight"] = s.fontWeight;
242
+ if (s.color !== undefined) props["colour"] = s.color;
243
+ if (s.textAlign !== undefined) props["align"] = mapTextAlign(s.textAlign);
244
+ if (s.lineHeight !== undefined) props["lineHeight"] = s.lineHeight;
245
+ }
246
+
247
+ function mapAlign(a: NonNullable<Extract<LSMLNode, { kind: "stack" }>["align"]>): string {
248
+ // LSML uses CSS-grid vocabulary; Solar's Stack consumes flexbox vocabulary.
249
+ switch (a) {
250
+ case "start":
251
+ return "flex-start";
252
+ case "center":
253
+ return "center";
254
+ case "end":
255
+ return "flex-end";
256
+ case "stretch":
257
+ return "stretch";
258
+ }
259
+ }
260
+
261
+ function mapJustify(j: NonNullable<Extract<LSMLNode, { kind: "stack" }>["justify"]>): string {
262
+ switch (j) {
263
+ case "start":
264
+ return "flex-start";
265
+ case "center":
266
+ return "center";
267
+ case "end":
268
+ return "flex-end";
269
+ case "space-between":
270
+ return "space-between";
271
+ case "space-around":
272
+ return "space-around";
273
+ }
274
+ }
275
+
276
+ function mapTextAlign(a: NonNullable<NonNullable<LSMLText["style"]>["textAlign"]>): string {
277
+ switch (a) {
278
+ case "start":
279
+ return "left";
280
+ case "end":
281
+ return "right";
282
+ default:
283
+ return a;
284
+ }
285
+ }
286
+
287
+ function compileAnimate(a: LSMLAnimateDirective):
288
+ | {
289
+ kind: "tween";
290
+ duration_ms: number;
291
+ ease?: "linear" | "cubic-in" | "cubic-out" | "cubic-in-out";
292
+ }
293
+ | { kind: "spring"; stiffness?: number; damping?: number }
294
+ | undefined {
295
+ const t = a.transition;
296
+ if (!t) return undefined;
297
+ if (t.easing === "spring") {
298
+ const out: { kind: "spring"; stiffness?: number; damping?: number } = { kind: "spring" };
299
+ if (t.stiffness !== undefined) out.stiffness = t.stiffness;
300
+ if (t.damping !== undefined) out.damping = t.damping;
301
+ return out;
302
+ }
303
+ return {
304
+ kind: "tween",
305
+ duration_ms: t.duration ?? 200,
306
+ ease: mapEase(t.easing),
307
+ };
308
+ }
309
+
310
+ function mapEase(
311
+ e: "linear" | "ease-in" | "ease-out" | "ease-in-out" | "spring" | undefined,
312
+ ): "linear" | "cubic-in" | "cubic-out" | "cubic-in-out" | undefined {
313
+ switch (e) {
314
+ case "linear":
315
+ return "linear";
316
+ case "ease-in":
317
+ return "cubic-in";
318
+ case "ease-out":
319
+ return "cubic-out";
320
+ case "ease-in-out":
321
+ return "cubic-in-out";
322
+ default:
323
+ return undefined;
324
+ }
325
+ }