@markdy/renderer-dom 0.5.8 → 0.7.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/README.md +3 -1
- package/dist/index.d.ts +16 -0
- package/dist/index.js +436 -75
- package/package.json +7 -4
package/README.md
CHANGED
|
@@ -5,10 +5,12 @@ Web Animations API renderer for [MarkdyScript](../../docs/SYNTAX.md) scenes. Tra
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- **Browser-native** — Web Animations API + CSS transforms, no Canvas or GSAP
|
|
8
|
-
- **Emoji stick figures** — `figure` actor type with articulatable limbs
|
|
8
|
+
- **Emoji stick figures** — `figure` actor type with articulatable limbs, shoulder/hip joints, and body-part rig
|
|
9
|
+
- **Expressive gestures** — built-in `wave`, `nod`, `jump`, `bounce`, and multi-part `pose` actions
|
|
9
10
|
- **Seek-safe** — manual `currentTime` control enables reliable `seek()` in any direction
|
|
10
11
|
- **Face expressions** — instant emoji face swaps that work correctly on seek-back
|
|
11
12
|
- **Speech bubbles** — auto-positioned bubbles with fade-in/fade-out
|
|
13
|
+
- **Z-index layering** — `z` modifier for actor depth ordering
|
|
12
14
|
- **Single dependency** — only `@markdy/core`
|
|
13
15
|
|
|
14
16
|
## Installation
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { SceneAST, ParseWarning } from '@markdy/core';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* @markdy/renderer-dom — Player
|
|
3
5
|
*
|
|
@@ -19,10 +21,18 @@
|
|
|
19
21
|
* each actor's before-phase state falls through to the inline style we set,
|
|
20
22
|
* which gives correct initial positions and opacity values.
|
|
21
23
|
*/
|
|
24
|
+
|
|
22
25
|
interface PlayerOptions {
|
|
23
26
|
container: HTMLElement;
|
|
24
27
|
code: string;
|
|
25
28
|
assets?: Record<string, string>;
|
|
29
|
+
/**
|
|
30
|
+
* Host-resolved ASTs for `import "<path>" as <ns>` statements. When
|
|
31
|
+
* provided, the namespaces' `vars`, `defs`, and `seqs` merge into the
|
|
32
|
+
* parsed scene under `ns.<name>`. Unresolved namespaces emit a soft
|
|
33
|
+
* `import-unresolved` warning.
|
|
34
|
+
*/
|
|
35
|
+
imports?: Record<string, SceneAST>;
|
|
26
36
|
autoplay?: boolean;
|
|
27
37
|
/** Loop the animation when it reaches the end. Defaults to true. */
|
|
28
38
|
loop?: boolean;
|
|
@@ -30,6 +40,12 @@ interface PlayerOptions {
|
|
|
30
40
|
copyright?: boolean;
|
|
31
41
|
/** Show a rainbow progress bar around the viewport border. Defaults to true. */
|
|
32
42
|
progressBar?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Invoked once per soft `ParseWarning` emitted by the parser — e.g. unknown
|
|
45
|
+
* actions, unknown modifier keys, unresolved imports. Defaults to `console.warn`.
|
|
46
|
+
* Pass a no-op to silence; pass a custom collector to surface warnings in a UI.
|
|
47
|
+
*/
|
|
48
|
+
onWarning?: (warning: ParseWarning) => void;
|
|
33
49
|
}
|
|
34
50
|
interface Player {
|
|
35
51
|
play(): void;
|
package/dist/index.js
CHANGED
|
@@ -14,6 +14,9 @@ function stateFrom(def) {
|
|
|
14
14
|
function tx(s) {
|
|
15
15
|
return `translate(${s.x}px, ${s.y}px) scale(${s.scale}) rotate(${s.rotate}deg)`;
|
|
16
16
|
}
|
|
17
|
+
function txCaption(s) {
|
|
18
|
+
return `translate(calc(${s.x}px - 50%), calc(${s.y}px - 50%)) scale(${s.scale}) rotate(${s.rotate}deg)`;
|
|
19
|
+
}
|
|
17
20
|
var EASE_MAP = {
|
|
18
21
|
linear: "linear",
|
|
19
22
|
in: "ease-in",
|
|
@@ -29,7 +32,7 @@ function createFigureEl(def) {
|
|
|
29
32
|
const skinColor = def.args[0] || "#ffdbac";
|
|
30
33
|
const isFemale = def.args[1] === "f";
|
|
31
34
|
const startFace = def.args[2] || (isFemale ? "\u{1F642}" : "\u{1F636}");
|
|
32
|
-
const ink = "
|
|
35
|
+
const ink = "currentColor";
|
|
33
36
|
const WRAP_W = 80;
|
|
34
37
|
const FACE_FS = 40;
|
|
35
38
|
const SHIRT_FS = isFemale ? 48 : 44;
|
|
@@ -39,6 +42,7 @@ function createFigureEl(def) {
|
|
|
39
42
|
const shY = Math.round(SHIRT_FS * 0.28);
|
|
40
43
|
const ARM_W = 36, ARM_H = 22;
|
|
41
44
|
const LEG_H = 54, LEG_STICK_H = 34;
|
|
45
|
+
const JOINT_SIZE = 6;
|
|
42
46
|
const wrap = document.createElement("div");
|
|
43
47
|
Object.assign(wrap.style, {
|
|
44
48
|
position: "relative",
|
|
@@ -62,13 +66,13 @@ function createFigureEl(def) {
|
|
|
62
66
|
});
|
|
63
67
|
const neck = document.createElement("div");
|
|
64
68
|
Object.assign(neck.style, {
|
|
65
|
-
width: "
|
|
66
|
-
height: "
|
|
69
|
+
width: "10px",
|
|
70
|
+
height: "10px",
|
|
67
71
|
background: skinColor,
|
|
68
|
-
borderRadius: "
|
|
72
|
+
borderRadius: "4px",
|
|
69
73
|
flexShrink: "0",
|
|
70
|
-
marginTop: "-
|
|
71
|
-
marginBottom: "-
|
|
74
|
+
marginTop: "-3px",
|
|
75
|
+
marginBottom: "-3px",
|
|
72
76
|
zIndex: "4"
|
|
73
77
|
});
|
|
74
78
|
const shirtRow = document.createElement("div");
|
|
@@ -91,6 +95,12 @@ function createFigureEl(def) {
|
|
|
91
95
|
pointerEvents: "none"
|
|
92
96
|
});
|
|
93
97
|
shirtRow.appendChild(torso);
|
|
98
|
+
const shoulderL = buildJoint("left", skinColor, JOINT_SIZE, shLx, shY, WRAP_W);
|
|
99
|
+
const shoulderR = buildJoint("right", skinColor, JOINT_SIZE, shRx, shY, WRAP_W);
|
|
100
|
+
shoulderL.dataset.figShoulderL = "";
|
|
101
|
+
shoulderR.dataset.figShoulderR = "";
|
|
102
|
+
shirtRow.appendChild(shoulderL);
|
|
103
|
+
shirtRow.appendChild(shoulderR);
|
|
94
104
|
const armHandEmoji = isFemale ? "\u{1F485}" : "\u{1F91C}";
|
|
95
105
|
shirtRow.appendChild(
|
|
96
106
|
buildArm("left", armHandEmoji, skinColor, {
|
|
@@ -112,21 +122,57 @@ function createFigureEl(def) {
|
|
|
112
122
|
flipFist: false
|
|
113
123
|
})
|
|
114
124
|
);
|
|
125
|
+
const hipRow = document.createElement("div");
|
|
126
|
+
Object.assign(hipRow.style, {
|
|
127
|
+
position: "relative",
|
|
128
|
+
display: "flex",
|
|
129
|
+
flexDirection: "column",
|
|
130
|
+
alignItems: "center",
|
|
131
|
+
flexShrink: "0",
|
|
132
|
+
overflow: "visible"
|
|
133
|
+
});
|
|
134
|
+
const hipBridge = document.createElement("div");
|
|
135
|
+
Object.assign(hipBridge.style, {
|
|
136
|
+
width: "28px",
|
|
137
|
+
height: "4px",
|
|
138
|
+
background: skinColor,
|
|
139
|
+
borderRadius: "2px",
|
|
140
|
+
marginBottom: "1px",
|
|
141
|
+
zIndex: "3"
|
|
142
|
+
});
|
|
143
|
+
hipRow.appendChild(hipBridge);
|
|
115
144
|
const legsRow = document.createElement("div");
|
|
116
145
|
Object.assign(legsRow.style, {
|
|
117
146
|
display: "flex",
|
|
118
147
|
justifyContent: "center",
|
|
119
148
|
gap: "10px",
|
|
120
149
|
flexShrink: "0",
|
|
121
|
-
overflow: "visible"
|
|
150
|
+
overflow: "visible",
|
|
151
|
+
position: "relative"
|
|
122
152
|
});
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
);
|
|
127
|
-
wrap.append(faceEl, neck, shirtRow,
|
|
153
|
+
const legL = buildLeg(true, isFemale, ink, skinColor, LEG_H, LEG_STICK_H, JOINT_SIZE);
|
|
154
|
+
const legR = buildLeg(false, isFemale, ink, skinColor, LEG_H, LEG_STICK_H, JOINT_SIZE);
|
|
155
|
+
legsRow.append(legL, legR);
|
|
156
|
+
hipRow.appendChild(legsRow);
|
|
157
|
+
wrap.append(faceEl, neck, shirtRow, hipRow);
|
|
128
158
|
return wrap;
|
|
129
159
|
}
|
|
160
|
+
function buildJoint(side, skinColor, size, anchorX, anchorY, wrapW) {
|
|
161
|
+
const joint = document.createElement("div");
|
|
162
|
+
const isLeft = side === "left";
|
|
163
|
+
Object.assign(joint.style, {
|
|
164
|
+
position: "absolute",
|
|
165
|
+
width: `${size}px`,
|
|
166
|
+
height: `${size}px`,
|
|
167
|
+
borderRadius: "50%",
|
|
168
|
+
background: skinColor,
|
|
169
|
+
...isLeft ? { right: `${wrapW - anchorX - size / 2}px` } : { left: `${anchorX - size / 2}px` },
|
|
170
|
+
top: `${anchorY - size / 2}px`,
|
|
171
|
+
zIndex: "5",
|
|
172
|
+
pointerEvents: "none"
|
|
173
|
+
});
|
|
174
|
+
return joint;
|
|
175
|
+
}
|
|
130
176
|
function buildArm(side, handEmoji, skinColor, g) {
|
|
131
177
|
const arm = document.createElement("div");
|
|
132
178
|
const ds = arm.dataset;
|
|
@@ -150,7 +196,7 @@ function buildArm(side, handEmoji, skinColor, g) {
|
|
|
150
196
|
...isLeft ? { right: "5px" } : { left: "5px" },
|
|
151
197
|
top: `${g.h / 2 - 2}px`,
|
|
152
198
|
width: "18px",
|
|
153
|
-
height: "
|
|
199
|
+
height: "4px",
|
|
154
200
|
background: skinColor,
|
|
155
201
|
borderRadius: "2px"
|
|
156
202
|
});
|
|
@@ -169,7 +215,7 @@ function buildArm(side, handEmoji, skinColor, g) {
|
|
|
169
215
|
arm.append(stick, fist);
|
|
170
216
|
return arm;
|
|
171
217
|
}
|
|
172
|
-
function buildLeg(isLeft, isFemale, ink, legH, stickH) {
|
|
218
|
+
function buildLeg(isLeft, isFemale, ink, skinColor, legH, stickH, jointSize) {
|
|
173
219
|
const leg = document.createElement("div");
|
|
174
220
|
const ds = leg.dataset;
|
|
175
221
|
ds[isLeft ? "figLegL" : "figLegR"] = "";
|
|
@@ -181,17 +227,44 @@ function buildLeg(isLeft, isFemale, ink, legH, stickH) {
|
|
|
181
227
|
transform: "rotate(0deg)",
|
|
182
228
|
overflow: "visible"
|
|
183
229
|
});
|
|
230
|
+
const hipJoint = document.createElement("div");
|
|
231
|
+
hipJoint.dataset[isLeft ? "figHipL" : "figHipR"] = "";
|
|
232
|
+
Object.assign(hipJoint.style, {
|
|
233
|
+
position: "absolute",
|
|
234
|
+
width: `${jointSize}px`,
|
|
235
|
+
height: `${jointSize}px`,
|
|
236
|
+
borderRadius: "50%",
|
|
237
|
+
background: skinColor,
|
|
238
|
+
left: "50%",
|
|
239
|
+
top: `-${jointSize / 2}px`,
|
|
240
|
+
transform: "translateX(-50%)",
|
|
241
|
+
zIndex: "3",
|
|
242
|
+
pointerEvents: "none"
|
|
243
|
+
});
|
|
184
244
|
const stick = document.createElement("div");
|
|
185
245
|
Object.assign(stick.style, {
|
|
186
246
|
position: "absolute",
|
|
187
|
-
width: "
|
|
247
|
+
width: "4px",
|
|
188
248
|
height: `${stickH}px`,
|
|
189
249
|
background: ink,
|
|
190
|
-
borderRadius: "
|
|
250
|
+
borderRadius: "2px",
|
|
191
251
|
left: "50%",
|
|
192
252
|
top: "0",
|
|
193
253
|
transform: "translateX(-50%)"
|
|
194
254
|
});
|
|
255
|
+
const knee = document.createElement("div");
|
|
256
|
+
Object.assign(knee.style, {
|
|
257
|
+
position: "absolute",
|
|
258
|
+
width: "4px",
|
|
259
|
+
height: "4px",
|
|
260
|
+
borderRadius: "50%",
|
|
261
|
+
background: skinColor,
|
|
262
|
+
left: "50%",
|
|
263
|
+
top: `${stickH * 0.55}px`,
|
|
264
|
+
transform: "translateX(-50%)",
|
|
265
|
+
zIndex: "2",
|
|
266
|
+
pointerEvents: "none"
|
|
267
|
+
});
|
|
195
268
|
const shoe = document.createElement("span");
|
|
196
269
|
shoe.textContent = isFemale ? "\u{1F460}" : "\u{1F45F}";
|
|
197
270
|
Object.assign(shoe.style, {
|
|
@@ -208,7 +281,7 @@ function buildLeg(isLeft, isFemale, ink, legH, stickH) {
|
|
|
208
281
|
} else {
|
|
209
282
|
shoe.style.right = "0";
|
|
210
283
|
}
|
|
211
|
-
leg.append(stick, shoe);
|
|
284
|
+
leg.append(hipJoint, stick, knee, shoe);
|
|
212
285
|
return leg;
|
|
213
286
|
}
|
|
214
287
|
var PART_SEL = {
|
|
@@ -265,6 +338,31 @@ function createActorEl(name, def, assetDefs, assetOverrides) {
|
|
|
265
338
|
el = div;
|
|
266
339
|
break;
|
|
267
340
|
}
|
|
341
|
+
case "caption": {
|
|
342
|
+
const div = document.createElement("div");
|
|
343
|
+
div.textContent = def.args[0] ?? "";
|
|
344
|
+
div.dataset.markdyCaption = def.anchor ?? "top";
|
|
345
|
+
Object.assign(div.style, {
|
|
346
|
+
fontSize: `${def.size ?? 32}px`,
|
|
347
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
348
|
+
fontWeight: "700",
|
|
349
|
+
whiteSpace: "nowrap",
|
|
350
|
+
textAlign: "center",
|
|
351
|
+
lineHeight: "1.1",
|
|
352
|
+
padding: "6px 14px",
|
|
353
|
+
borderRadius: "4px",
|
|
354
|
+
background: "rgba(0, 0, 0, 0.55)",
|
|
355
|
+
color: "#fff",
|
|
356
|
+
textShadow: "0 2px 6px rgba(0, 0, 0, 0.45)",
|
|
357
|
+
userSelect: "none",
|
|
358
|
+
pointerEvents: "none"
|
|
359
|
+
// Center the caption on its (x, y) point (x = sceneWidth/2).
|
|
360
|
+
// We combine translate-centering with the actor transform in the
|
|
361
|
+
// dataset below so the player can re-apply on state changes.
|
|
362
|
+
});
|
|
363
|
+
el = div;
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
268
366
|
case "figure": {
|
|
269
367
|
el = createFigureEl(def);
|
|
270
368
|
break;
|
|
@@ -284,24 +382,42 @@ function createActorEl(name, def, assetDefs, assetOverrides) {
|
|
|
284
382
|
el.style.left = "0";
|
|
285
383
|
el.style.top = "0";
|
|
286
384
|
el.style.transformOrigin = "center center";
|
|
287
|
-
el.style.transform = tx(stateFrom(def));
|
|
385
|
+
el.style.transform = def.type === "caption" ? txCaption(stateFrom(def)) : tx(stateFrom(def));
|
|
288
386
|
el.style.opacity = String(def.opacity ?? 1);
|
|
387
|
+
if (def.z !== void 0) el.style.zIndex = String(def.z);
|
|
388
|
+
else if (def.type === "caption") el.style.zIndex = "100";
|
|
289
389
|
return el;
|
|
290
390
|
}
|
|
291
391
|
|
|
292
392
|
// src/animations.ts
|
|
393
|
+
function isSceneDark(scene) {
|
|
394
|
+
const bg = scene.style.background || "white";
|
|
395
|
+
let hex = bg.trim().replace(/^#/, "");
|
|
396
|
+
if (hex.length === 3) hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
|
397
|
+
if (hex.length === 6) {
|
|
398
|
+
const r = parseInt(hex.slice(0, 2), 16);
|
|
399
|
+
const g = parseInt(hex.slice(2, 4), 16);
|
|
400
|
+
const b = parseInt(hex.slice(4, 6), 16);
|
|
401
|
+
return 0.299 * r + 0.587 * g + 0.114 * b <= 140;
|
|
402
|
+
}
|
|
403
|
+
const dark = { black: true, "#000": true, "#000000": true };
|
|
404
|
+
return dark[bg.toLowerCase()] ?? false;
|
|
405
|
+
}
|
|
293
406
|
function buildAnimations(ast, actorEls, scene, assetOverrides, faceSwaps) {
|
|
294
407
|
const anims = [];
|
|
295
408
|
const states = /* @__PURE__ */ new Map();
|
|
296
409
|
for (const [name, def] of Object.entries(ast.actors)) {
|
|
297
410
|
states.set(name, stateFrom(def));
|
|
298
411
|
}
|
|
412
|
+
const captionActors = /* @__PURE__ */ new Set();
|
|
413
|
+
for (const [name, def] of Object.entries(ast.actors)) {
|
|
414
|
+
if (def.type === "caption") captionActors.add(name);
|
|
415
|
+
}
|
|
416
|
+
const txFor = (actorName) => captionActors.has(actorName) ? txCaption : tx;
|
|
299
417
|
const events = [...ast.events].sort((a, b) => a.time - b.time);
|
|
300
|
-
preInitInlineStyles(ast, actorEls, states, events);
|
|
418
|
+
preInitInlineStyles(ast, actorEls, states, events, txFor);
|
|
419
|
+
const cameraState = freshCameraState();
|
|
301
420
|
for (const ev of events) {
|
|
302
|
-
const el = actorEls.get(ev.actor);
|
|
303
|
-
const s = states.get(ev.actor);
|
|
304
|
-
if (!el || !s) continue;
|
|
305
421
|
const delayMs = ev.time * 1e3;
|
|
306
422
|
const durMs = Math.max(
|
|
307
423
|
1,
|
|
@@ -314,13 +430,39 @@ function buildAnimations(ast, actorEls, scene, assetOverrides, faceSwaps) {
|
|
|
314
430
|
fill: "forwards",
|
|
315
431
|
easing
|
|
316
432
|
};
|
|
317
|
-
|
|
433
|
+
if (ev.actor === "camera") {
|
|
434
|
+
buildCameraAction(ev, scene, ast, baseOpts, anims, cameraState);
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
const el = actorEls.get(ev.actor);
|
|
438
|
+
const s = states.get(ev.actor);
|
|
439
|
+
if (!el || !s) continue;
|
|
440
|
+
buildAction(ev, el, s, baseOpts, delayMs, durMs, ast, states, scene, assetOverrides, faceSwaps, anims, txFor(ev.actor));
|
|
318
441
|
}
|
|
319
442
|
return anims;
|
|
320
443
|
}
|
|
321
|
-
function
|
|
444
|
+
function offscreenState(s, direction, sceneWidth, sceneHeight) {
|
|
445
|
+
const out = { ...s };
|
|
446
|
+
switch (direction) {
|
|
447
|
+
case "left":
|
|
448
|
+
out.x = -sceneWidth * 1.1;
|
|
449
|
+
break;
|
|
450
|
+
case "right":
|
|
451
|
+
out.x = sceneWidth * 2.1;
|
|
452
|
+
break;
|
|
453
|
+
case "top":
|
|
454
|
+
out.y = -sceneHeight * 1.1;
|
|
455
|
+
break;
|
|
456
|
+
case "bottom":
|
|
457
|
+
out.y = sceneHeight * 2.1;
|
|
458
|
+
break;
|
|
459
|
+
}
|
|
460
|
+
return out;
|
|
461
|
+
}
|
|
462
|
+
function preInitInlineStyles(ast, actorEls, states, events, txFor) {
|
|
322
463
|
const firstEventByActor = /* @__PURE__ */ new Map();
|
|
323
464
|
for (const ev of events) {
|
|
465
|
+
if (ev.actor === "camera") continue;
|
|
324
466
|
if (!firstEventByActor.has(ev.actor)) firstEventByActor.set(ev.actor, ev);
|
|
325
467
|
}
|
|
326
468
|
for (const [name, def] of Object.entries(ast.actors)) {
|
|
@@ -328,31 +470,78 @@ function preInitInlineStyles(ast, actorEls, states, events) {
|
|
|
328
470
|
const s = states.get(name);
|
|
329
471
|
if (!el || !s) continue;
|
|
330
472
|
const firstEv = firstEventByActor.get(name);
|
|
473
|
+
const txFn = txFor(name);
|
|
331
474
|
if (firstEv?.action === "enter") {
|
|
332
475
|
const from = String(firstEv.params.from ?? "left");
|
|
333
|
-
const offscreen =
|
|
334
|
-
|
|
335
|
-
case "left":
|
|
336
|
-
offscreen.x = -ast.meta.width * 1.1;
|
|
337
|
-
break;
|
|
338
|
-
case "right":
|
|
339
|
-
offscreen.x = ast.meta.width * 2.1;
|
|
340
|
-
break;
|
|
341
|
-
case "top":
|
|
342
|
-
offscreen.y = -ast.meta.height * 1.1;
|
|
343
|
-
break;
|
|
344
|
-
case "bottom":
|
|
345
|
-
offscreen.y = ast.meta.height * 2.1;
|
|
346
|
-
break;
|
|
347
|
-
}
|
|
348
|
-
el.style.transform = tx(offscreen);
|
|
476
|
+
const offscreen = offscreenState(s, from, ast.meta.width, ast.meta.height);
|
|
477
|
+
el.style.transform = txFn(offscreen);
|
|
349
478
|
}
|
|
350
479
|
if (firstEv?.action === "fade_in" && (def.opacity === void 0 || def.opacity > 0)) {
|
|
351
480
|
el.style.opacity = "0";
|
|
352
481
|
}
|
|
353
482
|
}
|
|
354
483
|
}
|
|
355
|
-
function
|
|
484
|
+
function freshCameraState() {
|
|
485
|
+
return { x: 0, y: 0, zoom: 1 };
|
|
486
|
+
}
|
|
487
|
+
function cameraTx(s) {
|
|
488
|
+
return `translate(${-s.x}px, ${-s.y}px) scale(${s.zoom})`;
|
|
489
|
+
}
|
|
490
|
+
function buildCameraAction(ev, scene, ast, baseOpts, anims, cameraState) {
|
|
491
|
+
const s = cameraState;
|
|
492
|
+
switch (ev.action) {
|
|
493
|
+
case "pan": {
|
|
494
|
+
const to = ev.params.to;
|
|
495
|
+
if (!to) break;
|
|
496
|
+
const sceneW = ast.meta.width;
|
|
497
|
+
const sceneH = ast.meta.height;
|
|
498
|
+
const targetX = to[0] - sceneW / 2;
|
|
499
|
+
const targetY = to[1] - sceneH / 2;
|
|
500
|
+
const next = { ...s, x: targetX, y: targetY };
|
|
501
|
+
anims.push(
|
|
502
|
+
scene.animate(
|
|
503
|
+
[{ transform: cameraTx(s) }, { transform: cameraTx(next) }],
|
|
504
|
+
baseOpts
|
|
505
|
+
)
|
|
506
|
+
);
|
|
507
|
+
s.x = next.x;
|
|
508
|
+
s.y = next.y;
|
|
509
|
+
break;
|
|
510
|
+
}
|
|
511
|
+
case "zoom": {
|
|
512
|
+
const to = typeof ev.params.to === "number" ? ev.params.to : s.zoom;
|
|
513
|
+
const next = { ...s, zoom: to };
|
|
514
|
+
anims.push(
|
|
515
|
+
scene.animate(
|
|
516
|
+
[{ transform: cameraTx(s) }, { transform: cameraTx(next) }],
|
|
517
|
+
baseOpts
|
|
518
|
+
)
|
|
519
|
+
);
|
|
520
|
+
s.zoom = next.zoom;
|
|
521
|
+
break;
|
|
522
|
+
}
|
|
523
|
+
case "shake": {
|
|
524
|
+
const mag = typeof ev.params.intensity === "number" ? ev.params.intensity : 8;
|
|
525
|
+
anims.push(
|
|
526
|
+
scene.animate(
|
|
527
|
+
[
|
|
528
|
+
{ transform: cameraTx(s), offset: 0 },
|
|
529
|
+
{ transform: cameraTx({ ...s, x: s.x - mag, y: s.y - mag * 0.4 }), offset: 0.15 },
|
|
530
|
+
{ transform: cameraTx({ ...s, x: s.x + mag, y: s.y + mag * 0.4 }), offset: 0.35 },
|
|
531
|
+
{ transform: cameraTx({ ...s, x: s.x - mag * 0.6, y: s.y + mag * 0.3 }), offset: 0.55 },
|
|
532
|
+
{ transform: cameraTx({ ...s, x: s.x + mag * 0.5, y: s.y - mag * 0.3 }), offset: 0.75 },
|
|
533
|
+
{ transform: cameraTx(s), offset: 1 }
|
|
534
|
+
],
|
|
535
|
+
{ ...baseOpts, easing: "linear" }
|
|
536
|
+
)
|
|
537
|
+
);
|
|
538
|
+
break;
|
|
539
|
+
}
|
|
540
|
+
default:
|
|
541
|
+
break;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
function buildAction(ev, el, s, baseOpts, delayMs, durMs, ast, states, scene, assetOverrides, faceSwaps, anims, txFn) {
|
|
356
545
|
switch (ev.action) {
|
|
357
546
|
// ── move ────────────────────────────────────────────────────────────────
|
|
358
547
|
case "move": {
|
|
@@ -361,7 +550,7 @@ function buildAction(ev, el, s, baseOpts, delayMs, durMs, ast, states, scene, as
|
|
|
361
550
|
const toY = toArr?.[1] ?? s.y;
|
|
362
551
|
anims.push(
|
|
363
552
|
el.animate(
|
|
364
|
-
[{ transform:
|
|
553
|
+
[{ transform: txFn(s) }, { transform: txFn({ ...s, x: toX, y: toY }) }],
|
|
365
554
|
baseOpts
|
|
366
555
|
)
|
|
367
556
|
);
|
|
@@ -370,29 +559,45 @@ function buildAction(ev, el, s, baseOpts, delayMs, durMs, ast, states, scene, as
|
|
|
370
559
|
break;
|
|
371
560
|
}
|
|
372
561
|
// ── enter ───────────────────────────────────────────────────────────────
|
|
562
|
+
// Slides the actor from the given screen edge into its current (s)
|
|
563
|
+
// position while also restoring opacity to 1. The opacity restore makes
|
|
564
|
+
// `enter` symmetric with `exit` so actors can re-enter after exiting
|
|
565
|
+
// without needing a separate fade_in.
|
|
373
566
|
case "enter": {
|
|
374
567
|
const from = String(ev.params.from ?? "left");
|
|
375
|
-
const fromState =
|
|
376
|
-
switch (from) {
|
|
377
|
-
case "left":
|
|
378
|
-
fromState.x = -ast.meta.width;
|
|
379
|
-
break;
|
|
380
|
-
case "right":
|
|
381
|
-
fromState.x = ast.meta.width * 2;
|
|
382
|
-
break;
|
|
383
|
-
case "top":
|
|
384
|
-
fromState.y = -ast.meta.height;
|
|
385
|
-
break;
|
|
386
|
-
case "bottom":
|
|
387
|
-
fromState.y = ast.meta.height * 2;
|
|
388
|
-
break;
|
|
389
|
-
}
|
|
568
|
+
const fromState = offscreenState(s, from, ast.meta.width, ast.meta.height);
|
|
390
569
|
anims.push(
|
|
391
570
|
el.animate(
|
|
392
|
-
[
|
|
571
|
+
[
|
|
572
|
+
{ transform: txFn(fromState), opacity: s.opacity },
|
|
573
|
+
{ transform: txFn(s), opacity: 1 }
|
|
574
|
+
],
|
|
393
575
|
baseOpts
|
|
394
576
|
)
|
|
395
577
|
);
|
|
578
|
+
s.opacity = 1;
|
|
579
|
+
break;
|
|
580
|
+
}
|
|
581
|
+
// ── exit ────────────────────────────────────────────────────────────────
|
|
582
|
+
// Mirror of enter: slides off-screen in the given direction while also
|
|
583
|
+
// fading opacity to 0. Universal — works on any actor type (caption, text,
|
|
584
|
+
// figure, box, sprite). Leaves `s` at the off-screen state since the
|
|
585
|
+
// actor is conceptually gone after this.
|
|
586
|
+
case "exit": {
|
|
587
|
+
const to = String(ev.params.to ?? "right");
|
|
588
|
+
const toState = offscreenState(s, to, ast.meta.width, ast.meta.height);
|
|
589
|
+
anims.push(
|
|
590
|
+
el.animate(
|
|
591
|
+
[
|
|
592
|
+
{ transform: txFn(s), opacity: s.opacity },
|
|
593
|
+
{ transform: txFn(toState), opacity: 0 }
|
|
594
|
+
],
|
|
595
|
+
baseOpts
|
|
596
|
+
)
|
|
597
|
+
);
|
|
598
|
+
s.x = toState.x;
|
|
599
|
+
s.y = toState.y;
|
|
600
|
+
s.opacity = 0;
|
|
396
601
|
break;
|
|
397
602
|
}
|
|
398
603
|
// ── fade_in ─────────────────────────────────────────────────────────────
|
|
@@ -414,7 +619,7 @@ function buildAction(ev, el, s, baseOpts, delayMs, durMs, ast, states, scene, as
|
|
|
414
619
|
const toScale = typeof ev.params.to === "number" ? ev.params.to : s.scale;
|
|
415
620
|
anims.push(
|
|
416
621
|
el.animate(
|
|
417
|
-
[{ transform:
|
|
622
|
+
[{ transform: txFn(s) }, { transform: txFn({ ...s, scale: toScale }) }],
|
|
418
623
|
baseOpts
|
|
419
624
|
)
|
|
420
625
|
);
|
|
@@ -426,7 +631,7 @@ function buildAction(ev, el, s, baseOpts, delayMs, durMs, ast, states, scene, as
|
|
|
426
631
|
const toDeg = typeof ev.params.to === "number" ? ev.params.to : s.rotate;
|
|
427
632
|
anims.push(
|
|
428
633
|
el.animate(
|
|
429
|
-
[{ transform:
|
|
634
|
+
[{ transform: txFn(s) }, { transform: txFn({ ...s, rotate: toDeg }) }],
|
|
430
635
|
baseOpts
|
|
431
636
|
)
|
|
432
637
|
);
|
|
@@ -439,12 +644,12 @@ function buildAction(ev, el, s, baseOpts, delayMs, durMs, ast, states, scene, as
|
|
|
439
644
|
anims.push(
|
|
440
645
|
el.animate(
|
|
441
646
|
[
|
|
442
|
-
{ transform:
|
|
443
|
-
{ transform:
|
|
444
|
-
{ transform:
|
|
445
|
-
{ transform:
|
|
446
|
-
{ transform:
|
|
447
|
-
{ transform:
|
|
647
|
+
{ transform: txFn(s), offset: 0 },
|
|
648
|
+
{ transform: txFn({ ...s, x: s.x + mag }), offset: 0.2 },
|
|
649
|
+
{ transform: txFn({ ...s, x: s.x - mag }), offset: 0.4 },
|
|
650
|
+
{ transform: txFn({ ...s, x: s.x + mag }), offset: 0.6 },
|
|
651
|
+
{ transform: txFn({ ...s, x: s.x - mag }), offset: 0.8 },
|
|
652
|
+
{ transform: txFn(s), offset: 1 }
|
|
448
653
|
],
|
|
449
654
|
{ ...baseOpts, easing: "linear" }
|
|
450
655
|
)
|
|
@@ -529,6 +734,11 @@ function buildAction(ev, el, s, baseOpts, delayMs, durMs, ast, states, scene, as
|
|
|
529
734
|
case "say": {
|
|
530
735
|
const text = String(ev.params.text ?? "");
|
|
531
736
|
const inverseScale = 1 / (s.scale || 1);
|
|
737
|
+
const sceneDark = isSceneDark(scene);
|
|
738
|
+
const bubbleBg = sceneDark ? "#1e2530" : "white";
|
|
739
|
+
const bubbleBorder = sceneDark ? "#475569" : "#222";
|
|
740
|
+
const bubbleText = sceneDark ? "#e2e8f0" : "#222";
|
|
741
|
+
const bubbleShadow = sceneDark ? "0 2px 8px rgba(0,0,0,0.35)" : "0 2px 8px rgba(0,0,0,0.12)";
|
|
532
742
|
const bubble = document.createElement("div");
|
|
533
743
|
bubble.textContent = text;
|
|
534
744
|
bubble.style.opacity = "0";
|
|
@@ -538,9 +748,9 @@ function buildAction(ev, el, s, baseOpts, delayMs, durMs, ast, states, scene, as
|
|
|
538
748
|
left: "50%",
|
|
539
749
|
transform: `translateX(-50%) scale(${inverseScale})`,
|
|
540
750
|
transformOrigin: "center bottom",
|
|
541
|
-
background:
|
|
542
|
-
border:
|
|
543
|
-
color:
|
|
751
|
+
background: bubbleBg,
|
|
752
|
+
border: `2px solid ${bubbleBorder}`,
|
|
753
|
+
color: bubbleText,
|
|
544
754
|
borderRadius: "12px",
|
|
545
755
|
padding: "6px 14px",
|
|
546
756
|
fontFamily: "system-ui, sans-serif",
|
|
@@ -552,7 +762,7 @@ function buildAction(ev, el, s, baseOpts, delayMs, durMs, ast, states, scene, as
|
|
|
552
762
|
textOverflow: "ellipsis",
|
|
553
763
|
pointerEvents: "none",
|
|
554
764
|
zIndex: "10",
|
|
555
|
-
boxShadow:
|
|
765
|
+
boxShadow: bubbleShadow
|
|
556
766
|
});
|
|
557
767
|
const tail = document.createElement("span");
|
|
558
768
|
Object.assign(tail.style, {
|
|
@@ -564,7 +774,7 @@ function buildAction(ev, el, s, baseOpts, delayMs, durMs, ast, states, scene, as
|
|
|
564
774
|
height: "0",
|
|
565
775
|
borderLeft: "7px solid transparent",
|
|
566
776
|
borderRight: "7px solid transparent",
|
|
567
|
-
borderTop:
|
|
777
|
+
borderTop: `10px solid ${bubbleBorder}`
|
|
568
778
|
});
|
|
569
779
|
bubble.appendChild(tail);
|
|
570
780
|
el.style.overflow = "visible";
|
|
@@ -584,6 +794,135 @@ function buildAction(ev, el, s, baseOpts, delayMs, durMs, ast, states, scene, as
|
|
|
584
794
|
);
|
|
585
795
|
break;
|
|
586
796
|
}
|
|
797
|
+
// ── pose ────────────────────────────────────────────────────────────────
|
|
798
|
+
// Sets multiple body parts to target angles simultaneously.
|
|
799
|
+
// Usage: @1.0: hero.pose(arm_left=45, arm_right=-45, leg_left=10, dur=0.4)
|
|
800
|
+
case "pose": {
|
|
801
|
+
const poseParts = ["arm_left", "arm_right", "leg_left", "leg_right", "head", "body"];
|
|
802
|
+
for (const partName of poseParts) {
|
|
803
|
+
if (typeof ev.params[partName] !== "number") continue;
|
|
804
|
+
const pSel = PART_SEL[partName];
|
|
805
|
+
if (!pSel) continue;
|
|
806
|
+
const pEl = el.querySelector(pSel);
|
|
807
|
+
if (!pEl) continue;
|
|
808
|
+
const fromDeg = readRotation(pEl);
|
|
809
|
+
const toDeg = ev.params[partName];
|
|
810
|
+
anims.push(
|
|
811
|
+
pEl.animate(
|
|
812
|
+
[
|
|
813
|
+
{ transform: `rotate(${fromDeg}deg)` },
|
|
814
|
+
{ transform: `rotate(${toDeg}deg)` }
|
|
815
|
+
],
|
|
816
|
+
{ ...baseOpts, fill: "forwards" }
|
|
817
|
+
)
|
|
818
|
+
);
|
|
819
|
+
pEl.style.transform = pEl.style.transform.replace(
|
|
820
|
+
/rotate\([^)]*\)/,
|
|
821
|
+
`rotate(${toDeg}deg)`
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
break;
|
|
825
|
+
}
|
|
826
|
+
// ── wave ────────────────────────────────────────────────────────────────
|
|
827
|
+
// Built-in wave gesture: raises arm, oscillates, returns.
|
|
828
|
+
// Usage: @2.0: hero.wave(side=right, dur=0.8)
|
|
829
|
+
case "wave": {
|
|
830
|
+
const wSide = String(ev.params.side ?? "right");
|
|
831
|
+
const wArmEl = el.querySelector(
|
|
832
|
+
wSide === "left" ? "[data-fig-arm-l]" : "[data-fig-arm-r]"
|
|
833
|
+
);
|
|
834
|
+
if (!wArmEl) break;
|
|
835
|
+
const wRest = readRotation(wArmEl);
|
|
836
|
+
const wUp = wSide === "left" ? 70 : -70;
|
|
837
|
+
const wMid1 = wSide === "left" ? 50 : -50;
|
|
838
|
+
const wMid2 = wSide === "left" ? 70 : -70;
|
|
839
|
+
anims.push(
|
|
840
|
+
wArmEl.animate(
|
|
841
|
+
[
|
|
842
|
+
{ transform: `rotate(${wRest}deg)`, offset: 0 },
|
|
843
|
+
{ transform: `rotate(${wUp}deg)`, offset: 0.2 },
|
|
844
|
+
{ transform: `rotate(${wMid1}deg)`, offset: 0.4 },
|
|
845
|
+
{ transform: `rotate(${wMid2}deg)`, offset: 0.55 },
|
|
846
|
+
{ transform: `rotate(${wMid1}deg)`, offset: 0.7 },
|
|
847
|
+
{ transform: `rotate(${wMid2}deg)`, offset: 0.85 },
|
|
848
|
+
{ transform: `rotate(${wRest}deg)`, offset: 1 }
|
|
849
|
+
],
|
|
850
|
+
{ ...baseOpts, easing: "ease-in-out", fill: "forwards" }
|
|
851
|
+
)
|
|
852
|
+
);
|
|
853
|
+
break;
|
|
854
|
+
}
|
|
855
|
+
// ── jump ────────────────────────────────────────────────────────────────
|
|
856
|
+
// Jumps the actor up with squash/stretch effect.
|
|
857
|
+
// Usage: @3.0: hero.jump(height=30, dur=0.5)
|
|
858
|
+
case "jump": {
|
|
859
|
+
const jHeight = typeof ev.params.height === "number" ? ev.params.height : 30;
|
|
860
|
+
anims.push(
|
|
861
|
+
el.animate(
|
|
862
|
+
[
|
|
863
|
+
{ transform: txFn(s), offset: 0 },
|
|
864
|
+
{ transform: txFn({ ...s, scale: s.scale * 0.9 }), offset: 0.1 },
|
|
865
|
+
{ transform: txFn({ ...s, y: s.y - jHeight, scale: s.scale * 1.1 }), offset: 0.45 },
|
|
866
|
+
{ transform: txFn({ ...s, y: s.y - jHeight * 0.3, scale: s.scale * 1.05 }), offset: 0.7 },
|
|
867
|
+
{ transform: txFn({ ...s, scale: s.scale * 0.92 }), offset: 0.88 },
|
|
868
|
+
{ transform: txFn(s), offset: 1 }
|
|
869
|
+
],
|
|
870
|
+
{ ...baseOpts, easing: "ease-in-out" }
|
|
871
|
+
)
|
|
872
|
+
);
|
|
873
|
+
break;
|
|
874
|
+
}
|
|
875
|
+
// ── nod ─────────────────────────────────────────────────────────────────
|
|
876
|
+
// Nods the head down and back up. Figure actors only.
|
|
877
|
+
// Usage: @2.0: hero.nod(dur=0.4)
|
|
878
|
+
case "nod": {
|
|
879
|
+
const nHeadEl = el.querySelector("[data-fig-head]");
|
|
880
|
+
if (!nHeadEl) break;
|
|
881
|
+
const nRest = readRotation(nHeadEl);
|
|
882
|
+
const nDown = 15;
|
|
883
|
+
anims.push(
|
|
884
|
+
nHeadEl.animate(
|
|
885
|
+
[
|
|
886
|
+
{ transform: `rotate(${nRest}deg)`, offset: 0 },
|
|
887
|
+
{ transform: `rotate(${nDown}deg)`, offset: 0.35 },
|
|
888
|
+
{ transform: `rotate(${nRest}deg)`, offset: 0.65 },
|
|
889
|
+
{ transform: `rotate(${nDown}deg)`, offset: 0.8 },
|
|
890
|
+
{ transform: `rotate(${nRest}deg)`, offset: 1 }
|
|
891
|
+
],
|
|
892
|
+
{ ...baseOpts, easing: "ease-in-out", fill: "forwards" }
|
|
893
|
+
)
|
|
894
|
+
);
|
|
895
|
+
break;
|
|
896
|
+
}
|
|
897
|
+
// ── bounce ──────────────────────────────────────────────────────────────
|
|
898
|
+
// Bounces the actor vertically with diminishing amplitude.
|
|
899
|
+
// Usage: @1.0: hero.bounce(intensity=15, count=3, dur=0.6)
|
|
900
|
+
case "bounce": {
|
|
901
|
+
const bIntensity = typeof ev.params.intensity === "number" ? ev.params.intensity : 15;
|
|
902
|
+
const bCount = typeof ev.params.count === "number" ? ev.params.count : 3;
|
|
903
|
+
const keyframes = [{ transform: txFn(s), offset: 0 }];
|
|
904
|
+
for (let bi = 0; bi < bCount; bi++) {
|
|
905
|
+
const amp = bIntensity * Math.pow(0.55, bi);
|
|
906
|
+
const baseOffset = (bi + 0.5) / (bCount + 0.5);
|
|
907
|
+
const peakOffset = Math.min(baseOffset, 0.98);
|
|
908
|
+
const valleyOffset = Math.min(baseOffset + 0.25 / (bCount + 0.5), 0.99);
|
|
909
|
+
keyframes.push({
|
|
910
|
+
transform: txFn({ ...s, y: s.y - amp }),
|
|
911
|
+
offset: peakOffset
|
|
912
|
+
});
|
|
913
|
+
if (bi < bCount - 1) {
|
|
914
|
+
keyframes.push({
|
|
915
|
+
transform: txFn(s),
|
|
916
|
+
offset: valleyOffset
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
keyframes.push({ transform: txFn(s), offset: 1 });
|
|
921
|
+
anims.push(
|
|
922
|
+
el.animate(keyframes, { ...baseOpts, easing: "ease-out" })
|
|
923
|
+
);
|
|
924
|
+
break;
|
|
925
|
+
}
|
|
587
926
|
// ── throw ───────────────────────────────────────────────────────────────
|
|
588
927
|
case "throw": {
|
|
589
928
|
const assetName = String(ev.params.asset ?? "");
|
|
@@ -654,9 +993,20 @@ function bgToTextColor(bg) {
|
|
|
654
993
|
return named[bg.toLowerCase()] ?? "#1a1a1a";
|
|
655
994
|
}
|
|
656
995
|
function createPlayer(opts) {
|
|
657
|
-
const {
|
|
658
|
-
|
|
996
|
+
const {
|
|
997
|
+
container,
|
|
998
|
+
code,
|
|
999
|
+
assets: assetOverrides = {},
|
|
1000
|
+
imports,
|
|
1001
|
+
autoplay = true,
|
|
1002
|
+
loop = true,
|
|
1003
|
+
copyright = true,
|
|
1004
|
+
progressBar = true,
|
|
1005
|
+
onWarning = (w) => console.warn(`[markdy] line ${w.line}: ${w.message} (${w.kind})`)
|
|
1006
|
+
} = opts;
|
|
1007
|
+
const ast = parse(code, imports ? { imports } : void 0);
|
|
659
1008
|
const totalDurationMs = (ast.meta.duration ?? 0) * 1e3;
|
|
1009
|
+
for (const w of ast.warnings) onWarning(w);
|
|
660
1010
|
const viewport = document.createElement("div");
|
|
661
1011
|
Object.assign(viewport.style, {
|
|
662
1012
|
position: "relative",
|
|
@@ -735,6 +1085,17 @@ function createPlayer(opts) {
|
|
|
735
1085
|
transformOrigin: "0 0"
|
|
736
1086
|
});
|
|
737
1087
|
viewport.appendChild(scene);
|
|
1088
|
+
const sceneContent = document.createElement("div");
|
|
1089
|
+
Object.assign(sceneContent.style, {
|
|
1090
|
+
position: "absolute",
|
|
1091
|
+
top: "0",
|
|
1092
|
+
left: "0",
|
|
1093
|
+
width: "100%",
|
|
1094
|
+
height: "100%",
|
|
1095
|
+
transformOrigin: "50% 50%",
|
|
1096
|
+
willChange: "transform"
|
|
1097
|
+
});
|
|
1098
|
+
scene.appendChild(sceneContent);
|
|
738
1099
|
function scaleScene() {
|
|
739
1100
|
const s = viewport.clientWidth / ast.meta.width;
|
|
740
1101
|
scene.style.transform = `scale(${s})`;
|
|
@@ -745,11 +1106,11 @@ function createPlayer(opts) {
|
|
|
745
1106
|
const actorEls = /* @__PURE__ */ new Map();
|
|
746
1107
|
for (const [name, def] of Object.entries(ast.actors)) {
|
|
747
1108
|
const el = createActorEl(name, def, ast.assets, assetOverrides);
|
|
748
|
-
|
|
1109
|
+
sceneContent.appendChild(el);
|
|
749
1110
|
actorEls.set(name, el);
|
|
750
1111
|
}
|
|
751
1112
|
const faceSwaps = [];
|
|
752
|
-
const allAnims = buildAnimations(ast, actorEls,
|
|
1113
|
+
const allAnims = buildAnimations(ast, actorEls, sceneContent, assetOverrides, faceSwaps);
|
|
753
1114
|
faceSwaps.sort((a, b) => a.timeMs - b.timeMs);
|
|
754
1115
|
for (const anim of allAnims) {
|
|
755
1116
|
anim.pause();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markdy/renderer-dom",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Web Animations API renderer for MarkdyScript scenes.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -43,15 +43,18 @@
|
|
|
43
43
|
"access": "public"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@markdy/core": "0.
|
|
46
|
+
"@markdy/core": "0.7.0"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
+
"jsdom": "^26.0.0",
|
|
49
50
|
"tsup": "^8.5.1",
|
|
50
|
-
"typescript": "^5.9.3"
|
|
51
|
+
"typescript": "^5.9.3",
|
|
52
|
+
"vitest": "^4.1.4"
|
|
51
53
|
},
|
|
52
54
|
"scripts": {
|
|
53
55
|
"build": "tsup",
|
|
54
56
|
"typecheck": "tsc --noEmit",
|
|
55
|
-
"lint": "tsc --noEmit"
|
|
57
|
+
"lint": "tsc --noEmit",
|
|
58
|
+
"test": "vitest run"
|
|
56
59
|
}
|
|
57
60
|
}
|