astro-magic-move 0.1.2 → 0.2.1
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/package.json +1 -1
- package/src/MagicMove.astro +59 -24
- package/src/types.ts +8 -1
- package/styles/magic-move.css +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "astro-magic-move",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Animated code morphing for Astro, powered by Shiki Magic Move. Build-time tokenization, zero-framework client JS, CSS-variable theming.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/MagicMove.astro
CHANGED
|
@@ -16,6 +16,7 @@ const {
|
|
|
16
16
|
stagger = 0.3,
|
|
17
17
|
threshold = 0.4,
|
|
18
18
|
lineNumbers = false,
|
|
19
|
+
minLines,
|
|
19
20
|
class: className,
|
|
20
21
|
} = Astro.props;
|
|
21
22
|
|
|
@@ -66,6 +67,7 @@ const stepsJson = JSON.stringify(compiledSteps);
|
|
|
66
67
|
data-threshold={threshold}
|
|
67
68
|
data-lang={lang}
|
|
68
69
|
{...(lineNumbers ? { "data-line-numbers": "" } : {})}
|
|
70
|
+
{...(minLines ? { style: `--min-lines: ${minLines}` } : {})}
|
|
69
71
|
>
|
|
70
72
|
<pre class="shiki-magic-move-container"><code>{steps[0]}</code></pre>
|
|
71
73
|
<script is:inline type="application/json" set:html={stepsJson} />
|
|
@@ -76,14 +78,30 @@ const stepsJson = JSON.stringify(compiledSteps);
|
|
|
76
78
|
|
|
77
79
|
class MagicMoveElement extends HTMLElement {
|
|
78
80
|
private renderer: MagicMoveRenderer | null = null;
|
|
79
|
-
private
|
|
80
|
-
private
|
|
81
|
+
private _steps: any[] = [];
|
|
82
|
+
private _currentStep = 0;
|
|
83
|
+
private _animating = false;
|
|
84
|
+
private _pendingStep: number | null = null;
|
|
81
85
|
private observer: IntersectionObserver | null = null;
|
|
82
86
|
|
|
87
|
+
get step(): number {
|
|
88
|
+
return this._currentStep;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
set step(value: number) {
|
|
92
|
+
const idx = Number(value);
|
|
93
|
+
if (Number.isNaN(idx)) return;
|
|
94
|
+
this.animateToStep(idx);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
get totalSteps(): number {
|
|
98
|
+
return this._steps.length;
|
|
99
|
+
}
|
|
100
|
+
|
|
83
101
|
connectedCallback() {
|
|
84
102
|
const dataScript = this.querySelector('script[type="application/json"]');
|
|
85
103
|
if (!dataScript?.textContent) return;
|
|
86
|
-
this.
|
|
104
|
+
this._steps = JSON.parse(dataScript.textContent);
|
|
87
105
|
dataScript.remove();
|
|
88
106
|
|
|
89
107
|
const container = this.querySelector("pre");
|
|
@@ -104,10 +122,10 @@ const stepsJson = JSON.stringify(compiledSteps);
|
|
|
104
122
|
});
|
|
105
123
|
|
|
106
124
|
if (trigger === "auto") {
|
|
107
|
-
this.
|
|
125
|
+
this._currentStep = -1;
|
|
108
126
|
} else {
|
|
109
|
-
this.renderer.replace(this.
|
|
110
|
-
this.
|
|
127
|
+
this.renderer.replace(this._steps[0]);
|
|
128
|
+
this._currentStep = 0;
|
|
111
129
|
}
|
|
112
130
|
this.setupTrigger(trigger, threshold);
|
|
113
131
|
}
|
|
@@ -124,13 +142,15 @@ const stepsJson = JSON.stringify(compiledSteps);
|
|
|
124
142
|
case "click":
|
|
125
143
|
this.style.cursor = "pointer";
|
|
126
144
|
this.addEventListener("click", () => {
|
|
127
|
-
const next = (this.
|
|
145
|
+
const next = (this._currentStep + 1) % this._steps.length;
|
|
128
146
|
this.animateToStep(next);
|
|
129
147
|
});
|
|
130
148
|
break;
|
|
131
149
|
case "auto":
|
|
132
150
|
requestAnimationFrame(() => this.autoPlay());
|
|
133
151
|
break;
|
|
152
|
+
case "none":
|
|
153
|
+
break;
|
|
134
154
|
}
|
|
135
155
|
}
|
|
136
156
|
|
|
@@ -148,28 +168,43 @@ const stepsJson = JSON.stringify(compiledSteps);
|
|
|
148
168
|
}
|
|
149
169
|
|
|
150
170
|
private async autoPlay() {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
171
|
+
const duration = Number(this.dataset.duration ?? 800);
|
|
172
|
+
for (let i = 0; i < this._steps.length; i++) {
|
|
173
|
+
await this.animateToStep(i);
|
|
174
|
+
if (i < this._steps.length - 1) {
|
|
175
|
+
await new Promise((r) => setTimeout(r, duration + 200));
|
|
157
176
|
}
|
|
158
177
|
}
|
|
178
|
+
}
|
|
159
179
|
|
|
160
|
-
|
|
161
|
-
if (!this.renderer || stepIndex === this.
|
|
162
|
-
if (stepIndex < 0 || stepIndex >= this.
|
|
180
|
+
private async animateToStep(stepIndex: number) {
|
|
181
|
+
if (!this.renderer || stepIndex === this._currentStep) return;
|
|
182
|
+
if (stepIndex < 0 || stepIndex >= this._steps.length) return;
|
|
163
183
|
|
|
164
|
-
|
|
165
|
-
|
|
184
|
+
if (this._animating) {
|
|
185
|
+
this._pendingStep = stepIndex;
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
166
188
|
|
|
167
|
-
this.
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
189
|
+
this._animating = true;
|
|
190
|
+
try {
|
|
191
|
+
await this.renderer.render(this._steps[stepIndex]);
|
|
192
|
+
this._currentStep = stepIndex;
|
|
193
|
+
this.dispatchEvent(
|
|
194
|
+
new CustomEvent("magic-move:step", {
|
|
195
|
+
detail: { step: stepIndex, total: this._steps.length },
|
|
196
|
+
bubbles: true,
|
|
197
|
+
}),
|
|
198
|
+
);
|
|
199
|
+
} finally {
|
|
200
|
+
this._animating = false;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (this._pendingStep !== null) {
|
|
204
|
+
const next = this._pendingStep;
|
|
205
|
+
this._pendingStep = null;
|
|
206
|
+
this.animateToStep(next);
|
|
207
|
+
}
|
|
173
208
|
}
|
|
174
209
|
}
|
|
175
210
|
|
package/src/types.ts
CHANGED
|
@@ -20,8 +20,9 @@ export interface MagicMoveProps {
|
|
|
20
20
|
* - `'scroll'`: animate when element enters viewport (default)
|
|
21
21
|
* - `'click'`: toggle forward on click, wraps around
|
|
22
22
|
* - `'auto'`: animate immediately when the element mounts
|
|
23
|
+
* - `'none'`: no built-in trigger; control steps externally via the element's `step` property
|
|
23
24
|
*/
|
|
24
|
-
trigger?: "scroll" | "click" | "auto";
|
|
25
|
+
trigger?: "scroll" | "click" | "auto" | "none";
|
|
25
26
|
|
|
26
27
|
/** Animation duration in ms. Default: `800` */
|
|
27
28
|
duration?: number;
|
|
@@ -38,6 +39,12 @@ export interface MagicMoveProps {
|
|
|
38
39
|
/** Whether to animate line numbers. Default: `false` */
|
|
39
40
|
lineNumbers?: boolean;
|
|
40
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Minimum height expressed as a number of lines.
|
|
44
|
+
* Prevents layout shift when stepping between code blocks of different lengths.
|
|
45
|
+
*/
|
|
46
|
+
minLines?: number;
|
|
47
|
+
|
|
41
48
|
/**
|
|
42
49
|
* CSS class(es) applied to the outer `<magic-move>` element.
|
|
43
50
|
* Use this for all visual styling (bg, border, rounded, shadow, padding, width, etc).
|