@motion-core/motion-gpu 0.7.0 → 0.8.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.
Files changed (59) hide show
  1. package/README.md +40 -34
  2. package/dist/motion-gpu.css +295 -0
  3. package/dist/vue/FragCanvas.js +8 -0
  4. package/dist/vue/FragCanvas.js.map +1 -0
  5. package/dist/vue/FragCanvas.vue.d.ts +49 -0
  6. package/dist/vue/FragCanvas.vue.d.ts.map +1 -0
  7. package/dist/vue/FragCanvas.vue_vue_type_script_setup_true_lang.js +228 -0
  8. package/dist/vue/FragCanvas.vue_vue_type_script_setup_true_lang.js.map +1 -0
  9. package/dist/vue/MotionGPUErrorOverlay.js +8 -0
  10. package/dist/vue/MotionGPUErrorOverlay.js.map +1 -0
  11. package/dist/vue/MotionGPUErrorOverlay.vue.d.ts +8 -0
  12. package/dist/vue/MotionGPUErrorOverlay.vue.d.ts.map +1 -0
  13. package/dist/vue/MotionGPUErrorOverlay.vue_vue_type_script_setup_true_lang.js +166 -0
  14. package/dist/vue/MotionGPUErrorOverlay.vue_vue_type_script_setup_true_lang.js.map +1 -0
  15. package/dist/vue/Portal.js +7 -0
  16. package/dist/vue/Portal.js.map +1 -0
  17. package/dist/vue/Portal.vue.d.ts +18 -0
  18. package/dist/vue/Portal.vue.d.ts.map +1 -0
  19. package/dist/vue/Portal.vue_vue_type_script_setup_true_lang.js +29 -0
  20. package/dist/vue/Portal.vue_vue_type_script_setup_true_lang.js.map +1 -0
  21. package/dist/vue/advanced.d.ts +12 -0
  22. package/dist/vue/advanced.d.ts.map +1 -0
  23. package/dist/vue/advanced.js +15 -0
  24. package/dist/vue/frame-context.d.ts +22 -0
  25. package/dist/vue/frame-context.d.ts.map +1 -0
  26. package/dist/vue/frame-context.js +38 -0
  27. package/dist/vue/frame-context.js.map +1 -0
  28. package/dist/vue/index.d.ts +21 -0
  29. package/dist/vue/index.d.ts.map +1 -0
  30. package/dist/vue/index.js +14 -0
  31. package/dist/vue/motiongpu-context.d.ts +81 -0
  32. package/dist/vue/motiongpu-context.d.ts.map +1 -0
  33. package/dist/vue/motiongpu-context.js +29 -0
  34. package/dist/vue/motiongpu-context.js.map +1 -0
  35. package/dist/vue/shims-vue.d.js +0 -0
  36. package/dist/vue/use-motiongpu-user-context.d.ts +44 -0
  37. package/dist/vue/use-motiongpu-user-context.d.ts.map +1 -0
  38. package/dist/vue/use-motiongpu-user-context.js +76 -0
  39. package/dist/vue/use-motiongpu-user-context.js.map +1 -0
  40. package/dist/vue/use-pointer.d.ts +94 -0
  41. package/dist/vue/use-pointer.d.ts.map +1 -0
  42. package/dist/vue/use-pointer.js +298 -0
  43. package/dist/vue/use-pointer.js.map +1 -0
  44. package/dist/vue/use-texture.d.ts +45 -0
  45. package/dist/vue/use-texture.d.ts.map +1 -0
  46. package/dist/vue/use-texture.js +135 -0
  47. package/dist/vue/use-texture.js.map +1 -0
  48. package/package.json +25 -7
  49. package/src/lib/vue/FragCanvas.vue +294 -0
  50. package/src/lib/vue/MotionGPUErrorOverlay.vue +518 -0
  51. package/src/lib/vue/Portal.vue +46 -0
  52. package/src/lib/vue/advanced.ts +32 -0
  53. package/src/lib/vue/frame-context.ts +96 -0
  54. package/src/lib/vue/index.ts +78 -0
  55. package/src/lib/vue/motiongpu-context.ts +97 -0
  56. package/src/lib/vue/shims-vue.d.ts +6 -0
  57. package/src/lib/vue/use-motiongpu-user-context.ts +145 -0
  58. package/src/lib/vue/use-pointer.ts +514 -0
  59. package/src/lib/vue/use-texture.ts +232 -0
