@ghia/native 0.0.0 → 0.11.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 (211) hide show
  1. package/LICENSE +201 -0
  2. package/browser.mjs +7 -0
  3. package/dist/aria.d.ts +72 -0
  4. package/dist/aria.d.ts.map +1 -0
  5. package/dist/aria.js +2 -0
  6. package/dist/aria.js.map +1 -0
  7. package/dist/automation/client.d.ts +243 -0
  8. package/dist/automation/client.d.ts.map +1 -0
  9. package/dist/automation/client.js +801 -0
  10. package/dist/automation/client.js.map +1 -0
  11. package/dist/automation/index.d.ts +5 -0
  12. package/dist/automation/index.d.ts.map +1 -0
  13. package/dist/automation/index.js +4 -0
  14. package/dist/automation/index.js.map +1 -0
  15. package/dist/automation/protocol.d.ts +309 -0
  16. package/dist/automation/protocol.d.ts.map +1 -0
  17. package/dist/automation/protocol.js +320 -0
  18. package/dist/automation/protocol.js.map +1 -0
  19. package/dist/blob-images.d.ts +32 -0
  20. package/dist/blob-images.d.ts.map +1 -0
  21. package/dist/blob-images.js +55 -0
  22. package/dist/blob-images.js.map +1 -0
  23. package/dist/bounds.d.ts +61 -0
  24. package/dist/bounds.d.ts.map +1 -0
  25. package/dist/bounds.js +85 -0
  26. package/dist/bounds.js.map +1 -0
  27. package/dist/clipboard-global.d.ts +11 -0
  28. package/dist/clipboard-global.d.ts.map +1 -0
  29. package/dist/clipboard-global.js +2 -0
  30. package/dist/clipboard-global.js.map +1 -0
  31. package/dist/clipboard.d.ts +19 -0
  32. package/dist/clipboard.d.ts.map +1 -0
  33. package/dist/clipboard.js +41 -0
  34. package/dist/clipboard.js.map +1 -0
  35. package/dist/cpu-throttle.d.ts +6 -0
  36. package/dist/cpu-throttle.d.ts.map +1 -0
  37. package/dist/cpu-throttle.js +42 -0
  38. package/dist/cpu-throttle.js.map +1 -0
  39. package/dist/events/data-transfer.d.ts +23 -0
  40. package/dist/events/data-transfer.d.ts.map +1 -0
  41. package/dist/events/data-transfer.js +97 -0
  42. package/dist/events/data-transfer.js.map +1 -0
  43. package/dist/events/dispatch.d.ts +144 -0
  44. package/dist/events/dispatch.d.ts.map +1 -0
  45. package/dist/events/dispatch.js +241 -0
  46. package/dist/events/dispatch.js.map +1 -0
  47. package/dist/events/drag.d.ts +21 -0
  48. package/dist/events/drag.d.ts.map +1 -0
  49. package/dist/events/drag.js +111 -0
  50. package/dist/events/drag.js.map +1 -0
  51. package/dist/events/event-props.d.ts +19 -0
  52. package/dist/events/event-props.d.ts.map +1 -0
  53. package/dist/events/event-props.js +104 -0
  54. package/dist/events/event-props.js.map +1 -0
  55. package/dist/events/keyboard-defaults.d.ts +18 -0
  56. package/dist/events/keyboard-defaults.d.ts.map +1 -0
  57. package/dist/events/keyboard-defaults.js +121 -0
  58. package/dist/events/keyboard-defaults.js.map +1 -0
  59. package/dist/events/listeners.d.ts +18 -0
  60. package/dist/events/listeners.d.ts.map +1 -0
  61. package/dist/events/listeners.js +49 -0
  62. package/dist/events/listeners.js.map +1 -0
  63. package/dist/events/route.d.ts +17 -0
  64. package/dist/events/route.d.ts.map +1 -0
  65. package/dist/events/route.js +36 -0
  66. package/dist/events/route.js.map +1 -0
  67. package/dist/events/types.d.ts +269 -0
  68. package/dist/events/types.d.ts.map +1 -0
  69. package/dist/events/types.js +2 -0
  70. package/dist/events/types.js.map +1 -0
  71. package/dist/events/window-target.d.ts +120 -0
  72. package/dist/events/window-target.d.ts.map +1 -0
  73. package/dist/events/window-target.js +311 -0
  74. package/dist/events/window-target.js.map +1 -0
  75. package/dist/eyedropper.d.ts +33 -0
  76. package/dist/eyedropper.d.ts.map +1 -0
  77. package/dist/eyedropper.js +66 -0
  78. package/dist/eyedropper.js.map +1 -0
  79. package/dist/focus.d.ts +18 -0
  80. package/dist/focus.d.ts.map +1 -0
  81. package/dist/focus.js +26 -0
  82. package/dist/focus.js.map +1 -0
  83. package/dist/fonts.d.ts +12 -0
  84. package/dist/fonts.d.ts.map +1 -0
  85. package/dist/fonts.js +17 -0
  86. package/dist/fonts.js.map +1 -0
  87. package/dist/forms.d.ts +10 -0
  88. package/dist/forms.d.ts.map +1 -0
  89. package/dist/forms.js +9 -0
  90. package/dist/forms.js.map +1 -0
  91. package/dist/host-runtime.d.ts +108 -0
  92. package/dist/host-runtime.d.ts.map +1 -0
  93. package/dist/host-runtime.js +213 -0
  94. package/dist/host-runtime.js.map +1 -0
  95. package/dist/host.d.ts +2403 -0
  96. package/dist/host.d.ts.map +1 -0
  97. package/dist/host.js +30 -0
  98. package/dist/host.js.map +1 -0
  99. package/dist/idle-gc.d.ts +36 -0
  100. package/dist/idle-gc.d.ts.map +1 -0
  101. package/dist/idle-gc.js +70 -0
  102. package/dist/idle-gc.js.map +1 -0
  103. package/dist/keyframes.d.ts +108 -0
  104. package/dist/keyframes.d.ts.map +1 -0
  105. package/dist/keyframes.js +107 -0
  106. package/dist/keyframes.js.map +1 -0
  107. package/dist/layout-animation.d.ts +38 -0
  108. package/dist/layout-animation.d.ts.map +1 -0
  109. package/dist/layout-animation.js +41 -0
  110. package/dist/layout-animation.js.map +1 -0
  111. package/dist/match-media.d.ts +62 -0
  112. package/dist/match-media.d.ts.map +1 -0
  113. package/dist/match-media.js +272 -0
  114. package/dist/match-media.js.map +1 -0
  115. package/dist/media-query.d.ts +21 -0
  116. package/dist/media-query.d.ts.map +1 -0
  117. package/dist/media-query.js +599 -0
  118. package/dist/media-query.js.map +1 -0
  119. package/dist/motion-bindings.d.ts +36 -0
  120. package/dist/motion-bindings.d.ts.map +1 -0
  121. package/dist/motion-bindings.js +107 -0
  122. package/dist/motion-bindings.js.map +1 -0
  123. package/dist/motion-config.d.ts +20 -0
  124. package/dist/motion-config.d.ts.map +1 -0
  125. package/dist/motion-config.js +51 -0
  126. package/dist/motion-config.js.map +1 -0
  127. package/dist/motion-span.d.ts +57 -0
  128. package/dist/motion-span.d.ts.map +1 -0
  129. package/dist/motion-span.js +150 -0
  130. package/dist/motion-span.js.map +1 -0
  131. package/dist/motion-value.d.ts +269 -0
  132. package/dist/motion-value.d.ts.map +1 -0
  133. package/dist/motion-value.js +941 -0
  134. package/dist/motion-value.js.map +1 -0
  135. package/dist/motion-variants.d.ts +190 -0
  136. package/dist/motion-variants.d.ts.map +1 -0
  137. package/dist/motion-variants.js +249 -0
  138. package/dist/motion-variants.js.map +1 -0
  139. package/dist/mutations.d.ts +65 -0
  140. package/dist/mutations.d.ts.map +1 -0
  141. package/dist/mutations.js +320 -0
  142. package/dist/mutations.js.map +1 -0
  143. package/dist/nativeview.d.ts +10 -0
  144. package/dist/nativeview.d.ts.map +1 -0
  145. package/dist/nativeview.js +92 -0
  146. package/dist/nativeview.js.map +1 -0
  147. package/dist/observers.d.ts +92 -0
  148. package/dist/observers.d.ts.map +1 -0
  149. package/dist/observers.js +579 -0
  150. package/dist/observers.js.map +1 -0
  151. package/dist/presence.d.ts +63 -0
  152. package/dist/presence.d.ts.map +1 -0
  153. package/dist/presence.js +54 -0
  154. package/dist/presence.js.map +1 -0
  155. package/dist/renderer-state.d.ts +155 -0
  156. package/dist/renderer-state.d.ts.map +1 -0
  157. package/dist/renderer-state.js +302 -0
  158. package/dist/renderer-state.js.map +1 -0
  159. package/dist/reorder.d.ts +63 -0
  160. package/dist/reorder.d.ts.map +1 -0
  161. package/dist/reorder.js +303 -0
  162. package/dist/reorder.js.map +1 -0
  163. package/dist/runtime.d.ts +23 -0
  164. package/dist/runtime.d.ts.map +1 -0
  165. package/dist/runtime.js +157 -0
  166. package/dist/runtime.js.map +1 -0
  167. package/dist/screen.d.ts +8 -0
  168. package/dist/screen.d.ts.map +1 -0
  169. package/dist/screen.js +16 -0
  170. package/dist/screen.js.map +1 -0
  171. package/dist/scroll.d.ts +79 -0
  172. package/dist/scroll.d.ts.map +1 -0
  173. package/dist/scroll.js +170 -0
  174. package/dist/scroll.js.map +1 -0
  175. package/dist/style-images.d.ts +20 -0
  176. package/dist/style-images.d.ts.map +1 -0
  177. package/dist/style-images.js +57 -0
  178. package/dist/style-images.js.map +1 -0
  179. package/dist/svg.d.ts +16 -0
  180. package/dist/svg.d.ts.map +1 -0
  181. package/dist/svg.js +119 -0
  182. package/dist/svg.js.map +1 -0
  183. package/dist/testing.d.ts +811 -0
  184. package/dist/testing.d.ts.map +1 -0
  185. package/dist/testing.js +1187 -0
  186. package/dist/testing.js.map +1 -0
  187. package/dist/text-match.d.ts +8 -0
  188. package/dist/text-match.d.ts.map +1 -0
  189. package/dist/text-match.js +10 -0
  190. package/dist/text-match.js.map +1 -0
  191. package/dist/text-selection.d.ts +34 -0
  192. package/dist/text-selection.d.ts.map +1 -0
  193. package/dist/text-selection.js +23 -0
  194. package/dist/text-selection.js.map +1 -0
  195. package/dist/universal-style.d.ts +22 -0
  196. package/dist/universal-style.d.ts.map +1 -0
  197. package/dist/universal-style.js +58 -0
  198. package/dist/universal-style.js.map +1 -0
  199. package/dist/web-animations.d.ts +300 -0
  200. package/dist/web-animations.d.ts.map +1 -0
  201. package/dist/web-animations.js +1032 -0
  202. package/dist/web-animations.js.map +1 -0
  203. package/dist/webview.d.ts +39 -0
  204. package/dist/webview.d.ts.map +1 -0
  205. package/dist/webview.js +26 -0
  206. package/dist/webview.js.map +1 -0
  207. package/gpuix-native.darwin-arm64.node +4 -0
  208. package/index.cjs +789 -0
  209. package/index.d.ts +2603 -0
  210. package/index.js +729 -0
  211. package/package.json +96 -4
