canvasengine 2.0.0-beta.5 → 2.0.0-beta.51

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.
Files changed (172) hide show
  1. package/dist/components/Button.d.ts +185 -0
  2. package/dist/components/Button.d.ts.map +1 -0
  3. package/dist/components/Canvas.d.ts +17 -0
  4. package/dist/components/Canvas.d.ts.map +1 -0
  5. package/dist/components/Container.d.ts +86 -0
  6. package/dist/components/Container.d.ts.map +1 -0
  7. package/dist/components/DOMContainer.d.ts +98 -0
  8. package/dist/components/DOMContainer.d.ts.map +1 -0
  9. package/dist/components/DOMElement.d.ts +54 -0
  10. package/dist/components/DOMElement.d.ts.map +1 -0
  11. package/dist/components/DOMSprite.d.ts +127 -0
  12. package/dist/components/DOMSprite.d.ts.map +1 -0
  13. package/dist/components/DisplayObject.d.ts +94 -0
  14. package/dist/components/DisplayObject.d.ts.map +1 -0
  15. package/dist/components/FocusContainer.d.ts +129 -0
  16. package/dist/components/FocusContainer.d.ts.map +1 -0
  17. package/dist/components/Graphic.d.ts +64 -0
  18. package/dist/components/Graphic.d.ts.map +1 -0
  19. package/dist/components/Joystick.d.ts +36 -0
  20. package/dist/components/Joystick.d.ts.map +1 -0
  21. package/dist/components/Mesh.d.ts +208 -0
  22. package/dist/components/Mesh.d.ts.map +1 -0
  23. package/dist/components/NineSliceSprite.d.ts +16 -0
  24. package/dist/components/NineSliceSprite.d.ts.map +1 -0
  25. package/dist/components/ParticleEmitter.d.ts +4 -0
  26. package/dist/components/ParticleEmitter.d.ts.map +1 -0
  27. package/dist/components/Scene.d.ts +2 -0
  28. package/dist/components/Scene.d.ts.map +1 -0
  29. package/dist/components/Sprite.d.ts +242 -0
  30. package/dist/components/Sprite.d.ts.map +1 -0
  31. package/dist/components/Text.d.ts +25 -0
  32. package/dist/components/Text.d.ts.map +1 -0
  33. package/dist/components/TilingSprite.d.ts +17 -0
  34. package/dist/components/TilingSprite.d.ts.map +1 -0
  35. package/dist/components/Video.d.ts +14 -0
  36. package/dist/components/Video.d.ts.map +1 -0
  37. package/dist/components/Viewport.d.ts +121 -0
  38. package/dist/components/Viewport.d.ts.map +1 -0
  39. package/dist/components/index.d.ts +20 -0
  40. package/dist/components/index.d.ts.map +1 -0
  41. package/dist/components/types/DisplayObject.d.ts +106 -0
  42. package/dist/components/types/DisplayObject.d.ts.map +1 -0
  43. package/dist/components/types/MouseEvent.d.ts +4 -0
  44. package/dist/components/types/MouseEvent.d.ts.map +1 -0
  45. package/dist/components/types/Spritesheet.d.ts +248 -0
  46. package/dist/components/types/Spritesheet.d.ts.map +1 -0
  47. package/dist/components/types/index.d.ts +4 -0
  48. package/dist/components/types/index.d.ts.map +1 -0
  49. package/dist/directives/Controls.d.ts +112 -0
  50. package/dist/directives/Controls.d.ts.map +1 -0
  51. package/dist/directives/ControlsBase.d.ts +199 -0
  52. package/dist/directives/ControlsBase.d.ts.map +1 -0
  53. package/dist/directives/Drag.d.ts +69 -0
  54. package/dist/directives/Drag.d.ts.map +1 -0
  55. package/dist/directives/Flash.d.ts +116 -0
  56. package/dist/directives/Flash.d.ts.map +1 -0
  57. package/dist/directives/FocusNavigation.d.ts +52 -0
  58. package/dist/directives/FocusNavigation.d.ts.map +1 -0
  59. package/dist/directives/GamepadControls.d.ts +224 -0
  60. package/dist/directives/GamepadControls.d.ts.map +1 -0
  61. package/dist/directives/JoystickControls.d.ts +171 -0
  62. package/dist/directives/JoystickControls.d.ts.map +1 -0
  63. package/dist/directives/KeyboardControls.d.ts +219 -0
  64. package/dist/directives/KeyboardControls.d.ts.map +1 -0
  65. package/dist/directives/Scheduler.d.ts +35 -0
  66. package/dist/directives/Scheduler.d.ts.map +1 -0
  67. package/dist/directives/Shake.d.ts +98 -0
  68. package/dist/directives/Shake.d.ts.map +1 -0
  69. package/dist/directives/Sound.d.ts +25 -0
  70. package/dist/directives/Sound.d.ts.map +1 -0
  71. package/dist/directives/Transition.d.ts +10 -0
  72. package/dist/directives/Transition.d.ts.map +1 -0
  73. package/dist/directives/ViewportCull.d.ts +11 -0
  74. package/dist/directives/ViewportCull.d.ts.map +1 -0
  75. package/dist/directives/ViewportFollow.d.ts +18 -0
  76. package/dist/directives/ViewportFollow.d.ts.map +1 -0
  77. package/dist/directives/index.d.ts +13 -0
  78. package/dist/directives/index.d.ts.map +1 -0
  79. package/dist/engine/FocusManager.d.ts +174 -0
  80. package/dist/engine/FocusManager.d.ts.map +1 -0
  81. package/dist/engine/animation.d.ts +72 -0
  82. package/dist/engine/animation.d.ts.map +1 -0
  83. package/dist/engine/bootstrap.d.ts +48 -0
  84. package/dist/engine/bootstrap.d.ts.map +1 -0
  85. package/dist/engine/directive.d.ts +13 -0
  86. package/dist/engine/directive.d.ts.map +1 -0
  87. package/dist/engine/reactive.d.ts +134 -0
  88. package/dist/engine/reactive.d.ts.map +1 -0
  89. package/dist/engine/signal.d.ts +71 -0
  90. package/dist/engine/signal.d.ts.map +1 -0
  91. package/dist/engine/trigger.d.ts +54 -0
  92. package/dist/engine/trigger.d.ts.map +1 -0
  93. package/dist/engine/utils.d.ts +89 -0
  94. package/dist/engine/utils.d.ts.map +1 -0
  95. package/dist/hooks/addContext.d.ts +2 -0
  96. package/dist/hooks/addContext.d.ts.map +1 -0
  97. package/dist/hooks/useFocus.d.ts +60 -0
  98. package/dist/hooks/useFocus.d.ts.map +1 -0
  99. package/dist/hooks/useProps.d.ts +42 -0
  100. package/dist/hooks/useProps.d.ts.map +1 -0
  101. package/dist/hooks/useRef.d.ts +4 -0
  102. package/dist/hooks/useRef.d.ts.map +1 -0
  103. package/dist/index-DaGekQUW.js +2218 -0
  104. package/dist/index-DaGekQUW.js.map +1 -0
  105. package/dist/index.d.ts +19 -1099
  106. package/dist/index.d.ts.map +1 -0
  107. package/dist/index.global.js +5 -0
  108. package/dist/index.global.js.map +1 -0
  109. package/dist/index.js +11749 -2901
  110. package/dist/index.js.map +1 -1
  111. package/dist/utils/Ease.d.ts +17 -0
  112. package/dist/utils/Ease.d.ts.map +1 -0
  113. package/dist/utils/GlobalAssetLoader.d.ts +141 -0
  114. package/dist/utils/GlobalAssetLoader.d.ts.map +1 -0
  115. package/dist/utils/RadialGradient.d.ts +57 -0
  116. package/dist/utils/RadialGradient.d.ts.map +1 -0
  117. package/dist/utils/functions.d.ts +2 -0
  118. package/dist/utils/functions.d.ts.map +1 -0
  119. package/dist/utils/tabindex.d.ts +16 -0
  120. package/dist/utils/tabindex.d.ts.map +1 -0
  121. package/package.json +13 -7
  122. package/src/components/Button.ts +399 -0
  123. package/src/components/Canvas.ts +62 -46
  124. package/src/components/Container.ts +21 -2
  125. package/src/components/DOMContainer.ts +379 -0
  126. package/src/components/DOMElement.ts +556 -0
  127. package/src/components/DOMSprite.ts +1040 -0
  128. package/src/components/DisplayObject.ts +392 -201
  129. package/src/components/FocusContainer.ts +368 -0
  130. package/src/components/Graphic.ts +227 -66
  131. package/src/components/Joystick.ts +363 -0
  132. package/src/components/Mesh.ts +222 -0
  133. package/src/components/NineSliceSprite.ts +4 -1
  134. package/src/components/ParticleEmitter.ts +12 -8
  135. package/src/components/Sprite.ts +297 -31
  136. package/src/components/Text.ts +125 -18
  137. package/src/components/Video.ts +2 -2
  138. package/src/components/Viewport.ts +118 -63
  139. package/src/components/index.ts +9 -2
  140. package/src/components/types/DisplayObject.ts +41 -4
  141. package/src/components/types/Spritesheet.ts +0 -118
  142. package/src/directives/Controls.ts +254 -0
  143. package/src/directives/ControlsBase.ts +267 -0
  144. package/src/directives/Drag.ts +357 -52
  145. package/src/directives/Flash.ts +419 -0
  146. package/src/directives/FocusNavigation.ts +113 -0
  147. package/src/directives/GamepadControls.ts +537 -0
  148. package/src/directives/JoystickControls.ts +396 -0
  149. package/src/directives/KeyboardControls.ts +85 -430
  150. package/src/directives/Scheduler.ts +12 -4
  151. package/src/directives/Shake.ts +298 -0
  152. package/src/directives/Sound.ts +94 -31
  153. package/src/directives/ViewportFollow.ts +40 -9
  154. package/src/directives/index.ts +12 -6
  155. package/src/engine/FocusManager.ts +510 -0
  156. package/src/engine/animation.ts +175 -21
  157. package/src/engine/bootstrap.ts +93 -3
  158. package/src/engine/directive.ts +4 -4
  159. package/src/engine/reactive.ts +901 -161
  160. package/src/engine/signal.ts +113 -25
  161. package/src/engine/trigger.ts +34 -7
  162. package/src/engine/utils.ts +19 -3
  163. package/src/hooks/useFocus.ts +91 -0
  164. package/src/hooks/useProps.ts +1 -1
  165. package/src/index.ts +8 -2
  166. package/src/types/pixi-cull.d.ts +7 -0
  167. package/src/utils/GlobalAssetLoader.ts +257 -0
  168. package/src/utils/functions.ts +7 -0
  169. package/src/utils/tabindex.ts +70 -0
  170. package/testing/index.ts +35 -4
  171. package/tsconfig.json +18 -0
  172. package/vite.config.ts +39 -0