@@ -0,0 +1,298 @@
1
+ import { createCurrentWritable } from "../core/current-value.js";
2
+ import { createInitialPointerState, getPointerCoordinates, getPointerNowSeconds, normalizePointerKind, resolvePointerFrameRequestMode } from "../core/pointer.js";
3
+ import { useMotionGPU } from "./motiongpu-context.js";
4
+ import { onBeforeUnmount, onMounted } from "vue";
5
+ //#region src/lib/vue/use-pointer.ts
6
+ /**
7
+ * Normalizes click button configuration with a primary-button fallback.
8
+ */
9
+ function normalizeClickButtons(buttons) {
10
+ const source = buttons && buttons.length > 0 ? buttons : [0];
11
+ return new Set(source);
12
+ }
13
+ /**
14
+ * Resolves a valid click duration threshold in milliseconds.
15
+ */
16
+ function resolveClickMaxDurationMs(value) {
17
+ if (typeof value !== "number" || Number.isNaN(value) || value <= 0) return 350;
18
+ return value;
19
+ }
20
+ /**
21
+ * Resolves a valid click travel threshold in pixels.
22
+ */
23
+ function resolveClickMaxMovePx(value) {
24
+ if (typeof value !== "number" || Number.isNaN(value) || value < 0) return 8;
25
+ return value;
26
+ }
27
+ /**
28
+ * Tracks normalized pointer coordinates and click/tap snapshots for the active `FragCanvas`.
29
+ */
30
+ function usePointer(options = {}) {
31
+ const motiongpu = useMotionGPU();
32
+ const pointerState = createCurrentWritable(createInitialPointerState());
33
+ const lastClick = createCurrentWritable(null);
34
+ const enabled = options.enabled ?? true;
35
+ const requestFrameMode = options.requestFrame ?? "auto";
36
+ const capturePointer = options.capturePointer ?? true;
37
+ const trackOutside = options.trackWhilePressedOutsideCanvas ?? true;
38
+ const clickEnabled = options.clickEnabled ?? true;
39
+ const clickMaxDurationMs = resolveClickMaxDurationMs(options.clickMaxDurationMs);
40
+ const clickMaxMovePx = resolveClickMaxMovePx(options.clickMaxMovePx);
41
+ const clickButtons = normalizeClickButtons(options.clickButtons);
42
+ let activePointerId = null;
43
+ let downSnapshot = null;
44
+ let clickCounter = 0;
45
+ let previousPx = null;
46
+ let previousUv = null;
47
+ let previousTimeSeconds = 0;
48
+ let cleanup = null;
49
+ const requestFrame = () => {
50
+ const mode = resolvePointerFrameRequestMode(requestFrameMode, motiongpu.renderMode.current);
51
+ if (mode === "invalidate") {
52
+ motiongpu.invalidate();
53
+ return;
54
+ }
55
+ if (mode === "advance") motiongpu.advance();
56
+ };
57
+ /**
58
+ * Commits a full pointer state snapshot with computed delta and velocity vectors.
59
+ */
60
+ const updatePointerState = (input) => {
61
+ const nowSeconds = getPointerNowSeconds();
62
+ const dt = previousTimeSeconds > 0 ? Math.max(nowSeconds - previousTimeSeconds, 1e-6) : 0;
63
+ const deltaPx = input.resetDelta || !previousPx ? [0, 0] : [input.point.px[0] - previousPx[0], input.point.px[1] - previousPx[1]];
64
+ const deltaUv = input.resetDelta || !previousUv ? [0, 0] : [input.point.uv[0] - previousUv[0], input.point.uv[1] - previousUv[1]];
65
+ const velocityPx = dt > 0 ? [deltaPx[0] / dt, deltaPx[1] / dt] : [0, 0];
66
+ const velocityUv = dt > 0 ? [deltaUv[0] / dt, deltaUv[1] / dt] : [0, 0];
67
+ const nextState = {
68
+ px: input.point.px,
69
+ uv: input.point.uv,
70
+ ndc: input.point.ndc,
71
+ inside: input.inside,
72
+ pressed: input.pressed,
73
+ dragging: input.dragging,
74
+ pointerType: input.pointerType,
75
+ pointerId: input.pointerId,
76
+ button: input.button,
77
+ buttons: input.buttons,
78
+ time: nowSeconds,
79
+ downPx: input.downPx,
80
+ downUv: input.downUv,
81
+ deltaPx,
82
+ deltaUv,
83
+ velocityPx,
84
+ velocityUv
85
+ };
86
+ pointerState.set(nextState);
87
+ previousPx = input.point.px;
88
+ previousUv = input.point.uv;
89
+ previousTimeSeconds = nowSeconds;
90
+ requestFrame();
91
+ return nextState;
92
+ };
93
+ /**
94
+ * Updates only the `inside` flag while keeping the latest pointer coordinates.
95
+ */
96
+ const updateInsideState = (inside) => {
97
+ const nextState = {
98
+ ...pointerState.current,
99
+ inside,
100
+ time: getPointerNowSeconds(),
101
+ deltaPx: [0, 0],
102
+ deltaUv: [0, 0],
103
+ velocityPx: [0, 0],
104
+ velocityUv: [0, 0]
105
+ };
106
+ pointerState.set(nextState);
107
+ requestFrame();
108
+ return nextState;
109
+ };
110
+ /**
111
+ * Checks whether an event belongs to the active tracked pointer.
112
+ */
113
+ const isTrackedPointer = (event) => activePointerId === null || event.pointerId === activePointerId;
114
+ const attachListeners = () => {
115
+ if (!enabled) return;
116
+ const canvas = motiongpu.canvas;
117
+ if (!canvas) return;
118
+ const handlePointerDown = (event) => {
119
+ const point = getPointerCoordinates(event.clientX, event.clientY, canvas.getBoundingClientRect());
120
+ const pointerType = normalizePointerKind(event.pointerType);
121
+ activePointerId = event.pointerId;
122
+ downSnapshot = {
123
+ pointerId: event.pointerId,
124
+ pointerType,
125
+ button: event.button,
126
+ timeMs: getPointerNowSeconds() * 1e3,
127
+ px: point.px,
128
+ uv: point.uv,
129
+ inside: point.inside
130
+ };
131
+ if (capturePointer) try {
132
+ canvas.setPointerCapture(event.pointerId);
133
+ } catch {}
134
+ const nextState = updatePointerState({
135
+ point,
136
+ inside: point.inside,
137
+ pressed: true,
138
+ dragging: false,
139
+ pointerType,
140
+ pointerId: event.pointerId,
141
+ button: event.button,
142
+ buttons: event.buttons,
143
+ downPx: point.px,
144
+ downUv: point.uv,
145
+ resetDelta: true
146
+ });
147
+ options.onDown?.(nextState, event);
148
+ };
149
+ const handleMove = (event) => {
150
+ if (!isTrackedPointer(event)) return;
151
+ const point = getPointerCoordinates(event.clientX, event.clientY, canvas.getBoundingClientRect());
152
+ const pressed = activePointerId !== null && event.pointerId === activePointerId;
153
+ const downPx = pressed ? downSnapshot?.px ?? point.px : null;
154
+ const downUv = pressed ? downSnapshot?.uv ?? point.uv : null;
155
+ let dragging = false;
156
+ if (pressed && downPx) {
157
+ const dx = point.px[0] - downPx[0];
158
+ const dy = point.px[1] - downPx[1];
159
+ dragging = Math.hypot(dx, dy) > 0;
160
+ }
161
+ const nextState = updatePointerState({
162
+ point,
163
+ inside: point.inside,
164
+ pressed,
165
+ dragging,
166
+ pointerType: normalizePointerKind(event.pointerType),
167
+ pointerId: event.pointerId,
168
+ button: pressed ? downSnapshot?.button ?? event.button : null,
169
+ buttons: event.buttons,
170
+ downPx,
171
+ downUv
172
+ });
173
+ options.onMove?.(nextState, event);
174
+ };
175
+ const handleWindowMove = (event) => {
176
+ if (!trackOutside || activePointerId === null || event.pointerId !== activePointerId) return;
177
+ const point = getPointerCoordinates(event.clientX, event.clientY, canvas.getBoundingClientRect());
178
+ if (point.inside) return;
179
+ const downPx = downSnapshot?.px ?? point.px;
180
+ const downUv = downSnapshot?.uv ?? point.uv;
181
+ const dx = point.px[0] - downPx[0];
182
+ const dy = point.px[1] - downPx[1];
183
+ const nextState = updatePointerState({
184
+ point,
185
+ inside: false,
186
+ pressed: true,
187
+ dragging: Math.hypot(dx, dy) > 0,
188
+ pointerType: downSnapshot?.pointerType ?? normalizePointerKind(event.pointerType),
189
+ pointerId: event.pointerId,
190
+ button: downSnapshot?.button ?? event.button,
191
+ buttons: event.buttons,
192
+ downPx,
193
+ downUv
194
+ });
195
+ options.onMove?.(nextState, event);
196
+ };
197
+ const releasePointer = (event, emitClick) => {
198
+ if (activePointerId === null || event.pointerId !== activePointerId) return;
199
+ const point = getPointerCoordinates(event.clientX, event.clientY, canvas.getBoundingClientRect());
200
+ const pointerType = downSnapshot?.pointerType ?? normalizePointerKind(event.pointerType);
201
+ const previous = downSnapshot;
202
+ const nextState = updatePointerState({
203
+ point,
204
+ inside: point.inside,
205
+ pressed: false,
206
+ dragging: false,
207
+ pointerType,
208
+ pointerId: null,
209
+ button: null,
210
+ buttons: event.buttons,
211
+ downPx: null,
212
+ downUv: null
213
+ });
214
+ options.onUp?.(nextState, event);
215
+ if (capturePointer && canvas.hasPointerCapture(event.pointerId)) try {
216
+ canvas.releasePointerCapture(event.pointerId);
217
+ } catch {}
218
+ if (emitClick && clickEnabled && previous && clickButtons.has(previous.button)) {
219
+ const durationMs = getPointerNowSeconds() * 1e3 - previous.timeMs;
220
+ const dx = point.px[0] - previous.px[0];
221
+ const dy = point.px[1] - previous.px[1];
222
+ const moveDistance = Math.hypot(dx, dy);
223
+ if (previous.inside && point.inside && durationMs <= clickMaxDurationMs && moveDistance <= clickMaxMovePx) {
224
+ clickCounter += 1;
225
+ const click = {
226
+ id: clickCounter,
227
+ time: getPointerNowSeconds(),
228
+ pointerType,
229
+ pointerId: event.pointerId,
230
+ button: previous.button,
231
+ modifiers: {
232
+ alt: event.altKey,
233
+ ctrl: event.ctrlKey,
234
+ shift: event.shiftKey,
235
+ meta: event.metaKey
236
+ },
237
+ px: point.px,
238
+ uv: point.uv,
239
+ ndc: point.ndc
240
+ };
241
+ lastClick.set(click);
242
+ options.onClick?.(click, nextState, event);
243
+ requestFrame();
244
+ }
245
+ }
246
+ activePointerId = null;
247
+ downSnapshot = null;
248
+ };
249
+ const handlePointerUp = (event) => {
250
+ releasePointer(event, true);
251
+ };
252
+ const handlePointerCancel = (event) => {
253
+ releasePointer(event, false);
254
+ };
255
+ const handlePointerLeave = () => {
256
+ if (activePointerId !== null) return;
257
+ updateInsideState(false);
258
+ };
259
+ canvas.addEventListener("pointerdown", handlePointerDown);
260
+ canvas.addEventListener("pointermove", handleMove);
261
+ canvas.addEventListener("pointerup", handlePointerUp);
262
+ canvas.addEventListener("pointercancel", handlePointerCancel);
263
+ canvas.addEventListener("pointerleave", handlePointerLeave);
264
+ if (trackOutside) {
265
+ window.addEventListener("pointermove", handleWindowMove);
266
+ window.addEventListener("pointerup", handlePointerUp);
267
+ window.addEventListener("pointercancel", handlePointerCancel);
268
+ }
269
+ cleanup = () => {
270
+ canvas.removeEventListener("pointerdown", handlePointerDown);
271
+ canvas.removeEventListener("pointermove", handleMove);
272
+ canvas.removeEventListener("pointerup", handlePointerUp);
273
+ canvas.removeEventListener("pointercancel", handlePointerCancel);
274
+ canvas.removeEventListener("pointerleave", handlePointerLeave);
275
+ if (trackOutside) {
276
+ window.removeEventListener("pointermove", handleWindowMove);
277
+ window.removeEventListener("pointerup", handlePointerUp);
278
+ window.removeEventListener("pointercancel", handlePointerCancel);
279
+ }
280
+ };
281
+ };
282
+ onMounted(attachListeners);
283
+ onBeforeUnmount(() => {
284
+ cleanup?.();
285
+ cleanup = null;
286
+ });
287
+ return {
288
+ state: pointerState,
289
+ lastClick,
290
+ resetClick: () => {
291
+ lastClick.set(null);
292
+ }
293
+ };
294
+ }
295
+ //#endregion
296
+ export { usePointer };
297
+
298
+ //# sourceMappingURL=use-pointer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-pointer.js","names":[],"sources":["../../src/lib/vue/use-pointer.ts"],"sourcesContent":["import { onBeforeUnmount, onMounted } from 'vue';\nimport {\n\tcreateCurrentWritable as currentWritable,\n\ttype CurrentReadable\n} from '../core/current-value.js';\nimport {\n\tcreateInitialPointerState,\n\tgetPointerCoordinates,\n\tgetPointerNowSeconds,\n\tnormalizePointerKind,\n\tresolvePointerFrameRequestMode,\n\ttype PointerClick,\n\ttype PointerFrameRequestMode,\n\ttype PointerState,\n\ttype PointerVec2\n} from '../core/pointer.js';\nimport { useMotionGPU } from './motiongpu-context.js';\n\nexport type {\n\tPointerClick,\n\tPointerFrameRequestMode,\n\tPointerKind,\n\tPointerPoint,\n\tPointerState\n} from '../core/pointer.js';\n\n/**\n * Configuration for pointer input handling in `usePointer`.\n */\nexport interface UsePointerOptions {\n\t/**\n\t * Enables pointer listeners.\n\t *\n\t * @default true\n\t */\n\tenabled?: boolean;\n\t/**\n\t * Frame wake-up strategy for pointer-driven state changes.\n\t *\n\t * @default 'auto'\n\t */\n\trequestFrame?: PointerFrameRequestMode;\n\t/**\n\t * Requests pointer capture on pointer down.\n\t *\n\t * @default true\n\t */\n\tcapturePointer?: boolean;\n\t/**\n\t * Tracks pointer move/up outside canvas while pointer is pressed.\n\t *\n\t * @default true\n\t */\n\ttrackWhilePressedOutsideCanvas?: boolean;\n\t/**\n\t * Enables click/tap synthesis on pointer up.\n\t *\n\t * @default true\n\t */\n\tclickEnabled?: boolean;\n\t/**\n\t * Maximum press duration to consider pointer up a click (milliseconds).\n\t *\n\t * @default 350\n\t */\n\tclickMaxDurationMs?: number;\n\t/**\n\t * Maximum pointer travel from down to up to consider pointer up a click (pixels).\n\t *\n\t * @default 8\n\t */\n\tclickMaxMovePx?: number;\n\t/**\n\t * Allowed pointer buttons for click synthesis.\n\t *\n\t * @default [0]\n\t */\n\tclickButtons?: number[];\n\t/**\n\t * Called after pointer move state update.\n\t */\n\tonMove?: (state: PointerState, event: PointerEvent) => void;\n\t/**\n\t * Called after pointer down state update.\n\t */\n\tonDown?: (state: PointerState, event: PointerEvent) => void;\n\t/**\n\t * Called after pointer up/cancel state update.\n\t */\n\tonUp?: (state: PointerState, event: PointerEvent) => void;\n\t/**\n\t * Called when click/tap is synthesized.\n\t */\n\tonClick?: (click: PointerClick, state: PointerState, event: PointerEvent) => void;\n}\n\n/**\n * Reactive state returned by `usePointer`.\n */\nexport interface UsePointerResult {\n\t/**\n\t * Current pointer state.\n\t */\n\tstate: CurrentReadable<PointerState>;\n\t/**\n\t * Last synthesized click/tap event.\n\t */\n\tlastClick: CurrentReadable<PointerClick | null>;\n\t/**\n\t * Clears last click snapshot.\n\t */\n\tresetClick: () => void;\n}\n\ninterface PointerDownSnapshot {\n\tbutton: number;\n\tinside: boolean;\n\tpointerId: number;\n\tpointerType: 'mouse' | 'pen' | 'touch';\n\tpx: PointerVec2;\n\ttimeMs: number;\n\tuv: PointerVec2;\n}\n\n/**\n * Normalizes click button configuration with a primary-button fallback.\n */\nfunction normalizeClickButtons(buttons: number[] | undefined): Set<number> {\n\tconst source = buttons && buttons.length > 0 ? buttons : [0];\n\treturn new Set(source);\n}\n\n/**\n * Resolves a valid click duration threshold in milliseconds.\n */\nfunction resolveClickMaxDurationMs(value: number | undefined): number {\n\tif (typeof value !== 'number' || Number.isNaN(value) || value <= 0) {\n\t\treturn 350;\n\t}\n\n\treturn value;\n}\n\n/**\n * Resolves a valid click travel threshold in pixels.\n */\nfunction resolveClickMaxMovePx(value: number | undefined): number {\n\tif (typeof value !== 'number' || Number.isNaN(value) || value < 0) {\n\t\treturn 8;\n\t}\n\n\treturn value;\n}\n\n/**\n * Tracks normalized pointer coordinates and click/tap snapshots for the active `FragCanvas`.\n */\nexport function usePointer(options: UsePointerOptions = {}): UsePointerResult {\n\tconst motiongpu = useMotionGPU();\n\tconst pointerState = currentWritable<PointerState>(createInitialPointerState());\n\tconst lastClick = currentWritable<PointerClick | null>(null);\n\tconst enabled = options.enabled ?? true;\n\tconst requestFrameMode = options.requestFrame ?? 'auto';\n\tconst capturePointer = options.capturePointer ?? true;\n\tconst trackOutside = options.trackWhilePressedOutsideCanvas ?? true;\n\tconst clickEnabled = options.clickEnabled ?? true;\n\tconst clickMaxDurationMs = resolveClickMaxDurationMs(options.clickMaxDurationMs);\n\tconst clickMaxMovePx = resolveClickMaxMovePx(options.clickMaxMovePx);\n\tconst clickButtons = normalizeClickButtons(options.clickButtons);\n\tlet activePointerId: number | null = null;\n\tlet downSnapshot: PointerDownSnapshot | null = null;\n\tlet clickCounter = 0;\n\tlet previousPx: PointerVec2 | null = null;\n\tlet previousUv: PointerVec2 | null = null;\n\tlet previousTimeSeconds = 0;\n\tlet cleanup: (() => void) | null = null;\n\n\tconst requestFrame = (): void => {\n\t\tconst mode = resolvePointerFrameRequestMode(requestFrameMode, motiongpu.renderMode.current);\n\t\tif (mode === 'invalidate') {\n\t\t\tmotiongpu.invalidate();\n\t\t\treturn;\n\t\t}\n\t\tif (mode === 'advance') {\n\t\t\tmotiongpu.advance();\n\t\t}\n\t};\n\n\t/**\n\t * Commits a full pointer state snapshot with computed delta and velocity vectors.\n\t */\n\tconst updatePointerState = (input: {\n\t\tbutton: number | null;\n\t\tbuttons: number;\n\t\tdownPx: PointerVec2 | null;\n\t\tdownUv: PointerVec2 | null;\n\t\tdragging: boolean;\n\t\tinside: boolean;\n\t\tpointerId: number | null;\n\t\tpointerType: 'mouse' | 'pen' | 'touch' | null;\n\t\tpressed: boolean;\n\t\tpoint: {\n\t\t\tndc: PointerVec2;\n\t\t\tpx: PointerVec2;\n\t\t\tuv: PointerVec2;\n\t\t};\n\t\tresetDelta?: boolean;\n\t}): PointerState => {\n\t\tconst nowSeconds = getPointerNowSeconds();\n\t\tconst dt = previousTimeSeconds > 0 ? Math.max(nowSeconds - previousTimeSeconds, 1e-6) : 0;\n\t\tconst deltaPx: PointerVec2 =\n\t\t\tinput.resetDelta || !previousPx\n\t\t\t\t? [0, 0]\n\t\t\t\t: [input.point.px[0] - previousPx[0], input.point.px[1] - previousPx[1]];\n\t\tconst deltaUv: PointerVec2 =\n\t\t\tinput.resetDelta || !previousUv\n\t\t\t\t? [0, 0]\n\t\t\t\t: [input.point.uv[0] - previousUv[0], input.point.uv[1] - previousUv[1]];\n\t\tconst velocityPx: PointerVec2 = dt > 0 ? [deltaPx[0] / dt, deltaPx[1] / dt] : [0, 0];\n\t\tconst velocityUv: PointerVec2 = dt > 0 ? [deltaUv[0] / dt, deltaUv[1] / dt] : [0, 0];\n\t\tconst nextState: PointerState = {\n\t\t\tpx: input.point.px,\n\t\t\tuv: input.point.uv,\n\t\t\tndc: input.point.ndc,\n\t\t\tinside: input.inside,\n\t\t\tpressed: input.pressed,\n\t\t\tdragging: input.dragging,\n\t\t\tpointerType: input.pointerType,\n\t\t\tpointerId: input.pointerId,\n\t\t\tbutton: input.button,\n\t\t\tbuttons: input.buttons,\n\t\t\ttime: nowSeconds,\n\t\t\tdownPx: input.downPx,\n\t\t\tdownUv: input.downUv,\n\t\t\tdeltaPx,\n\t\t\tdeltaUv,\n\t\t\tvelocityPx,\n\t\t\tvelocityUv\n\t\t};\n\t\tpointerState.set(nextState);\n\t\tpreviousPx = input.point.px;\n\t\tpreviousUv = input.point.uv;\n\t\tpreviousTimeSeconds = nowSeconds;\n\t\trequestFrame();\n\t\treturn nextState;\n\t};\n\n\t/**\n\t * Updates only the `inside` flag while keeping the latest pointer coordinates.\n\t */\n\tconst updateInsideState = (inside: boolean): PointerState => {\n\t\tconst current = pointerState.current;\n\t\tconst nextState: PointerState = {\n\t\t\t...current,\n\t\t\tinside,\n\t\t\ttime: getPointerNowSeconds(),\n\t\t\tdeltaPx: [0, 0],\n\t\t\tdeltaUv: [0, 0],\n\t\t\tvelocityPx: [0, 0],\n\t\t\tvelocityUv: [0, 0]\n\t\t};\n\t\tpointerState.set(nextState);\n\t\trequestFrame();\n\t\treturn nextState;\n\t};\n\n\t/**\n\t * Checks whether an event belongs to the active tracked pointer.\n\t */\n\tconst isTrackedPointer = (event: PointerEvent): boolean =>\n\t\tactivePointerId === null || event.pointerId === activePointerId;\n\n\tconst attachListeners = (): void => {\n\t\tif (!enabled) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst canvas = motiongpu.canvas;\n\t\tif (!canvas) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst handlePointerDown = (event: PointerEvent): void => {\n\t\t\tconst point = getPointerCoordinates(\n\t\t\t\tevent.clientX,\n\t\t\t\tevent.clientY,\n\t\t\t\tcanvas.getBoundingClientRect()\n\t\t\t);\n\t\t\tconst pointerType = normalizePointerKind(event.pointerType);\n\t\t\tactivePointerId = event.pointerId;\n\t\t\tdownSnapshot = {\n\t\t\t\tpointerId: event.pointerId,\n\t\t\t\tpointerType,\n\t\t\t\tbutton: event.button,\n\t\t\t\ttimeMs: getPointerNowSeconds() * 1000,\n\t\t\t\tpx: point.px,\n\t\t\t\tuv: point.uv,\n\t\t\t\tinside: point.inside\n\t\t\t};\n\t\t\tif (capturePointer) {\n\t\t\t\ttry {\n\t\t\t\t\tcanvas.setPointerCapture(event.pointerId);\n\t\t\t\t} catch {\n\t\t\t\t\t// Browser rejected capture (e.g. unsupported pointer state).\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst nextState = updatePointerState({\n\t\t\t\tpoint,\n\t\t\t\tinside: point.inside,\n\t\t\t\tpressed: true,\n\t\t\t\tdragging: false,\n\t\t\t\tpointerType,\n\t\t\t\tpointerId: event.pointerId,\n\t\t\t\tbutton: event.button,\n\t\t\t\tbuttons: event.buttons,\n\t\t\t\tdownPx: point.px,\n\t\t\t\tdownUv: point.uv,\n\t\t\t\tresetDelta: true\n\t\t\t});\n\t\t\toptions.onDown?.(nextState, event);\n\t\t};\n\n\t\tconst handleMove = (event: PointerEvent): void => {\n\t\t\tif (!isTrackedPointer(event)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst point = getPointerCoordinates(\n\t\t\t\tevent.clientX,\n\t\t\t\tevent.clientY,\n\t\t\t\tcanvas.getBoundingClientRect()\n\t\t\t);\n\t\t\tconst pressed = activePointerId !== null && event.pointerId === activePointerId;\n\t\t\tconst downPx = pressed ? (downSnapshot?.px ?? point.px) : null;\n\t\t\tconst downUv = pressed ? (downSnapshot?.uv ?? point.uv) : null;\n\t\t\tlet dragging = false;\n\t\t\tif (pressed && downPx) {\n\t\t\t\tconst dx = point.px[0] - downPx[0];\n\t\t\t\tconst dy = point.px[1] - downPx[1];\n\t\t\t\tdragging = Math.hypot(dx, dy) > 0;\n\t\t\t}\n\t\t\tconst nextState = updatePointerState({\n\t\t\t\tpoint,\n\t\t\t\tinside: point.inside,\n\t\t\t\tpressed,\n\t\t\t\tdragging,\n\t\t\t\tpointerType: normalizePointerKind(event.pointerType),\n\t\t\t\tpointerId: event.pointerId,\n\t\t\t\tbutton: pressed ? (downSnapshot?.button ?? event.button) : null,\n\t\t\t\tbuttons: event.buttons,\n\t\t\t\tdownPx,\n\t\t\t\tdownUv\n\t\t\t});\n\t\t\toptions.onMove?.(nextState, event);\n\t\t};\n\n\t\tconst handleWindowMove = (event: PointerEvent): void => {\n\t\t\tif (!trackOutside || activePointerId === null || event.pointerId !== activePointerId) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst point = getPointerCoordinates(\n\t\t\t\tevent.clientX,\n\t\t\t\tevent.clientY,\n\t\t\t\tcanvas.getBoundingClientRect()\n\t\t\t);\n\t\t\tif (point.inside) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst downPx = downSnapshot?.px ?? point.px;\n\t\t\tconst downUv = downSnapshot?.uv ?? point.uv;\n\t\t\tconst dx = point.px[0] - downPx[0];\n\t\t\tconst dy = point.px[1] - downPx[1];\n\t\t\tconst nextState = updatePointerState({\n\t\t\t\tpoint,\n\t\t\t\tinside: false,\n\t\t\t\tpressed: true,\n\t\t\t\tdragging: Math.hypot(dx, dy) > 0,\n\t\t\t\tpointerType: downSnapshot?.pointerType ?? normalizePointerKind(event.pointerType),\n\t\t\t\tpointerId: event.pointerId,\n\t\t\t\tbutton: downSnapshot?.button ?? event.button,\n\t\t\t\tbuttons: event.buttons,\n\t\t\t\tdownPx,\n\t\t\t\tdownUv\n\t\t\t});\n\t\t\toptions.onMove?.(nextState, event);\n\t\t};\n\n\t\tconst releasePointer = (event: PointerEvent, emitClick: boolean): void => {\n\t\t\tif (activePointerId === null || event.pointerId !== activePointerId) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst point = getPointerCoordinates(\n\t\t\t\tevent.clientX,\n\t\t\t\tevent.clientY,\n\t\t\t\tcanvas.getBoundingClientRect()\n\t\t\t);\n\t\t\tconst pointerType = downSnapshot?.pointerType ?? normalizePointerKind(event.pointerType);\n\t\t\tconst previous = downSnapshot;\n\t\t\tconst nextState = updatePointerState({\n\t\t\t\tpoint,\n\t\t\t\tinside: point.inside,\n\t\t\t\tpressed: false,\n\t\t\t\tdragging: false,\n\t\t\t\tpointerType,\n\t\t\t\tpointerId: null,\n\t\t\t\tbutton: null,\n\t\t\t\tbuttons: event.buttons,\n\t\t\t\tdownPx: null,\n\t\t\t\tdownUv: null\n\t\t\t});\n\t\t\toptions.onUp?.(nextState, event);\n\n\t\t\tif (capturePointer && canvas.hasPointerCapture(event.pointerId)) {\n\t\t\t\ttry {\n\t\t\t\t\tcanvas.releasePointerCapture(event.pointerId);\n\t\t\t\t} catch {\n\t\t\t\t\t// Browser rejected release for this pointer id.\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (emitClick && clickEnabled && previous && clickButtons.has(previous.button)) {\n\t\t\t\tconst durationMs = getPointerNowSeconds() * 1000 - previous.timeMs;\n\t\t\t\tconst dx = point.px[0] - previous.px[0];\n\t\t\t\tconst dy = point.px[1] - previous.px[1];\n\t\t\t\tconst moveDistance = Math.hypot(dx, dy);\n\t\t\t\tif (\n\t\t\t\t\tprevious.inside &&\n\t\t\t\t\tpoint.inside &&\n\t\t\t\t\tdurationMs <= clickMaxDurationMs &&\n\t\t\t\t\tmoveDistance <= clickMaxMovePx\n\t\t\t\t) {\n\t\t\t\t\tclickCounter += 1;\n\t\t\t\t\tconst click: PointerClick = {\n\t\t\t\t\t\tid: clickCounter,\n\t\t\t\t\t\ttime: getPointerNowSeconds(),\n\t\t\t\t\t\tpointerType,\n\t\t\t\t\t\tpointerId: event.pointerId,\n\t\t\t\t\t\tbutton: previous.button,\n\t\t\t\t\t\tmodifiers: {\n\t\t\t\t\t\t\talt: event.altKey,\n\t\t\t\t\t\t\tctrl: event.ctrlKey,\n\t\t\t\t\t\t\tshift: event.shiftKey,\n\t\t\t\t\t\t\tmeta: event.metaKey\n\t\t\t\t\t\t},\n\t\t\t\t\t\tpx: point.px,\n\t\t\t\t\t\tuv: point.uv,\n\t\t\t\t\t\tndc: point.ndc\n\t\t\t\t\t};\n\t\t\t\t\tlastClick.set(click);\n\t\t\t\t\toptions.onClick?.(click, nextState, event);\n\t\t\t\t\trequestFrame();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tactivePointerId = null;\n\t\t\tdownSnapshot = null;\n\t\t};\n\n\t\tconst handlePointerUp = (event: PointerEvent): void => {\n\t\t\treleasePointer(event, true);\n\t\t};\n\n\t\tconst handlePointerCancel = (event: PointerEvent): void => {\n\t\t\treleasePointer(event, false);\n\t\t};\n\n\t\tconst handlePointerLeave = (): void => {\n\t\t\tif (activePointerId !== null) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tupdateInsideState(false);\n\t\t};\n\n\t\tcanvas.addEventListener('pointerdown', handlePointerDown);\n\t\tcanvas.addEventListener('pointermove', handleMove);\n\t\tcanvas.addEventListener('pointerup', handlePointerUp);\n\t\tcanvas.addEventListener('pointercancel', handlePointerCancel);\n\t\tcanvas.addEventListener('pointerleave', handlePointerLeave);\n\t\tif (trackOutside) {\n\t\t\twindow.addEventListener('pointermove', handleWindowMove);\n\t\t\twindow.addEventListener('pointerup', handlePointerUp);\n\t\t\twindow.addEventListener('pointercancel', handlePointerCancel);\n\t\t}\n\n\t\tcleanup = (): void => {\n\t\t\tcanvas.removeEventListener('pointerdown', handlePointerDown);\n\t\t\tcanvas.removeEventListener('pointermove', handleMove);\n\t\t\tcanvas.removeEventListener('pointerup', handlePointerUp);\n\t\t\tcanvas.removeEventListener('pointercancel', handlePointerCancel);\n\t\t\tcanvas.removeEventListener('pointerleave', handlePointerLeave);\n\t\t\tif (trackOutside) {\n\t\t\t\twindow.removeEventListener('pointermove', handleWindowMove);\n\t\t\t\twindow.removeEventListener('pointerup', handlePointerUp);\n\t\t\t\twindow.removeEventListener('pointercancel', handlePointerCancel);\n\t\t\t}\n\t\t};\n\t};\n\n\tonMounted(attachListeners);\n\tonBeforeUnmount(() => {\n\t\tcleanup?.();\n\t\tcleanup = null;\n\t});\n\n\treturn {\n\t\tstate: pointerState,\n\t\tlastClick,\n\t\tresetClick: () => {\n\t\t\tlastClick.set(null);\n\t\t}\n\t};\n}\n"],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ import { type CurrentReadable } from '../core/current-value.js';
2
+ import { type LoadedTexture, type TextureLoadOptions } from '../core/texture-loader.js';
3
+ import { type MotionGPUErrorReport } from '../core/error-report.js';
4
+ /**
5
+ * Reactive state returned by {@link useTexture}.
6
+ */
7
+ export interface UseTextureResult {
8
+ /**
9
+ * Loaded textures or `null` when unavailable/failed.
10
+ */
11
+ textures: CurrentReadable<LoadedTexture[] | null>;
12
+ /**
13
+ * `true` while an active load request is running.
14
+ */
15
+ loading: CurrentReadable<boolean>;
16
+ /**
17
+ * Last loading error.
18
+ */
19
+ error: CurrentReadable<Error | null>;
20
+ /**
21
+ * Last loading error normalized to MotionGPU diagnostics report shape.
22
+ */
23
+ errorReport: CurrentReadable<MotionGPUErrorReport | null>;
24
+ /**
25
+ * Reloads all textures using current URL input.
26
+ */
27
+ reload: () => Promise<void>;
28
+ }
29
+ /**
30
+ * Supported URL input variants for `useTexture`.
31
+ */
32
+ export type TextureUrlInput = string[] | (() => string[]);
33
+ /**
34
+ * Supported options input variants for `useTexture`.
35
+ */
36
+ export type TextureOptionsInput = TextureLoadOptions | (() => TextureLoadOptions);
37
+ /**
38
+ * Loads textures from URLs and exposes reactive loading/error state.
39
+ *
40
+ * @param urlInput - URLs array or lazy URL provider.
41
+ * @param optionsInput - Loader options object or lazy options provider.
42
+ * @returns Reactive texture loading state with reload support.
43
+ */
44
+ export declare function useTexture(urlInput: TextureUrlInput, optionsInput?: TextureOptionsInput): UseTextureResult;
45
+ //# sourceMappingURL=use-texture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-texture.d.ts","sourceRoot":"","sources":["../../src/lib/vue/use-texture.ts"],"names":[],"mappings":"AACA,OAAO,EAEN,KAAK,eAAe,EACpB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAGN,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAA0B,KAAK,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAE5F;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAChC;;OAEG;IACH,QAAQ,EAAE,eAAe,CAAC,aAAa,EAAE,GAAG,IAAI,CAAC,CAAC;IAClD;;OAEG;IACH,OAAO,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC;IAClC;;OAEG;IACH,KAAK,EAAE,eAAe,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IACrC;;OAEG;IACH,WAAW,EAAE,eAAe,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IAC1D;;OAEG;IACH,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,EAAE,GAAG,CAAC,MAAM,MAAM,EAAE,CAAC,CAAC;AAE1D;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG,kBAAkB,GAAG,CAAC,MAAM,kBAAkB,CAAC,CAAC;AAkElF;;;;;;GAMG;AACH,wBAAgB,UAAU,CACzB,QAAQ,EAAE,eAAe,EACzB,YAAY,GAAE,mBAAwB,GACpC,gBAAgB,CA4GlB"}
@@ -0,0 +1,135 @@
1
+ import { toMotionGPUErrorReport } from "../core/error-report.js";
2
+ import { createCurrentWritable } from "../core/current-value.js";
3
+ import { isAbortError, loadTexturesFromUrls } from "../core/texture-loader.js";
4
+ import { onBeforeUnmount } from "vue";
5
+ //#region src/lib/vue/use-texture.ts
6
+ /**
7
+ * Normalizes unknown thrown values to an `Error` instance.
8
+ */
9
+ function toError(error) {
10
+ if (error instanceof Error) return error;
11
+ return /* @__PURE__ */ new Error("Unknown texture loading error");
12
+ }
13
+ /**
14
+ * Releases GPU-side resources for a list of loaded textures.
15
+ */
16
+ function disposeTextures(list) {
17
+ for (const texture of list ?? []) texture.dispose();
18
+ }
19
+ function mergeAbortSignals(primary, secondary) {
20
+ if (!secondary) return {
21
+ signal: primary,
22
+ dispose: () => {}
23
+ };
24
+ if (typeof AbortSignal.any === "function") return {
25
+ signal: AbortSignal.any([primary, secondary]),
26
+ dispose: () => {}
27
+ };
28
+ const fallback = new AbortController();
29
+ let disposed = false;
30
+ const cleanup = () => {
31
+ if (disposed) return;
32
+ disposed = true;
33
+ primary.removeEventListener("abort", abort);
34
+ secondary.removeEventListener("abort", abort);
35
+ };
36
+ const abort = () => fallback.abort();
37
+ primary.addEventListener("abort", abort, { once: true });
38
+ secondary.addEventListener("abort", abort, { once: true });
39
+ return {
40
+ signal: fallback.signal,
41
+ dispose: cleanup
42
+ };
43
+ }
44
+ /**
45
+ * Loads textures from URLs and exposes reactive loading/error state.
46
+ *
47
+ * @param urlInput - URLs array or lazy URL provider.
48
+ * @param optionsInput - Loader options object or lazy options provider.
49
+ * @returns Reactive texture loading state with reload support.
50
+ */
51
+ function useTexture(urlInput, optionsInput = {}) {
52
+ const textures = createCurrentWritable(null);
53
+ const loading = createCurrentWritable(true);
54
+ const error = createCurrentWritable(null);
55
+ const errorReport = createCurrentWritable(null);
56
+ let disposed = false;
57
+ let requestVersion = 0;
58
+ let activeController = null;
59
+ let runningLoad = null;
60
+ let reloadQueued = false;
61
+ const getUrls = typeof urlInput === "function" ? urlInput : () => urlInput;
62
+ const getOptions = typeof optionsInput === "function" ? optionsInput : () => optionsInput;
63
+ const executeLoad = async () => {
64
+ if (disposed) return;
65
+ const version = ++requestVersion;
66
+ const controller = new AbortController();
67
+ activeController = controller;
68
+ loading.set(true);
69
+ error.set(null);
70
+ errorReport.set(null);
71
+ const previous = textures.current;
72
+ const options = getOptions() ?? {};
73
+ const mergedSignal = mergeAbortSignals(controller.signal, options.signal);
74
+ try {
75
+ const loaded = await loadTexturesFromUrls(getUrls(), {
76
+ ...options,
77
+ signal: mergedSignal.signal
78
+ });
79
+ if (disposed || version !== requestVersion) {
80
+ disposeTextures(loaded);
81
+ return;
82
+ }
83
+ textures.set(loaded);
84
+ disposeTextures(previous);
85
+ } catch (nextError) {
86
+ if (disposed || version !== requestVersion) return;
87
+ if (isAbortError(nextError)) return;
88
+ disposeTextures(previous);
89
+ textures.set(null);
90
+ const normalizedError = toError(nextError);
91
+ error.set(normalizedError);
92
+ errorReport.set(toMotionGPUErrorReport(normalizedError, "initialization"));
93
+ } finally {
94
+ if (!disposed && version === requestVersion) loading.set(false);
95
+ if (activeController === controller) activeController = null;
96
+ mergedSignal.dispose();
97
+ }
98
+ };
99
+ const runLoadLoop = async () => {
100
+ do {
101
+ reloadQueued = false;
102
+ await executeLoad();
103
+ } while (reloadQueued && !disposed);
104
+ };
105
+ const load = () => {
106
+ activeController?.abort();
107
+ if (runningLoad) {
108
+ reloadQueued = true;
109
+ return runningLoad;
110
+ }
111
+ const trackedPending = runLoadLoop().finally(() => {
112
+ if (runningLoad === trackedPending) runningLoad = null;
113
+ });
114
+ runningLoad = trackedPending;
115
+ return trackedPending;
116
+ };
117
+ load();
118
+ onBeforeUnmount(() => {
119
+ disposed = true;
120
+ requestVersion += 1;
121
+ activeController?.abort();
122
+ disposeTextures(textures.current);
123
+ });
124
+ return {
125
+ textures,
126
+ loading,
127
+ error,
128
+ errorReport,
129
+ reload: load
130
+ };
131
+ }
132
+ //#endregion
133
+ export { useTexture };
134
+
135
+ //# sourceMappingURL=use-texture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-texture.js","names":[],"sources":["../../src/lib/vue/use-texture.ts"],"sourcesContent":["import { onBeforeUnmount } from 'vue';\nimport {\n\tcreateCurrentWritable as currentWritable,\n\ttype CurrentReadable\n} from '../core/current-value.js';\nimport {\n\tisAbortError,\n\tloadTexturesFromUrls,\n\ttype LoadedTexture,\n\ttype TextureLoadOptions\n} from '../core/texture-loader.js';\nimport { toMotionGPUErrorReport, type MotionGPUErrorReport } from '../core/error-report.js';\n\n/**\n * Reactive state returned by {@link useTexture}.\n */\nexport interface UseTextureResult {\n\t/**\n\t * Loaded textures or `null` when unavailable/failed.\n\t */\n\ttextures: CurrentReadable<LoadedTexture[] | null>;\n\t/**\n\t * `true` while an active load request is running.\n\t */\n\tloading: CurrentReadable<boolean>;\n\t/**\n\t * Last loading error.\n\t */\n\terror: CurrentReadable<Error | null>;\n\t/**\n\t * Last loading error normalized to MotionGPU diagnostics report shape.\n\t */\n\terrorReport: CurrentReadable<MotionGPUErrorReport | null>;\n\t/**\n\t * Reloads all textures using current URL input.\n\t */\n\treload: () => Promise<void>;\n}\n\n/**\n * Supported URL input variants for `useTexture`.\n */\nexport type TextureUrlInput = string[] | (() => string[]);\n\n/**\n * Supported options input variants for `useTexture`.\n */\nexport type TextureOptionsInput = TextureLoadOptions | (() => TextureLoadOptions);\n\n/**\n * Normalizes unknown thrown values to an `Error` instance.\n */\nfunction toError(error: unknown): Error {\n\tif (error instanceof Error) {\n\t\treturn error;\n\t}\n\n\treturn new Error('Unknown texture loading error');\n}\n\n/**\n * Releases GPU-side resources for a list of loaded textures.\n */\nfunction disposeTextures(list: LoadedTexture[] | null): void {\n\tfor (const texture of list ?? []) {\n\t\ttexture.dispose();\n\t}\n}\n\ninterface MergedAbortSignal {\n\tsignal: AbortSignal;\n\tdispose: () => void;\n}\n\nfunction mergeAbortSignals(\n\tprimary: AbortSignal,\n\tsecondary: AbortSignal | undefined\n): MergedAbortSignal {\n\tif (!secondary) {\n\t\treturn {\n\t\t\tsignal: primary,\n\t\t\tdispose: () => {}\n\t\t};\n\t}\n\n\tif (typeof AbortSignal.any === 'function') {\n\t\treturn {\n\t\t\tsignal: AbortSignal.any([primary, secondary]),\n\t\t\tdispose: () => {}\n\t\t};\n\t}\n\n\tconst fallback = new AbortController();\n\tlet disposed = false;\n\tconst cleanup = (): void => {\n\t\tif (disposed) {\n\t\t\treturn;\n\t\t}\n\t\tdisposed = true;\n\t\tprimary.removeEventListener('abort', abort);\n\t\tsecondary.removeEventListener('abort', abort);\n\t};\n\tconst abort = (): void => fallback.abort();\n\n\tprimary.addEventListener('abort', abort, { once: true });\n\tsecondary.addEventListener('abort', abort, { once: true });\n\n\treturn {\n\t\tsignal: fallback.signal,\n\t\tdispose: cleanup\n\t};\n}\n\n/**\n * Loads textures from URLs and exposes reactive loading/error state.\n *\n * @param urlInput - URLs array or lazy URL provider.\n * @param optionsInput - Loader options object or lazy options provider.\n * @returns Reactive texture loading state with reload support.\n */\nexport function useTexture(\n\turlInput: TextureUrlInput,\n\toptionsInput: TextureOptionsInput = {}\n): UseTextureResult {\n\tconst textures = currentWritable<LoadedTexture[] | null>(null);\n\tconst loading = currentWritable(true);\n\tconst error = currentWritable<Error | null>(null);\n\tconst errorReport = currentWritable<MotionGPUErrorReport | null>(null);\n\tlet disposed = false;\n\tlet requestVersion = 0;\n\tlet activeController: AbortController | null = null;\n\tlet runningLoad: Promise<void> | null = null;\n\tlet reloadQueued = false;\n\tconst getUrls = typeof urlInput === 'function' ? urlInput : () => urlInput;\n\tconst getOptions =\n\t\ttypeof optionsInput === 'function'\n\t\t\t? (optionsInput as () => TextureLoadOptions)\n\t\t\t: () => optionsInput;\n\n\tconst executeLoad = async (): Promise<void> => {\n\t\tif (disposed) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst version = ++requestVersion;\n\t\tconst controller = new AbortController();\n\t\tactiveController = controller;\n\t\tloading.set(true);\n\t\terror.set(null);\n\t\terrorReport.set(null);\n\n\t\tconst previous = textures.current;\n\t\tconst options = getOptions() ?? {};\n\t\tconst mergedSignal = mergeAbortSignals(controller.signal, options.signal);\n\t\ttry {\n\t\t\tconst loaded = await loadTexturesFromUrls(getUrls(), {\n\t\t\t\t...options,\n\t\t\t\tsignal: mergedSignal.signal\n\t\t\t});\n\t\t\tif (disposed || version !== requestVersion) {\n\t\t\t\tdisposeTextures(loaded);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttextures.set(loaded);\n\t\t\tdisposeTextures(previous);\n\t\t} catch (nextError) {\n\t\t\tif (disposed || version !== requestVersion) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (isAbortError(nextError)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tdisposeTextures(previous);\n\t\t\ttextures.set(null);\n\t\t\tconst normalizedError = toError(nextError);\n\t\t\terror.set(normalizedError);\n\t\t\terrorReport.set(toMotionGPUErrorReport(normalizedError, 'initialization'));\n\t\t} finally {\n\t\t\tif (!disposed && version === requestVersion) {\n\t\t\t\tloading.set(false);\n\t\t\t}\n\t\t\tif (activeController === controller) {\n\t\t\t\tactiveController = null;\n\t\t\t}\n\t\t\tmergedSignal.dispose();\n\t\t}\n\t};\n\n\tconst runLoadLoop = async (): Promise<void> => {\n\t\tdo {\n\t\t\treloadQueued = false;\n\t\t\tawait executeLoad();\n\t\t} while (reloadQueued && !disposed);\n\t};\n\n\tconst load = (): Promise<void> => {\n\t\tactiveController?.abort();\n\t\tif (runningLoad) {\n\t\t\treloadQueued = true;\n\t\t\treturn runningLoad;\n\t\t}\n\n\t\tconst pending = runLoadLoop();\n\t\tconst trackedPending = pending.finally(() => {\n\t\t\tif (runningLoad === trackedPending) {\n\t\t\t\trunningLoad = null;\n\t\t\t}\n\t\t});\n\t\trunningLoad = trackedPending;\n\t\treturn trackedPending;\n\t};\n\n\tvoid load();\n\n\tonBeforeUnmount(() => {\n\t\tdisposed = true;\n\t\trequestVersion += 1;\n\t\tactiveController?.abort();\n\t\tdisposeTextures(textures.current);\n\t});\n\n\treturn {\n\t\ttextures,\n\t\tloading,\n\t\terror,\n\t\terrorReport,\n\t\treload: load\n\t};\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@motion-core/motion-gpu",
3
- "version": "0.7.0",
4
- "description": "Framework-agnostic WebGPU runtime for fullscreen WGSL shaders with explicit Svelte and React adapter entrypoints.",
3
+ "version": "0.8.0",
4
+ "description": "Framework-agnostic WebGPU runtime for fullscreen WGSL shaders with explicit Svelte, React, and Vue adapter entrypoints.",
5
5
  "keywords": [
6
6
  "svelte",
7
7
  "svelte5",
@@ -56,6 +56,14 @@
56
56
  "types": "./dist/react/advanced.d.ts",
57
57
  "default": "./dist/react/advanced.js"
58
58
  },
