@effing/annie-player 0.1.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/LICENSE ADDED
@@ -0,0 +1,11 @@
1
+ O'Saasy License
2
+
3
+ Copyright © 2026, Trackuity BV.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ 1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ 2. No licensee or downstream recipient may use the Software (including any modified or derivative versions) to directly compete with the original Licensor by offering it to third parties as a hosted, managed, or Software-as-a-Service (SaaS) product or cloud service where the primary value of the service is the functionality of the Software itself.
10
+
11
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,264 @@
1
+ # @effing/annie-player
2
+
3
+ **Browser player for Annie animations.**
4
+
5
+ > Part of the [**Effing**](../../README.md) family — programmatic video creation with TypeScript.
6
+
7
+ Play TAR archives of PNG or JPEG frames in the browser using canvas. Framework-agnostic core with optional React component.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install @effing/annie-player
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ### React
18
+
19
+ ```tsx
20
+ import { AnniePlayer } from "@effing/annie-player/react";
21
+
22
+ function App() {
23
+ return (
24
+ <AnniePlayer
25
+ src="https://example.com/animation.tar"
26
+ height={540}
27
+ autoLoad
28
+ autoPlay
29
+ fps={30}
30
+ />
31
+ );
32
+ }
33
+ ```
34
+
35
+ The React component includes built-in inline styles for:
36
+
37
+ - Wrapper positioning
38
+ - Load/Play/Pause buttons with hover states
39
+ - Status overlay
40
+ - Canvas styling
41
+
42
+ To customize the root container, use `className` and `style` props:
43
+
44
+ ```tsx
45
+ <AnniePlayer
46
+ src="https://example.com/animation.tar"
47
+ height={540}
48
+ className="my-player"
49
+ style={{ border: "2px solid blue" }}
50
+ />
51
+ ```
52
+
53
+ #### Compound Components
54
+
55
+ For full control over styling and layout, use compound components:
56
+
57
+ ```tsx
58
+ import { AnniePlayer } from "@effing/annie-player/react";
59
+
60
+ function CustomPlayer() {
61
+ return (
62
+ <AnniePlayer.Root
63
+ src="https://example.com/animation.tar"
64
+ height={540}
65
+ autoLoad
66
+ autoPlay
67
+ >
68
+ <AnniePlayer.Wrapper style={{ border: "2px solid blue" }}>
69
+ <AnniePlayer.Canvas style={{ backgroundColor: "#000" }} />
70
+ <AnniePlayer.Controls
71
+ style={{ position: "absolute", bottom: 16, top: "auto" }}
72
+ >
73
+ <AnniePlayer.LoadButton />
74
+ <AnniePlayer.PlayButton />
75
+ <AnniePlayer.PauseButton />
76
+ </AnniePlayer.Controls>
77
+ <AnniePlayer.Status />
78
+ </AnniePlayer.Wrapper>
79
+ </AnniePlayer.Root>
80
+ );
81
+ }
82
+ ```
83
+
84
+ #### Headless (Render Function)
85
+
86
+ For maximum control, use the render function pattern:
87
+
88
+ ```tsx
89
+ import { AnniePlayer } from "@effing/annie-player/react";
90
+
91
+ function HeadlessPlayer() {
92
+ return (
93
+ <AnniePlayer.Root src="https://example.com/animation.tar" height={540}>
94
+ {({ state, actions, canvasRef, canvasDimensions }) => (
95
+ <div className="my-custom-player">
96
+ <canvas
97
+ ref={canvasRef}
98
+ width={canvasDimensions.width}
99
+ height={canvasDimensions.height}
100
+ />
101
+ <div className="my-controls">
102
+ <button onClick={actions.load} disabled={state.isLoading}>
103
+ {state.isLoading ? "Loading..." : "Load"}
104
+ </button>
105
+ <button
106
+ onClick={actions.play}
107
+ disabled={!state.frameCount || state.isPlaying}
108
+ >
109
+ Play
110
+ </button>
111
+ <button onClick={actions.pause} disabled={!state.isPlaying}>
112
+ Pause
113
+ </button>
114
+ </div>
115
+ <p className="my-status">{state.status}</p>
116
+ {state.error && <p className="my-error">Error: {state.error}</p>}
117
+ </div>
118
+ )}
119
+ </AnniePlayer.Root>
120
+ );
121
+ }
122
+ ```
123
+
124
+ #### Available Sub-components
125
+
126
+ | Component | Description |
127
+ | ------------------------- | ----------------------------------------------------------------------------------------------------------- |
128
+ | `AnniePlayer.Root` | Provider that manages player state. Accepts `src`, `height`, `defaultWidth`, `fps`, `autoLoad`, `autoPlay`. |
129
+ | `AnniePlayer.Wrapper` | Container with hover detection. Sets `data-hovering` and `data-playing` attributes. |
130
+ | `AnniePlayer.Canvas` | The canvas element where frames are rendered. |
131
+ | `AnniePlayer.Controls` | Container for buttons. Auto-hides when playing (unless hovering). |
132
+ | `AnniePlayer.LoadButton` | Load button with default "Load"/"Loading..." text. |
133
+ | `AnniePlayer.PlayButton` | Play button with default "Play" text. |
134
+ | `AnniePlayer.PauseButton` | Pause button with default "Pause" text. |
135
+ | `AnniePlayer.Status` | Status overlay showing current state. |
136
+
137
+ ### Vanilla JavaScript
138
+
139
+ ```typescript
140
+ import { AnniePlayerCore } from "@effing/annie-player";
141
+
142
+ const canvas = document.getElementById("canvas") as HTMLCanvasElement;
143
+
144
+ const player = new AnniePlayerCore({
145
+ src: "https://example.com/animation.tar",
146
+ fps: 30,
147
+ autoPlay: true,
148
+ });
149
+
150
+ player.attachCanvas(canvas);
151
+ player.on("load", ({ frameCount, dimensions }) => {
152
+ canvas.width = dimensions.width;
153
+ canvas.height = dimensions.height;
154
+ });
155
+
156
+ await player.load();
157
+ ```
158
+
159
+ ## API Overview
160
+
161
+ ### AnniePlayerCore
162
+
163
+ ```typescript
164
+ class AnniePlayerCore {
165
+ constructor(options: AnniePlayerOptions);
166
+
167
+ // Canvas management
168
+ attachCanvas(canvas: HTMLCanvasElement): void;
169
+ detachCanvas(): void;
170
+
171
+ // Lifecycle
172
+ load(): Promise<void>;
173
+ destroy(): void;
174
+
175
+ // Playback
176
+ play(): void;
177
+ pause(): void;
178
+ stop(): void;
179
+
180
+ // State
181
+ getState(): AnniePlayerState;
182
+
183
+ // Events
184
+ on<K extends keyof AnniePlayerEvents>(
185
+ event: K,
186
+ callback: (data: AnniePlayerEvents[K]) => void,
187
+ ): () => void;
188
+ }
189
+ ```
190
+
191
+ **Options:**
192
+
193
+ | Option | Type | Default | Description |
194
+ | ---------- | --------- | -------- | ------------------------ |
195
+ | `src` | `string` | required | URL to TAR archive |
196
+ | `fps` | `number` | `30` | Playback frame rate |
197
+ | `autoPlay` | `boolean` | `false` | Start playing after load |
198
+
199
+ **State:**
200
+
201
+ ```typescript
202
+ type AnniePlayerState = {
203
+ status: string;
204
+ error: string | null;
205
+ isLoading: boolean;
206
+ isPlaying: boolean;
207
+ frameCount: number;
208
+ dimensions: { width: number; height: number } | null;
209
+ };
210
+ ```
211
+
212
+ **Events:**
213
+
214
+ | Event | Data | Description |
215
+ | ------------- | ---------------------------- | -------------- |
216
+ | `statechange` | `AnniePlayerState` | State updated |
217
+ | `load` | `{ frameCount, dimensions }` | Frames loaded |
218
+ | `error` | `Error` | Error occurred |
219
+
220
+ ### AnniePlayer (React)
221
+
222
+ ```tsx
223
+ <AnniePlayer
224
+ src={string} // TAR archive URL
225
+ height={number} // Canvas height
226
+ defaultWidth={number} // Width before load (default: same as height)
227
+ autoLoad={boolean} // Load on mount (default: false)
228
+ autoPlay={boolean} // Play after load (default: false)
229
+ fps={number} // Frame rate (default: 30)
230
+ className={string} // Class name for root container
231
+ style={CSSProperties} // Style for root container
232
+ />
233
+ ```
234
+
235
+ The React component:
236
+
237
+ - Manages core lifecycle automatically
238
+ - Adjusts canvas width based on loaded frame aspect ratio
239
+ - Shows load/play/pause buttons with built-in styling
240
+ - Displays status overlay on hover
241
+
242
+ ## Examples
243
+
244
+ ### Custom Controls
245
+
246
+ ```typescript
247
+ const player = new AnniePlayerCore({ src, fps: 30 });
248
+ player.attachCanvas(canvas);
249
+
250
+ loadButton.onclick = () => player.load();
251
+ playButton.onclick = () => player.play();
252
+ pauseButton.onclick = () => player.pause();
253
+
254
+ player.on("statechange", (state) => {
255
+ playButton.disabled = state.isPlaying || state.frameCount === 0;
256
+ pauseButton.disabled = !state.isPlaying;
257
+ statusText.textContent = state.status;
258
+ });
259
+ ```
260
+
261
+ ## Related Packages
262
+
263
+ - [`@effing/annie`](../annie) — Generate Annie animations server-side
264
+ - [`@effing/effie-preview`](../effie-preview) — Preview components that use AnniePlayer
@@ -0,0 +1,247 @@
1
+ // src/core.ts
2
+ import { untar } from "@andrewbranch/untar.js";
3
+ var AnniePlayerCore = class {
4
+ src;
5
+ fps;
6
+ autoPlay;
7
+ frameInterval;
8
+ frames = [];
9
+ canvas = null;
10
+ context = null;
11
+ animationFrameId = null;
12
+ currentFrameIndex = 0;
13
+ lastFrameTime = 0;
14
+ _isLoading = false;
15
+ _isPlaying = false;
16
+ _error = null;
17
+ _status = "Ready to load animation.";
18
+ _dimensions = null;
19
+ listeners = /* @__PURE__ */ new Map();
20
+ constructor(options) {
21
+ this.src = options.src;
22
+ this.fps = options.fps ?? 30;
23
+ this.autoPlay = options.autoPlay ?? false;
24
+ this.frameInterval = 1e3 / this.fps;
25
+ }
26
+ /**
27
+ * Attach a canvas element to render the animation on.
28
+ */
29
+ attachCanvas(canvas) {
30
+ this.canvas = canvas;
31
+ this.context = canvas.getContext("2d");
32
+ }
33
+ /**
34
+ * Detach the canvas and stop playback.
35
+ */
36
+ detachCanvas() {
37
+ this.stop();
38
+ this.canvas = null;
39
+ this.context = null;
40
+ }
41
+ /**
42
+ * Get the current state of the player.
43
+ */
44
+ getState() {
45
+ return {
46
+ status: this._status,
47
+ error: this._error,
48
+ isLoading: this._isLoading,
49
+ isPlaying: this._isPlaying,
50
+ frameCount: this.frames.length,
51
+ dimensions: this._dimensions
52
+ };
53
+ }
54
+ /**
55
+ * Subscribe to player events.
56
+ */
57
+ on(event, callback) {
58
+ if (!this.listeners.has(event)) {
59
+ this.listeners.set(event, /* @__PURE__ */ new Set());
60
+ }
61
+ const callbacks = this.listeners.get(event);
62
+ callbacks.add(
63
+ callback
64
+ );
65
+ return () => {
66
+ callbacks.delete(
67
+ callback
68
+ );
69
+ };
70
+ }
71
+ emit(event, data) {
72
+ const callbacks = this.listeners.get(event);
73
+ if (callbacks) {
74
+ callbacks.forEach((cb) => cb(data));
75
+ }
76
+ }
77
+ emitStateChange() {
78
+ this.emit("statechange", this.getState());
79
+ }
80
+ setStatus(status) {
81
+ this._status = status;
82
+ this.emitStateChange();
83
+ }
84
+ setError(error) {
85
+ this._error = error;
86
+ if (error) {
87
+ this.emit("error", new Error(error));
88
+ }
89
+ this.emitStateChange();
90
+ }
91
+ /**
92
+ * Load the animation from the source URL.
93
+ */
94
+ async load() {
95
+ if (this._isLoading) return;
96
+ this.stop();
97
+ this._isLoading = true;
98
+ this._error = null;
99
+ this.setStatus("Loading Annie file...");
100
+ this.cleanup();
101
+ const imageFiles = [];
102
+ const imageLoadPromises = [];
103
+ try {
104
+ const response = await fetch(this.src);
105
+ if (!response.ok) {
106
+ throw new Error(`Failed to fetch animation: ${response.statusText}`);
107
+ }
108
+ if (!response.body) {
109
+ throw new Error("Response body is null");
110
+ }
111
+ this.setStatus("Extracting frames...");
112
+ untar(await response.arrayBuffer()).forEach((file) => {
113
+ if (!file.name || file.typeflag !== "0") return;
114
+ if (!file.fileData) return;
115
+ const blob = new Blob([new Uint8Array(file.fileData)]);
116
+ const promise = new Promise((resolve, reject) => {
117
+ const img = new Image();
118
+ img.onload = () => {
119
+ imageFiles.push({ name: file.name, image: img });
120
+ if (imageFiles.length === 1) {
121
+ this._dimensions = {
122
+ width: img.naturalWidth,
123
+ height: img.naturalHeight
124
+ };
125
+ }
126
+ resolve();
127
+ };
128
+ img.onerror = (err) => {
129
+ reject(new Error(`Could not load image: ${file.name} (${err})`));
130
+ };
131
+ img.src = URL.createObjectURL(blob);
132
+ });
133
+ imageLoadPromises.push(promise);
134
+ });
135
+ await Promise.all(imageLoadPromises);
136
+ if (imageFiles.length === 0) {
137
+ throw new Error("No frames found in the TAR file.");
138
+ }
139
+ imageFiles.sort((a, b) => a.name.localeCompare(b.name));
140
+ this.frames = imageFiles.map((f) => f.image);
141
+ this._isLoading = false;
142
+ this.setStatus(`Loaded ${this.frames.length} frames. Ready to play.`);
143
+ this.emit("load", {
144
+ frameCount: this.frames.length,
145
+ dimensions: this._dimensions
146
+ });
147
+ if (this.autoPlay && this.frames.length > 0) {
148
+ this.play();
149
+ }
150
+ } catch (err) {
151
+ const errorMessage = err instanceof Error ? err.message : "Unknown error";
152
+ this._isLoading = false;
153
+ this.setError(errorMessage);
154
+ this.setStatus(`Error: ${errorMessage}`);
155
+ }
156
+ }
157
+ /**
158
+ * Start playing the animation.
159
+ */
160
+ play() {
161
+ if (this._isPlaying || this.frames.length === 0) return;
162
+ this._isPlaying = true;
163
+ this.currentFrameIndex = 0;
164
+ this.lastFrameTime = 0;
165
+ this.setStatus("Playing...");
166
+ this.animate(performance.now());
167
+ }
168
+ /**
169
+ * Pause the animation.
170
+ */
171
+ pause() {
172
+ if (!this._isPlaying) return;
173
+ this._isPlaying = false;
174
+ if (this.animationFrameId !== null) {
175
+ cancelAnimationFrame(this.animationFrameId);
176
+ this.animationFrameId = null;
177
+ }
178
+ this.setStatus("Paused.");
179
+ }
180
+ /**
181
+ * Stop the animation and reset to the beginning.
182
+ */
183
+ stop() {
184
+ this._isPlaying = false;
185
+ if (this.animationFrameId !== null) {
186
+ cancelAnimationFrame(this.animationFrameId);
187
+ this.animationFrameId = null;
188
+ }
189
+ this.currentFrameIndex = 0;
190
+ this.lastFrameTime = 0;
191
+ }
192
+ animate = (timestamp) => {
193
+ if (!this._isPlaying || this.frames.length === 0) {
194
+ this.animationFrameId = null;
195
+ return;
196
+ }
197
+ if (!this.canvas || !this.context) {
198
+ this.animationFrameId = null;
199
+ return;
200
+ }
201
+ if (this.lastFrameTime === 0) {
202
+ this.lastFrameTime = timestamp;
203
+ }
204
+ const elapsed = timestamp - this.lastFrameTime;
205
+ if (elapsed >= this.frameInterval) {
206
+ this.lastFrameTime = timestamp - elapsed % this.frameInterval;
207
+ this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
208
+ const frameToDraw = this.frames[this.currentFrameIndex];
209
+ if (frameToDraw) {
210
+ this.context.drawImage(
211
+ frameToDraw,
212
+ 0,
213
+ 0,
214
+ this.canvas.width,
215
+ this.canvas.height
216
+ );
217
+ }
218
+ this.currentFrameIndex = (this.currentFrameIndex + 1) % this.frames.length;
219
+ }
220
+ this.animationFrameId = requestAnimationFrame(this.animate);
221
+ };
222
+ /**
223
+ * Clean up resources (revoke blob URLs).
224
+ */
225
+ cleanup() {
226
+ this.frames.forEach((img) => {
227
+ if (img.src.startsWith("blob:")) {
228
+ URL.revokeObjectURL(img.src);
229
+ }
230
+ });
231
+ this.frames = [];
232
+ }
233
+ /**
234
+ * Destroy the player and clean up all resources.
235
+ */
236
+ destroy() {
237
+ this.stop();
238
+ this.cleanup();
239
+ this.detachCanvas();
240
+ this.listeners.clear();
241
+ }
242
+ };
243
+
244
+ export {
245
+ AnniePlayerCore
246
+ };
247
+ //# sourceMappingURL=chunk-AFGBXFMF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core.ts"],"sourcesContent":["import { untar } from \"@andrewbranch/untar.js\";\n\nexport type AnniePlayerState = {\n status: string;\n error: string | null;\n isLoading: boolean;\n isPlaying: boolean;\n frameCount: number;\n dimensions: { width: number; height: number } | null;\n};\n\nexport type AnniePlayerEvents = {\n statechange: AnniePlayerState;\n load: { frameCount: number; dimensions: { width: number; height: number } };\n error: Error;\n};\n\nexport type AnniePlayerOptions = {\n src: string;\n fps?: number;\n autoPlay?: boolean;\n};\n\ntype EventCallback<T> = (data: T) => void;\n\n/**\n * Framework-agnostic Annie animation player.\n * Handles loading TAR archives of image frames and playing them on a canvas.\n */\nexport class AnniePlayerCore {\n private src: string;\n private fps: number;\n private autoPlay: boolean;\n private frameInterval: number;\n\n private frames: HTMLImageElement[] = [];\n private canvas: HTMLCanvasElement | null = null;\n private context: CanvasRenderingContext2D | null = null;\n\n private animationFrameId: number | null = null;\n private currentFrameIndex = 0;\n private lastFrameTime = 0;\n\n private _isLoading = false;\n private _isPlaying = false;\n private _error: string | null = null;\n private _status = \"Ready to load animation.\";\n private _dimensions: { width: number; height: number } | null = null;\n\n private listeners: Map<\n keyof AnniePlayerEvents,\n Set<EventCallback<AnniePlayerEvents[keyof AnniePlayerEvents]>>\n > = new Map();\n\n constructor(options: AnniePlayerOptions) {\n this.src = options.src;\n this.fps = options.fps ?? 30;\n this.autoPlay = options.autoPlay ?? false;\n this.frameInterval = 1000 / this.fps;\n }\n\n /**\n * Attach a canvas element to render the animation on.\n */\n attachCanvas(canvas: HTMLCanvasElement): void {\n this.canvas = canvas;\n this.context = canvas.getContext(\"2d\");\n }\n\n /**\n * Detach the canvas and stop playback.\n */\n detachCanvas(): void {\n this.stop();\n this.canvas = null;\n this.context = null;\n }\n\n /**\n * Get the current state of the player.\n */\n getState(): AnniePlayerState {\n return {\n status: this._status,\n error: this._error,\n isLoading: this._isLoading,\n isPlaying: this._isPlaying,\n frameCount: this.frames.length,\n dimensions: this._dimensions,\n };\n }\n\n /**\n * Subscribe to player events.\n */\n on<K extends keyof AnniePlayerEvents>(\n event: K,\n callback: EventCallback<AnniePlayerEvents[K]>,\n ): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n const callbacks = this.listeners.get(event)!;\n callbacks.add(\n callback as EventCallback<AnniePlayerEvents[keyof AnniePlayerEvents]>,\n );\n\n return () => {\n callbacks.delete(\n callback as EventCallback<AnniePlayerEvents[keyof AnniePlayerEvents]>,\n );\n };\n }\n\n private emit<K extends keyof AnniePlayerEvents>(\n event: K,\n data: AnniePlayerEvents[K],\n ): void {\n const callbacks = this.listeners.get(event);\n if (callbacks) {\n callbacks.forEach((cb) => cb(data));\n }\n }\n\n private emitStateChange(): void {\n this.emit(\"statechange\", this.getState());\n }\n\n private setStatus(status: string): void {\n this._status = status;\n this.emitStateChange();\n }\n\n private setError(error: string | null): void {\n this._error = error;\n if (error) {\n this.emit(\"error\", new Error(error));\n }\n this.emitStateChange();\n }\n\n /**\n * Load the animation from the source URL.\n */\n async load(): Promise<void> {\n if (this._isLoading) return;\n\n this.stop();\n this._isLoading = true;\n this._error = null;\n this.setStatus(\"Loading Annie file...\");\n this.cleanup();\n\n const imageFiles: { name: string; image: HTMLImageElement }[] = [];\n const imageLoadPromises: Promise<void>[] = [];\n\n try {\n const response = await fetch(this.src);\n if (!response.ok) {\n throw new Error(`Failed to fetch animation: ${response.statusText}`);\n }\n if (!response.body) {\n throw new Error(\"Response body is null\");\n }\n\n this.setStatus(\"Extracting frames...\");\n\n untar(await response.arrayBuffer()).forEach((file) => {\n if (!file.name || file.typeflag !== \"0\") return;\n if (!file.fileData) return;\n\n const blob = new Blob([new Uint8Array(file.fileData)]);\n\n const promise = new Promise<void>((resolve, reject) => {\n const img = new Image();\n img.onload = () => {\n imageFiles.push({ name: file.name!, image: img });\n if (imageFiles.length === 1) {\n this._dimensions = {\n width: img.naturalWidth,\n height: img.naturalHeight,\n };\n }\n resolve();\n };\n img.onerror = (err) => {\n reject(new Error(`Could not load image: ${file.name} (${err})`));\n };\n img.src = URL.createObjectURL(blob);\n });\n imageLoadPromises.push(promise);\n });\n\n await Promise.all(imageLoadPromises);\n\n if (imageFiles.length === 0) {\n throw new Error(\"No frames found in the TAR file.\");\n }\n\n imageFiles.sort((a, b) => a.name.localeCompare(b.name));\n this.frames = imageFiles.map((f) => f.image);\n this._isLoading = false;\n this.setStatus(`Loaded ${this.frames.length} frames. Ready to play.`);\n\n this.emit(\"load\", {\n frameCount: this.frames.length,\n dimensions: this._dimensions!,\n });\n\n if (this.autoPlay && this.frames.length > 0) {\n this.play();\n }\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : \"Unknown error\";\n this._isLoading = false;\n this.setError(errorMessage);\n this.setStatus(`Error: ${errorMessage}`);\n }\n }\n\n /**\n * Start playing the animation.\n */\n play(): void {\n if (this._isPlaying || this.frames.length === 0) return;\n\n this._isPlaying = true;\n this.currentFrameIndex = 0;\n this.lastFrameTime = 0;\n this.setStatus(\"Playing...\");\n this.animate(performance.now());\n }\n\n /**\n * Pause the animation.\n */\n pause(): void {\n if (!this._isPlaying) return;\n\n this._isPlaying = false;\n if (this.animationFrameId !== null) {\n cancelAnimationFrame(this.animationFrameId);\n this.animationFrameId = null;\n }\n this.setStatus(\"Paused.\");\n }\n\n /**\n * Stop the animation and reset to the beginning.\n */\n stop(): void {\n this._isPlaying = false;\n if (this.animationFrameId !== null) {\n cancelAnimationFrame(this.animationFrameId);\n this.animationFrameId = null;\n }\n this.currentFrameIndex = 0;\n this.lastFrameTime = 0;\n }\n\n private animate = (timestamp: number): void => {\n if (!this._isPlaying || this.frames.length === 0) {\n this.animationFrameId = null;\n return;\n }\n\n if (!this.canvas || !this.context) {\n this.animationFrameId = null;\n return;\n }\n\n if (this.lastFrameTime === 0) {\n this.lastFrameTime = timestamp;\n }\n\n const elapsed = timestamp - this.lastFrameTime;\n\n if (elapsed >= this.frameInterval) {\n this.lastFrameTime = timestamp - (elapsed % this.frameInterval);\n this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);\n const frameToDraw = this.frames[this.currentFrameIndex];\n if (frameToDraw) {\n this.context.drawImage(\n frameToDraw,\n 0,\n 0,\n this.canvas.width,\n this.canvas.height,\n );\n }\n this.currentFrameIndex =\n (this.currentFrameIndex + 1) % this.frames.length;\n }\n\n this.animationFrameId = requestAnimationFrame(this.animate);\n };\n\n /**\n * Clean up resources (revoke blob URLs).\n */\n cleanup(): void {\n this.frames.forEach((img) => {\n if (img.src.startsWith(\"blob:\")) {\n URL.revokeObjectURL(img.src);\n }\n });\n this.frames = [];\n }\n\n /**\n * Destroy the player and clean up all resources.\n */\n destroy(): void {\n this.stop();\n this.cleanup();\n this.detachCanvas();\n this.listeners.clear();\n }\n}\n"],"mappings":";AAAA,SAAS,aAAa;AA6Bf,IAAM,kBAAN,MAAsB;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA,SAA6B,CAAC;AAAA,EAC9B,SAAmC;AAAA,EACnC,UAA2C;AAAA,EAE3C,mBAAkC;AAAA,EAClC,oBAAoB;AAAA,EACpB,gBAAgB;AAAA,EAEhB,aAAa;AAAA,EACb,aAAa;AAAA,EACb,SAAwB;AAAA,EACxB,UAAU;AAAA,EACV,cAAwD;AAAA,EAExD,YAGJ,oBAAI,IAAI;AAAA,EAEZ,YAAY,SAA6B;AACvC,SAAK,MAAM,QAAQ;AACnB,SAAK,MAAM,QAAQ,OAAO;AAC1B,SAAK,WAAW,QAAQ,YAAY;AACpC,SAAK,gBAAgB,MAAO,KAAK;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,QAAiC;AAC5C,SAAK,SAAS;AACd,SAAK,UAAU,OAAO,WAAW,IAAI;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,eAAqB;AACnB,SAAK,KAAK;AACV,SAAK,SAAS;AACd,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKA,WAA6B;AAC3B,WAAO;AAAA,MACL,QAAQ,KAAK;AAAA,MACb,OAAO,KAAK;AAAA,MACZ,WAAW,KAAK;AAAA,MAChB,WAAW,KAAK;AAAA,MAChB,YAAY,KAAK,OAAO;AAAA,MACxB,YAAY,KAAK;AAAA,IACnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,GACE,OACA,UACY;AACZ,QAAI,CAAC,KAAK,UAAU,IAAI,KAAK,GAAG;AAC9B,WAAK,UAAU,IAAI,OAAO,oBAAI,IAAI,CAAC;AAAA,IACrC;AACA,UAAM,YAAY,KAAK,UAAU,IAAI,KAAK;AAC1C,cAAU;AAAA,MACR;AAAA,IACF;AAEA,WAAO,MAAM;AACX,gBAAU;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,KACN,OACA,MACM;AACN,UAAM,YAAY,KAAK,UAAU,IAAI,KAAK;AAC1C,QAAI,WAAW;AACb,gBAAU,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC;AAAA,IACpC;AAAA,EACF;AAAA,EAEQ,kBAAwB;AAC9B,SAAK,KAAK,eAAe,KAAK,SAAS,CAAC;AAAA,EAC1C;AAAA,EAEQ,UAAU,QAAsB;AACtC,SAAK,UAAU;AACf,SAAK,gBAAgB;AAAA,EACvB;AAAA,EAEQ,SAAS,OAA4B;AAC3C,SAAK,SAAS;AACd,QAAI,OAAO;AACT,WAAK,KAAK,SAAS,IAAI,MAAM,KAAK,CAAC;AAAA,IACrC;AACA,SAAK,gBAAgB;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAsB;AAC1B,QAAI,KAAK,WAAY;AAErB,SAAK,KAAK;AACV,SAAK,aAAa;AAClB,SAAK,SAAS;AACd,SAAK,UAAU,uBAAuB;AACtC,SAAK,QAAQ;AAEb,UAAM,aAA0D,CAAC;AACjE,UAAM,oBAAqC,CAAC;AAE5C,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,KAAK,GAAG;AACrC,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,IAAI,MAAM,8BAA8B,SAAS,UAAU,EAAE;AAAA,MACrE;AACA,UAAI,CAAC,SAAS,MAAM;AAClB,cAAM,IAAI,MAAM,uBAAuB;AAAA,MACzC;AAEA,WAAK,UAAU,sBAAsB;AAErC,YAAM,MAAM,SAAS,YAAY,CAAC,EAAE,QAAQ,CAAC,SAAS;AACpD,YAAI,CAAC,KAAK,QAAQ,KAAK,aAAa,IAAK;AACzC,YAAI,CAAC,KAAK,SAAU;AAEpB,cAAM,OAAO,IAAI,KAAK,CAAC,IAAI,WAAW,KAAK,QAAQ,CAAC,CAAC;AAErD,cAAM,UAAU,IAAI,QAAc,CAAC,SAAS,WAAW;AACrD,gBAAM,MAAM,IAAI,MAAM;AACtB,cAAI,SAAS,MAAM;AACjB,uBAAW,KAAK,EAAE,MAAM,KAAK,MAAO,OAAO,IAAI,CAAC;AAChD,gBAAI,WAAW,WAAW,GAAG;AAC3B,mBAAK,cAAc;AAAA,gBACjB,OAAO,IAAI;AAAA,gBACX,QAAQ,IAAI;AAAA,cACd;AAAA,YACF;AACA,oBAAQ;AAAA,UACV;AACA,cAAI,UAAU,CAAC,QAAQ;AACrB,mBAAO,IAAI,MAAM,yBAAyB,KAAK,IAAI,KAAK,GAAG,GAAG,CAAC;AAAA,UACjE;AACA,cAAI,MAAM,IAAI,gBAAgB,IAAI;AAAA,QACpC,CAAC;AACD,0BAAkB,KAAK,OAAO;AAAA,MAChC,CAAC;AAED,YAAM,QAAQ,IAAI,iBAAiB;AAEnC,UAAI,WAAW,WAAW,GAAG;AAC3B,cAAM,IAAI,MAAM,kCAAkC;AAAA,MACpD;AAEA,iBAAW,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AACtD,WAAK,SAAS,WAAW,IAAI,CAAC,MAAM,EAAE,KAAK;AAC3C,WAAK,aAAa;AAClB,WAAK,UAAU,UAAU,KAAK,OAAO,MAAM,yBAAyB;AAEpE,WAAK,KAAK,QAAQ;AAAA,QAChB,YAAY,KAAK,OAAO;AAAA,QACxB,YAAY,KAAK;AAAA,MACnB,CAAC;AAED,UAAI,KAAK,YAAY,KAAK,OAAO,SAAS,GAAG;AAC3C,aAAK,KAAK;AAAA,MACZ;AAAA,IACF,SAAS,KAAK;AACZ,YAAM,eAAe,eAAe,QAAQ,IAAI,UAAU;AAC1D,WAAK,aAAa;AAClB,WAAK,SAAS,YAAY;AAC1B,WAAK,UAAU,UAAU,YAAY,EAAE;AAAA,IACzC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,OAAa;AACX,QAAI,KAAK,cAAc,KAAK,OAAO,WAAW,EAAG;AAEjD,SAAK,aAAa;AAClB,SAAK,oBAAoB;AACzB,SAAK,gBAAgB;AACrB,SAAK,UAAU,YAAY;AAC3B,SAAK,QAAQ,YAAY,IAAI,CAAC;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,QAAI,CAAC,KAAK,WAAY;AAEtB,SAAK,aAAa;AAClB,QAAI,KAAK,qBAAqB,MAAM;AAClC,2BAAqB,KAAK,gBAAgB;AAC1C,WAAK,mBAAmB;AAAA,IAC1B;AACA,SAAK,UAAU,SAAS;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKA,OAAa;AACX,SAAK,aAAa;AAClB,QAAI,KAAK,qBAAqB,MAAM;AAClC,2BAAqB,KAAK,gBAAgB;AAC1C,WAAK,mBAAmB;AAAA,IAC1B;AACA,SAAK,oBAAoB;AACzB,SAAK,gBAAgB;AAAA,EACvB;AAAA,EAEQ,UAAU,CAAC,cAA4B;AAC7C,QAAI,CAAC,KAAK,cAAc,KAAK,OAAO,WAAW,GAAG;AAChD,WAAK,mBAAmB;AACxB;AAAA,IACF;AAEA,QAAI,CAAC,KAAK,UAAU,CAAC,KAAK,SAAS;AACjC,WAAK,mBAAmB;AACxB;AAAA,IACF;AAEA,QAAI,KAAK,kBAAkB,GAAG;AAC5B,WAAK,gBAAgB;AAAA,IACvB;AAEA,UAAM,UAAU,YAAY,KAAK;AAEjC,QAAI,WAAW,KAAK,eAAe;AACjC,WAAK,gBAAgB,YAAa,UAAU,KAAK;AACjD,WAAK,QAAQ,UAAU,GAAG,GAAG,KAAK,OAAO,OAAO,KAAK,OAAO,MAAM;AAClE,YAAM,cAAc,KAAK,OAAO,KAAK,iBAAiB;AACtD,UAAI,aAAa;AACf,aAAK,QAAQ;AAAA,UACX;AAAA,UACA;AAAA,UACA;AAAA,UACA,KAAK,OAAO;AAAA,UACZ,KAAK,OAAO;AAAA,QACd;AAAA,MACF;AACA,WAAK,qBACF,KAAK,oBAAoB,KAAK,KAAK,OAAO;AAAA,IAC/C;AAEA,SAAK,mBAAmB,sBAAsB,KAAK,OAAO;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;AACd,SAAK,OAAO,QAAQ,CAAC,QAAQ;AAC3B,UAAI,IAAI,IAAI,WAAW,OAAO,GAAG;AAC/B,YAAI,gBAAgB,IAAI,GAAG;AAAA,MAC7B;AAAA,IACF,CAAC;AACD,SAAK,SAAS,CAAC;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;AACd,SAAK,KAAK;AACV,SAAK,QAAQ;AACb,SAAK,aAAa;AAClB,SAAK,UAAU,MAAM;AAAA,EACvB;AACF;","names":[]}
@@ -0,0 +1,98 @@
1
+ type AnniePlayerState = {
2
+ status: string;
3
+ error: string | null;
4
+ isLoading: boolean;
5
+ isPlaying: boolean;
6
+ frameCount: number;
7
+ dimensions: {
8
+ width: number;
9
+ height: number;
10
+ } | null;
11
+ };
12
+ type AnniePlayerEvents = {
13
+ statechange: AnniePlayerState;
14
+ load: {
15
+ frameCount: number;
16
+ dimensions: {
17
+ width: number;
18
+ height: number;
19
+ };
20
+ };
21
+ error: Error;
22
+ };
23
+ type AnniePlayerOptions = {
24
+ src: string;
25
+ fps?: number;
26
+ autoPlay?: boolean;
27
+ };
28
+ type EventCallback<T> = (data: T) => void;
29
+ /**
30
+ * Framework-agnostic Annie animation player.
31
+ * Handles loading TAR archives of image frames and playing them on a canvas.
32
+ */
33
+ declare class AnniePlayerCore {
34
+ private src;
35
+ private fps;
36
+ private autoPlay;
37
+ private frameInterval;
38
+ private frames;
39
+ private canvas;
40
+ private context;
41
+ private animationFrameId;
42
+ private currentFrameIndex;
43
+ private lastFrameTime;
44
+ private _isLoading;
45
+ private _isPlaying;
46
+ private _error;
47
+ private _status;
48
+ private _dimensions;
49
+ private listeners;
50
+ constructor(options: AnniePlayerOptions);
51
+ /**
52
+ * Attach a canvas element to render the animation on.
53
+ */
54
+ attachCanvas(canvas: HTMLCanvasElement): void;
55
+ /**
56
+ * Detach the canvas and stop playback.
57
+ */
58
+ detachCanvas(): void;
59
+ /**
60
+ * Get the current state of the player.
61
+ */
62
+ getState(): AnniePlayerState;
63
+ /**
64
+ * Subscribe to player events.
65
+ */
66
+ on<K extends keyof AnniePlayerEvents>(event: K, callback: EventCallback<AnniePlayerEvents[K]>): () => void;
67
+ private emit;
68
+ private emitStateChange;
69
+ private setStatus;
70
+ private setError;
71
+ /**
72
+ * Load the animation from the source URL.
73
+ */
74
+ load(): Promise<void>;
75
+ /**
76
+ * Start playing the animation.
77
+ */
78
+ play(): void;
79
+ /**
80
+ * Pause the animation.
81
+ */
82
+ pause(): void;
83
+ /**
84
+ * Stop the animation and reset to the beginning.
85
+ */
86
+ stop(): void;
87
+ private animate;
88
+ /**
89
+ * Clean up resources (revoke blob URLs).
90
+ */
91
+ cleanup(): void;
92
+ /**
93
+ * Destroy the player and clean up all resources.
94
+ */
95
+ destroy(): void;
96
+ }
97
+
98
+ export { AnniePlayerCore, type AnniePlayerEvents, type AnniePlayerOptions, type AnniePlayerState };
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ import {
2
+ AnniePlayerCore
3
+ } from "./chunk-AFGBXFMF.js";
4
+ export {
5
+ AnniePlayerCore
6
+ };
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,107 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { AnniePlayerState } from '../index.js';
3
+
4
+ type AnniePlayerContextValue = {
5
+ state: AnniePlayerState;
6
+ actions: {
7
+ load: () => void;
8
+ play: () => void;
9
+ pause: () => void;
10
+ };
11
+ canvasRef: React.RefObject<HTMLCanvasElement | null>;
12
+ canvasDimensions: {
13
+ width: number;
14
+ height: number;
15
+ };
16
+ isHovering: boolean;
17
+ setIsHovering: (v: boolean) => void;
18
+ };
19
+ type AnniePlayerRootProps = {
20
+ src: string;
21
+ height: number;
22
+ defaultWidth?: number;
23
+ autoLoad?: boolean;
24
+ autoPlay?: boolean;
25
+ fps?: number;
26
+ /** Style for the root container */
27
+ style?: React.CSSProperties;
28
+ /** Class name for the root container */
29
+ className?: string;
30
+ /** Children can be React nodes or a render function for headless usage */
31
+ children?: React.ReactNode | ((value: AnniePlayerContextValue) => React.ReactNode);
32
+ };
33
+ declare function AnniePlayerRoot({ src, height, defaultWidth, autoLoad, autoPlay, fps, style, className, children, }: AnniePlayerRootProps): react_jsx_runtime.JSX.Element;
34
+ type AnniePlayerWrapperProps = {
35
+ children: React.ReactNode;
36
+ className?: string;
37
+ style?: React.CSSProperties;
38
+ };
39
+ declare function AnniePlayerWrapper({ children, className, style, }: AnniePlayerWrapperProps): react_jsx_runtime.JSX.Element;
40
+ type AnniePlayerCanvasProps = {
41
+ className?: string;
42
+ style?: React.CSSProperties;
43
+ };
44
+ declare function AnniePlayerCanvas({ className, style }: AnniePlayerCanvasProps): react_jsx_runtime.JSX.Element;
45
+ type AnniePlayerControlsProps = {
46
+ children: React.ReactNode;
47
+ className?: string;
48
+ style?: React.CSSProperties;
49
+ };
50
+ declare function AnniePlayerControls({ children, className, style, }: AnniePlayerControlsProps): react_jsx_runtime.JSX.Element;
51
+ type AnniePlayerLoadButtonProps = {
52
+ children?: React.ReactNode;
53
+ className?: string;
54
+ style?: React.CSSProperties;
55
+ };
56
+ declare function AnniePlayerLoadButton({ children, className, style, }: AnniePlayerLoadButtonProps): react_jsx_runtime.JSX.Element;
57
+ type AnniePlayerPlayButtonProps = {
58
+ children?: React.ReactNode;
59
+ className?: string;
60
+ style?: React.CSSProperties;
61
+ };
62
+ declare function AnniePlayerPlayButton({ children, className, style, }: AnniePlayerPlayButtonProps): react_jsx_runtime.JSX.Element;
63
+ type AnniePlayerPauseButtonProps = {
64
+ children?: React.ReactNode;
65
+ className?: string;
66
+ style?: React.CSSProperties;
67
+ };
68
+ declare function AnniePlayerPauseButton({ children, className, style, }: AnniePlayerPauseButtonProps): react_jsx_runtime.JSX.Element;
69
+ type AnniePlayerStatusProps = {
70
+ children?: React.ReactNode;
71
+ className?: string;
72
+ style?: React.CSSProperties;
73
+ };
74
+ declare function AnniePlayerStatus({ children, className, style, }: AnniePlayerStatusProps): react_jsx_runtime.JSX.Element;
75
+ type AnniePlayerProps = {
76
+ src: string;
77
+ height: number;
78
+ defaultWidth?: number;
79
+ autoLoad?: boolean;
80
+ autoPlay?: boolean;
81
+ fps?: number;
82
+ /** Style for the root container */
83
+ style?: React.CSSProperties;
84
+ /** Class name for the root container */
85
+ className?: string;
86
+ };
87
+ declare function AnniePlayerSimple({ src, height, defaultWidth, autoLoad, autoPlay, fps, style, className, }: AnniePlayerProps): react_jsx_runtime.JSX.Element;
88
+ /**
89
+ * AnniePlayer - Play TAR archives of PNG/JPEG frames in the browser.
90
+ *
91
+ * Usage:
92
+ * - Simple: `<AnniePlayer src={url} height={540} />`
93
+ * - Compound: `<AnniePlayer.Root src={url} height={540}>...</AnniePlayer.Root>`
94
+ * - Headless: `<AnniePlayer.Root src={url} height={540}>{({ state, actions }) => ...}</AnniePlayer.Root>`
95
+ */
96
+ declare const AnniePlayer: typeof AnniePlayerSimple & {
97
+ Root: typeof AnniePlayerRoot;
98
+ Wrapper: typeof AnniePlayerWrapper;
99
+ Canvas: typeof AnniePlayerCanvas;
100
+ Controls: typeof AnniePlayerControls;
101
+ LoadButton: typeof AnniePlayerLoadButton;
102
+ PlayButton: typeof AnniePlayerPlayButton;
103
+ PauseButton: typeof AnniePlayerPauseButton;
104
+ Status: typeof AnniePlayerStatus;
105
+ };
106
+
107
+ export { AnniePlayer, type AnniePlayerProps, type AnniePlayerRootProps };
@@ -0,0 +1,411 @@
1
+ import {
2
+ AnniePlayerCore
3
+ } from "../chunk-AFGBXFMF.js";
4
+
5
+ // src/react/index.tsx
6
+ import { createContext, useContext, useEffect, useRef, useState } from "react";
7
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
+ var AnniePlayerContext = createContext(null);
9
+ function useAnniePlayerContext() {
10
+ const ctx = useContext(AnniePlayerContext);
11
+ if (!ctx) {
12
+ throw new Error(
13
+ "AnniePlayer compound components must be used within AnniePlayer.Root"
14
+ );
15
+ }
16
+ return ctx;
17
+ }
18
+ function AnniePlayerRoot({
19
+ src,
20
+ height,
21
+ defaultWidth,
22
+ autoLoad = false,
23
+ autoPlay = false,
24
+ fps = 30,
25
+ style,
26
+ className,
27
+ children
28
+ }) {
29
+ const canvasRef = useRef(null);
30
+ const playerRef = useRef(null);
31
+ const [state, setState] = useState({
32
+ status: "Ready to load animation.",
33
+ error: null,
34
+ isLoading: false,
35
+ isPlaying: false,
36
+ frameCount: 0,
37
+ dimensions: null
38
+ });
39
+ const [canvasDimensions, setCanvasDimensions] = useState({
40
+ width: defaultWidth ?? height,
41
+ height
42
+ });
43
+ const [isHovering, setIsHovering] = useState(false);
44
+ useEffect(() => {
45
+ const player = new AnniePlayerCore({ src, fps, autoPlay });
46
+ playerRef.current = player;
47
+ const unsubscribeState = player.on("statechange", (newState) => {
48
+ setState(newState);
49
+ });
50
+ const unsubscribeLoad = player.on("load", ({ dimensions }) => {
51
+ setCanvasDimensions({
52
+ width: dimensions.width / dimensions.height * height,
53
+ height
54
+ });
55
+ });
56
+ return () => {
57
+ unsubscribeState();
58
+ unsubscribeLoad();
59
+ player.destroy();
60
+ };
61
+ }, [src, fps, autoPlay, height]);
62
+ useEffect(() => {
63
+ const player = playerRef.current;
64
+ const canvas = canvasRef.current;
65
+ if (player && canvas) {
66
+ player.attachCanvas(canvas);
67
+ }
68
+ }, [canvasDimensions]);
69
+ useEffect(() => {
70
+ if (autoLoad && playerRef.current) {
71
+ playerRef.current.load();
72
+ }
73
+ }, [autoLoad]);
74
+ const actions = {
75
+ load: () => playerRef.current?.load(),
76
+ play: () => playerRef.current?.play(),
77
+ pause: () => playerRef.current?.pause()
78
+ };
79
+ const value = {
80
+ state,
81
+ actions,
82
+ canvasRef,
83
+ canvasDimensions,
84
+ isHovering,
85
+ setIsHovering
86
+ };
87
+ if (typeof children === "function") {
88
+ return /* @__PURE__ */ jsx(Fragment, { children: children(value) });
89
+ }
90
+ return /* @__PURE__ */ jsx(AnniePlayerContext.Provider, { value, children: /* @__PURE__ */ jsx(
91
+ "div",
92
+ {
93
+ style: { display: "inline-block", width: "fit-content", ...style },
94
+ className,
95
+ children
96
+ }
97
+ ) });
98
+ }
99
+ function AnniePlayerWrapper({
100
+ children,
101
+ className,
102
+ style
103
+ }) {
104
+ const { isHovering, setIsHovering, state } = useAnniePlayerContext();
105
+ return /* @__PURE__ */ jsx(
106
+ "div",
107
+ {
108
+ style: {
109
+ position: "relative",
110
+ display: "inline-block",
111
+ width: "fit-content",
112
+ ...style
113
+ },
114
+ className,
115
+ onMouseEnter: () => setIsHovering(true),
116
+ onMouseLeave: () => setIsHovering(false),
117
+ "data-hovering": isHovering,
118
+ "data-playing": state.isPlaying,
119
+ children
120
+ }
121
+ );
122
+ }
123
+ function AnniePlayerCanvas({ className, style }) {
124
+ const { canvasRef, canvasDimensions } = useAnniePlayerContext();
125
+ return /* @__PURE__ */ jsx(
126
+ "canvas",
127
+ {
128
+ ref: canvasRef,
129
+ width: canvasDimensions.width,
130
+ height: canvasDimensions.height,
131
+ className,
132
+ style: { display: "block", ...style }
133
+ }
134
+ );
135
+ }
136
+ function AnniePlayerControls({
137
+ children,
138
+ className,
139
+ style
140
+ }) {
141
+ const { state, isHovering } = useAnniePlayerContext();
142
+ const visible = !state.isPlaying || isHovering;
143
+ return /* @__PURE__ */ jsx(
144
+ "div",
145
+ {
146
+ className,
147
+ style: {
148
+ transition: "opacity 0.3s ease",
149
+ opacity: visible ? 1 : 0,
150
+ ...style
151
+ },
152
+ children
153
+ }
154
+ );
155
+ }
156
+ function AnniePlayerLoadButton({
157
+ children,
158
+ className,
159
+ style
160
+ }) {
161
+ const { state, actions } = useAnniePlayerContext();
162
+ const disabled = state.isLoading;
163
+ return /* @__PURE__ */ jsx(
164
+ "button",
165
+ {
166
+ onClick: actions.load,
167
+ disabled,
168
+ "data-disabled": disabled,
169
+ className,
170
+ style,
171
+ children: children ?? (state.isLoading ? "Loading..." : "Load")
172
+ }
173
+ );
174
+ }
175
+ function AnniePlayerPlayButton({
176
+ children,
177
+ className,
178
+ style
179
+ }) {
180
+ const { state, actions } = useAnniePlayerContext();
181
+ const disabled = state.isLoading || state.isPlaying || state.frameCount === 0;
182
+ return /* @__PURE__ */ jsx(
183
+ "button",
184
+ {
185
+ onClick: actions.play,
186
+ disabled,
187
+ "data-disabled": disabled,
188
+ className,
189
+ style,
190
+ children: children ?? "Play"
191
+ }
192
+ );
193
+ }
194
+ function AnniePlayerPauseButton({
195
+ children,
196
+ className,
197
+ style
198
+ }) {
199
+ const { state, actions } = useAnniePlayerContext();
200
+ const disabled = !state.isPlaying;
201
+ return /* @__PURE__ */ jsx(
202
+ "button",
203
+ {
204
+ onClick: actions.pause,
205
+ disabled,
206
+ "data-disabled": disabled,
207
+ className,
208
+ style,
209
+ children: children ?? "Pause"
210
+ }
211
+ );
212
+ }
213
+ function AnniePlayerStatus({
214
+ children,
215
+ className,
216
+ style
217
+ }) {
218
+ const { state, isHovering } = useAnniePlayerContext();
219
+ const visible = isHovering;
220
+ return /* @__PURE__ */ jsx(
221
+ "div",
222
+ {
223
+ className,
224
+ style: {
225
+ transition: "opacity 0.3s ease",
226
+ opacity: visible ? 1 : 0,
227
+ ...style
228
+ },
229
+ children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
230
+ state.status,
231
+ state.error && /* @__PURE__ */ jsxs("span", { children: [
232
+ " Error: ",
233
+ state.error
234
+ ] })
235
+ ] })
236
+ }
237
+ );
238
+ }
239
+ function AnniePlayerSimple({
240
+ src,
241
+ height,
242
+ defaultWidth,
243
+ autoLoad = false,
244
+ autoPlay = false,
245
+ fps = 30,
246
+ style,
247
+ className
248
+ }) {
249
+ return /* @__PURE__ */ jsx(
250
+ AnniePlayerRoot,
251
+ {
252
+ src,
253
+ height,
254
+ defaultWidth,
255
+ autoLoad,
256
+ autoPlay,
257
+ fps,
258
+ style,
259
+ className,
260
+ children: ({
261
+ state,
262
+ actions,
263
+ canvasRef,
264
+ canvasDimensions,
265
+ isHovering,
266
+ setIsHovering
267
+ }) => {
268
+ const loadDisabled = state.isLoading;
269
+ const playDisabled = state.isLoading || state.isPlaying || state.frameCount === 0;
270
+ const pauseDisabled = !state.isPlaying;
271
+ const buttonContainerVisible = !state.isPlaying || isHovering;
272
+ const statusVisible = isHovering;
273
+ const buttonStyle = (disabled) => ({
274
+ backgroundColor: "#222",
275
+ color: "white",
276
+ border: "none",
277
+ borderRadius: 4,
278
+ padding: "0.5rem 1rem",
279
+ cursor: "pointer",
280
+ margin: "0 4px",
281
+ fontSize: "14px",
282
+ fontWeight: 500,
283
+ opacity: disabled ? 0.6 : 1
284
+ });
285
+ return /* @__PURE__ */ jsxs(
286
+ "div",
287
+ {
288
+ style: {
289
+ position: "relative",
290
+ display: "inline-block",
291
+ width: "fit-content"
292
+ },
293
+ "data-hovering": isHovering,
294
+ "data-playing": state.isPlaying,
295
+ onMouseEnter: () => setIsHovering(true),
296
+ onMouseLeave: () => setIsHovering(false),
297
+ children: [
298
+ /* @__PURE__ */ jsxs(
299
+ "div",
300
+ {
301
+ style: {
302
+ position: "absolute",
303
+ width: "90%",
304
+ top: 16,
305
+ left: "50%",
306
+ transform: "translateX(-50%)",
307
+ display: "flex",
308
+ flexWrap: "wrap",
309
+ justifyContent: "center",
310
+ gap: 4,
311
+ zIndex: 10,
312
+ transition: "opacity 0.3s ease",
313
+ opacity: buttonContainerVisible ? 1 : 0
314
+ },
315
+ children: [
316
+ /* @__PURE__ */ jsx(
317
+ "button",
318
+ {
319
+ onClick: actions.load,
320
+ disabled: loadDisabled,
321
+ "data-disabled": loadDisabled,
322
+ style: buttonStyle(loadDisabled),
323
+ children: state.isLoading ? "Loading..." : "Load"
324
+ }
325
+ ),
326
+ /* @__PURE__ */ jsx(
327
+ "button",
328
+ {
329
+ onClick: actions.play,
330
+ disabled: playDisabled,
331
+ "data-disabled": playDisabled,
332
+ style: buttonStyle(playDisabled),
333
+ children: "Play"
334
+ }
335
+ ),
336
+ /* @__PURE__ */ jsx(
337
+ "button",
338
+ {
339
+ onClick: actions.pause,
340
+ disabled: pauseDisabled,
341
+ "data-disabled": pauseDisabled,
342
+ style: buttonStyle(pauseDisabled),
343
+ children: "Pause"
344
+ }
345
+ )
346
+ ]
347
+ }
348
+ ),
349
+ /* @__PURE__ */ jsxs(
350
+ "div",
351
+ {
352
+ style: {
353
+ position: "absolute",
354
+ bottom: 16,
355
+ left: 0,
356
+ right: 0,
357
+ margin: "0 auto",
358
+ width: "fit-content",
359
+ maxWidth: "90%",
360
+ zIndex: 10,
361
+ backgroundColor: "rgba(0, 0, 0, 0.8)",
362
+ color: "white",
363
+ padding: 8,
364
+ borderRadius: 4,
365
+ fontSize: 14,
366
+ transition: "opacity 0.3s ease",
367
+ opacity: statusVisible ? 1 : 0
368
+ },
369
+ children: [
370
+ state.status,
371
+ state.error && /* @__PURE__ */ jsxs("span", { children: [
372
+ " Error: ",
373
+ state.error
374
+ ] })
375
+ ]
376
+ }
377
+ ),
378
+ /* @__PURE__ */ jsx(
379
+ "canvas",
380
+ {
381
+ ref: canvasRef,
382
+ width: canvasDimensions.width,
383
+ height: canvasDimensions.height,
384
+ style: {
385
+ display: "block",
386
+ border: "1px solid #ccc",
387
+ backgroundColor: "#f5f5f5"
388
+ }
389
+ }
390
+ )
391
+ ]
392
+ }
393
+ );
394
+ }
395
+ }
396
+ );
397
+ }
398
+ var AnniePlayer = Object.assign(AnniePlayerSimple, {
399
+ Root: AnniePlayerRoot,
400
+ Wrapper: AnniePlayerWrapper,
401
+ Canvas: AnniePlayerCanvas,
402
+ Controls: AnniePlayerControls,
403
+ LoadButton: AnniePlayerLoadButton,
404
+ PlayButton: AnniePlayerPlayButton,
405
+ PauseButton: AnniePlayerPauseButton,
406
+ Status: AnniePlayerStatus
407
+ });
408
+ export {
409
+ AnniePlayer
410
+ };
411
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/react/index.tsx"],"sourcesContent":["import { createContext, useContext, useEffect, useRef, useState } from \"react\";\nimport { AnniePlayerCore, type AnniePlayerState } from \"../core\";\n\n// ============ Context ============\n\ntype AnniePlayerContextValue = {\n state: AnniePlayerState;\n actions: {\n load: () => void;\n play: () => void;\n pause: () => void;\n };\n canvasRef: React.RefObject<HTMLCanvasElement | null>;\n canvasDimensions: { width: number; height: number };\n isHovering: boolean;\n setIsHovering: (v: boolean) => void;\n};\n\nconst AnniePlayerContext = createContext<AnniePlayerContextValue | null>(null);\n\nfunction useAnniePlayerContext() {\n const ctx = useContext(AnniePlayerContext);\n if (!ctx) {\n throw new Error(\n \"AnniePlayer compound components must be used within AnniePlayer.Root\",\n );\n }\n return ctx;\n}\n\n// ============ Root Component ============\n\nexport type AnniePlayerRootProps = {\n src: string;\n height: number;\n defaultWidth?: number;\n autoLoad?: boolean;\n autoPlay?: boolean;\n fps?: number;\n /** Style for the root container */\n style?: React.CSSProperties;\n /** Class name for the root container */\n className?: string;\n /** Children can be React nodes or a render function for headless usage */\n children?:\n | React.ReactNode\n | ((value: AnniePlayerContextValue) => React.ReactNode);\n};\n\nfunction AnniePlayerRoot({\n src,\n height,\n defaultWidth,\n autoLoad = false,\n autoPlay = false,\n fps = 30,\n style,\n className,\n children,\n}: AnniePlayerRootProps) {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const playerRef = useRef<AnniePlayerCore | null>(null);\n const [state, setState] = useState<AnniePlayerState>({\n status: \"Ready to load animation.\",\n error: null,\n isLoading: false,\n isPlaying: false,\n frameCount: 0,\n dimensions: null,\n });\n const [canvasDimensions, setCanvasDimensions] = useState({\n width: defaultWidth ?? height,\n height,\n });\n const [isHovering, setIsHovering] = useState(false);\n\n // Initialize the core player\n useEffect(() => {\n const player = new AnniePlayerCore({ src, fps, autoPlay });\n playerRef.current = player;\n\n const unsubscribeState = player.on(\"statechange\", (newState) => {\n setState(newState);\n });\n\n const unsubscribeLoad = player.on(\"load\", ({ dimensions }) => {\n setCanvasDimensions({\n width: (dimensions.width / dimensions.height) * height,\n height,\n });\n });\n\n return () => {\n unsubscribeState();\n unsubscribeLoad();\n player.destroy();\n };\n }, [src, fps, autoPlay, height]);\n\n // Attach canvas to player when ready\n useEffect(() => {\n const player = playerRef.current;\n const canvas = canvasRef.current;\n if (player && canvas) {\n player.attachCanvas(canvas);\n }\n }, [canvasDimensions]);\n\n // Auto-load if enabled\n useEffect(() => {\n if (autoLoad && playerRef.current) {\n playerRef.current.load();\n }\n }, [autoLoad]);\n\n const actions = {\n load: () => playerRef.current?.load(),\n play: () => playerRef.current?.play(),\n pause: () => playerRef.current?.pause(),\n };\n\n const value: AnniePlayerContextValue = {\n state,\n actions,\n canvasRef,\n canvasDimensions,\n isHovering,\n setIsHovering,\n };\n\n // Render function pattern for headless usage\n if (typeof children === \"function\") {\n return <>{children(value)}</>;\n }\n\n return (\n <AnniePlayerContext.Provider value={value}>\n <div\n style={{ display: \"inline-block\", width: \"fit-content\", ...style }}\n className={className}\n >\n {children}\n </div>\n </AnniePlayerContext.Provider>\n );\n}\n\n// ============ Wrapper Component ============\n\ntype AnniePlayerWrapperProps = {\n children: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n};\n\nfunction AnniePlayerWrapper({\n children,\n className,\n style,\n}: AnniePlayerWrapperProps) {\n const { isHovering, setIsHovering, state } = useAnniePlayerContext();\n return (\n <div\n style={{\n position: \"relative\",\n display: \"inline-block\",\n width: \"fit-content\",\n ...style,\n }}\n className={className}\n onMouseEnter={() => setIsHovering(true)}\n onMouseLeave={() => setIsHovering(false)}\n data-hovering={isHovering}\n data-playing={state.isPlaying}\n >\n {children}\n </div>\n );\n}\n\n// ============ Canvas Component ============\n\ntype AnniePlayerCanvasProps = {\n className?: string;\n style?: React.CSSProperties;\n};\n\nfunction AnniePlayerCanvas({ className, style }: AnniePlayerCanvasProps) {\n const { canvasRef, canvasDimensions } = useAnniePlayerContext();\n return (\n <canvas\n ref={canvasRef}\n width={canvasDimensions.width}\n height={canvasDimensions.height}\n className={className}\n style={{ display: \"block\", ...style }}\n />\n );\n}\n\n// ============ Controls Component ============\n\ntype AnniePlayerControlsProps = {\n children: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n};\n\nfunction AnniePlayerControls({\n children,\n className,\n style,\n}: AnniePlayerControlsProps) {\n const { state, isHovering } = useAnniePlayerContext();\n const visible = !state.isPlaying || isHovering;\n return (\n <div\n className={className}\n style={{\n transition: \"opacity 0.3s ease\",\n opacity: visible ? 1 : 0,\n ...style,\n }}\n >\n {children}\n </div>\n );\n}\n\n// ============ Button Components ============\n\ntype AnniePlayerLoadButtonProps = {\n children?: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n};\n\nfunction AnniePlayerLoadButton({\n children,\n className,\n style,\n}: AnniePlayerLoadButtonProps) {\n const { state, actions } = useAnniePlayerContext();\n const disabled = state.isLoading;\n return (\n <button\n onClick={actions.load}\n disabled={disabled}\n data-disabled={disabled}\n className={className}\n style={style}\n >\n {children ?? (state.isLoading ? \"Loading...\" : \"Load\")}\n </button>\n );\n}\n\ntype AnniePlayerPlayButtonProps = {\n children?: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n};\n\nfunction AnniePlayerPlayButton({\n children,\n className,\n style,\n}: AnniePlayerPlayButtonProps) {\n const { state, actions } = useAnniePlayerContext();\n const disabled = state.isLoading || state.isPlaying || state.frameCount === 0;\n return (\n <button\n onClick={actions.play}\n disabled={disabled}\n data-disabled={disabled}\n className={className}\n style={style}\n >\n {children ?? \"Play\"}\n </button>\n );\n}\n\ntype AnniePlayerPauseButtonProps = {\n children?: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n};\n\nfunction AnniePlayerPauseButton({\n children,\n className,\n style,\n}: AnniePlayerPauseButtonProps) {\n const { state, actions } = useAnniePlayerContext();\n const disabled = !state.isPlaying;\n return (\n <button\n onClick={actions.pause}\n disabled={disabled}\n data-disabled={disabled}\n className={className}\n style={style}\n >\n {children ?? \"Pause\"}\n </button>\n );\n}\n\n// ============ Status Component ============\n\ntype AnniePlayerStatusProps = {\n children?: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n};\n\nfunction AnniePlayerStatus({\n children,\n className,\n style,\n}: AnniePlayerStatusProps) {\n const { state, isHovering } = useAnniePlayerContext();\n const visible = isHovering;\n return (\n <div\n className={className}\n style={{\n transition: \"opacity 0.3s ease\",\n opacity: visible ? 1 : 0,\n ...style,\n }}\n >\n {children ?? (\n <>\n {state.status}\n {state.error && <span> Error: {state.error}</span>}\n </>\n )}\n </div>\n );\n}\n\n// ============ Simple Component ============\n\nexport type AnniePlayerProps = {\n src: string;\n height: number;\n defaultWidth?: number;\n autoLoad?: boolean;\n autoPlay?: boolean;\n fps?: number;\n /** Style for the root container */\n style?: React.CSSProperties;\n /** Class name for the root container */\n className?: string;\n};\n\nfunction AnniePlayerSimple({\n src,\n height,\n defaultWidth,\n autoLoad = false,\n autoPlay = false,\n fps = 30,\n style,\n className,\n}: AnniePlayerProps) {\n return (\n <AnniePlayerRoot\n src={src}\n height={height}\n defaultWidth={defaultWidth}\n autoLoad={autoLoad}\n autoPlay={autoPlay}\n fps={fps}\n style={style}\n className={className}\n >\n {({\n state,\n actions,\n canvasRef,\n canvasDimensions,\n isHovering,\n setIsHovering,\n }) => {\n const loadDisabled = state.isLoading;\n const playDisabled =\n state.isLoading || state.isPlaying || state.frameCount === 0;\n const pauseDisabled = !state.isPlaying;\n const buttonContainerVisible = !state.isPlaying || isHovering;\n const statusVisible = isHovering;\n\n const buttonStyle = (disabled: boolean): React.CSSProperties => ({\n backgroundColor: \"#222\",\n color: \"white\",\n border: \"none\",\n borderRadius: 4,\n padding: \"0.5rem 1rem\",\n cursor: \"pointer\",\n margin: \"0 4px\",\n fontSize: \"14px\",\n fontWeight: 500,\n opacity: disabled ? 0.6 : 1,\n });\n\n return (\n <div\n style={{\n position: \"relative\",\n display: \"inline-block\",\n width: \"fit-content\",\n }}\n data-hovering={isHovering}\n data-playing={state.isPlaying}\n onMouseEnter={() => setIsHovering(true)}\n onMouseLeave={() => setIsHovering(false)}\n >\n <div\n style={{\n position: \"absolute\",\n width: \"90%\",\n top: 16,\n left: \"50%\",\n transform: \"translateX(-50%)\",\n display: \"flex\",\n flexWrap: \"wrap\",\n justifyContent: \"center\",\n gap: 4,\n zIndex: 10,\n transition: \"opacity 0.3s ease\",\n opacity: buttonContainerVisible ? 1 : 0,\n }}\n >\n <button\n onClick={actions.load}\n disabled={loadDisabled}\n data-disabled={loadDisabled}\n style={buttonStyle(loadDisabled)}\n >\n {state.isLoading ? \"Loading...\" : \"Load\"}\n </button>\n <button\n onClick={actions.play}\n disabled={playDisabled}\n data-disabled={playDisabled}\n style={buttonStyle(playDisabled)}\n >\n Play\n </button>\n <button\n onClick={actions.pause}\n disabled={pauseDisabled}\n data-disabled={pauseDisabled}\n style={buttonStyle(pauseDisabled)}\n >\n Pause\n </button>\n </div>\n\n <div\n style={{\n position: \"absolute\",\n bottom: 16,\n left: 0,\n right: 0,\n margin: \"0 auto\",\n width: \"fit-content\",\n maxWidth: \"90%\",\n zIndex: 10,\n backgroundColor: \"rgba(0, 0, 0, 0.8)\",\n color: \"white\",\n padding: 8,\n borderRadius: 4,\n fontSize: 14,\n transition: \"opacity 0.3s ease\",\n opacity: statusVisible ? 1 : 0,\n }}\n >\n {state.status}\n {state.error && <span> Error: {state.error}</span>}\n </div>\n\n <canvas\n ref={canvasRef}\n width={canvasDimensions.width}\n height={canvasDimensions.height}\n style={{\n display: \"block\",\n border: \"1px solid #ccc\",\n backgroundColor: \"#f5f5f5\",\n }}\n />\n </div>\n );\n }}\n </AnniePlayerRoot>\n );\n}\n\n/**\n * AnniePlayer - Play TAR archives of PNG/JPEG frames in the browser.\n *\n * Usage:\n * - Simple: `<AnniePlayer src={url} height={540} />`\n * - Compound: `<AnniePlayer.Root src={url} height={540}>...</AnniePlayer.Root>`\n * - Headless: `<AnniePlayer.Root src={url} height={540}>{({ state, actions }) => ...}</AnniePlayer.Root>`\n */\nexport const AnniePlayer = Object.assign(AnniePlayerSimple, {\n Root: AnniePlayerRoot,\n Wrapper: AnniePlayerWrapper,\n Canvas: AnniePlayerCanvas,\n Controls: AnniePlayerControls,\n LoadButton: AnniePlayerLoadButton,\n PlayButton: AnniePlayerPlayButton,\n PauseButton: AnniePlayerPauseButton,\n Status: AnniePlayerStatus,\n});\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@effing/annie-player",
3
+ "version": "0.1.0",
4
+ "description": "Annie animation player that runs in the browser",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "import": "./dist/index.js"
10
+ },
11
+ "./react": {
12
+ "types": "./dist/react/index.d.ts",
13
+ "import": "./dist/react/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "dependencies": {
20
+ "@andrewbranch/untar.js": "^1.0.3"
21
+ },
22
+ "devDependencies": {
23
+ "@types/react": "^19.0.0",
24
+ "tsup": "^8.0.0",
25
+ "typescript": "^5.9.3"
26
+ },
27
+ "peerDependencies": {
28
+ "react": "^18.0.0 || ^19.0.0"
29
+ },
30
+ "peerDependenciesMeta": {
31
+ "react": {
32
+ "optional": true
33
+ }
34
+ },
35
+ "keywords": [
36
+ "animation",
37
+ "player",
38
+ "annie",
39
+ "react"
40
+ ],
41
+ "license": "O'Saasy",
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "scripts": {
46
+ "build": "tsup",
47
+ "typecheck": "tsc --noEmit"
48
+ }
49
+ }