@@ -0,0 +1,1040 @@
1
+ import { isSignal, Signal } from "@signe/reactive";
2
+ import { Subscription } from "rxjs";
3
+ import { createComponent, Element, registerComponent } from "../engine/reactive";
4
+ import { ComponentFunction } from "../engine/signal";
5
+ import { arrayEquals, fps2ms, isBrowser, isFunction, preciseNow } from "../engine/utils";
6
+ import { DisplayObjectProps } from "./types/DisplayObject";
7
+ import { CanvasDOMElement, DOMElementProps } from "./DOMElement";
8
+ import { OnHook } from "./DisplayObject";
9
+ import { Tick } from "../directives/Scheduler";
10
+ import {
11
+ AnimationFrames,
12
+ FrameOptions,
13
+ SpritesheetOptions,
14
+ TextureOptions,
15
+ } from "./types/Spritesheet";
16
+
17
+ export interface DOMSpriteFrame {
18
+ x: number;
19
+ y: number;
20
+ width: number;
21
+ height: number;
22
+ }
23
+
24
+ export interface DOMSpriteProps extends DOMElementProps {
25
+ image?: string;
26
+ rectangle?: DOMSpriteFrame | { value?: DOMSpriteFrame };
27
+ frames?: DOMSpriteFrame[];
28
+ frameIndex?: number;
29
+ fps?: number;
30
+ playing?: boolean;
31
+ loop?: boolean;
32
+ sheet?: {
33
+ definition?:
34
+ | DOMSpriteSheetDefinition
35
+ | { value?: DOMSpriteSheetDefinition }
36
+ | Signal<DOMSpriteSheetDefinition | undefined>
37
+ | Promise<DOMSpriteSheetDefinition>;
38
+ playing?: string;
39
+ params?: any;
40
+ onFinish?: () => void;
41
+ };
42
+ element?: "div" | "img";
43
+ class?: any;
44
+ style?: any;
45
+ attrs?: Record<string, any> & {
46
+ class?:
47
+ | string
48
+ | string[]
49
+ | Record<string, boolean>
50
+ | { items?: string[] }
51
+ | { value?: string | string[] | Record<string, boolean> };
52
+ style?:
53
+ | string
54
+ | Record<string, string | number>
55
+ | { value?: string | Record<string, string | number> };
56
+ };
57
+ onBeforeDestroy?: OnHook;
58
+ context?: {
59
+ tick?: Signal<Tick | null>;
60
+ };
61
+ }
62
+
63
+ const EVENTS = [
64
+ "click",
65
+ "mouseover",
66
+ "mouseout",
67
+ "mouseenter",
68
+ "mouseleave",
69
+ "mousemove",
70
+ "mouseup",
71
+ "mousedown",
72
+ "touchstart",
73
+ "touchend",
74
+ "touchmove",
75
+ "touchcancel",
76
+ "wheel",
77
+ "scroll",
78
+ "resize",
79
+ "focus",
80
+ "blur",
81
+ "change",
82
+ "input",
83
+ "submit",
84
+ "reset",
85
+ "keydown",
86
+ "keyup",
87
+ "keypress",
88
+ "contextmenu",
89
+ "drag",
90
+ "dragend",
91
+ "dragenter",
92
+ "dragleave",
93
+ "dragover",
94
+ "drop",
95
+ "dragstart",
96
+ "select",
97
+ "selectstart",
98
+ "selectend",
99
+ "selectall",
100
+ "selectnone",
101
+ ];
102
+
103
+ type DOMSpriteTextureOptionsMerging = TextureOptions & {
104
+ spriteWidth: number;
105
+ spriteHeight: number;
106
+ image?: string;
107
+ };
108
+
109
+ type DOMSpriteFrameOptions = FrameOptions & DOMSpriteFrame;
110
+
111
+ type DOMSpriteAnimationData = {
112
+ frames: DOMSpriteFrameOptions[];
113
+ animations: AnimationFrames;
114
+ params: any[];
115
+ data: DOMSpriteTextureOptionsMerging;
116
+ name: string;
117
+ };
118
+
119
+ type DOMSpriteSheetDefinition = SpritesheetOptions & {
120
+ image?: string;
121
+ };
122
+
123
+ export class CanvasDOMSprite extends CanvasDOMElement {
124
+ private frameIndex = 0;
125
+ private frames: DOMSpriteFrame[] = [];
126
+ private rectangle?: DOMSpriteFrame;
127
+ private image?: string;
128
+ private fps = 120;
129
+ private loop = true;
130
+ private playing = true;
131
+ private hasExternalFrameIndex = false;
132
+ private elapsed = 0;
133
+ private tickSignal?: Signal<Tick | null>;
134
+ private tickSubscription?: Subscription;
135
+ private sheetSubscriptions: Subscription[] = [];
136
+ private sheetDefinition?: DOMSpriteSheetDefinition;
137
+ private sheetAnimations: Map<string, DOMSpriteAnimationData> = new Map();
138
+ private sheetCurrentAnimation?: DOMSpriteAnimationData;
139
+ private sheetCurrentName?: string;
140
+ private sheetParams: any = {};
141
+ private sheetTime = 0;
142
+ private sheetFrameIndex = 0;
143
+ private sheetFinished = false;
144
+ private sheetLoadToken = 0;
145
+ private sheetOnFinish?: () => void;
146
+ private rafId?: number;
147
+ private lastRafTimestamp?: number;
148
+ private lastTickTimestamp?: number;
149
+ private renderElementType: "div" | "img" = "div";
150
+ private wrapperElementType: "div" | "img" = "div";
151
+ private isAnimating = false;
152
+ private playingSubscription?: Subscription;
153
+ private playingSignal?: Signal<boolean>;
154
+ private explicitWidth?: string;
155
+ private explicitHeight?: string;
156
+ private frameWidth = 0;
157
+ private frameHeight = 0;
158
+ private fitMode?: string;
159
+ private renderElement?: HTMLElement;
160
+ private isContained = false;
161
+
162
+ onInit(props: DOMElementProps) {
163
+ const spriteProps = props as DOMSpriteProps;
164
+ const hasSheet = spriteProps.sheet !== undefined;
165
+ const defaultElement: "div" | "img" = !hasSheet && spriteProps.image ? "img" : "div";
166
+ this.renderElementType = spriteProps.element ?? defaultElement;
167
+ const resolvedFit = this.resolveValue(spriteProps.objectFit);
168
+ this.fitMode = resolvedFit ?? undefined;
169
+ this.wrapperElementType =
170
+ this.fitMode === "contain" && this.renderElementType === "img"
171
+ ? "div"
172
+ : this.renderElementType;
173
+ const nextProps = this.mergeEventAttrs({ ...spriteProps, element: this.wrapperElementType });
174
+ this.tickSignal = nextProps.context?.tick;
175
+ this.applyProps(nextProps);
176
+ super.onInit(nextProps as any);
177
+ this.syncRenderElement();
178
+ this.applyDisplayProps(nextProps);
179
+ this.render();
180
+ this.updateAnimationLoop();
181
+ }
182
+
183
+ onMount(context: Element<CanvasDOMElement>) {
184
+ this.tickSignal = context.props.context?.tick;
185
+ super.onMount(context);
186
+ this.bindPlayingSignal(context);
187
+ this.bindSheetParams(context);
188
+ this.updateAnimationLoop();
189
+ }
190
+
191
+ onUpdate(props: DOMElementProps) {
192
+ const nextProps = this.mergeEventAttrs(props as DOMSpriteProps);
193
+ super.onUpdate(nextProps as any);
194
+ this.applyProps(nextProps);
195
+ this.syncRenderElement();
196
+ this.applyDisplayProps(nextProps);
197
+ this.render();
198
+ this.updateAnimationLoop();
199
+ }
200
+
201
+ async onDestroy(
202
+ parent: Element<CanvasDOMElement>,
203
+ afterDestroy: () => void
204
+ ): Promise<void> {
205
+ if (this.playingSubscription) {
206
+ this.playingSubscription.unsubscribe();
207
+ this.playingSubscription = undefined;
208
+ }
209
+ if (this.sheetSubscriptions.length > 0) {
210
+ this.sheetSubscriptions.forEach((sub) => sub.unsubscribe());
211
+ this.sheetSubscriptions = [];
212
+ }
213
+ this.stopAnimationLoop();
214
+ await super.onDestroy(parent, afterDestroy);
215
+ }
216
+
217
+ private resolveRectangle(
218
+ rectangle?: DOMSpriteFrame | { value?: DOMSpriteFrame } | Signal<DOMSpriteFrame | undefined>
219
+ ): DOMSpriteFrame | undefined {
220
+ if (!rectangle) return undefined;
221
+ const signalResolved = isSignal(rectangle as any)
222
+ ? (rectangle as Signal<DOMSpriteFrame | undefined>)()
223
+ : rectangle;
224
+ if (!signalResolved) return undefined;
225
+ const resolved = (signalResolved as any).value ?? signalResolved;
226
+ if (!resolved) return undefined;
227
+ return resolved as DOMSpriteFrame;
228
+ }
229
+
230
+ private resolveSheetDefinition(
231
+ definition?:
232
+ | DOMSpriteSheetDefinition
233
+ | { value?: DOMSpriteSheetDefinition }
234
+ | Signal<DOMSpriteSheetDefinition | undefined>
235
+ | Promise<DOMSpriteSheetDefinition>
236
+ ): DOMSpriteSheetDefinition | Promise<DOMSpriteSheetDefinition | undefined> | undefined {
237
+ if (!definition) return undefined;
238
+ const signalResolved = isSignal(definition as any)
239
+ ? (definition as Signal<DOMSpriteSheetDefinition | undefined>)()
240
+ : definition;
241
+ if (!signalResolved) return undefined;
242
+ return (signalResolved as any).value ?? signalResolved;
243
+ }
244
+
245
+ private async detectImageDimensions(
246
+ imagePath: string
247
+ ): Promise<{ width: number; height: number }> {
248
+ if (!isBrowser() || !imagePath) {
249
+ return { width: 0, height: 0 };
250
+ }
251
+ return new Promise((resolve, reject) => {
252
+ const img = new Image();
253
+ img.onload = () => resolve({ width: img.naturalWidth, height: img.naturalHeight });
254
+ img.onerror = () => reject(new Error(`Failed to load image: ${imagePath}`));
255
+ img.src = imagePath;
256
+ });
257
+ }
258
+
259
+ private async createSheetAnimations(definition: DOMSpriteSheetDefinition) {
260
+ this.sheetAnimations.clear();
261
+ const { textures } = definition;
262
+ if (!textures) return;
263
+
264
+ const parentProps: (keyof TextureOptions)[] = [
265
+ "width",
266
+ "height",
267
+ "framesHeight",
268
+ "framesWidth",
269
+ "rectWidth",
270
+ "rectHeight",
271
+ "offset",
272
+ ];
273
+
274
+ for (const animationName in textures) {
275
+ const baseOptions = parentProps.reduce(
276
+ (prev, val) => ({ ...prev, [val]: (definition as any)[val] }),
277
+ {}
278
+ );
279
+ const optionsTextures: DOMSpriteTextureOptionsMerging = {
280
+ ...baseOptions,
281
+ ...textures[animationName],
282
+ spriteWidth: 0,
283
+ spriteHeight: 0,
284
+ };
285
+ optionsTextures.image =
286
+ (textures[animationName] as any).image ?? definition.image;
287
+
288
+ const {
289
+ rectWidth,
290
+ rectHeight,
291
+ framesWidth = 1,
292
+ framesHeight = 1,
293
+ image,
294
+ } = optionsTextures;
295
+
296
+ let width = optionsTextures.width || 0;
297
+ let height = optionsTextures.height || 0;
298
+
299
+ if (image && ((!width || width <= 0) || (!height || height <= 0))) {
300
+ const dimensions = await this.detectImageDimensions(image);
301
+ if (!width || width <= 0) {
302
+ width = dimensions.width;
303
+ }
304
+ if (!height || height <= 0) {
305
+ height = dimensions.height;
306
+ }
307
+ }
308
+
309
+ if (!width || !height || !framesWidth || !framesHeight) {
310
+ continue;
311
+ }
312
+
313
+ optionsTextures.width = width;
314
+ optionsTextures.height = height;
315
+ optionsTextures.spriteWidth = rectWidth ? rectWidth : width / framesWidth;
316
+ optionsTextures.spriteHeight = rectHeight ? rectHeight : height / framesHeight;
317
+
318
+ this.sheetAnimations.set(animationName, {
319
+ frames: [],
320
+ name: animationName,
321
+ animations: textures[animationName].animations,
322
+ params: [],
323
+ data: optionsTextures,
324
+ });
325
+ }
326
+ }
327
+
328
+ private async setSheetDefinition(
329
+ definition?: DOMSpriteSheetDefinition | Promise<DOMSpriteSheetDefinition>
330
+ ) {
331
+ const token = ++this.sheetLoadToken;
332
+ if (!definition) {
333
+ this.sheetDefinition = undefined;
334
+ this.sheetAnimations.clear();
335
+ this.sheetCurrentAnimation = undefined;
336
+ this.sheetCurrentName = undefined;
337
+ return;
338
+ }
339
+
340
+ const resolved = await definition;
341
+ if (token !== this.sheetLoadToken) return;
342
+ if (!resolved) {
343
+ this.sheetDefinition = undefined;
344
+ this.sheetAnimations.clear();
345
+ this.sheetCurrentAnimation = undefined;
346
+ this.sheetCurrentName = undefined;
347
+ return;
348
+ }
349
+ this.sheetDefinition = resolved;
350
+ if (resolved.image) {
351
+ this.image = resolved.image;
352
+ }
353
+
354
+ await this.createSheetAnimations(resolved);
355
+
356
+ const textureKeys = resolved.textures ? Object.keys(resolved.textures) : [];
357
+ const fallbackName =
358
+ this.sheetCurrentName ||
359
+ (textureKeys.includes("stand") ? "stand" : textureKeys[0]) ||
360
+ undefined;
361
+ if (fallbackName) {
362
+ this.playSheet(fallbackName, [this.sheetParams]);
363
+ }
364
+ this.render();
365
+ this.updateAnimationLoop();
366
+ }
367
+
368
+ private playSheet(name: string, params: any[] = []) {
369
+ const animParams = this.sheetCurrentAnimation?.params;
370
+ if (this.sheetCurrentAnimation && this.sheetCurrentAnimation.name === name) {
371
+ if (arrayEquals(params, animParams || [])) return;
372
+ }
373
+
374
+ const animation = this.sheetAnimations.get(name);
375
+ if (!animation) {
376
+ throw new Error(
377
+ `Impossible to play the ${name} animation because it doesn't exist on the spritesheet`
378
+ );
379
+ }
380
+
381
+ const cloneParams = structuredClone(params);
382
+ let animations: any = animation.animations;
383
+ animations = isFunction(animations) ? (animations as Function)(...cloneParams) : animations;
384
+
385
+ animation.frames = [];
386
+ animation.params = cloneParams;
387
+ this.sheetCurrentAnimation = animation;
388
+ this.sheetCurrentName = name;
389
+ this.sheetTime = 0;
390
+ this.sheetFrameIndex = 0;
391
+ this.sheetFinished = false;
392
+
393
+ const data = animation.data;
394
+ const spriteWidth = data.spriteWidth;
395
+ const spriteHeight = data.spriteHeight;
396
+ const offsetX = data.offset?.x ?? 0;
397
+ const offsetY = data.offset?.y ?? 0;
398
+
399
+ for (const container of animations as FrameOptions[][]) {
400
+ for (const frame of container) {
401
+ const frameX = frame.frameX ?? 0;
402
+ const frameY = frame.frameY ?? 0;
403
+ animation.frames.push({
404
+ ...frame,
405
+ x: frameX * spriteWidth + offsetX,
406
+ y: frameY * spriteHeight + offsetY,
407
+ width: spriteWidth,
408
+ height: spriteHeight,
409
+ });
410
+ }
411
+ }
412
+
413
+ this.render();
414
+ this.updateAnimationLoop();
415
+ }
416
+
417
+ private getCurrentSheetFrame(): DOMSpriteFrame | null {
418
+ if (!this.sheetCurrentAnimation || this.sheetCurrentAnimation.frames.length === 0) {
419
+ return null;
420
+ }
421
+ return this.sheetCurrentAnimation.frames[this.sheetFrameIndex] ?? null;
422
+ }
423
+
424
+ private advanceSheet(tick: Tick) {
425
+ if (!this.playing || !this.sheetCurrentAnimation) return;
426
+
427
+ const frames = this.sheetCurrentAnimation.frames;
428
+ if (frames.length <= 1) return;
429
+
430
+ const baseFps = this.fps > 0 ? this.fps : 60;
431
+ let deltaRatio = 0;
432
+ if (tick.deltaTime) {
433
+ deltaRatio = tick.deltaTime / fps2ms(baseFps);
434
+ } else if (typeof tick.deltaRatio === "number") {
435
+ deltaRatio = tick.deltaRatio * (baseFps / 60);
436
+ }
437
+
438
+ const nextFrame = frames[this.sheetFrameIndex + 1];
439
+ if (!nextFrame) {
440
+ if (this.loop) {
441
+ this.sheetTime = 0;
442
+ this.sheetFrameIndex = 0;
443
+ this.applyFrame(frames[0]);
444
+ return;
445
+ } else if (!this.sheetFinished) {
446
+ this.sheetFinished = true;
447
+ if (this.sheetOnFinish) this.sheetOnFinish();
448
+ }
449
+ this.applyFrame(frames[frames.length - 1]);
450
+ return;
451
+ }
452
+
453
+ this.sheetTime += deltaRatio || 0;
454
+ if (this.sheetTime >= nextFrame.time) {
455
+ this.sheetFrameIndex += 1;
456
+ }
457
+ this.applyFrame(frames[this.sheetFrameIndex]);
458
+ }
459
+
460
+ private mergeEventAttrs(props: DOMSpriteProps): DOMSpriteProps {
461
+ let merged = props.attrs ? { ...props.attrs } : undefined;
462
+ for (const event of EVENTS) {
463
+ const handler = (props as any)[event];
464
+ if (handler && !merged?.[event]) {
465
+ if (!merged) merged = {};
466
+ merged[event] = handler;
467
+ }
468
+ }
469
+ if (props.class !== undefined) {
470
+ if (!merged) merged = {};
471
+ if (merged.class) {
472
+ merged.class = [props.class, merged.class];
473
+ } else {
474
+ merged.class = props.class;
475
+ }
476
+ }
477
+ if (props.style !== undefined) {
478
+ if (!merged) merged = {};
479
+ if (
480
+ typeof merged.style === "object"
481
+ && merged.style !== null
482
+ && typeof props.style === "object"
483
+ && props.style !== null
484
+ ) {
485
+ merged.style = { ...merged.style, ...props.style };
486
+ } else if (merged.style === undefined) {
487
+ merged.style = props.style;
488
+ } else if (typeof merged.style === "string" && typeof props.style === "string") {
489
+ merged.style = `${merged.style}; ${props.style}`;
490
+ } else {
491
+ merged.style = props.style;
492
+ }
493
+ }
494
+ if (!merged) return props;
495
+ return { ...props, attrs: merged };
496
+ }
497
+
498
+ private applyProps(props: DOMSpriteProps) {
499
+ if (props.image !== undefined) {
500
+ this.image = isSignal(props.image as any) ? (props.image as any)() : props.image;
501
+ }
502
+ if (props.rectangle !== undefined) {
503
+ this.rectangle = this.resolveRectangle(props.rectangle);
504
+ }
505
+ if (props.frames !== undefined) {
506
+ const resolvedFrames = isSignal(props.frames as any)
507
+ ? (props.frames as any)()
508
+ : props.frames;
509
+ this.frames = resolvedFrames ?? [];
510
+ }
511
+ if (props.sheet !== undefined) {
512
+ const resolvedSheet = isSignal(props.sheet as any)
513
+ ? (props.sheet as any)()
514
+ : props.sheet;
515
+ if (resolvedSheet?.definition !== undefined) {
516
+ const resolvedDefinition = this.resolveSheetDefinition(resolvedSheet.definition);
517
+ if (resolvedDefinition instanceof Promise) {
518
+ void this.setSheetDefinition(resolvedDefinition);
519
+ } else if (resolvedDefinition && resolvedDefinition !== this.sheetDefinition) {
520
+ void this.setSheetDefinition(resolvedDefinition);
521
+ }
522
+ }
523
+ if (resolvedSheet?.params !== undefined) {
524
+ this.sheetParams = { ...this.sheetParams, ...resolvedSheet.params };
525
+ }
526
+ if (resolvedSheet?.playing !== undefined) {
527
+ this.sheetCurrentName = resolvedSheet.playing;
528
+ }
529
+ if (resolvedSheet?.onFinish !== undefined) {
530
+ this.sheetOnFinish = resolvedSheet.onFinish;
531
+ }
532
+ if (this.sheetAnimations.size > 0 && this.sheetCurrentName) {
533
+ this.playSheet(this.sheetCurrentName, [this.sheetParams]);
534
+ }
535
+ }
536
+ if (props.frameIndex !== undefined) {
537
+ const isFrameIndexSignal = isSignal(props.frameIndex as any);
538
+ const resolvedIndex = isFrameIndexSignal
539
+ ? (props.frameIndex as any)()
540
+ : props.frameIndex;
541
+ if (resolvedIndex !== undefined) {
542
+ this.frameIndex = resolvedIndex;
543
+ }
544
+ this.hasExternalFrameIndex = isFrameIndexSignal;
545
+ }
546
+ if (props.fps !== undefined) {
547
+ const resolvedFps = isSignal(props.fps as any) ? (props.fps as any)() : props.fps;
548
+ if (resolvedFps !== undefined) {
549
+ this.fps = resolvedFps;
550
+ }
551
+ }
552
+ if (props.playing !== undefined) {
553
+ const resolvedPlaying = isSignal(props.playing as any)
554
+ ? (props.playing as any)()
555
+ : props.playing;
556
+ this.playing = resolvedPlaying === true;
557
+ if (!this.playing) {
558
+ this.elapsed = 0;
559
+ }
560
+ }
561
+ if (props.loop !== undefined) {
562
+ const resolvedLoop = isSignal(props.loop as any) ? (props.loop as any)() : props.loop;
563
+ if (resolvedLoop !== undefined) {
564
+ this.loop = resolvedLoop;
565
+ }
566
+ }
567
+ if (props.objectFit !== undefined) {
568
+ const resolvedFit = this.resolveValue(props.objectFit);
569
+ this.fitMode = resolvedFit ?? undefined;
570
+ }
571
+ }
572
+
573
+ private resolveValue<T>(value: T | Signal<T> | { value?: T } | undefined): T | undefined {
574
+ if (value === undefined) return undefined;
575
+ const resolved = isSignal(value as any) ? (value as any)() : value;
576
+ if (resolved && typeof resolved === "object" && "value" in (resolved as any)) {
577
+ return (resolved as any).value as T;
578
+ }
579
+ return resolved as T;
580
+ }
581
+
582
+ private resolvePoint(
583
+ value: DOMSpriteProps["scale"] | DOMSpriteProps["anchor"] | DOMSpriteProps["skew"] | DOMSpriteProps["pivot"]
584
+ ): { x: number; y: number } | undefined {
585
+ const resolved = this.resolveValue<any>(value as any);
586
+ if (resolved === undefined || resolved === null) return undefined;
587
+ if (typeof resolved === "number") {
588
+ return { x: resolved, y: resolved };
589
+ }
590
+ if (Array.isArray(resolved)) {
591
+ const [x, y] = resolved;
592
+ return { x: x ?? 0, y: y ?? x ?? 0 };
593
+ }
594
+ if (typeof resolved === "object") {
595
+ return { x: resolved.x ?? 0, y: resolved.y ?? 0 };
596
+ }
597
+ return undefined;
598
+ }
599
+
600
+ private resolveSize(value: DOMSpriteProps["width"] | DOMSpriteProps["height"]): string | undefined {
601
+ const resolved = this.resolveValue<any>(value as any);
602
+ if (resolved === undefined || resolved === null) return undefined;
603
+ if (typeof resolved === "number") return `${resolved}px`;
604
+ if (typeof resolved === "string") return resolved;
605
+ return undefined;
606
+ }
607
+
608
+ private resolvePixelSize(value?: string): number | undefined {
609
+ if (!value) return undefined;
610
+ if (value.endsWith("px")) {
611
+ const parsed = parseFloat(value);
612
+ return Number.isNaN(parsed) ? undefined : parsed;
613
+ }
614
+ if (/^\d+(\.\d+)?$/.test(value)) {
615
+ const parsed = parseFloat(value);
616
+ return Number.isNaN(parsed) ? undefined : parsed;
617
+ }
618
+ return undefined;
619
+ }
620
+
621
+ private toCssColor(tint: number): string {
622
+ const clamped = Math.max(0, Math.min(0xffffff, tint));
623
+ return `#${clamped.toString(16).padStart(6, "0")}`;
624
+ }
625
+
626
+ private applyDisplayProps(props: DOMSpriteProps) {
627
+ if (!this.element) return;
628
+
629
+ if (props.width !== undefined) {
630
+ this.explicitWidth = this.resolveSize(props.width);
631
+ } else {
632
+ this.explicitWidth = undefined;
633
+ }
634
+ if (props.height !== undefined) {
635
+ this.explicitHeight = this.resolveSize(props.height);
636
+ } else {
637
+ this.explicitHeight = undefined;
638
+ }
639
+ if (this.explicitWidth !== undefined) {
640
+ this.element.style.width = this.explicitWidth;
641
+ }
642
+ if (this.explicitHeight !== undefined) {
643
+ this.element.style.height = this.explicitHeight;
644
+ }
645
+
646
+ if (props.alpha !== undefined) {
647
+ const alpha = this.resolveValue(props.alpha);
648
+ if (alpha !== undefined) {
649
+ this.element.style.opacity = String(alpha);
650
+ }
651
+ }
652
+
653
+ if (props.visible !== undefined) {
654
+ const visible = this.resolveValue(props.visible);
655
+ this.element.style.display = visible === false ? "none" : "";
656
+ }
657
+
658
+ if (props.zIndex !== undefined) {
659
+ const zIndex = this.resolveValue(props.zIndex);
660
+ if (zIndex !== undefined) {
661
+ this.element.style.zIndex = String(zIndex);
662
+ }
663
+ }
664
+
665
+ if (props.cursor !== undefined) {
666
+ const cursor = this.resolveValue(props.cursor);
667
+ if (cursor !== undefined) {
668
+ this.element.style.cursor = String(cursor);
669
+ }
670
+ }
671
+
672
+ if (props.tint !== undefined) {
673
+ const tint = this.resolveValue(props.tint);
674
+ if (typeof tint === "number") {
675
+ this.element.style.filter = `drop-shadow(0 0 0 ${this.toCssColor(tint)})`;
676
+ }
677
+ }
678
+
679
+ const hasTransformProps = [
680
+ props.x,
681
+ props.y,
682
+ props.scale,
683
+ props.rotation,
684
+ props.angle,
685
+ props.skew,
686
+ props.roundPixels,
687
+ ].some((value) => value !== undefined);
688
+
689
+ if (hasTransformProps) {
690
+ let x = this.resolveValue(props.x) ?? 0;
691
+ let y = this.resolveValue(props.y) ?? 0;
692
+ const roundPixels = this.resolveValue(props.roundPixels);
693
+ if (roundPixels) {
694
+ x = Math.round(x);
695
+ y = Math.round(y);
696
+ }
697
+
698
+ const scale = this.resolvePoint(props.scale) ?? { x: 1, y: 1 };
699
+ const skew = this.resolvePoint(props.skew);
700
+
701
+ const angle = this.resolveValue(props.angle);
702
+ const rotation = this.resolveValue(props.rotation);
703
+ const rotationDeg = angle !== undefined
704
+ ? angle
705
+ : rotation !== undefined
706
+ ? (rotation * 180) / Math.PI
707
+ : 0;
708
+
709
+ const transformParts = [
710
+ `translate3d(${x}px, ${y}px, 0)`,
711
+ ];
712
+
713
+ if (rotationDeg !== 0) {
714
+ transformParts.push(`rotate(${rotationDeg}deg)`);
715
+ }
716
+
717
+ if (skew) {
718
+ const skewX = (skew.x * 180) / Math.PI;
719
+ const skewY = (skew.y * 180) / Math.PI;
720
+ if (skewX !== 0 || skewY !== 0) {
721
+ transformParts.push(`skew(${skewX}deg, ${skewY}deg)`);
722
+ }
723
+ }
724
+
725
+ if (scale.x !== 1 || scale.y !== 1) {
726
+ transformParts.push(`scale(${scale.x}, ${scale.y})`);
727
+ }
728
+
729
+ this.element.style.transform = transformParts.join(" ");
730
+
731
+ }
732
+
733
+ const pivot = this.resolvePoint(props.pivot);
734
+ const anchor = this.resolvePoint(props.anchor);
735
+ if (pivot) {
736
+ this.element.style.transformOrigin = `${pivot.x}px ${pivot.y}px`;
737
+ } else if (anchor) {
738
+ this.element.style.transformOrigin = `${anchor.x * 100}% ${anchor.y * 100}%`;
739
+ }
740
+ }
741
+
742
+ private syncRenderElement() {
743
+ if (!this.element) return;
744
+ if (this.fitMode === "contain" && !(this.element instanceof HTMLImageElement)) {
745
+ if (!this.isContained) {
746
+ const inner = document.createElement(this.renderElementType);
747
+ this.element.style.position = "relative";
748
+ this.element.style.overflow = "hidden";
749
+ inner.style.position = "absolute";
750
+ inner.style.left = "0";
751
+ inner.style.top = "0";
752
+ inner.style.transformOrigin = "0 0";
753
+ this.element.appendChild(inner);
754
+ this.renderElement = inner;
755
+ this.isContained = true;
756
+ }
757
+ return;
758
+ }
759
+
760
+ if (this.isContained) {
761
+ if (this.renderElement && this.renderElement !== this.element) {
762
+ this.renderElement.remove();
763
+ }
764
+ this.renderElement = undefined;
765
+ this.isContained = false;
766
+ }
767
+ }
768
+
769
+ private getRenderElement() {
770
+ return this.renderElement ?? this.element;
771
+ }
772
+
773
+ private applyContainScale() {
774
+ if (!this.isContained) return;
775
+ const target = this.getRenderElement();
776
+ if (!target || !this.element) return;
777
+ if (this.frameWidth <= 0 || this.frameHeight <= 0) return;
778
+
779
+ const containerWidth =
780
+ this.resolvePixelSize(this.explicitWidth) ?? this.element.clientWidth;
781
+ const containerHeight =
782
+ this.resolvePixelSize(this.explicitHeight) ?? this.element.clientHeight;
783
+
784
+ if (!containerWidth || !containerHeight) return;
785
+
786
+ const scale = Math.min(containerWidth / this.frameWidth, containerHeight / this.frameHeight);
787
+ if (!Number.isFinite(scale) || scale <= 0) return;
788
+ target.style.transform = `scale(${scale})`;
789
+ }
790
+
791
+ private bindPlayingSignal(context: Element<CanvasDOMElement>) {
792
+ const playingValue = context.propObservables?.playing as any;
793
+ if (!playingValue || !isSignal(playingValue)) return;
794
+ if (this.playingSignal === playingValue) return;
795
+
796
+ if (this.playingSubscription) {
797
+ this.playingSubscription.unsubscribe();
798
+ this.playingSubscription = undefined;
799
+ }
800
+
801
+ this.playingSignal = playingValue;
802
+ this.playing = playingValue();
803
+ this.playingSubscription = playingValue.observable.subscribe((value) => {
804
+ this.playing = value === true;
805
+ if (!this.playing) {
806
+ this.elapsed = 0;
807
+ }
808
+ this.updateAnimationLoop();
809
+ });
810
+ }
811
+
812
+ private bindSheetParams(context: Element<CanvasDOMElement>) {
813
+ const sheetProps = context.propObservables?.sheet as any;
814
+ const params = sheetProps?.params as any;
815
+ if (!params || typeof params !== "object") return;
816
+ for (const key in params) {
817
+ const value = params[key];
818
+ if (!isSignal(value)) continue;
819
+ this.sheetSubscriptions.push(
820
+ value.observable.subscribe((nextValue) => {
821
+ this.sheetParams = { ...this.sheetParams, [key]: nextValue };
822
+ if (this.sheetCurrentName) {
823
+ this.playSheet(this.sheetCurrentName, [this.sheetParams]);
824
+ }
825
+ })
826
+ );
827
+ }
828
+ }
829
+
830
+ private getFrames(): DOMSpriteFrame[] {
831
+ if (this.frames && this.frames.length > 0) return this.frames;
832
+ if (this.rectangle) return [this.rectangle];
833
+ return [];
834
+ }
835
+
836
+ private normalizeIndex(index: number, length: number) {
837
+ if (length <= 0) return 0;
838
+ if (this.loop) {
839
+ const mod = index % length;
840
+ return mod < 0 ? mod + length : mod;
841
+ }
842
+ if (index < 0) return 0;
843
+ if (index >= length) return length - 1;
844
+ return index;
845
+ }
846
+
847
+ private render() {
848
+ if (!this.element) return;
849
+ const target = this.getRenderElement();
850
+ const sheetFrame = this.getCurrentSheetFrame();
851
+ if (sheetFrame) {
852
+ this.applyFrame(sheetFrame);
853
+ return;
854
+ }
855
+ const frames = this.getFrames();
856
+ if (frames.length === 0) {
857
+ if (this.renderElementType === "img" && this.image && target) {
858
+ (target as HTMLImageElement).src = this.image;
859
+ } else if (this.image) {
860
+ target.style.backgroundImage = `url("${this.image}")`;
861
+ }
862
+ return;
863
+ }
864
+
865
+ const normalizedIndex = this.normalizeIndex(this.frameIndex, frames.length);
866
+ if (normalizedIndex !== this.frameIndex && !this.loop) {
867
+ this.frameIndex = normalizedIndex;
868
+ }
869
+
870
+ const frame = frames[normalizedIndex];
871
+ this.applyFrame(frame);
872
+ }
873
+
874
+ private applyFrame(frame: DOMSpriteFrame) {
875
+ if (!this.element) return;
876
+ const target = this.getRenderElement();
877
+ if (!target) return;
878
+ this.frameWidth = frame.width;
879
+ this.frameHeight = frame.height;
880
+ if (this.fitMode === "contain") {
881
+ target.style.width = `${frame.width}px`;
882
+ target.style.height = `${frame.height}px`;
883
+ } else {
884
+ target.style.width = this.explicitWidth ?? `${frame.width}px`;
885
+ target.style.height = this.explicitHeight ?? `${frame.height}px`;
886
+ }
887
+
888
+ const x = frame.x ?? 0;
889
+ const y = frame.y ?? 0;
890
+
891
+ if (this.renderElementType === "img") {
892
+ const img = target as HTMLImageElement;
893
+ if (this.image) {
894
+ img.src = this.image;
895
+ }
896
+ img.style.objectFit = "none";
897
+ img.style.objectPosition = `-${x}px -${y}px`;
898
+ this.applyContainScale();
899
+ return;
900
+ }
901
+
902
+ if (this.image) {
903
+ target.style.backgroundImage = `url("${this.image}")`;
904
+ }
905
+ target.style.backgroundRepeat = "no-repeat";
906
+ target.style.backgroundPosition = `-${x}px -${y}px`;
907
+ this.applyContainScale();
908
+ }
909
+
910
+ private updateAnimationLoop() {
911
+ const sheetFrames = this.sheetCurrentAnimation?.frames ?? [];
912
+ const shouldAnimate = this.sheetCurrentAnimation
913
+ ? this.playing && sheetFrames.length > 1
914
+ : this.playing &&
915
+ !this.hasExternalFrameIndex &&
916
+ this.getFrames().length > 1 &&
917
+ this.fps > 0;
918
+
919
+ if (shouldAnimate) {
920
+ this.startAnimationLoop();
921
+ } else {
922
+ this.stopAnimationLoop();
923
+ }
924
+ }
925
+
926
+ private startAnimationLoop() {
927
+ if (this.tickSubscription || this.rafId !== undefined) {
928
+ this.stopAnimationLoop();
929
+ }
930
+ this.isAnimating = true;
931
+ this.elapsed = 0;
932
+ this.lastTickTimestamp = undefined;
933
+
934
+ if (this.tickSignal?.observable) {
935
+ this.tickSubscription = (this.tickSignal.observable as any).subscribe((result: any) => {
936
+ const tick = result?.value ?? result;
937
+ if (!tick) return;
938
+ if (this.sheetCurrentAnimation) {
939
+ this.advanceSheet(tick);
940
+ return;
941
+ }
942
+ let deltaTime = tick.deltaTime || 0;
943
+ if (deltaTime <= 0) {
944
+ const now = preciseNow();
945
+ if (this.lastTickTimestamp === undefined) {
946
+ this.lastTickTimestamp = now;
947
+ return;
948
+ }
949
+ deltaTime = now - this.lastTickTimestamp;
950
+ this.lastTickTimestamp = now;
951
+ }
952
+ if (deltaTime > 0) {
953
+ this.advance(deltaTime);
954
+ }
955
+ });
956
+ return;
957
+ }
958
+
959
+ const step = (timestamp: number) => {
960
+ if (!this.playing || this.hasExternalFrameIndex) {
961
+ this.stopAnimationLoop();
962
+ return;
963
+ }
964
+ if (this.lastRafTimestamp === undefined) {
965
+ this.lastRafTimestamp = timestamp;
966
+ }
967
+ const delta = timestamp - this.lastRafTimestamp;
968
+ this.lastRafTimestamp = timestamp;
969
+ if (this.sheetCurrentAnimation) {
970
+ this.advanceSheet({
971
+ timestamp,
972
+ deltaTime: delta,
973
+ frame: 0,
974
+ deltaRatio: delta / fps2ms(this.fps),
975
+ });
976
+ } else {
977
+ this.advance(delta);
978
+ }
979
+
980
+ if (isBrowser()) {
981
+ this.rafId = window.requestAnimationFrame(step);
982
+ } else {
983
+ this.rafId = setTimeout(() => {
984
+ step(preciseNow());
985
+ }, fps2ms(this.fps)) as unknown as number;
986
+ }
987
+ };
988
+
989
+ if (isBrowser()) {
990
+ this.rafId = window.requestAnimationFrame(step);
991
+ } else {
992
+ this.rafId = setTimeout(() => {
993
+ step(preciseNow());
994
+ }, fps2ms(this.fps)) as unknown as number;
995
+ }
996
+ }
997
+
998
+ private stopAnimationLoop() {
999
+ this.isAnimating = false;
1000
+ if (this.tickSubscription) {
1001
+ this.tickSubscription.unsubscribe();
1002
+ this.tickSubscription = undefined;
1003
+ }
1004
+ if (this.rafId !== undefined) {
1005
+ if (isBrowser()) {
1006
+ window.cancelAnimationFrame(this.rafId);
1007
+ } else {
1008
+ clearTimeout(this.rafId);
1009
+ }
1010
+ this.rafId = undefined;
1011
+ this.lastRafTimestamp = undefined;
1012
+ }
1013
+ this.lastTickTimestamp = undefined;
1014
+ }
1015
+
1016
+ private advance(deltaTime: number) {
1017
+ const frames = this.getFrames();
1018
+ if (!this.playing || frames.length <= 1 || this.fps <= 0) return;
1019
+
1020
+ this.elapsed += deltaTime;
1021
+ const frameDuration = fps2ms(this.fps);
1022
+
1023
+ while (this.elapsed >= frameDuration) {
1024
+ this.elapsed -= frameDuration;
1025
+ this.frameIndex += 1;
1026
+ if (!this.loop && this.frameIndex >= frames.length) {
1027
+ this.frameIndex = frames.length - 1;
1028
+ break;
1029
+ }
1030
+ }
1031
+
1032
+ this.render();
1033
+ }
1034
+ }
1035
+
1036
+ registerComponent("DOMSprite", CanvasDOMSprite);
1037
+
1038
+ export const DOMSprite: ComponentFunction<DOMSpriteProps> = (props) => {
1039
+ return createComponent("DOMSprite", props);
1040
+ };