59
+ "./vue": {
60
+ "types": "./dist/vue/index.d.ts",
61
+ "default": "./dist/vue/index.js"
62
+ },
63
+ "./vue/advanced": {
64
+ "types": "./dist/vue/advanced.d.ts",
65
+ "default": "./dist/vue/advanced.js"
66
+ },
59
67
  "./core": {
60
68
  "types": "./dist/core/index.d.ts",
61
69
  "default": "./dist/core/index.js"
@@ -77,7 +85,7 @@
77
85
  "build": "vite build --config ./vite.package.config.ts",
78
86
  "build:watch": "vite build --watch --config ./vite.package.config.ts",
79
87
  "prepack": "bun run build",
80
- "check": "svelte-check --tsconfig ./tsconfig.json && publint",
88
+ "check": "svelte-check --tsconfig ./tsconfig.json && vue-tsc --project ./tsconfig.vue.json --noEmit && publint",
81
89
  "test": "vitest run",
82
90
  "test:coverage": "vitest run --coverage",
83
91
  "test:watch": "vitest",
@@ -86,6 +94,7 @@
86
94
  "e2e:serve": "vite --config e2e/vite.config.ts",
87
95
  "e2e:serve:svelte": "MOTION_GPU_E2E_FRAMEWORK=svelte vite --config e2e/vite.config.ts",
88
96
  "e2e:serve:react": "MOTION_GPU_E2E_FRAMEWORK=react vite --config e2e/vite.config.ts",
97
+ "e2e:serve:vue": "MOTION_GPU_E2E_FRAMEWORK=vue vite --config e2e/vite.config.ts",
89
98
  "perf:core": "bun run ./scripts/perf/core-benchmark.ts",
90
99
  "perf:core:check": "bun run ./scripts/perf/core-benchmark.ts --strict",
91
100
  "perf:core:baseline": "bun run ./scripts/perf/core-benchmark.ts --update-baseline",
@@ -98,7 +107,8 @@
98
107
  "peerDependencies": {
99
108
  "react": "^18.0.0 || ^19.0.0",
100
109
  "react-dom": "^18.0.0 || ^19.0.0",
101
- "svelte": "^5.0.0"
110
+ "svelte": "^5.0.0",
111
+ "vue": "^3.5.0"
102
112
  },
103
113
  "peerDependenciesMeta": {
104
114
  "react": {
@@ -109,6 +119,9 @@
109
119
  },
110
120
  "svelte": {
111
121
  "optional": true
122
+ },
123
+ "vue": {
124
+ "optional": true
112
125
  }
113
126
  },
114
127
  "dependencies": {
@@ -121,10 +134,13 @@
121
134
  "@sveltejs/vite-plugin-svelte": "^7.0.0",
122
135
  "@testing-library/react": "^16.3.0",
123
136
  "@testing-library/svelte": "^5.2.8",
137
+ "@testing-library/vue": "^8.1.0",
138
+ "@types/node": "^25.3.3",
124
139
  "@types/react": "^19.2.2",
125
140
  "@types/react-dom": "^19.2.2",
141
+ "@vitejs/plugin-vue": "^6.0.1",
126
142
  "@vitest/coverage-v8": "^4.0.18",
127
- "@types/node": "^25.3.3",
143
+ "@vue/tsconfig": "^0.8.1",
128
144
  "eslint": "^10.0.2",
129
145
  "eslint-config-prettier": "^10.1.8",
130
146
  "eslint-plugin-svelte": "^3.14.0",
@@ -137,11 +153,13 @@
137
153
  "react": "^19.2.0",
138
154
  "react-dom": "^19.2.0",
139
155
  "svelte": "^5.51.0",
140
- "svelte2tsx": "^0.7.51",
141
156
  "svelte-check": "^4.3.6",
157
+ "svelte2tsx": "^0.7.51",
142
158
  "typescript": "^5.9.3",
143
159
  "typescript-eslint": "^8.54.0",
144
160
  "vite": "^8.0.1",
145
- "vitest": "^4.0.18"
161
+ "vitest": "^4.0.18",
162
+ "vue": "^3.5.13",
163
+ "vue-tsc": "^3.1.0"
146
164
  }
147
165
  }