@ghia/native 0.0.0-stage → 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 (212) 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 +94 -3
  212. package/README.md +0 -3
@@ -0,0 +1,941 @@
1
+ /// Motion values: values that live in the renderer's native graph, are sampled
2
+ /// at the frame clock, and bind to element style through the `motionValues`
3
+ /// custom prop. This file is the framework-free core; the React and Solid
4
+ /// adapters wrap it.
5
+ ///
6
+ /// A value starts in JS, with its state here, and gets a native node when it is
7
+ /// bound to a renderer (`bind`). Every binding and every listener keeps the node
8
+ /// alive, and the node is disposed when the last one goes away, so a value
9
+ /// outlives its elements without leaking. `destroy()` is for values the caller
10
+ /// drops.
11
+ import { createRendererState } from "./renderer-state.js";
12
+ const HOST_METHODS = [
13
+ "createMotionValue",
14
+ "createMotionTransform",
15
+ "createMotionFollower",
16
+ "createMotionSpring",
17
+ "disposeMotionValue",
18
+ "getMotionValue",
19
+ "getMotionValueVelocity",
20
+ "isMotionValueAnimating",
21
+ "setMotionValue",
22
+ "jumpMotionValue",
23
+ "animateMotionValue",
24
+ "stopMotionValue",
25
+ "pauseMotionValue",
26
+ "playMotionValue",
27
+ "seekMotionValue",
28
+ "setMotionValueSpeed",
29
+ "completeMotionValue",
30
+ "detachMotionValueClock",
31
+ "getMotionValuePlayback",
32
+ "setMotionValueListener",
33
+ ];
34
+ const INSTANT = { duration: 0 };
35
+ export function wireTransition(transition) {
36
+ if (transition?.repeat !== Infinity)
37
+ return transition;
38
+ return { ...transition, repeat: "Infinity" };
39
+ }
40
+ /**
41
+ * `wireTransition` for what a `motion.*` component takes: the repeat of the
42
+ * curve at the top, of `default`, and of each property's own.
43
+ */
44
+ export function wireMotionTransition(transition) {
45
+ if (transition === undefined)
46
+ return undefined;
47
+ const wired = { ...wireTransition(transition) };
48
+ for (const key of Object.keys(wired)) {
49
+ const value = wired[key];
50
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
51
+ wired[key] = wireTransition(value);
52
+ }
53
+ }
54
+ return wired;
55
+ }
56
+ function isShadowLayer(item) {
57
+ return typeof item === "object" && item !== null && !Array.isArray(item);
58
+ }
59
+ /**
60
+ * Where an animation of `target` ends. The renderer reads an array as keyframes
61
+ * unless it is a list of the kind `from` holds, and this reads it the same way.
62
+ */
63
+ function endOf(target, from) {
64
+ if (!Array.isArray(target))
65
+ return target;
66
+ const items = target;
67
+ const holdsNumbers = Array.isArray(from) && typeof from[0] === "number";
68
+ const holdsShadows = !holdsNumbers && typeof from === "object";
69
+ if ((holdsNumbers && items.every((item) => typeof item === "number")) ||
70
+ (holdsShadows && items.every(isShadowLayer))) {
71
+ return target;
72
+ }
73
+ for (let index = items.length - 1; index >= 0; index--) {
74
+ if (items[index] !== null)
75
+ return items[index];
76
+ }
77
+ return from;
78
+ }
79
+ /** Where an animation of `target` begins: its first keyframe, or the value it started from. */
80
+ function startOf(target, from) {
81
+ if (!Array.isArray(target) || endOf(target, from) === target)
82
+ return from;
83
+ const first = target[0];
84
+ return first === null ? from : first;
85
+ }
86
+ /** What the controls read before the animation started: what they were told so far. */
87
+ function pendingPlayback(animation) {
88
+ const delay = animation.transition?.delay ?? 0;
89
+ let time = 0;
90
+ let speed = 1;
91
+ let state = "running";
92
+ for (const command of animation.pending) {
93
+ switch (command.kind) {
94
+ case "pause":
95
+ state = "paused";
96
+ break;
97
+ case "play":
98
+ if (state === "finished")
99
+ time = 0;
100
+ state = "running";
101
+ break;
102
+ case "complete":
103
+ state = "finished";
104
+ break;
105
+ case "seek":
106
+ time = Math.max(0, command.seconds - delay);
107
+ if (state === "finished")
108
+ state = "paused";
109
+ break;
110
+ case "speed":
111
+ speed = command.speed;
112
+ break;
113
+ case "attach":
114
+ break;
115
+ }
116
+ }
117
+ return { time, speed, duration: undefined, iterationDuration: undefined, state, startTime: null };
118
+ }
119
+ /** A cancelled animation is back at its start. */
120
+ function rewound(animation) {
121
+ if (animation.ended)
122
+ animation.ended = { ...animation.ended, time: 0 };
123
+ }
124
+ const MEASURER_KEY = Symbol.for("@ghia/native/motion-measurer");
125
+ /** Called by the entry points that load the native addon, as `registerKeyframesValidator` is. */
126
+ export function registerAnimationMeasurer(measure) {
127
+ Reflect.set(globalThis, MEASURER_KEY, measure);
128
+ }
129
+ /** How long the animation lasts, before the value it animates is bound to a renderer. */
130
+ function measured(animation) {
131
+ const measure = Reflect.get(globalThis, MEASURER_KEY);
132
+ if (!measure) {
133
+ throw new Error("The duration of an animation is not known until its value is bound to a renderer");
134
+ }
135
+ return measure(animation.from, animation.target, wireTransition(animation.transition));
136
+ }
137
+ /** `finished` is for callers; what cleans up after an animation waits on this one. */
138
+ const settlements = new WeakMap();
139
+ const COUNTER_KEY = Symbol.for("@ghia/native/motion-value-ids");
140
+ /** One counter per process, so values from two copies of this module never share an id. */
141
+ function nextId() {
142
+ const next = (Reflect.get(globalThis, COUNTER_KEY) ?? 0) + 1;
143
+ Reflect.set(globalThis, COUNTER_KEY, next);
144
+ return next;
145
+ }
146
+ let tracker;
147
+ function tracking(onRead, run) {
148
+ const outer = tracker;
149
+ tracker = onRead;
150
+ try {
151
+ return run();
152
+ }
153
+ finally {
154
+ tracker = outer;
155
+ }
156
+ }
157
+ function deferred() {
158
+ let resolve = () => { };
159
+ const promise = new Promise((done) => {
160
+ resolve = done;
161
+ });
162
+ return { promise, resolve };
163
+ }
164
+ function sameValue(a, b) {
165
+ return a === b || JSON.stringify(a) === JSON.stringify(b);
166
+ }
167
+ function assertHost(renderer) {
168
+ const missing = HOST_METHODS.filter((method) => typeof Reflect.get(renderer, method) !== "function");
169
+ if (missing.length > 0) {
170
+ throw new Error(`This renderer has no motion value graph (missing ${missing.join(", ")}), so a motion value cannot be bound to it`);
171
+ }
172
+ }
173
+ export class MotionValue {
174
+ id = nextId();
175
+ #definition;
176
+ /** The value while there is no native node. Sources and springs only. */
177
+ #state;
178
+ #attachment;
179
+ #bindings = 0;
180
+ /** Elements that bind this value to `width`, `height` or an inset under reduced motion. */
181
+ #reducers = 0;
182
+ #destroyed = false;
183
+ #animation;
184
+ /**
185
+ * Motion's `value.animation`, which `isAnimating()` reads: set by `animate()`, cleared when the
186
+ * animation finishes and by the value's own `stop()` and `jump()`, not by the controls' `stop()`
187
+ * and `cancel()`.
188
+ */
189
+ #driving;
190
+ /** The animation `cancel()` ended last, which `play()` may start again until another one takes the value. */
191
+ #cancelled;
192
+ #counts = {
193
+ change: 0,
194
+ animationStart: 0,
195
+ animationComplete: 0,
196
+ };
197
+ #listeners = {
198
+ change: [],
199
+ animationStart: [],
200
+ animationComplete: [],
201
+ };
202
+ /** Use `motionValue`, `transformValue`, `springValue`, `computedValue` or `velocityValue`. */
203
+ constructor(definition, initial) {
204
+ this.#definition = definition;
205
+ this.#state = initial;
206
+ }
207
+ /** The renderer this value is bound to, if any. */
208
+ get host() {
209
+ return this.#attachment?.renderer;
210
+ }
211
+ /**
212
+ * The current value. Bound, it is read from the renderer at its clock. A
213
+ * transform and a spring follower have no state of their own, so they can
214
+ * only be read while bound.
215
+ */
216
+ get() {
217
+ tracker?.(this);
218
+ this.#assertAlive();
219
+ if (this.#attachment)
220
+ return this.#attachment.renderer.getMotionValue(this.id);
221
+ const definition = this.#definition;
222
+ switch (definition.kind) {
223
+ case "source":
224
+ case "spring":
225
+ return this.#state;
226
+ case "computed":
227
+ return definition.compute();
228
+ case "transform":
229
+ case "follower":
230
+ throw new Error(`A ${definition.kind} motion value has no state until it is bound to a renderer, so it cannot be read yet`);
231
+ }
232
+ }
233
+ /**
234
+ * Writes the value. It does not stop a running animation: call `stop()`
235
+ * first to take the value from the animation. On a spring it moves the
236
+ * target.
237
+ */
238
+ set(value) {
239
+ this.#write("set", value);
240
+ }
241
+ /** Writes the value, ending any animation and dropping its velocity. */
242
+ jump(value) {
243
+ // Like Motion's, it stops an animation only while the value has one: a finished one stays finished.
244
+ if (this.#driving)
245
+ this.#ending(() => this.#write("jump", value), "stopped");
246
+ else
247
+ this.#write("jump", value);
248
+ this.#driving = undefined;
249
+ }
250
+ /** Units per second, for a value that holds a number. A value that is not bound has none. */
251
+ getVelocity() {
252
+ tracker?.(this);
253
+ this.#assertAlive();
254
+ if (this.#attachment)
255
+ return this.#attachment.renderer.getMotionValueVelocity(this.id);
256
+ if (typeof this.get() !== "number") {
257
+ throw new Error("getVelocity is for motion values that hold a number");
258
+ }
259
+ return 0;
260
+ }
261
+ /** Whether an animation drives the value: it stays true after the controls' `stop()` and `cancel()`. */
262
+ isAnimating() {
263
+ this.#assertAlive();
264
+ // A spring and a transform move without an animate(): the renderer says.
265
+ if (this.#definition.kind !== "source" && this.#attachment) {
266
+ return this.#attachment.renderer.isMotionValueAnimating(this.id);
267
+ }
268
+ const driving = this.#driving;
269
+ if (driving?.started && !driving.done && !driving.ended && this.#attachment) {
270
+ // The completion event may not have reached JS yet.
271
+ if (this.#attachment.renderer.getMotionValuePlayback(this.id)?.state === "finished")
272
+ this.#finish(driving);
273
+ }
274
+ return this.#driving !== undefined;
275
+ }
276
+ /** Ends an animation where it is, keeping the speed for the next one. */
277
+ stop() {
278
+ this.#assertAlive();
279
+ if (this.#driving)
280
+ this.#endAnimation();
281
+ this.#driving = undefined;
282
+ }
283
+ #endAnimation() {
284
+ this.#ending(() => this.#attachment?.renderer.stopMotionValue(this.id), "stopped");
285
+ }
286
+ /**
287
+ * Animates from where the value is, with the speed it has. Before the value
288
+ * is bound the animation waits, and starts from the value at that point.
289
+ * Only a `motionValue()` animates: a spring is moved with `set()`. The controls
290
+ * it returns have the names and semantics of Motion's.
291
+ */
292
+ animate(target, transition) {
293
+ this.#assertAlive();
294
+ if (this.#definition.kind !== "source") {
295
+ throw new Error(this.#definition.kind === "spring"
296
+ ? "A spring motion value cannot be animated: set it to move its target"
297
+ : `A ${this.#definition.kind} motion value is computed from other values and cannot be animated`);
298
+ }
299
+ this.#cancelled = undefined;
300
+ const animation = {
301
+ target: target,
302
+ from: this.get(),
303
+ transition: this.#reducers > 0 ? INSTANT : transition,
304
+ started: false,
305
+ pending: [],
306
+ done: false,
307
+ finished: deferred(),
308
+ settled: deferred(),
309
+ };
310
+ const previous = this.#animation;
311
+ // Read before the new animation takes the value's place in the renderer.
312
+ const leaving = previous && this.#snapshot(previous);
313
+ this.#animation = animation;
314
+ const drivingBefore = this.#driving;
315
+ this.#driving = animation;
316
+ try {
317
+ if (this.#attachment)
318
+ this.#start(animation);
319
+ }
320
+ catch (error) {
321
+ if (this.#animation === undefined)
322
+ this.#animation = previous;
323
+ this.#driving = drivingBefore;
324
+ throw error;
325
+ }
326
+ // The new animation holds the node first, so replacing the last one
327
+ // cannot be the release that disposes it.
328
+ if (previous && leaving)
329
+ this.#end(previous, leaving, "released");
330
+ return this.#controls(animation);
331
+ }
332
+ #controls(animation) {
333
+ const playback = () => this.#snapshot(animation);
334
+ const command = (command) => this.#command(animation, command);
335
+ const controls = {
336
+ get time() {
337
+ return playback().time;
338
+ },
339
+ set time(seconds) {
340
+ command({ kind: "seek", seconds });
341
+ },
342
+ get speed() {
343
+ return playback().speed;
344
+ },
345
+ set speed(speed) {
346
+ command({ kind: "speed", speed });
347
+ },
348
+ get duration() {
349
+ return playback().duration ?? measured(animation).duration;
350
+ },
351
+ get iterationDuration() {
352
+ return playback().iterationDuration ?? measured(animation).iterationDuration;
353
+ },
354
+ get state() {
355
+ return playback().state;
356
+ },
357
+ get startTime() {
358
+ return playback().startTime;
359
+ },
360
+ get finished() {
361
+ return animation.finished.promise;
362
+ },
363
+ then: (onFulfilled, onRejected) => animation.finished.promise.then(onFulfilled, onRejected),
364
+ stop: () => {
365
+ if (animation.ended)
366
+ animation.cleanup?.();
367
+ else
368
+ this.#endAnimation();
369
+ },
370
+ cancel: () => {
371
+ if (animation.ended)
372
+ return;
373
+ const finished = playback().state === "finished";
374
+ // Motion's cancel puts the time of a finished animation at its end.
375
+ if (finished && animation.started)
376
+ this.#attached().renderer.completeMotionValue(this.id);
377
+ animation.cancelling = true;
378
+ this.#endAnimation();
379
+ this.#cancelled = animation;
380
+ if (finished)
381
+ return;
382
+ this.#write("jump", startOf(animation.target, animation.from));
383
+ rewound(animation);
384
+ },
385
+ complete: () => command({ kind: "complete" }),
386
+ play: () => command({ kind: "play" }),
387
+ pause: () => command({ kind: "pause" }),
388
+ attachTimeline: (timeline) => {
389
+ this.#attachTimeline(animation, controls, timeline);
390
+ return () => {
391
+ controls.stop();
392
+ controls.stop();
393
+ };
394
+ },
395
+ };
396
+ animation.controls = controls;
397
+ settlements.set(controls, animation.settled.promise);
398
+ return controls;
399
+ }
400
+ #attachTimeline(animation, controls, timeline) {
401
+ if (!animation.started && !animation.ended) {
402
+ // Motion keeps one pending timeline.
403
+ const earlier = animation.pending.findIndex((command) => command.kind === "attach");
404
+ if (earlier >= 0)
405
+ animation.pending.splice(earlier, 1);
406
+ animation.pending.push({ kind: "attach", timeline });
407
+ return;
408
+ }
409
+ if (!animation.ended)
410
+ this.#attached().renderer.detachMotionValueClock(this.id);
411
+ animation.cleanup = timeline.observe(controls);
412
+ }
413
+ #command(animation, command) {
414
+ if (animation.ended) {
415
+ if (command.kind === "play")
416
+ this.#restart(animation);
417
+ return;
418
+ }
419
+ if (!animation.started) {
420
+ animation.pending.push(command);
421
+ return;
422
+ }
423
+ this.#apply(animation, command);
424
+ }
425
+ /** `play()` after `cancel()`: the animation runs again from its first keyframe, at the speed it had. */
426
+ #restart(animation) {
427
+ const ended = animation.ended;
428
+ if (this.#destroyed || !ended || this.#cancelled !== animation || this.#animation)
429
+ return;
430
+ this.#cancelled = undefined;
431
+ animation.ended = undefined;
432
+ animation.started = false;
433
+ animation.done = false;
434
+ animation.last = undefined;
435
+ animation.pending.length = 0;
436
+ if (ended.speed !== 1)
437
+ animation.pending.push({ kind: "speed", speed: ended.speed });
438
+ // A cancel that found the animation finished left the value on its end.
439
+ this.#write("jump", startOf(animation.target, animation.from));
440
+ this.#animation = animation;
441
+ animation.cancelling = false;
442
+ if (this.#attachment)
443
+ this.#start(animation);
444
+ }
445
+ /** Runs a control on the renderer, which owns the animation once it has started. */
446
+ #apply(animation, command) {
447
+ const renderer = this.#attached().renderer;
448
+ switch (command.kind) {
449
+ case "pause":
450
+ renderer.pauseMotionValue(this.id);
451
+ return;
452
+ case "play":
453
+ this.#play(animation);
454
+ return;
455
+ case "seek":
456
+ renderer.seekMotionValue(this.id, command.seconds);
457
+ return;
458
+ case "speed":
459
+ renderer.setMotionValueSpeed(this.id, command.speed);
460
+ return;
461
+ case "complete":
462
+ renderer.completeMotionValue(this.id);
463
+ this.#finish(animation);
464
+ return;
465
+ case "attach":
466
+ this.#attachTimeline(animation, this.#controlsOf(animation), command.timeline);
467
+ return;
468
+ }
469
+ }
470
+ #controlsOf(animation) {
471
+ if (!animation.controls)
472
+ throw new Error(`Motion value ${this.id} has an animation without controls`);
473
+ return animation.controls;
474
+ }
475
+ #play(animation) {
476
+ const renderer = this.#attached().renderer;
477
+ const playback = renderer.getMotionValuePlayback(this.id);
478
+ if (playback === null)
479
+ return;
480
+ if (playback.state === "finished") {
481
+ // Held through the hand-over, so letting go of the finished run cannot be the release
482
+ // that disposes the node.
483
+ const hold = this.#watch("animationComplete");
484
+ this.#finish(animation);
485
+ animation.finished = deferred();
486
+ animation.done = false;
487
+ animation.unwatch = hold;
488
+ }
489
+ renderer.playMotionValue(this.id);
490
+ }
491
+ /** What the controls of `animation` read now. */
492
+ #snapshot(animation) {
493
+ if (animation.ended)
494
+ return animation.ended;
495
+ if (!animation.started)
496
+ return pendingPlayback(animation);
497
+ const playback = this.#attached().renderer.getMotionValuePlayback(this.id);
498
+ if (playback === null) {
499
+ // The renderer let go of a finished animation when the value was written.
500
+ return {
501
+ ...(animation.last ?? pendingPlayback(animation)),
502
+ state: animation.done ? "finished" : "idle",
503
+ startTime: null,
504
+ };
505
+ }
506
+ animation.last = { ...playback, startTime: playback.startTime ?? null, state: playback.state };
507
+ return animation.last;
508
+ }
509
+ #attached() {
510
+ if (!this.#attachment)
511
+ throw new Error(`Motion value ${this.id} has no renderer to control an animation on`);
512
+ return this.#attachment;
513
+ }
514
+ /**
515
+ * Makes `animate()` jump to its target, finishing on the next frame as a
516
+ * zero-length animation does, for as long as the returned release is not
517
+ * called. The adapters call it for a value that a reduced-motion
518
+ * `motion.*` element binds to `width`, `height` or an inset.
519
+ */
520
+ reduceMotion() {
521
+ this.#reducers += 1;
522
+ let released = false;
523
+ return () => {
524
+ if (released)
525
+ return;
526
+ released = true;
527
+ this.#reducers -= 1;
528
+ };
529
+ }
530
+ on(event, callback) {
531
+ this.#assertAlive();
532
+ const listener = { callback: callback };
533
+ this.#listeners[event].push(listener);
534
+ const unwatch = this.#watch(event);
535
+ return () => {
536
+ const index = this.#listeners[event].indexOf(listener);
537
+ if (index >= 0)
538
+ this.#listeners[event].splice(index, 1);
539
+ unwatch();
540
+ };
541
+ }
542
+ /**
543
+ * Binds the value to a renderer, creating its native node on the first
544
+ * bind. Returns the release. The node goes when every binding is released
545
+ * and no listener is left; the value keeps its state in JS.
546
+ */
547
+ bind(renderer) {
548
+ this.#assertAlive();
549
+ assertHost(renderer);
550
+ if (this.#attachment && this.#attachment.renderer !== renderer) {
551
+ throw new Error(`Motion value ${this.id} is bound to another renderer. A value drives one window at a time; release it there first`);
552
+ }
553
+ // Counted before the node is attached, so an animation that ends while it is cannot take the node away.
554
+ this.#bindings += 1;
555
+ if (!this.#attachment) {
556
+ try {
557
+ this.#attach(renderer);
558
+ }
559
+ catch (error) {
560
+ this.#bindings -= 1;
561
+ throw error;
562
+ }
563
+ }
564
+ let released = false;
565
+ return () => {
566
+ if (released)
567
+ return;
568
+ released = true;
569
+ if (this.#destroyed)
570
+ return;
571
+ this.#bindings -= 1;
572
+ this.#maybeDetach();
573
+ };
574
+ }
575
+ /**
576
+ * Drops the value: its node is disposed whatever still binds it, its
577
+ * listeners are removed and any animation ends. Every later call throws.
578
+ */
579
+ destroy() {
580
+ if (this.#destroyed)
581
+ return;
582
+ this.#ending(() => { }, "released");
583
+ this.#driving = undefined;
584
+ this.#detach(false);
585
+ this.#destroyed = true;
586
+ this.#bindings = 0;
587
+ for (const kind of Object.keys(this.#listeners)) {
588
+ this.#listeners[kind].length = 0;
589
+ this.#counts[kind] = 0;
590
+ }
591
+ }
592
+ #assertAlive() {
593
+ if (this.#destroyed)
594
+ throw new Error(`Motion value ${this.id} was destroyed`);
595
+ }
596
+ #write(method, value) {
597
+ this.#assertAlive();
598
+ const definition = this.#definition;
599
+ if (definition.kind === "transform" || definition.kind === "computed") {
600
+ throw new Error(`A ${definition.kind} motion value is computed from other values and cannot be written`);
601
+ }
602
+ const attachment = this.#attachment;
603
+ if (attachment) {
604
+ if (method === "set")
605
+ attachment.renderer.setMotionValue(this.id, value);
606
+ else
607
+ attachment.renderer.jumpMotionValue(this.id, value);
608
+ return;
609
+ }
610
+ if (definition.kind === "follower") {
611
+ throw new Error("A spring follower has no state until it is bound to a renderer, so it cannot be written yet");
612
+ }
613
+ const changed = !sameValue(this.#state, value);
614
+ this.#state = value;
615
+ if (changed)
616
+ this.#emit("change", value);
617
+ }
618
+ #emit(event, ...args) {
619
+ for (const listener of [...this.#listeners[event]]) {
620
+ ;
621
+ listener.callback(...args);
622
+ }
623
+ }
624
+ #sink = {
625
+ change: (value) => this.#emit("change", value),
626
+ animationStart: () => this.#emit("animationStart"),
627
+ animationComplete: () => {
628
+ this.#emit("animationComplete");
629
+ const attachment = this.#attachment;
630
+ const animation = this.#animation;
631
+ if (!attachment || !animation?.started || animation.done)
632
+ return;
633
+ // A stop and a new animation in one frame deliver the old completion
634
+ // after the new start, so ask whether the animation is over.
635
+ if (attachment.renderer.getMotionValuePlayback(this.id)?.state === "finished")
636
+ this.#finish(animation);
637
+ },
638
+ };
639
+ #watch(kind) {
640
+ this.#counts[kind] += 1;
641
+ this.#syncListener();
642
+ let watching = true;
643
+ return () => {
644
+ if (!watching)
645
+ return;
646
+ watching = false;
647
+ if (this.#destroyed)
648
+ return;
649
+ this.#counts[kind] -= 1;
650
+ this.#syncListener();
651
+ this.#maybeDetach();
652
+ };
653
+ }
654
+ #syncListener() {
655
+ const attachment = this.#attachment;
656
+ if (!attachment)
657
+ return;
658
+ const change = this.#counts.change > 0;
659
+ const animation = this.#counts.animationStart + this.#counts.animationComplete > 0;
660
+ if (attachment.sentChange === change && attachment.sentAnimation === animation)
661
+ return;
662
+ attachment.sentChange = change;
663
+ attachment.sentAnimation = animation;
664
+ attachment.renderer.setMotionValueListener(this.id, change, animation);
665
+ }
666
+ #maybeDetach() {
667
+ if (!this.#attachment || this.#bindings > 0)
668
+ return;
669
+ if (Object.values(this.#counts).some((count) => count > 0))
670
+ return;
671
+ this.#detach(true);
672
+ }
673
+ #attach(renderer) {
674
+ const attachment = {
675
+ renderer,
676
+ teardown: [],
677
+ created: false,
678
+ sentChange: false,
679
+ sentAnimation: false,
680
+ };
681
+ this.#attachment = attachment;
682
+ try {
683
+ this.#create(attachment);
684
+ attachment.created = true;
685
+ attachment.unsubscribe = createRendererState(renderer).subscribeMotionValue(this.id, this.#sink);
686
+ this.#syncListener();
687
+ const animation = this.#animation;
688
+ if (animation && !animation.started)
689
+ this.#start(animation);
690
+ }
691
+ catch (error) {
692
+ this.#attachment = undefined;
693
+ attachment.unsubscribe?.();
694
+ if (attachment.created)
695
+ renderer.disposeMotionValue(this.id);
696
+ for (const undo of attachment.teardown.reverse())
697
+ undo();
698
+ throw error;
699
+ }
700
+ }
701
+ #create(attachment) {
702
+ const renderer = attachment.renderer;
703
+ const definition = this.#definition;
704
+ switch (definition.kind) {
705
+ case "source":
706
+ renderer.createMotionValue(this.id, this.#state);
707
+ return;
708
+ case "spring":
709
+ renderer.createMotionSpring(this.id, this.#state, definition.config);
710
+ return;
711
+ case "transform":
712
+ attachment.teardown.push(definition.input.bind(renderer));
713
+ renderer.createMotionTransform(this.id, definition.input.id, definition.inputRange, definition.outputRange, definition.options);
714
+ return;
715
+ case "follower":
716
+ attachment.teardown.push(definition.source.bind(renderer));
717
+ renderer.createMotionFollower(this.id, definition.source.id, definition.config);
718
+ return;
719
+ case "computed":
720
+ this.#createComputed(attachment, definition);
721
+ return;
722
+ }
723
+ }
724
+ /**
725
+ * A computed value is a source that JS keeps current: it follows the change
726
+ * events of every value its function read, so it is not linked natively and
727
+ * trails them by a frame.
728
+ */
729
+ #createComputed(attachment, definition) {
730
+ const renderer = attachment.renderer;
731
+ const watched = new Map();
732
+ let reads = new Set();
733
+ const read = (dep) => {
734
+ if (dep === this || reads.has(dep))
735
+ return;
736
+ reads.add(dep);
737
+ if (watched.has(dep))
738
+ return;
739
+ // Bound before the read goes on, so a transform can be read at all.
740
+ const release = dep.bind(renderer);
741
+ const stops = definition.triggers.map((trigger) => dep.on(trigger, update));
742
+ watched.set(dep, () => {
743
+ for (const stop of stops)
744
+ stop();
745
+ release();
746
+ });
747
+ };
748
+ const evaluate = () => {
749
+ reads = new Set();
750
+ const result = tracking(read, definition.compute);
751
+ for (const [dep, release] of watched) {
752
+ if (reads.has(dep))
753
+ continue;
754
+ watched.delete(dep);
755
+ release();
756
+ }
757
+ return result;
758
+ };
759
+ const update = () => {
760
+ if (this.#attachment !== attachment)
761
+ return;
762
+ renderer.setMotionValue(this.id, evaluate());
763
+ };
764
+ attachment.teardown.push(() => {
765
+ for (const release of watched.values())
766
+ release();
767
+ watched.clear();
768
+ });
769
+ renderer.createMotionValue(this.id, evaluate());
770
+ }
771
+ #detach(keepState) {
772
+ const attachment = this.#attachment;
773
+ if (!attachment)
774
+ return;
775
+ const animation = this.#animation;
776
+ const leaving = animation && this.#snapshot(animation);
777
+ this.#attachment = undefined;
778
+ const kind = this.#definition.kind;
779
+ if (keepState && (kind === "source" || kind === "spring")) {
780
+ this.#state = attachment.renderer.getMotionValue(this.id);
781
+ }
782
+ if (animation && leaving)
783
+ this.#end(animation, leaving, "released");
784
+ // The sink goes first, then the node, then the inputs the node read.
785
+ attachment.unsubscribe?.();
786
+ attachment.renderer.disposeMotionValue(this.id);
787
+ for (const undo of attachment.teardown.reverse())
788
+ undo();
789
+ }
790
+ #start(animation) {
791
+ const attachment = this.#attachment;
792
+ if (!attachment)
793
+ throw new Error(`Motion value ${this.id} has no renderer to animate on`);
794
+ animation.started = true;
795
+ // Listened before the animation starts, so its start and end are seen.
796
+ animation.unwatch = this.#watch("animationComplete");
797
+ const renderer = attachment.renderer;
798
+ try {
799
+ renderer.animateMotionValue(this.id, animation.target, wireTransition(animation.transition));
800
+ for (const command of animation.pending.splice(0))
801
+ this.#apply(animation, command);
802
+ }
803
+ catch (error) {
804
+ this.#end(animation, { ...pendingPlayback(animation), state: "idle" }, "stopped");
805
+ throw error;
806
+ }
807
+ const playback = renderer.getMotionValuePlayback(this.id);
808
+ if (playback === null)
809
+ throw new Error(`Motion value ${this.id} has no animation after it was started`);
810
+ animation.last = { ...playback, startTime: playback.startTime ?? null, state: playback.state };
811
+ if (playback.state === "finished")
812
+ this.#finish(animation);
813
+ }
814
+ /**
815
+ * Runs `operation`, which takes the value from the animation, and ends the animation. What the
816
+ * controls read is taken first, while the renderer still has the animation.
817
+ */
818
+ #ending(operation, how) {
819
+ const animation = this.#animation;
820
+ const leaving = animation && this.#snapshot(animation);
821
+ operation();
822
+ if (animation && leaving)
823
+ this.#end(animation, leaving, how);
824
+ }
825
+ /** The animation got to its end, or `complete()` took it there: `finished` resolves. */
826
+ #finish(animation) {
827
+ if (animation.done || animation.ended)
828
+ return;
829
+ if (this.#driving === animation)
830
+ this.#driving = undefined;
831
+ animation.done = true;
832
+ animation.finished.resolve();
833
+ animation.settled.resolve();
834
+ const unwatch = animation.unwatch;
835
+ animation.unwatch = undefined;
836
+ unwatch?.();
837
+ }
838
+ /**
839
+ * The animation is over for good: stopped, cancelled, replaced, or its value went away. Its
840
+ * controls read as `leaving` from then on, and `finished` stays as it was.
841
+ */
842
+ #end(animation, leaving, how) {
843
+ if (animation.ended)
844
+ return;
845
+ const finished = how === "released" && (animation.done || leaving.state === "finished");
846
+ animation.ended = { ...leaving, state: finished ? "finished" : "idle", startTime: null };
847
+ if (this.#animation === animation)
848
+ this.#animation = undefined;
849
+ const unwatch = animation.unwatch;
850
+ animation.unwatch = undefined;
851
+ animation.settled.resolve();
852
+ unwatch?.();
853
+ if (!animation.cancelling)
854
+ animation.cleanup?.();
855
+ }
856
+ }
857
+ /** A value that is written with `set` and `jump` or animated. */
858
+ export function motionValue(initial) {
859
+ return new MotionValue({ kind: "source" }, initial);
860
+ }
861
+ export function isMotionValue(value) {
862
+ return value instanceof MotionValue;
863
+ }
864
+ /**
865
+ * Maps a number value through `inputRange` and `outputRange`, natively: it is
866
+ * sampled at the same instant as its input. The input range is ascending or
867
+ * descending and may repeat a value, which is a step. The output holds
868
+ * numbers, colours or shadows. A bad range is reported when the value is bound.
869
+ */
870
+ export function transformValue(input, inputRange, outputRange, options) {
871
+ return new MotionValue({
872
+ kind: "transform",
873
+ input,
874
+ inputRange,
875
+ outputRange,
876
+ options,
877
+ });
878
+ }
879
+ export function springValue(source, config = {}) {
880
+ if (isMotionValue(source))
881
+ return new MotionValue({ kind: "follower", source, config });
882
+ return new MotionValue({ kind: "spring", config }, source);
883
+ }
884
+ /**
885
+ * A value computed in JS from the motion values `compute` reads with `get()`.
886
+ * It is not native-linked: it follows their change events and writes a source,
887
+ * so it trails them by a frame, and it only updates while it is bound.
888
+ * Unbound, `get()` runs `compute`.
889
+ */
890
+ export function computedValue(compute) {
891
+ return new MotionValue({
892
+ kind: "computed",
893
+ compute,
894
+ triggers: ["change"],
895
+ });
896
+ }
897
+ /**
898
+ * The velocity of a number value, in units per second. Like `computedValue`
899
+ * it is JS-derived and not native-linked: it reads `getVelocity()` of the
900
+ * source on each of its changes, and again when its animation completes.
901
+ */
902
+ export function velocityValue(source) {
903
+ return new MotionValue({
904
+ kind: "computed",
905
+ compute: () => source.getVelocity(),
906
+ triggers: ["change", "animationComplete"],
907
+ });
908
+ }
909
+ export function animate(first, second, third) {
910
+ if (first instanceof MotionValue)
911
+ return first.animate(second, third);
912
+ const options = third;
913
+ if (!options?.host)
914
+ throw new Error("animate(from, to) needs options.host, the renderer that samples the animation");
915
+ const curve = { ...options };
916
+ delete curve.host;
917
+ delete curve.onUpdate;
918
+ const temporary = motionValue(first);
919
+ const release = temporary.bind(options.host);
920
+ const onUpdate = options.onUpdate;
921
+ const stopUpdates = onUpdate ? temporary.on("change", onUpdate) : () => { };
922
+ try {
923
+ const controls = temporary.animate(second, curve);
924
+ const settled = settlements.get(controls);
925
+ if (!settled)
926
+ throw new Error("An animation of a motion value has no settlement");
927
+ void settled.then(() => {
928
+ stopUpdates();
929
+ release();
930
+ temporary.destroy();
931
+ });
932
+ return controls;
933
+ }
934
+ catch (error) {
935
+ stopUpdates();
936
+ release();
937
+ temporary.destroy();
938
+ throw error;
939
+ }
940
+ }
941
+ //# sourceMappingURL=motion-value.js.map