@@ -0,0 +1,1032 @@
1
+ import { flushPendingMutations } from "./bounds.js";
2
+ import { EVENT_PHASE_AT_TARGET, EVENT_PHASE_NONE, reportUncaught } from "./events/dispatch.js";
3
+ import { elementOf, GpuixEventTarget, setDocumentAnimations, } from "./events/window-target.js";
4
+ const HOST_METHODS = [
5
+ "getAnimations",
6
+ "getSubtreeAnimations",
7
+ "getDocumentAnimations",
8
+ "getAnimation",
9
+ "pauseAnimation",
10
+ "playAnimation",
11
+ "cancelAnimation",
12
+ "finishAnimation",
13
+ "reverseAnimation",
14
+ "setAnimationCurrentTime",
15
+ "setAnimationPlaybackRate",
16
+ "updateAnimationPlaybackRate",
17
+ "setAnimationStartTime",
18
+ "getAnimationTimelineTime",
19
+ "releaseAnimation",
20
+ "forgetAnimation",
21
+ "getAnimationEffect",
22
+ "getAnimationKeyframes",
23
+ "updateAnimationTiming",
24
+ ];
25
+ const FINISH_EVENT = "webanimationfinish";
26
+ const CANCEL_EVENT = "webanimationcancel";
27
+ const READY_EVENT = "webanimationready";
28
+ /** What native says when a command refuses, before it is a `DOMException`. */
29
+ const INVALID_STATE_PREFIX = "InvalidStateError: ";
30
+ const TYPE_ERROR_PREFIX = "TypeError: ";
31
+ function deferred() {
32
+ let settle;
33
+ const promise = new Promise((resolve, reject) => {
34
+ settle = { resolve, reject };
35
+ });
36
+ // A cancel rejects it whether or not anyone is listening, and that is not an error to report.
37
+ promise.catch(() => { });
38
+ const result = {
39
+ promise,
40
+ settled: "pending",
41
+ resolve(animation) {
42
+ result.settled = "resolved";
43
+ settle.resolve(animation);
44
+ },
45
+ reject(error) {
46
+ result.settled = "rejected";
47
+ settle.reject(error);
48
+ },
49
+ };
50
+ return result;
51
+ }
52
+ function resolved(animation) {
53
+ const result = deferred();
54
+ result.resolve(animation);
55
+ return result;
56
+ }
57
+ const states = new WeakMap();
58
+ function stateOf(animation) {
59
+ const state = states.get(animation);
60
+ if (!state)
61
+ throw new TypeError("Illegal invocation");
62
+ return state;
63
+ }
64
+ function invalidState(message) {
65
+ return new DOMException(message, "InvalidStateError");
66
+ }
67
+ /**
68
+ * The timeline of every animation of a renderer: its time is the clock of the renderer, in
69
+ * milliseconds, at the last frame. `startTime` and the `timelineTime` of the events are on it.
70
+ * A renderer is the document of its animations, so there is one for each, and no
71
+ * `document.timeline`: the `document` of GPUIX spans every window.
72
+ */
73
+ export class DocumentTimeline {
74
+ #host;
75
+ /** Not for apps: the timeline of an animation is `animation.timeline`. */
76
+ constructor(host) {
77
+ this.#host = host;
78
+ }
79
+ get currentTime() {
80
+ return this.#host.getAnimationTimelineTime();
81
+ }
82
+ }
83
+ /** `readLive` can find the run gone, which the earlier checks of the mode cannot see. */
84
+ function isGone(state) {
85
+ return state.mode === "gone";
86
+ }
87
+ function readLive(state) {
88
+ try {
89
+ const info = state.host.getAnimation(state.id);
90
+ if (info.playState === "idle" && state.last.playState !== "idle" && state.cancelEvents === 0) {
91
+ state.cancelNoticed = { pending: state.last.pending };
92
+ }
93
+ if (info.playState !== "finished")
94
+ state.holdFinished = false;
95
+ state.last = info;
96
+ }
97
+ catch (error) {
98
+ if (!isUnknownAnimation(error))
99
+ throw error;
100
+ // The idle run of a `cancel()` that the style stopped naming: native dropped it without a word.
101
+ state.mode = "gone";
102
+ state.last = { ...state.last, playState: "idle", pending: false, startTime: undefined };
103
+ }
104
+ return state.last;
105
+ }
106
+ /** What native refused with, as the DOM would. */
107
+ function refusal(error) {
108
+ if (error instanceof Error && error.message.startsWith(INVALID_STATE_PREFIX)) {
109
+ return invalidState(error.message.slice(INVALID_STATE_PREFIX.length));
110
+ }
111
+ if (error instanceof Error && error.message.startsWith(TYPE_ERROR_PREFIX)) {
112
+ return new TypeError(error.message.slice(TYPE_ERROR_PREFIX.length));
113
+ }
114
+ return error;
115
+ }
116
+ /**
117
+ * Sends a command to native, then finds what it did: a play or pause that was
118
+ * waiting and is not (`ready`), a finish, and the end of one. A run native no
119
+ * longer has is one that was cancelled since the frame JS last heard of, as far
120
+ * as JS is concerned.
121
+ */
122
+ function command(animation, action, send) {
123
+ const state = stateOf(animation);
124
+ const cancelled = () => invalidState(`Cannot ${action} an animation that was cancelled`);
125
+ if (state.mode === "gone")
126
+ throw cancelled();
127
+ let before;
128
+ try {
129
+ before = readLive(state);
130
+ if (isGone(state))
131
+ throw cancelled();
132
+ send(state);
133
+ }
134
+ catch (error) {
135
+ throw isUnknownAnimation(error) ? cancelled() : refusal(error);
136
+ }
137
+ // A command can bring back an animation that was idle and only weakly held: it plays now, and events are owed to it.
138
+ register(state.registry, state.elementId, state.id, animation);
139
+ settle(animation, state, before);
140
+ }
141
+ /** What the state native is in says about the promises, after a command. */
142
+ function settle(animation, state, before) {
143
+ if (state.mode !== "live")
144
+ return;
145
+ const info = readLive(state);
146
+ state.renewedForCancel = false;
147
+ if (before.pending && !info.pending) {
148
+ if (state.ready.settled === "pending")
149
+ state.ready.resolve(animation);
150
+ }
151
+ else if (info.pending && !before.pending) {
152
+ state.ready = deferred();
153
+ }
154
+ if (info.playState === "finished") {
155
+ if (state.finished.settled === "pending")
156
+ state.finished.resolve(animation);
157
+ }
158
+ else if (state.finished.settled === "resolved") {
159
+ state.finished = deferred();
160
+ }
161
+ }
162
+ function snapshot(read) {
163
+ try {
164
+ return { value: read() };
165
+ }
166
+ catch (error) {
167
+ if (isUnknownAnimation(error))
168
+ throw error;
169
+ return { error: new DOMException(error instanceof Error ? error.message : String(error), "NotSupportedError") };
170
+ }
171
+ }
172
+ function unwrap(snapshot) {
173
+ if ("error" in snapshot)
174
+ throw snapshot.error;
175
+ return snapshot.value;
176
+ }
177
+ const effects = new WeakMap();
178
+ function effectOf(effect) {
179
+ const init = effects.get(effect);
180
+ if (!init)
181
+ throw new TypeError("Illegal invocation");
182
+ return init;
183
+ }
184
+ /** What native says of the effect now; what it said last once it no longer has the run. */
185
+ function readEffect(state) {
186
+ let read = state.effect;
187
+ if (state.mode === "live") {
188
+ try {
189
+ read = state.effect = snapshot(() => state.host.getAnimationEffect(state.id));
190
+ }
191
+ catch (error) {
192
+ if (!isUnknownAnimation(error))
193
+ throw error;
194
+ }
195
+ }
196
+ const info = unwrap(read);
197
+ // A run native let go of is idle, whatever it showed when it went.
198
+ if (state.mode === "gone" || readLive(state).playState === "idle") {
199
+ return { ...info, localTime: undefined, progress: undefined, currentIteration: undefined };
200
+ }
201
+ return info;
202
+ }
203
+ /** A member the DOM takes as a finite number, which `updateTiming()` refuses to leave anything else. */
204
+ function finite(name, value) {
205
+ const number = Number(value);
206
+ if (!Number.isFinite(number))
207
+ throw new TypeError(`Failed to execute 'updateTiming' on 'AnimationEffect': ${name} must be a finite number`);
208
+ return number;
209
+ }
210
+ function oneOf(name, value, names) {
211
+ if (typeof value !== "string" || !names.includes(value)) {
212
+ throw new TypeError(`Failed to execute 'updateTiming' on 'AnimationEffect': ${String(value)} is not a valid ${name}`);
213
+ }
214
+ return value;
215
+ }
216
+ /** The `updateTiming()` argument as native takes it, or the `TypeError` the DOM throws for a member that is wrong. */
217
+ function timingUpdate(timing) {
218
+ if (timing === undefined || timing === null)
219
+ return {};
220
+ if (typeof timing !== "object") {
221
+ throw new TypeError("Failed to execute 'updateTiming' on 'AnimationEffect': the timing is not an object");
222
+ }
223
+ const update = {};
224
+ if (timing.delay !== undefined)
225
+ update.delay = finite("delay", timing.delay);
226
+ if (timing.endDelay !== undefined)
227
+ update.endDelay = finite("endDelay", timing.endDelay);
228
+ if (timing.iterationStart !== undefined) {
229
+ update.iterationStart = finite("iterationStart", timing.iterationStart);
230
+ if (update.iterationStart < 0)
231
+ throw new TypeError("Failed to execute 'updateTiming' on 'AnimationEffect': iterationStart must not be negative");
232
+ }
233
+ if (timing.iterations !== undefined) {
234
+ const iterations = Number(timing.iterations);
235
+ if (Number.isNaN(iterations) || iterations < 0) {
236
+ throw new TypeError("Failed to execute 'updateTiming' on 'AnimationEffect': iterations must not be negative");
237
+ }
238
+ update.iterations = iterations;
239
+ }
240
+ if (timing.duration !== undefined) {
241
+ if (typeof timing.duration === "string") {
242
+ if (timing.duration !== "auto")
243
+ throw new TypeError(`Failed to execute 'updateTiming' on 'AnimationEffect': ${timing.duration} is not a valid duration`);
244
+ update.duration = "auto";
245
+ }
246
+ else {
247
+ const duration = Number(timing.duration);
248
+ if (Number.isNaN(duration) || duration < 0) {
249
+ throw new TypeError("Failed to execute 'updateTiming' on 'AnimationEffect': duration must not be negative");
250
+ }
251
+ update.duration = duration;
252
+ }
253
+ }
254
+ // `auto` is no fill: an effect has none of its own.
255
+ if (timing.fill !== undefined) {
256
+ const fill = oneOf("fill", timing.fill, ["none", "forwards", "backwards", "both", "auto"]);
257
+ update.fill = fill === "auto" ? "none" : fill;
258
+ }
259
+ if (timing.direction !== undefined) {
260
+ update.direction = oneOf("direction", timing.direction, ["normal", "reverse", "alternate", "alternate-reverse"]);
261
+ }
262
+ if (timing.easing !== undefined)
263
+ update.easing = String(timing.easing);
264
+ return update;
265
+ }
266
+ const EASING_KEYWORDS = ["linear", "ease", "ease-in", "ease-out", "ease-in-out", "step-start", "step-end"];
267
+ /** `updateTiming()` of a run native no longer has: it changes what the effect reports from now on. */
268
+ function updateGone(state, update) {
269
+ const info = unwrap(state.effect);
270
+ if (update.easing !== undefined && !EASING_KEYWORDS.includes(update.easing.trim())) {
271
+ throw new DOMException("The easing of an animation that was cancelled for good can be set to a keyword only", "NotSupportedError");
272
+ }
273
+ const next = { ...info };
274
+ if (update.delay !== undefined)
275
+ next.delay = update.delay;
276
+ if (update.endDelay !== undefined)
277
+ next.endDelay = update.endDelay;
278
+ if (update.fill !== undefined)
279
+ next.fill = update.fill;
280
+ if (update.iterationStart !== undefined)
281
+ next.iterationStart = update.iterationStart;
282
+ if (update.iterations !== undefined)
283
+ next.iterations = update.iterations;
284
+ if (update.direction !== undefined)
285
+ next.direction = update.direction;
286
+ if (update.duration !== undefined)
287
+ next.duration = update.duration === "auto" ? undefined : update.duration;
288
+ if (update.easing !== undefined)
289
+ next.easing = update.easing.trim() === "step-start" ? "steps(1, start)" : update.easing.trim() === "step-end" ? "steps(1)" : update.easing.trim();
290
+ const duration = next.duration ?? 0;
291
+ const active = duration === 0 || next.iterations === 0 ? 0 : duration * next.iterations;
292
+ next.activeDuration = active;
293
+ next.endTime = Math.max(next.delay + active + next.endDelay, 0);
294
+ state.effect = { value: next };
295
+ }
296
+ /**
297
+ * The effect of a CSS animation or transition, as the DOM's `KeyframeEffect`: the keyframes
298
+ * and the timing it plays by. `animation.effect` is the same object every time. It cannot be
299
+ * made, and there is no `setKeyframes()`, `composite` or `pseudoElement`.
300
+ */
301
+ export class KeyframeEffect {
302
+ /** Not for apps: the effect of an animation is `animation.effect`. */
303
+ constructor(init) {
304
+ effects.set(this, init);
305
+ }
306
+ /** The element the run is of, also after the element is gone. */
307
+ get target() {
308
+ return effectOf(this).target;
309
+ }
310
+ /**
311
+ * The keyframes with computed values: those of the `@keyframes` rule with the ones Chrome adds
312
+ * for a property that has none at 0% or 100% (`composite: "replace"`, at the value the element
313
+ * has without the animation), or the start and the end of a transition. As of the last frame.
314
+ * Throws a `NotSupportedError` for a value whose computed text is not known yet.
315
+ */
316
+ getKeyframes() {
317
+ const state = stateOf(effectOf(this).animation);
318
+ if (state.mode === "live") {
319
+ try {
320
+ state.keyframes = snapshot(() => state.host.getAnimationKeyframes(state.id));
321
+ }
322
+ catch (error) {
323
+ if (!isUnknownAnimation(error))
324
+ throw error;
325
+ }
326
+ }
327
+ return unwrap(state.keyframes).map((frame) => {
328
+ const keyframe = {
329
+ offset: frame.offset ?? null,
330
+ easing: frame.easing,
331
+ composite: frame.composite,
332
+ computedOffset: frame.computedOffset,
333
+ };
334
+ for (const value of frame.values)
335
+ keyframe[value.property] = value.value;
336
+ return keyframe;
337
+ });
338
+ }
339
+ /**
340
+ * The timing in the units of the DOM: milliseconds, `iterations` `Infinity` for an animation
341
+ * that repeats for ever. For a CSS animation the `easing` is `"linear"` and its
342
+ * `animation-timing-function` is the easing of its keyframes; for a transition it is the
343
+ * `transition-timing-function`.
344
+ */
345
+ getTiming() {
346
+ const info = readEffect(stateOf(effectOf(this).animation));
347
+ return {
348
+ delay: info.delay,
349
+ endDelay: info.endDelay,
350
+ fill: info.fill,
351
+ iterationStart: info.iterationStart,
352
+ iterations: info.iterations,
353
+ duration: info.duration ?? "auto",
354
+ direction: info.direction,
355
+ easing: info.easing,
356
+ };
357
+ }
358
+ /**
359
+ * The timing with what it works out to at the current time of the animation: `localTime`,
360
+ * `progress` and `currentIteration` are `null` for an idle animation and `progress` and
361
+ * `currentIteration` outside the active phase unless a fill holds the effect.
362
+ */
363
+ getComputedTiming() {
364
+ const info = readEffect(stateOf(effectOf(this).animation));
365
+ return {
366
+ delay: info.delay,
367
+ endDelay: info.endDelay,
368
+ fill: info.fill,
369
+ iterationStart: info.iterationStart,
370
+ iterations: info.iterations,
371
+ duration: info.duration ?? 0,
372
+ direction: info.direction,
373
+ easing: info.easing,
374
+ endTime: info.endTime,
375
+ activeDuration: info.activeDuration,
376
+ localTime: info.localTime ?? null,
377
+ progress: info.progress ?? null,
378
+ currentIteration: info.currentIteration ?? null,
379
+ };
380
+ }
381
+ /**
382
+ * Sets the timing members given, for this animation only. What a CSS animation is set to here
383
+ * stays against a later change of its `animation-*` style, which still moves the members that
384
+ * were not set; a running transition never follows a change of its `transition-*` style. A
385
+ * member that is wrong throws a `TypeError` and changes nothing. An effect that ends later than
386
+ * the animation has gone makes a finished animation run again.
387
+ */
388
+ updateTiming(timing) {
389
+ const owner = effectOf(this);
390
+ const update = timingUpdate(timing);
391
+ const state = stateOf(owner.animation);
392
+ if (state.mode === "live") {
393
+ try {
394
+ command(owner.animation, "update the timing of", (live) => live.host.updateAnimationTiming(live.id, update));
395
+ // What the animation keeps once native lets the run go.
396
+ try {
397
+ state.effect = snapshot(() => state.host.getAnimationEffect(state.id));
398
+ }
399
+ catch (error) {
400
+ if (!isUnknownAnimation(error))
401
+ throw error;
402
+ }
403
+ return;
404
+ }
405
+ catch (error) {
406
+ // Cancelled since the frame JS last heard of: the effect is what it was.
407
+ if (!(error instanceof DOMException && error.name === "InvalidStateError"))
408
+ throw error;
409
+ }
410
+ }
411
+ updateGone(state, update);
412
+ }
413
+ }
414
+ /**
415
+ * A CSS animation or transition of an element, or an animation of a `motion` element that Motion would hand to the
416
+ * browser's Web Animations (an `opacity` or `backgroundColor` animation, a colour in `oklch()`, ...; a plain
417
+ * `Animation`, as the one `element.animate()` makes), as `element.getAnimations()`
418
+ * returns it: the Web Animations `Animation` of the DOM, for what the CSS
419
+ * engine runs. `getAnimations()` gives the same object for the same run every
420
+ * time. `playState`, `currentTime`, `playbackRate`, `startTime` and `pending`
421
+ * are read when asked, as of the last frame, which is also the time of
422
+ * `timeline`. The promises `ready` and `finished` and the `finish` and `cancel`
423
+ * events follow Chrome's order.
424
+ *
425
+ * Not built: `replaceState`, `persist()`, `commitStyles()`,
426
+ * `onremove`, and setting `effect`. Every command works on an animation of the motion engine as it does on the one Motion
427
+ * makes with the browser: the motion shows what the animation does, `finish()` completes it, `cancel()` takes it back to
428
+ * where it started without a completion (Motion never hears of a cancel). An animation the style or Motion cancelled is idle,
429
+ * and a command plays it again as in Chrome, without the style owning it any more, for as long as the app holds it.
430
+ */
431
+ export class Animation extends GpuixEventTarget {
432
+ /** Empty for what `getAnimations()` lists: the animations of CSS and of Motion have no id. */
433
+ id = "";
434
+ constructor(init) {
435
+ super();
436
+ const state = {
437
+ host: init.host,
438
+ registry: init.registry,
439
+ timeline: init.timeline,
440
+ id: init.info.id,
441
+ elementId: init.elementId,
442
+ mode: "live",
443
+ last: init.info,
444
+ effect: snapshot(() => init.host.getAnimationEffect(init.info.id)),
445
+ keyframes: snapshot(() => init.host.getAnimationKeyframes(init.info.id)),
446
+ keyframeEffect: new KeyframeEffect({ animation: this, target: init.target }),
447
+ finished: deferred(),
448
+ ready: deferred(),
449
+ cancelEvents: 0,
450
+ renewedForCancel: false,
451
+ cancelNoticed: undefined,
452
+ holdFinished: false,
453
+ handlers: new Map(),
454
+ };
455
+ states.set(this, state);
456
+ // A finished animation that waits for its first frame is told of its finish by the first command or
457
+ // event, not when it is made.
458
+ if (init.info.playState === "finished" && !init.info.pending)
459
+ state.finished.resolve(this);
460
+ if (!init.info.pending)
461
+ state.ready.resolve(this);
462
+ // An animation made finished (an effect with no length) has nothing to wait a frame for: Chrome
463
+ // runs its play right after the script that first read it, which a seek to where it is does here.
464
+ if (init.info.pending && init.info.playState === "finished")
465
+ queueMicrotask(() => startFinished(this));
466
+ }
467
+ /** The timeline all the animations of a renderer share. */
468
+ get timeline() {
469
+ return stateOf(this).timeline;
470
+ }
471
+ /** The keyframes and the timing the run plays by. The same object every time. */
472
+ get effect() {
473
+ return stateOf(this).keyframeEffect;
474
+ }
475
+ /**
476
+ * Milliseconds from the start, the delay inside it: 0 at the start whatever
477
+ * the delay, and the end of the effect when it ends. `null` once the
478
+ * animation is cancelled. Setting it seeks at once and the next frame paints
479
+ * it; `null` cannot be set.
480
+ */
481
+ get currentTime() {
482
+ const state = stateOf(this);
483
+ switch (state.mode) {
484
+ case "live": {
485
+ const info = readLive(state);
486
+ return info.playState === "idle" ? null : info.currentTime;
487
+ }
488
+ case "gone":
489
+ return null;
490
+ }
491
+ }
492
+ set currentTime(value) {
493
+ if (typeof value !== "number" || !Number.isFinite(value)) {
494
+ throw new TypeError(`Failed to set currentTime: ${String(value)} is not a finite number`);
495
+ }
496
+ command(this, "set currentTime of", (state) => state.host.setAnimationCurrentTime(state.id, value));
497
+ }
498
+ /**
499
+ * When the animation started on `timeline`, in milliseconds: `currentTime` is
500
+ * `(timeline.currentTime - startTime) * playbackRate`. `null` while it is
501
+ * pending, paused or idle. Setting it moves the animation to that time; `null`
502
+ * holds the time it is at.
503
+ */
504
+ get startTime() {
505
+ const state = stateOf(this);
506
+ switch (state.mode) {
507
+ case "live":
508
+ return readLive(state).startTime ?? null;
509
+ case "gone":
510
+ return null;
511
+ }
512
+ }
513
+ set startTime(value) {
514
+ if (value !== null && (typeof value !== "number" || !Number.isFinite(value))) {
515
+ throw new TypeError(`Failed to set startTime: ${String(value)} is not a finite number or null`);
516
+ }
517
+ command(this, "set startTime of", (state) => state.host.setAnimationStartTime(state.id, value));
518
+ }
519
+ /** The speed of time, negative to run back. Changing it keeps `currentTime`. */
520
+ get playbackRate() {
521
+ const state = stateOf(this);
522
+ return state.mode === "live" ? readLive(state).playbackRate : state.last.playbackRate;
523
+ }
524
+ set playbackRate(value) {
525
+ if (typeof value !== "number" || !Number.isFinite(value)) {
526
+ throw new TypeError(`Failed to set playbackRate: ${String(value)} is not a finite number`);
527
+ }
528
+ command(this, "set playbackRate of", (state) => state.host.setAnimationPlaybackRate(state.id, value));
529
+ }
530
+ get playState() {
531
+ const state = stateOf(this);
532
+ switch (state.mode) {
533
+ case "live":
534
+ return readLive(state).playState;
535
+ case "gone":
536
+ return "idle";
537
+ }
538
+ }
539
+ /** Whether a play or pause waits for the next frame to run. */
540
+ get pending() {
541
+ const state = stateOf(this);
542
+ return state.mode === "live" && readLive(state).pending;
543
+ }
544
+ /**
545
+ * Resolves with the animation when the play or pause that is pending has
546
+ * run, at the next frame; already resolved when none is. A `pause()` of a
547
+ * running animation, a `play()` of a paused, finished or cancelled one and
548
+ * the others that wait for a frame make a new one, unless one is already
549
+ * waiting. A cancel rejects the one that is waiting with an `AbortError`.
550
+ */
551
+ get ready() {
552
+ return stateOf(this).ready.promise;
553
+ }
554
+ /**
555
+ * Resolves with the animation when it finishes; rejects with an
556
+ * `AbortError` when it is cancelled (the style stopped naming it, a new
557
+ * transition replaced it, its element went). A new promise replaces a
558
+ * resolved one when the animation leaves `finished` again, and a cancel
559
+ * leaves a new pending one.
560
+ */
561
+ get finished() {
562
+ const state = stateOf(this);
563
+ if (state.mode === "live" && state.finished.settled === "resolved") {
564
+ const playState = readLive(state).playState;
565
+ if (playState !== "finished") {
566
+ state.finished = deferred();
567
+ state.renewedForCancel = playState === "idle";
568
+ }
569
+ }
570
+ return state.finished.promise;
571
+ }
572
+ /** Runs when the animation finishes, in the order it was first set among the listeners. */
573
+ get onfinish() {
574
+ return this.#handler("finish");
575
+ }
576
+ set onfinish(handler) {
577
+ this.#setHandler("finish", handler);
578
+ }
579
+ /** Runs when the animation is cancelled, in the order it was first set among the listeners. */
580
+ get oncancel() {
581
+ return this.#handler("cancel");
582
+ }
583
+ set oncancel(handler) {
584
+ this.#setHandler("cancel", handler);
585
+ }
586
+ #handler(type) {
587
+ return stateOf(this).handlers.get(type) ?? null;
588
+ }
589
+ #setHandler(type, handler) {
590
+ const state = stateOf(this);
591
+ const registered = state.handlers.has(type);
592
+ state.handlers.set(type, typeof handler === "function" ? handler : null);
593
+ // The handler is a listener from the moment it is first set, as in the DOM, so the
594
+ // ones added after it come after it.
595
+ if (!registered) {
596
+ this.addEventListener(type, (event) => state.handlers.get(type)?.call(this, event));
597
+ }
598
+ }
599
+ /** Holds the current time at once. From here `animation-play-state` no longer decides. */
600
+ pause() {
601
+ command(this, "pause", (state) => state.host.pauseAnimation(state.id));
602
+ }
603
+ /**
604
+ * Continues from the held time at the next frame. A finished animation
605
+ * starts over: from 0, or from the end when the rate is negative; so does a
606
+ * cancelled one. From here `animation-play-state` no longer decides.
607
+ */
608
+ play() {
609
+ command(this, "play", (state) => state.host.playAnimation(state.id));
610
+ }
611
+ /**
612
+ * Stops the animation: idle, with no current time, out of `getAnimations()`,
613
+ * its element as if it had none. A style change does not start it again;
614
+ * `play()` does, from the start. Its `finished` promise rejects with an
615
+ * `AbortError` and `ready` too if it was waiting; the `cancel` event comes
616
+ * with the next frame.
617
+ */
618
+ cancel() {
619
+ const state = stateOf(this);
620
+ if (state.mode === "gone")
621
+ return;
622
+ const before = readLive(state);
623
+ if (before.playState === "idle")
624
+ return;
625
+ try {
626
+ state.host.cancelAnimation(state.id);
627
+ }
628
+ catch (error) {
629
+ // Cancelled since the frame JS last heard of: its cancel event is on its way.
630
+ if (isUnknownAnimation(error))
631
+ return;
632
+ throw error;
633
+ }
634
+ state.cancelEvents++;
635
+ abort(state, before.pending);
636
+ readLive(state);
637
+ }
638
+ /**
639
+ * Goes to the end the rate runs toward at once, the end of the effect or 0.
640
+ * Throws an `InvalidStateError` at a rate of 0 and for an animation that
641
+ * never ends running forward.
642
+ */
643
+ finish() {
644
+ command(this, "finish", (state) => state.host.finishAnimation(state.id));
645
+ }
646
+ /**
647
+ * Plays the animation the other way: the rate flips when the next frame
648
+ * comes, and an animation at the end it was running from goes to the other.
649
+ * Throws an `InvalidStateError` for an animation that never ends, from its start.
650
+ */
651
+ reverse() {
652
+ command(this, "reverse", (state) => state.host.reverseAnimation(state.id));
653
+ }
654
+ /**
655
+ * Sets the rate the way `playbackRate` does, but keeps the time continuous across the change:
656
+ * a running animation takes the new rate at the next frame, a paused, finished or cancelled
657
+ * one at once.
658
+ */
659
+ updatePlaybackRate(rate) {
660
+ if (typeof rate !== "number" || !Number.isFinite(rate)) {
661
+ throw new TypeError(`Failed to execute updatePlaybackRate: ${String(rate)} is not a finite number`);
662
+ }
663
+ command(this, "update the playbackRate of", (state) => state.host.updateAnimationPlaybackRate(state.id, rate));
664
+ }
665
+ }
666
+ /** A CSS animation: the run of an `animation-name`. */
667
+ export class CSSAnimation extends Animation {
668
+ animationName;
669
+ constructor(init) {
670
+ super(init);
671
+ if (init.info.animationName === undefined) {
672
+ throw new TypeError(`Animation ${init.info.id} is not a CSS animation`);
673
+ }
674
+ this.animationName = init.info.animationName;
675
+ }
676
+ }
677
+ /** A CSS transition: the run of one property. */
678
+ export class CSSTransition extends Animation {
679
+ /** The CSS name of the property, `background-color`. */
680
+ transitionProperty;
681
+ constructor(init) {
682
+ super(init);
683
+ if (init.info.transitionProperty === undefined) {
684
+ throw new TypeError(`Animation ${init.info.id} is not a CSS transition`);
685
+ }
686
+ this.transitionProperty = init.info.transitionProperty;
687
+ }
688
+ }
689
+ /** The animation of a run native has, held or not. */
690
+ function lookup(registry, id) {
691
+ return registry.byId.get(id) ?? registry.weak.get(id)?.deref();
692
+ }
693
+ // Shared through `globalThis` like the renderer states: the JSX runtime can load
694
+ // a second copy of this module, and a copy that lists animations must be heard
695
+ // by the copy that routes the events.
696
+ const REGISTRIES_KEY = Symbol.for("@ghia/native/web-animations");
697
+ function registryOf(renderer) {
698
+ let all = Reflect.get(globalThis, REGISTRIES_KEY);
699
+ if (!all) {
700
+ all = new WeakMap();
701
+ Reflect.set(globalThis, REGISTRIES_KEY, all);
702
+ }
703
+ let registry = all.get(renderer);
704
+ if (!registry) {
705
+ const weak = new Map();
706
+ const made = {
707
+ byId: new Map(),
708
+ byElement: new Map(),
709
+ weak,
710
+ closed: false,
711
+ cancelTasks: new Set(),
712
+ finalizer: new FinalizationRegistry((id) => {
713
+ // A new object for the same run, made since, holds it.
714
+ if (weak.get(id)?.deref())
715
+ return;
716
+ weak.delete(id);
717
+ if (!made.closed)
718
+ renderer.forgetAnimation?.(id);
719
+ }),
720
+ };
721
+ registry = made;
722
+ all.set(renderer, registry);
723
+ }
724
+ return registry;
725
+ }
726
+ /** The window of `renderer` is gone, which the animations it handed out outlive: native has nothing left for them. */
727
+ export function closeWebAnimations(renderer) {
728
+ const all = Reflect.get(globalThis, REGISTRIES_KEY);
729
+ const registry = all?.get(renderer);
730
+ if (!registry)
731
+ return;
732
+ registry.closed = true;
733
+ for (const task of registry.cancelTasks)
734
+ clearTimeout(task);
735
+ registry.cancelTasks.clear();
736
+ }
737
+ function hostOf(renderer) {
738
+ for (const method of HOST_METHODS) {
739
+ if (!renderer[method])
740
+ throw new Error(`This renderer does not support ${method}`);
741
+ }
742
+ return renderer;
743
+ }
744
+ /** The error `getAnimation` throws for a run native no longer has. */
745
+ function isUnknownAnimation(error) {
746
+ return error instanceof Error && error.message.includes("No running CSS animation or transition has the id");
747
+ }
748
+ function unregister(registry, state, animation) {
749
+ registry.byId.delete(state.id);
750
+ if (registry.weak.delete(state.id))
751
+ registry.finalizer.unregister(animation);
752
+ const siblings = registry.byElement.get(state.elementId);
753
+ siblings?.delete(animation);
754
+ if (siblings?.size === 0)
755
+ registry.byElement.delete(state.elementId);
756
+ }
757
+ function register(registry, elementId, id, animation) {
758
+ if (registry.weak.delete(id))
759
+ registry.finalizer.unregister(animation);
760
+ registry.byId.set(id, animation);
761
+ const siblings = registry.byElement.get(elementId) ?? new Set();
762
+ siblings.add(animation);
763
+ registry.byElement.set(elementId, siblings);
764
+ }
765
+ /** The class of the animation of a run: the DOM makes an `Animation` for a script and a subclass for CSS. */
766
+ function animationOf(kind, init) {
767
+ switch (kind) {
768
+ case "transition":
769
+ return new CSSTransition(init);
770
+ case "script":
771
+ return new Animation(init);
772
+ default:
773
+ return new CSSAnimation(init);
774
+ }
775
+ }
776
+ function adopt(registry, host, timeline, target, elementId, info) {
777
+ const known = lookup(registry, info.id);
778
+ if (known) {
779
+ register(registry, elementId, info.id, known);
780
+ return known;
781
+ }
782
+ const init = { host, registry, timeline, target, elementId, info };
783
+ const animation = animationOf(info.kind, init);
784
+ register(registry, elementId, info.id, animation);
785
+ return animation;
786
+ }
787
+ /**
788
+ * `element.getAnimations()`: the CSS transitions, then the CSS animations,
789
+ * that are running, paused or in effect through a fill, in composite order.
790
+ * With `subtree`, those of the element and everything below it: every
791
+ * transition of the elements in document order, then every animation.
792
+ * Pending mutations are sent first, so a style just set is seen.
793
+ */
794
+ export function getElementAnimations(renderer, elementId, target, options) {
795
+ const host = hostOf(renderer);
796
+ flushPendingMutations(renderer);
797
+ const registry = registryOf(renderer);
798
+ const timeline = (registry.timeline ??= new DocumentTimeline(host));
799
+ if (!options?.subtree) {
800
+ return host.getAnimations(elementId).map((info) => adopt(registry, host, timeline, target, elementId, info));
801
+ }
802
+ return host
803
+ .getSubtreeAnimations(elementId)
804
+ .map((listed) => adoptListed(registry, host, timeline, renderer, listed, elementId, target));
805
+ }
806
+ /** `document.getAnimations()` of one renderer: the animations of its window, in document order. */
807
+ function getRendererAnimations(renderer) {
808
+ const host = hostOf(renderer);
809
+ flushPendingMutations(renderer);
810
+ const registry = registryOf(renderer);
811
+ const timeline = (registry.timeline ??= new DocumentTimeline(host));
812
+ return host
813
+ .getDocumentAnimations()
814
+ .map((listed) => adoptListed(registry, host, timeline, renderer, listed, undefined, undefined));
815
+ }
816
+ function adoptListed(registry, host, timeline, renderer, listed, readOn, readOnTarget) {
817
+ const known = lookup(registry, listed.info.id);
818
+ if (known) {
819
+ register(registry, listed.elementId, listed.info.id, known);
820
+ return known;
821
+ }
822
+ const target = listed.elementId === readOn ? readOnTarget : elementOf(renderer, listed.elementId);
823
+ if (target === undefined) {
824
+ throw new Error(`Element ${listed.elementId} has an animation but its root has no element for it`);
825
+ }
826
+ return adopt(registry, host, timeline, target, listed.elementId, listed.info);
827
+ }
828
+ setDocumentAnimations((renderer) => getRendererAnimations(renderer));
829
+ /** The cancel of an animation: its promises, as `cancel()` and a style that stops naming it leave them. */
830
+ function abort(state, wasPending) {
831
+ const error = () => new DOMException("The animation was cancelled", "AbortError");
832
+ if (wasPending && state.ready.settled === "pending") {
833
+ state.ready.reject(error());
834
+ state.ready = deferred();
835
+ }
836
+ if (state.finished.settled === "pending")
837
+ state.finished.reject(error());
838
+ state.finished = deferred();
839
+ }
840
+ /** Runs the play of an animation that was made finished, which has no frame to wait for. */
841
+ function startFinished(animation) {
842
+ const state = stateOf(animation);
843
+ if (state.mode !== "live" || state.registry.closed)
844
+ return;
845
+ try {
846
+ if (!readLive(state).pending)
847
+ return;
848
+ command(animation, "start", (live) => live.host.setAnimationCurrentTime(live.id, readLive(live).currentTime));
849
+ }
850
+ catch (error) {
851
+ // Cancelled since: there is nothing left to start.
852
+ if (!(error instanceof DOMException && error.name === "InvalidStateError"))
853
+ throw error;
854
+ }
855
+ }
856
+ /** The animation went for good: nothing native has stays to be played again. */
857
+ function lose(animation) {
858
+ const state = stateOf(animation);
859
+ state.mode = "gone";
860
+ cancelPromises(state);
861
+ }
862
+ function cancelPromises(state) {
863
+ const pending = state.cancelNoticed?.pending ?? state.last.pending;
864
+ state.cancelNoticed = undefined;
865
+ if (state.renewedForCancel) {
866
+ state.renewedForCancel = false;
867
+ return;
868
+ }
869
+ abort(state, pending);
870
+ }
871
+ /**
872
+ * The style or Motion cancelled the animation and native keeps the run: it is idle, a command brings it back, and the
873
+ * registry holds it weakly until one does.
874
+ */
875
+ function detach(animation) {
876
+ const state = stateOf(animation);
877
+ cancelPromises(state);
878
+ const now = readLive(state);
879
+ // A command can have played it again since the cancel, which the event only now tells of.
880
+ if (now.playState === "finished")
881
+ state.holdFinished = true;
882
+ if (now.playState !== "idle")
883
+ return;
884
+ const registry = state.registry;
885
+ registry.byId.delete(state.id);
886
+ const siblings = registry.byElement.get(state.elementId);
887
+ siblings?.delete(animation);
888
+ if (siblings?.size === 0)
889
+ registry.byElement.delete(state.elementId);
890
+ registry.weak.set(state.id, new WeakRef(animation));
891
+ registry.finalizer.register(animation, state.id, animation);
892
+ }
893
+ function dispatchPlaybackEvent(animation, type, currentTime, timelineTime) {
894
+ let stopped = false;
895
+ const event = {
896
+ type,
897
+ target: animation,
898
+ currentTarget: null,
899
+ currentTime,
900
+ timelineTime,
901
+ eventPhase: EVENT_PHASE_NONE,
902
+ bubbles: false,
903
+ cancelable: false,
904
+ defaultPrevented: false,
905
+ timeStamp: performance.now(),
906
+ composedPath: () => [animation],
907
+ stopPropagation() { },
908
+ stopImmediatePropagation() {
909
+ stopped = true;
910
+ },
911
+ preventDefault() { },
912
+ };
913
+ const errors = [];
914
+ animation.run(event, {
915
+ propagationStopped: false,
916
+ get immediatePropagationStopped() {
917
+ return stopped;
918
+ },
919
+ }, false, EVENT_PHASE_AT_TARGET, (error) => errors.push(error));
920
+ event.currentTarget = null;
921
+ for (const error of errors)
922
+ reportUncaught(error);
923
+ }
924
+ /** Whether the event is one of the internal ones `deliverWebAnimationEvent` takes. */
925
+ export function isWebAnimationEvent(eventType) {
926
+ return eventType === FINISH_EVENT || eventType === CANCEL_EVENT || eventType === READY_EVENT;
927
+ }
928
+ /**
929
+ * Takes the ready, finish or cancel of a CSS animation or transition for the
930
+ * `Animation` object listed for it, then tells native a finish or cancel is
931
+ * consumed, which stops answering for the run as it ended. Nothing else hears
932
+ * these events: they are not DOM events. A run no one listed is released all
933
+ * the same.
934
+ */
935
+ export function deliverWebAnimationEvent(renderer, event) {
936
+ if (event.animationId === undefined) {
937
+ throw new Error(`Event "${event.eventType}" arrived without its animationId`);
938
+ }
939
+ let released = false;
940
+ try {
941
+ const registry = registryOf(renderer);
942
+ const animation = lookup(registry, event.animationId);
943
+ if (!animation) {
944
+ // No object is left to bring a cancelled run back.
945
+ if (event.eventType === CANCEL_EVENT)
946
+ renderer.forgetAnimation?.(event.animationId);
947
+ return;
948
+ }
949
+ const state = stateOf(animation);
950
+ const timelineTime = event.timelineTime ?? null;
951
+ switch (event.eventType) {
952
+ case READY_EVENT:
953
+ if (state.ready.settled === "pending")
954
+ state.ready.resolve(animation);
955
+ return;
956
+ case FINISH_EVENT:
957
+ // A command that finished the animation resolved it then. This is the finish by itself.
958
+ if (state.holdFinished) {
959
+ state.holdFinished = false;
960
+ }
961
+ else if (state.finished.settled === "pending" && readLive(state).playState === "finished") {
962
+ state.finished.resolve(animation);
963
+ }
964
+ dispatchPlaybackEvent(animation, "finish", event.currentTime ?? null, timelineTime);
965
+ return;
966
+ default:
967
+ if (state.cancelEvents > 0) {
968
+ state.cancelEvents--;
969
+ }
970
+ else {
971
+ renderer.releaseAnimation?.(event.animationId);
972
+ released = true;
973
+ if (keptByNative(renderer, state))
974
+ detach(animation);
975
+ else
976
+ lose(animation);
977
+ }
978
+ if (state.mode === "gone")
979
+ unregister(registry, state, animation);
980
+ dispatchPlaybackEvent(animation, "cancel", null, timelineTime);
981
+ }
982
+ }
983
+ finally {
984
+ if (event.eventType !== READY_EVENT && !released) {
985
+ renderer.releaseAnimation?.(event.animationId);
986
+ }
987
+ }
988
+ }
989
+ /**
990
+ * Whether native still has the run of a cancel it just told of, and not one that went with its element or was replaced.
991
+ * Until the event is released native answers for the run as it ended, so release it first.
992
+ */
993
+ function keptByNative(renderer, state) {
994
+ try {
995
+ renderer.getAnimation?.(state.id);
996
+ return true;
997
+ }
998
+ catch (error) {
999
+ if (!isUnknownAnimation(error))
1000
+ throw error;
1001
+ return false;
1002
+ }
1003
+ }
1004
+ /**
1005
+ * A removed element's animations are cancelled, as the DOM does: native sends
1006
+ * nothing when an element goes, so the ids the mutation queue was told are
1007
+ * destroyed end here. The `cancel` event comes in a task, as the DOM's comes at
1008
+ * its next frame.
1009
+ */
1010
+ export function cancelAnimationsOfElements(renderer, elementIds) {
1011
+ const registry = registryOf(renderer);
1012
+ for (const elementId of elementIds) {
1013
+ // Chrome cancels the last of an element's animations first.
1014
+ for (const animation of [...(registry.byElement.get(elementId) ?? [])].reverse()) {
1015
+ const state = stateOf(animation);
1016
+ const wasIdle = state.mode === "gone" || state.last.playState === "idle";
1017
+ lose(animation);
1018
+ unregister(registry, state, animation);
1019
+ if (wasIdle)
1020
+ continue;
1021
+ const timelineTime = state.timeline.currentTime;
1022
+ if (registry.closed)
1023
+ continue;
1024
+ const task = setTimeout(() => {
1025
+ registry.cancelTasks.delete(task);
1026
+ dispatchPlaybackEvent(animation, "cancel", null, timelineTime);
1027
+ }, 0);
1028
+ registry.cancelTasks.add(task);
1029
+ }
1030
+ }
1031
+ }
1032
+ //# sourceMappingURL=web-animations.js.map