@voila.dev/motion 0.0.0-stage → 5.0.113

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 (273) hide show
  1. package/README.md +105 -2
  2. package/dist/accessibility/reduced-motion.d.ts +13 -0
  3. package/dist/accessibility/reduced-motion.d.ts.map +1 -0
  4. package/dist/accessibility/reduced-motion.js +56 -0
  5. package/dist/accessibility/reduced-motion.js.map +7 -0
  6. package/dist/animate/animate.d.ts +21 -0
  7. package/dist/animate/animate.d.ts.map +1 -0
  8. package/dist/animate/animate.js +76 -0
  9. package/dist/animate/animate.js.map +7 -0
  10. package/dist/animate/controls.d.ts +16 -0
  11. package/dist/animate/controls.d.ts.map +1 -0
  12. package/dist/animate/controls.js +62 -0
  13. package/dist/animate/controls.js.map +7 -0
  14. package/dist/animate/dom.d.ts +11 -0
  15. package/dist/animate/dom.d.ts.map +1 -0
  16. package/dist/animate/dom.js +112 -0
  17. package/dist/animate/dom.js.map +7 -0
  18. package/dist/animate/js-animation.d.ts +25 -0
  19. package/dist/animate/js-animation.d.ts.map +1 -0
  20. package/dist/animate/js-animation.js +117 -0
  21. package/dist/animate/js-animation.js.map +7 -0
  22. package/dist/animate/plan.d.ts +17 -0
  23. package/dist/animate/plan.d.ts.map +1 -0
  24. package/dist/animate/plan.js +48 -0
  25. package/dist/animate/plan.js.map +7 -0
  26. package/dist/animate/style.d.ts +27 -0
  27. package/dist/animate/style.d.ts.map +1 -0
  28. package/dist/animate/style.js +96 -0
  29. package/dist/animate/style.js.map +7 -0
  30. package/dist/animate/types.d.ts +62 -0
  31. package/dist/animate/types.d.ts.map +1 -0
  32. package/dist/animate/types.js +1 -0
  33. package/dist/animate/types.js.map +7 -0
  34. package/dist/animate/value.d.ts +13 -0
  35. package/dist/animate/value.d.ts.map +1 -0
  36. package/dist/animate/value.js +71 -0
  37. package/dist/animate/value.js.map +7 -0
  38. package/dist/animate/waapi.d.ts +20 -0
  39. package/dist/animate/waapi.d.ts.map +1 -0
  40. package/dist/animate/waapi.js +152 -0
  41. package/dist/animate/waapi.js.map +7 -0
  42. package/dist/dom.d.ts +12 -0
  43. package/dist/dom.d.ts.map +1 -0
  44. package/dist/dom.js +17 -0
  45. package/dist/dom.js.map +7 -0
  46. package/dist/easing/cubic-bezier.d.ts +4 -0
  47. package/dist/easing/cubic-bezier.d.ts.map +1 -0
  48. package/dist/easing/cubic-bezier.js +54 -0
  49. package/dist/easing/cubic-bezier.js.map +7 -0
  50. package/dist/easing/linear-css.d.ts +9 -0
  51. package/dist/easing/linear-css.d.ts.map +1 -0
  52. package/dist/easing/linear-css.js +30 -0
  53. package/dist/easing/linear-css.js.map +7 -0
  54. package/dist/easing/named.d.ts +16 -0
  55. package/dist/easing/named.d.ts.map +1 -0
  56. package/dist/easing/named.js +82 -0
  57. package/dist/easing/named.js.map +7 -0
  58. package/dist/easing/spring.d.ts +11 -0
  59. package/dist/easing/spring.d.ts.map +1 -0
  60. package/dist/easing/spring.js +18 -0
  61. package/dist/easing/spring.js.map +7 -0
  62. package/dist/easing/steps.d.ts +4 -0
  63. package/dist/easing/steps.d.ts.map +1 -0
  64. package/dist/easing/steps.js +14 -0
  65. package/dist/easing/steps.js.map +7 -0
  66. package/dist/easing/types.d.ts +5 -0
  67. package/dist/easing/types.d.ts.map +1 -0
  68. package/dist/easing/types.js +1 -0
  69. package/dist/easing/types.js.map +7 -0
  70. package/dist/frame/driver.d.ts +13 -0
  71. package/dist/frame/driver.d.ts.map +1 -0
  72. package/dist/frame/driver.js +42 -0
  73. package/dist/frame/driver.js.map +7 -0
  74. package/dist/frame/frame.d.ts +20 -0
  75. package/dist/frame/frame.d.ts.map +1 -0
  76. package/dist/frame/frame.js +77 -0
  77. package/dist/frame/frame.js.map +7 -0
  78. package/dist/generators/inertia.d.ts +20 -0
  79. package/dist/generators/inertia.d.ts.map +1 -0
  80. package/dist/generators/inertia.js +59 -0
  81. package/dist/generators/inertia.js.map +7 -0
  82. package/dist/generators/spring.d.ts +17 -0
  83. package/dist/generators/spring.d.ts.map +1 -0
  84. package/dist/generators/spring.js +87 -0
  85. package/dist/generators/spring.js.map +7 -0
  86. package/dist/generators/tween.d.ts +15 -0
  87. package/dist/generators/tween.d.ts.map +1 -0
  88. package/dist/generators/tween.js +51 -0
  89. package/dist/generators/tween.js.map +7 -0
  90. package/dist/generators/types.d.ts +30 -0
  91. package/dist/generators/types.d.ts.map +1 -0
  92. package/dist/generators/types.js +1 -0
  93. package/dist/generators/types.js.map +7 -0
  94. package/dist/index.d.ts +26 -0
  95. package/dist/index.d.ts.map +1 -0
  96. package/dist/index.js +97 -0
  97. package/dist/index.js.map +7 -0
  98. package/dist/interpolate/array.d.ts +3 -0
  99. package/dist/interpolate/array.d.ts.map +1 -0
  100. package/dist/interpolate/array.js +12 -0
  101. package/dist/interpolate/array.js.map +7 -0
  102. package/dist/interpolate/color-parse.d.ts +14 -0
  103. package/dist/interpolate/color-parse.d.ts.map +1 -0
  104. package/dist/interpolate/color-parse.js +111 -0
  105. package/dist/interpolate/color-parse.js.map +7 -0
  106. package/dist/interpolate/color.d.ts +11 -0
  107. package/dist/interpolate/color.d.ts.map +1 -0
  108. package/dist/interpolate/color.js +74 -0
  109. package/dist/interpolate/color.js.map +7 -0
  110. package/dist/interpolate/complex.d.ts +7 -0
  111. package/dist/interpolate/complex.d.ts.map +1 -0
  112. package/dist/interpolate/complex.js +56 -0
  113. package/dist/interpolate/complex.js.map +7 -0
  114. package/dist/interpolate/interpolate.d.ts +16 -0
  115. package/dist/interpolate/interpolate.d.ts.map +1 -0
  116. package/dist/interpolate/interpolate.js +39 -0
  117. package/dist/interpolate/interpolate.js.map +7 -0
  118. package/dist/interpolate/mix.d.ts +8 -0
  119. package/dist/interpolate/mix.d.ts.map +1 -0
  120. package/dist/interpolate/mix.js +48 -0
  121. package/dist/interpolate/mix.js.map +7 -0
  122. package/dist/interpolate/number.d.ts +2 -0
  123. package/dist/interpolate/number.d.ts.map +1 -0
  124. package/dist/interpolate/number.js +7 -0
  125. package/dist/interpolate/number.js.map +7 -0
  126. package/dist/interpolate/object.d.ts +5 -0
  127. package/dist/interpolate/object.d.ts.map +1 -0
  128. package/dist/interpolate/object.js +17 -0
  129. package/dist/interpolate/object.js.map +7 -0
  130. package/dist/interpolate/path/flatten.d.ts +10 -0
  131. package/dist/interpolate/path/flatten.d.ts.map +1 -0
  132. package/dist/interpolate/path/flatten.js +135 -0
  133. package/dist/interpolate/path/flatten.js.map +7 -0
  134. package/dist/interpolate/path/morph.d.ts +9 -0
  135. package/dist/interpolate/path/morph.d.ts.map +1 -0
  136. package/dist/interpolate/path/morph.js +72 -0
  137. package/dist/interpolate/path/morph.js.map +7 -0
  138. package/dist/interpolate/path/parse.d.ts +24 -0
  139. package/dist/interpolate/path/parse.d.ts.map +1 -0
  140. package/dist/interpolate/path/parse.js +125 -0
  141. package/dist/interpolate/path/parse.js.map +7 -0
  142. package/dist/interpolate/path/rings.d.ts +13 -0
  143. package/dist/interpolate/path/rings.d.ts.map +1 -0
  144. package/dist/interpolate/path/rings.js +117 -0
  145. package/dist/interpolate/path/rings.js.map +7 -0
  146. package/dist/react/index.d.ts +7 -0
  147. package/dist/react/index.d.ts.map +1 -0
  148. package/dist/react/index.js +19 -0
  149. package/dist/react/index.js.map +7 -0
  150. package/dist/react/presence.d.ts +27 -0
  151. package/dist/react/presence.d.ts.map +1 -0
  152. package/dist/react/presence.js +135 -0
  153. package/dist/react/presence.js.map +7 -0
  154. package/dist/react/use-animate.d.ts +17 -0
  155. package/dist/react/use-animate.d.ts.map +1 -0
  156. package/dist/react/use-animate.js +34 -0
  157. package/dist/react/use-animate.js.map +7 -0
  158. package/dist/react/use-motion-value.d.ts +4 -0
  159. package/dist/react/use-motion-value.d.ts.map +1 -0
  160. package/dist/react/use-motion-value.js +11 -0
  161. package/dist/react/use-motion-value.js.map +7 -0
  162. package/dist/react/use-reduced-motion.d.ts +7 -0
  163. package/dist/react/use-reduced-motion.d.ts.map +1 -0
  164. package/dist/react/use-reduced-motion.js +19 -0
  165. package/dist/react/use-reduced-motion.js.map +7 -0
  166. package/dist/react/use-spring.d.ts +9 -0
  167. package/dist/react/use-spring.d.ts.map +1 -0
  168. package/dist/react/use-spring.js +27 -0
  169. package/dist/react/use-spring.js.map +7 -0
  170. package/dist/react/use-transform.d.ts +13 -0
  171. package/dist/react/use-transform.d.ts.map +1 -0
  172. package/dist/react/use-transform.js +44 -0
  173. package/dist/react/use-transform.js.map +7 -0
  174. package/dist/sequence/sequence.d.ts +25 -0
  175. package/dist/sequence/sequence.d.ts.map +1 -0
  176. package/dist/sequence/sequence.js +68 -0
  177. package/dist/sequence/sequence.js.map +7 -0
  178. package/dist/stagger/stagger.d.ts +12 -0
  179. package/dist/stagger/stagger.d.ts.map +1 -0
  180. package/dist/stagger/stagger.js +21 -0
  181. package/dist/stagger/stagger.js.map +7 -0
  182. package/dist/testing/manual-frames.d.ts +11 -0
  183. package/dist/testing/manual-frames.d.ts.map +1 -0
  184. package/dist/testing/manual-frames.js +40 -0
  185. package/dist/testing/manual-frames.js.map +7 -0
  186. package/dist/value/motion-value.d.ts +27 -0
  187. package/dist/value/motion-value.d.ts.map +1 -0
  188. package/dist/value/motion-value.js +88 -0
  189. package/dist/value/motion-value.js.map +7 -0
  190. package/dist/value/transform.d.ts +16 -0
  191. package/dist/value/transform.d.ts.map +1 -0
  192. package/dist/value/transform.js +28 -0
  193. package/dist/value/transform.js.map +7 -0
  194. package/dist/view/in-view.d.ts +19 -0
  195. package/dist/view/in-view.d.ts.map +1 -0
  196. package/dist/view/in-view.js +46 -0
  197. package/dist/view/in-view.js.map +7 -0
  198. package/dist/view/resize.d.ts +13 -0
  199. package/dist/view/resize.d.ts.map +1 -0
  200. package/dist/view/resize.js +72 -0
  201. package/dist/view/resize.js.map +7 -0
  202. package/dist/view/resolve-elements.d.ts +3 -0
  203. package/dist/view/resolve-elements.d.ts.map +1 -0
  204. package/dist/view/resolve-elements.js +10 -0
  205. package/dist/view/resolve-elements.js.map +7 -0
  206. package/dist/view/scroll-measure.d.ts +16 -0
  207. package/dist/view/scroll-measure.d.ts.map +1 -0
  208. package/dist/view/scroll-measure.js +51 -0
  209. package/dist/view/scroll-measure.js.map +7 -0
  210. package/dist/view/scroll-offset.d.ts +28 -0
  211. package/dist/view/scroll-offset.d.ts.map +1 -0
  212. package/dist/view/scroll-offset.js +55 -0
  213. package/dist/view/scroll-offset.js.map +7 -0
  214. package/dist/view/scroll.d.ts +34 -0
  215. package/dist/view/scroll.d.ts.map +1 -0
  216. package/dist/view/scroll.js +83 -0
  217. package/dist/view/scroll.js.map +7 -0
  218. package/package.json +57 -5
  219. package/src/accessibility/reduced-motion.ts +75 -0
  220. package/src/animate/animate.ts +155 -0
  221. package/src/animate/controls.ts +76 -0
  222. package/src/animate/dom.ts +166 -0
  223. package/src/animate/js-animation.ts +151 -0
  224. package/src/animate/plan.ts +74 -0
  225. package/src/animate/style.ts +131 -0
  226. package/src/animate/types.ts +69 -0
  227. package/src/animate/value.ts +93 -0
  228. package/src/animate/waapi.ts +212 -0
  229. package/src/dom.ts +35 -0
  230. package/src/easing/cubic-bezier.ts +64 -0
  231. package/src/easing/linear-css.ts +41 -0
  232. package/src/easing/named.ts +88 -0
  233. package/src/easing/spring.ts +25 -0
  234. package/src/easing/steps.ts +16 -0
  235. package/src/easing/types.ts +18 -0
  236. package/src/frame/driver.ts +56 -0
  237. package/src/frame/frame.ts +102 -0
  238. package/src/generators/inertia.ts +88 -0
  239. package/src/generators/spring.ts +117 -0
  240. package/src/generators/tween.ts +74 -0
  241. package/src/generators/types.ts +31 -0
  242. package/src/index.ts +91 -0
  243. package/src/interpolate/array.ts +13 -0
  244. package/src/interpolate/color-parse.ts +136 -0
  245. package/src/interpolate/color.ts +97 -0
  246. package/src/interpolate/complex.ts +80 -0
  247. package/src/interpolate/interpolate.ts +60 -0
  248. package/src/interpolate/mix.ts +74 -0
  249. package/src/interpolate/number.ts +3 -0
  250. package/src/interpolate/object.ts +20 -0
  251. package/src/interpolate/path/flatten.ts +185 -0
  252. package/src/interpolate/path/morph.ts +100 -0
  253. package/src/interpolate/path/parse.ts +162 -0
  254. package/src/interpolate/path/rings.ts +151 -0
  255. package/src/react/index.ts +11 -0
  256. package/src/react/presence.tsx +194 -0
  257. package/src/react/use-animate.ts +46 -0
  258. package/src/react/use-motion-value.ts +9 -0
  259. package/src/react/use-reduced-motion.ts +22 -0
  260. package/src/react/use-spring.ts +35 -0
  261. package/src/react/use-transform.ts +73 -0
  262. package/src/sequence/sequence.ts +129 -0
  263. package/src/stagger/stagger.ts +36 -0
  264. package/src/testing/browser-commands.d.ts +11 -0
  265. package/src/testing/manual-frames.ts +49 -0
  266. package/src/value/motion-value.ts +131 -0
  267. package/src/value/transform.ts +61 -0
  268. package/src/view/in-view.ts +74 -0
  269. package/src/view/resize.ts +101 -0
  270. package/src/view/resolve-elements.ts +11 -0
  271. package/src/view/scroll-measure.ts +83 -0
  272. package/src/view/scroll-offset.ts +100 -0
  273. package/src/view/scroll.ts +143 -0
@@ -0,0 +1,83 @@
1
+ import type { ScrollGeometry } from "#/view/scroll-offset.ts";
2
+
3
+ export type ScrollAxis = "x" | "y";
4
+
5
+ interface AxisKeys {
6
+ readonly scroll: "scrollTop" | "scrollLeft";
7
+ readonly scrollLength: "scrollHeight" | "scrollWidth";
8
+ readonly client: "clientHeight" | "clientWidth";
9
+ readonly clientStart: "clientTop" | "clientLeft";
10
+ readonly rectStart: "top" | "left";
11
+ readonly rectLength: "height" | "width";
12
+ }
13
+
14
+ const AXES: Readonly<Record<ScrollAxis, AxisKeys>> = {
15
+ y: {
16
+ scroll: "scrollTop",
17
+ scrollLength: "scrollHeight",
18
+ client: "clientHeight",
19
+ clientStart: "clientTop",
20
+ rectStart: "top",
21
+ rectLength: "height",
22
+ },
23
+ x: {
24
+ scroll: "scrollLeft",
25
+ scrollLength: "scrollWidth",
26
+ client: "clientWidth",
27
+ clientStart: "clientLeft",
28
+ rectStart: "left",
29
+ rectLength: "width",
30
+ },
31
+ };
32
+
33
+ /** The page's own scroller, which reports its scroll events on `window`. */
34
+ export function documentScroller(): Element {
35
+ return document.scrollingElement ?? document.documentElement;
36
+ }
37
+
38
+ export interface ScrollMeasurement {
39
+ readonly scroll: number;
40
+ /** The furthest the container can scroll along the axis. */
41
+ readonly scrollLength: number;
42
+ readonly geometry: ScrollGeometry;
43
+ }
44
+
45
+ /**
46
+ * Reads layout, so it belongs in the frame's read phase only. Without a
47
+ * target, the target is the scrolled content itself.
48
+ */
49
+ export function measureScroll(
50
+ container: Element,
51
+ axis: ScrollAxis,
52
+ target: Element | undefined,
53
+ ): ScrollMeasurement {
54
+ const keys = AXES[axis];
55
+ const scroll = container[keys.scroll];
56
+ const viewportLength = container[keys.client];
57
+ const contentLength = container[keys.scrollLength];
58
+ const scrollLength = Math.max(0, contentLength - viewportLength);
59
+ if (target === undefined) {
60
+ return {
61
+ scroll,
62
+ scrollLength,
63
+ geometry: { targetStart: 0, targetLength: contentLength, viewportLength },
64
+ };
65
+ }
66
+ // The page scroller's own rect moves with its scroll; its scrollport is
67
+ // the viewport, which starts at 0.
68
+ const scrollportStart =
69
+ container === documentScroller()
70
+ ? 0
71
+ : container.getBoundingClientRect()[keys.rectStart] +
72
+ container[keys.clientStart];
73
+ const targetRect = target.getBoundingClientRect();
74
+ return {
75
+ scroll,
76
+ scrollLength,
77
+ geometry: {
78
+ targetStart: targetRect[keys.rectStart] - scrollportStart + scroll,
79
+ targetLength: targetRect[keys.rectLength],
80
+ viewportLength,
81
+ },
82
+ };
83
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Where a scroll animation starts and ends, as Motion writes it: each offset
3
+ * is "<target edge> <container edge>", the scroll position at which that
4
+ * point of the target meets that point of the scrollport. "start end" is the
5
+ * target's top touching the viewport's bottom.
6
+ */
7
+ export type ScrollEdge =
8
+ | "start"
9
+ | "center"
10
+ | "end"
11
+ | number
12
+ | `${number}px`
13
+ | `${number}%`;
14
+
15
+ export type ScrollOffsetPoint =
16
+ | `${string} ${string}`
17
+ | readonly [ScrollEdge, ScrollEdge];
18
+
19
+ export type ScrollOffset = readonly [ScrollOffsetPoint, ScrollOffsetPoint];
20
+
21
+ /** The target crossing the scrollport, from first pixel in to last pixel out. */
22
+ export const ENTER_TO_EXIT: ScrollOffset = ["start end", "end start"];
23
+ /** The whole scroll range, when there is no target. */
24
+ export const FULL_RANGE: ScrollOffset = ["start start", "end end"];
25
+
26
+ const NAMED_EDGES: Readonly<Record<string, number>> = {
27
+ start: 0,
28
+ center: 0.5,
29
+ end: 1,
30
+ };
31
+
32
+ /** An edge as pixels along a length: named and bare numbers are fractions. */
33
+ export function resolveEdge(edge: ScrollEdge, length: number): number {
34
+ if (typeof edge === "number") {
35
+ return edge * length;
36
+ }
37
+ const named = NAMED_EDGES[edge];
38
+ if (named !== undefined) {
39
+ return named * length;
40
+ }
41
+ const value = Number.parseFloat(edge);
42
+ if (Number.isNaN(value)) {
43
+ throw new Error(`Unknown scroll edge: ${edge}`);
44
+ }
45
+ if (edge.endsWith("%")) {
46
+ return (value / 100) * length;
47
+ }
48
+ return edge.endsWith("px") ? value : value * length;
49
+ }
50
+
51
+ export function parseOffsetPoint(
52
+ point: ScrollOffsetPoint,
53
+ ): readonly [ScrollEdge, ScrollEdge] {
54
+ if (typeof point !== "string") {
55
+ return point;
56
+ }
57
+ const [target, container = target] = point.trim().split(/\s+/);
58
+ return [parseEdge(target), parseEdge(container)];
59
+ }
60
+
61
+ function parseEdge(text: string): ScrollEdge {
62
+ return text in NAMED_EDGES || /[a-z%]$/.test(text)
63
+ ? (text as ScrollEdge)
64
+ : Number(text);
65
+ }
66
+
67
+ export interface ScrollGeometry {
68
+ /** The target's start along the axis, in the container's content coordinates. */
69
+ readonly targetStart: number;
70
+ readonly targetLength: number;
71
+ /** The scrollport's visible length. */
72
+ readonly viewportLength: number;
73
+ }
74
+
75
+ /** The scroll position at which one offset point is reached. */
76
+ export function offsetPosition(
77
+ point: ScrollOffsetPoint,
78
+ geometry: ScrollGeometry,
79
+ ): number {
80
+ const [target, container] = parseOffsetPoint(point);
81
+ return (
82
+ geometry.targetStart +
83
+ resolveEdge(target, geometry.targetLength) -
84
+ resolveEdge(container, geometry.viewportLength)
85
+ );
86
+ }
87
+
88
+ /** Progress 0..1 of `scroll` between the two offsets, clamped. */
89
+ export function scrollProgress(
90
+ scroll: number,
91
+ offset: ScrollOffset,
92
+ geometry: ScrollGeometry,
93
+ ): number {
94
+ const from = offsetPosition(offset[0], geometry);
95
+ const to = offsetPosition(offset[1], geometry);
96
+ if (to === from) {
97
+ return scroll >= to ? 1 : 0;
98
+ }
99
+ return Math.min(1, Math.max(0, (scroll - from) / (to - from)));
100
+ }
@@ -0,0 +1,143 @@
1
+ import { cancelFrame, frame } from "#/frame/frame.ts";
2
+ import { resize } from "#/view/resize.ts";
3
+ import {
4
+ documentScroller,
5
+ measureScroll,
6
+ type ScrollAxis,
7
+ } from "#/view/scroll-measure.ts";
8
+ import {
9
+ ENTER_TO_EXIT,
10
+ FULL_RANGE,
11
+ type ScrollOffset,
12
+ scrollProgress,
13
+ } from "#/view/scroll-offset.ts";
14
+
15
+ /** What `animate()` returns, seen from the scroll side. */
16
+ export interface ScrollDrivable {
17
+ time: number;
18
+ readonly duration: number;
19
+ /** Hands the native timeline to WAAPI animations; false when it can't (JS-driven). */
20
+ attachTimeline?(timeline: AnimationTimeline): boolean;
21
+ /** Scroll alone moves it once driven: its own clock must stop. */
22
+ pause?(): void;
23
+ }
24
+
25
+ export interface ScrollInfo {
26
+ readonly progress: number;
27
+ /** Pixels scrolled along the axis. */
28
+ readonly scroll: number;
29
+ /** The furthest the container can scroll along the axis. */
30
+ readonly scrollLength: number;
31
+ readonly axis: ScrollAxis;
32
+ }
33
+
34
+ export interface ScrollOptions {
35
+ /** The scrolling element; the page by default. */
36
+ readonly container?: Element;
37
+ /** Track this element crossing the container instead of the whole scroll range. */
38
+ readonly target?: Element;
39
+ readonly axis?: ScrollAxis;
40
+ readonly offset?: ScrollOffset;
41
+ }
42
+
43
+ export type OnScroll = (progress: number, info: ScrollInfo) => void;
44
+
45
+ type TimelineConstructor = new (options: object) => AnimationTimeline;
46
+
47
+ function sameOffset(a: ScrollOffset, b: ScrollOffset): boolean {
48
+ return String(a[0]) === String(b[0]) && String(a[1]) === String(b[1]);
49
+ }
50
+
51
+ /**
52
+ * The browser's own timeline, which runs the animation off the main thread.
53
+ * Only for the offsets a bare timeline means by itself: the full range for
54
+ * a ScrollTimeline, enter-to-exit ("cover") for a ViewTimeline.
55
+ */
56
+ function nativeTimeline(
57
+ container: Element,
58
+ axis: ScrollAxis,
59
+ target: Element | undefined,
60
+ offset: ScrollOffset,
61
+ ): AnimationTimeline | undefined {
62
+ const scope = globalThis as unknown as Record<string, TimelineConstructor>;
63
+ const Timeline = target ? scope.ViewTimeline : scope.ScrollTimeline;
64
+ const expected = target ? ENTER_TO_EXIT : FULL_RANGE;
65
+ if (Timeline === undefined || !sameOffset(offset, expected)) {
66
+ return undefined;
67
+ }
68
+ return target
69
+ ? new Timeline({ subject: target, axis })
70
+ : new Timeline({ source: container, axis });
71
+ }
72
+
73
+ /**
74
+ * Drives a callback, or an animation's time, from scroll progress. Returns a
75
+ * function that stops listening.
76
+ */
77
+ export function scroll(
78
+ onScroll: OnScroll | ScrollDrivable,
79
+ options: ScrollOptions = {},
80
+ ): () => void {
81
+ const container = options.container ?? documentScroller();
82
+ const axis = options.axis ?? "y";
83
+ const target = options.target;
84
+ const offset = options.offset ?? (target ? ENTER_TO_EXIT : FULL_RANGE);
85
+
86
+ if (typeof onScroll !== "function" && onScroll.attachTimeline) {
87
+ const timeline = nativeTimeline(container, axis, target, offset);
88
+ if (timeline && onScroll.attachTimeline(timeline)) {
89
+ return () => {};
90
+ }
91
+ }
92
+
93
+ if (typeof onScroll !== "function") onScroll.pause?.();
94
+ const apply: OnScroll =
95
+ typeof onScroll === "function"
96
+ ? onScroll
97
+ : (progress) => {
98
+ onScroll.time = progress * onScroll.duration;
99
+ };
100
+ let info: ScrollInfo | undefined;
101
+ let scheduled = false;
102
+
103
+ function update() {
104
+ if (info) {
105
+ apply(info.progress, info);
106
+ }
107
+ }
108
+ function measure() {
109
+ scheduled = false;
110
+ const measured = measureScroll(container, axis, target);
111
+ info = {
112
+ progress: scrollProgress(measured.scroll, offset, measured.geometry),
113
+ scroll: measured.scroll,
114
+ scrollLength: measured.scrollLength,
115
+ axis,
116
+ };
117
+ frame.update(update);
118
+ }
119
+ function schedule() {
120
+ if (!scheduled) {
121
+ scheduled = true;
122
+ frame.read(measure);
123
+ }
124
+ }
125
+
126
+ // The page scroller fires its scroll events on window, not on itself.
127
+ const eventSource: EventTarget =
128
+ container === documentScroller() ? window : container;
129
+ eventSource.addEventListener("scroll", schedule, { passive: true });
130
+ const stopResize = [
131
+ resize(schedule),
132
+ resize(target ? [container, target] : [container], schedule),
133
+ ];
134
+ schedule();
135
+ return () => {
136
+ eventSource.removeEventListener("scroll", schedule);
137
+ for (const stop of stopResize) {
138
+ stop();
139
+ }
140
+ cancelFrame(measure);
141
+ cancelFrame(update);
142
+ };
143
+ }