@bquery/bquery 1.2.0 → 1.4.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 (309) hide show
  1. package/README.md +127 -27
  2. package/dist/batch-x7b2eZST.js +13 -0
  3. package/dist/batch-x7b2eZST.js.map +1 -0
  4. package/dist/component/component.d.ts +69 -0
  5. package/dist/component/component.d.ts.map +1 -0
  6. package/dist/component/html.d.ts +35 -0
  7. package/dist/component/html.d.ts.map +1 -0
  8. package/dist/component/index.d.ts +3 -126
  9. package/dist/component/index.d.ts.map +1 -1
  10. package/dist/component/props.d.ts +18 -0
  11. package/dist/component/props.d.ts.map +1 -0
  12. package/dist/component/types.d.ts +77 -0
  13. package/dist/component/types.d.ts.map +1 -0
  14. package/dist/component.es.mjs +90 -59
  15. package/dist/component.es.mjs.map +1 -1
  16. package/dist/core/collection.d.ts +55 -3
  17. package/dist/core/collection.d.ts.map +1 -1
  18. package/dist/core/dom.d.ts +6 -0
  19. package/dist/core/dom.d.ts.map +1 -0
  20. package/dist/core/element.d.ts +31 -4
  21. package/dist/core/element.d.ts.map +1 -1
  22. package/dist/core/index.d.ts +2 -0
  23. package/dist/core/index.d.ts.map +1 -1
  24. package/dist/core/utils/array.d.ts +74 -0
  25. package/dist/core/utils/array.d.ts.map +1 -0
  26. package/dist/core/utils/function.d.ts +87 -0
  27. package/dist/core/utils/function.d.ts.map +1 -0
  28. package/dist/core/utils/index.d.ts +70 -0
  29. package/dist/core/utils/index.d.ts.map +1 -0
  30. package/dist/core/utils/misc.d.ts +63 -0
  31. package/dist/core/utils/misc.d.ts.map +1 -0
  32. package/dist/core/utils/number.d.ts +65 -0
  33. package/dist/core/utils/number.d.ts.map +1 -0
  34. package/dist/core/utils/object.d.ts +133 -0
  35. package/dist/core/utils/object.d.ts.map +1 -0
  36. package/dist/core/utils/string.d.ts +80 -0
  37. package/dist/core/utils/string.d.ts.map +1 -0
  38. package/dist/core/utils/type-guards.d.ts +79 -0
  39. package/dist/core/utils/type-guards.d.ts.map +1 -0
  40. package/dist/core-BhpuvPhy.js +170 -0
  41. package/dist/core-BhpuvPhy.js.map +1 -0
  42. package/dist/core.es.mjs +495 -489
  43. package/dist/core.es.mjs.map +1 -1
  44. package/dist/full.d.ts +2 -2
  45. package/dist/full.d.ts.map +1 -1
  46. package/dist/full.es.mjs +87 -64
  47. package/dist/full.es.mjs.map +1 -1
  48. package/dist/full.iife.js +2 -2
  49. package/dist/full.iife.js.map +1 -1
  50. package/dist/full.umd.js +2 -2
  51. package/dist/full.umd.js.map +1 -1
  52. package/dist/index.es.mjs +138 -68
  53. package/dist/index.es.mjs.map +1 -1
  54. package/dist/motion/animate.d.ts +25 -0
  55. package/dist/motion/animate.d.ts.map +1 -0
  56. package/dist/motion/easing.d.ts +30 -0
  57. package/dist/motion/easing.d.ts.map +1 -0
  58. package/dist/motion/flip.d.ts +55 -0
  59. package/dist/motion/flip.d.ts.map +1 -0
  60. package/dist/motion/index.d.ts +11 -138
  61. package/dist/motion/index.d.ts.map +1 -1
  62. package/dist/motion/keyframes.d.ts +21 -0
  63. package/dist/motion/keyframes.d.ts.map +1 -0
  64. package/dist/motion/reduced-motion.d.ts +12 -0
  65. package/dist/motion/reduced-motion.d.ts.map +1 -0
  66. package/dist/motion/scroll.d.ts +15 -0
  67. package/dist/motion/scroll.d.ts.map +1 -0
  68. package/dist/motion/spring.d.ts +42 -0
  69. package/dist/motion/spring.d.ts.map +1 -0
  70. package/dist/motion/stagger.d.ts +22 -0
  71. package/dist/motion/stagger.d.ts.map +1 -0
  72. package/dist/motion/timeline.d.ts +21 -0
  73. package/dist/motion/timeline.d.ts.map +1 -0
  74. package/dist/motion/transition.d.ts +22 -0
  75. package/dist/motion/transition.d.ts.map +1 -0
  76. package/dist/motion/types.d.ts +182 -0
  77. package/dist/motion/types.d.ts.map +1 -0
  78. package/dist/motion.es.mjs +320 -61
  79. package/dist/motion.es.mjs.map +1 -1
  80. package/dist/persisted-DHoi3uEs.js +278 -0
  81. package/dist/persisted-DHoi3uEs.js.map +1 -0
  82. package/dist/platform/storage.d.ts.map +1 -1
  83. package/dist/platform.es.mjs +12 -7
  84. package/dist/platform.es.mjs.map +1 -1
  85. package/dist/reactive/batch.d.ts +13 -0
  86. package/dist/reactive/batch.d.ts.map +1 -0
  87. package/dist/reactive/computed.d.ts +50 -0
  88. package/dist/reactive/computed.d.ts.map +1 -0
  89. package/dist/reactive/core.d.ts +72 -0
  90. package/dist/reactive/core.d.ts.map +1 -0
  91. package/dist/reactive/effect.d.ts +15 -0
  92. package/dist/reactive/effect.d.ts.map +1 -0
  93. package/dist/reactive/index.d.ts +2 -2
  94. package/dist/reactive/index.d.ts.map +1 -1
  95. package/dist/reactive/internals.d.ts +42 -0
  96. package/dist/reactive/internals.d.ts.map +1 -0
  97. package/dist/reactive/linked.d.ts +36 -0
  98. package/dist/reactive/linked.d.ts.map +1 -0
  99. package/dist/reactive/persisted.d.ts +14 -0
  100. package/dist/reactive/persisted.d.ts.map +1 -0
  101. package/dist/reactive/readonly.d.ts +26 -0
  102. package/dist/reactive/readonly.d.ts.map +1 -0
  103. package/dist/reactive/signal.d.ts +13 -312
  104. package/dist/reactive/signal.d.ts.map +1 -1
  105. package/dist/reactive/type-guards.d.ts +20 -0
  106. package/dist/reactive/type-guards.d.ts.map +1 -0
  107. package/dist/reactive/untrack.d.ts +29 -0
  108. package/dist/reactive/untrack.d.ts.map +1 -0
  109. package/dist/reactive/watch.d.ts +42 -0
  110. package/dist/reactive/watch.d.ts.map +1 -0
  111. package/dist/reactive.es.mjs +30 -163
  112. package/dist/reactive.es.mjs.map +1 -1
  113. package/dist/router/index.d.ts +6 -252
  114. package/dist/router/index.d.ts.map +1 -1
  115. package/dist/router/links.d.ts +44 -0
  116. package/dist/router/links.d.ts.map +1 -0
  117. package/dist/router/match.d.ts +20 -0
  118. package/dist/router/match.d.ts.map +1 -0
  119. package/dist/router/navigation.d.ts +45 -0
  120. package/dist/router/navigation.d.ts.map +1 -0
  121. package/dist/router/query.d.ts +16 -0
  122. package/dist/router/query.d.ts.map +1 -0
  123. package/dist/router/router.d.ts +34 -0
  124. package/dist/router/router.d.ts.map +1 -0
  125. package/dist/router/state.d.ts +27 -0
  126. package/dist/router/state.d.ts.map +1 -0
  127. package/dist/router/types.d.ts +88 -0
  128. package/dist/router/types.d.ts.map +1 -0
  129. package/dist/router/utils.d.ts +65 -0
  130. package/dist/router/utils.d.ts.map +1 -0
  131. package/dist/router.es.mjs +168 -132
  132. package/dist/router.es.mjs.map +1 -1
  133. package/dist/sanitize-Cxvxa-DX.js +283 -0
  134. package/dist/sanitize-Cxvxa-DX.js.map +1 -0
  135. package/dist/security/constants.d.ts +42 -0
  136. package/dist/security/constants.d.ts.map +1 -0
  137. package/dist/security/csp.d.ts +24 -0
  138. package/dist/security/csp.d.ts.map +1 -0
  139. package/dist/security/index.d.ts +4 -2
  140. package/dist/security/index.d.ts.map +1 -1
  141. package/dist/security/sanitize-core.d.ts +13 -0
  142. package/dist/security/sanitize-core.d.ts.map +1 -0
  143. package/dist/security/sanitize.d.ts +5 -57
  144. package/dist/security/sanitize.d.ts.map +1 -1
  145. package/dist/security/trusted-types.d.ts +25 -0
  146. package/dist/security/trusted-types.d.ts.map +1 -0
  147. package/dist/security/types.d.ts +36 -0
  148. package/dist/security/types.d.ts.map +1 -0
  149. package/dist/security.es.mjs +50 -277
  150. package/dist/security.es.mjs.map +1 -1
  151. package/dist/store/create-store.d.ts +15 -0
  152. package/dist/store/create-store.d.ts.map +1 -0
  153. package/dist/store/define-store.d.ts +28 -0
  154. package/dist/store/define-store.d.ts.map +1 -0
  155. package/dist/store/devtools.d.ts +22 -0
  156. package/dist/store/devtools.d.ts.map +1 -0
  157. package/dist/store/index.d.ts +10 -286
  158. package/dist/store/index.d.ts.map +1 -1
  159. package/dist/store/mapping.d.ts +28 -0
  160. package/dist/store/mapping.d.ts.map +1 -0
  161. package/dist/store/persisted.d.ts +13 -0
  162. package/dist/store/persisted.d.ts.map +1 -0
  163. package/dist/store/plugins.d.ts +13 -0
  164. package/dist/store/plugins.d.ts.map +1 -0
  165. package/dist/store/registry.d.ts +28 -0
  166. package/dist/store/registry.d.ts.map +1 -0
  167. package/dist/store/types.d.ts +71 -0
  168. package/dist/store/types.d.ts.map +1 -0
  169. package/dist/store/utils.d.ts +28 -0
  170. package/dist/store/utils.d.ts.map +1 -0
  171. package/dist/store/watch.d.ts +23 -0
  172. package/dist/store/watch.d.ts.map +1 -0
  173. package/dist/store.es.mjs +22 -224
  174. package/dist/store.es.mjs.map +1 -1
  175. package/dist/type-guards-BdKlYYlS.js +32 -0
  176. package/dist/type-guards-BdKlYYlS.js.map +1 -0
  177. package/dist/untrack-DNnnqdlR.js +6 -0
  178. package/dist/untrack-DNnnqdlR.js.map +1 -0
  179. package/dist/view/directives/bind.d.ts +7 -0
  180. package/dist/view/directives/bind.d.ts.map +1 -0
  181. package/dist/view/directives/class.d.ts +8 -0
  182. package/dist/view/directives/class.d.ts.map +1 -0
  183. package/dist/view/directives/for.d.ts +23 -0
  184. package/dist/view/directives/for.d.ts.map +1 -0
  185. package/dist/view/directives/html.d.ts +7 -0
  186. package/dist/view/directives/html.d.ts.map +1 -0
  187. package/dist/view/directives/if.d.ts +7 -0
  188. package/dist/view/directives/if.d.ts.map +1 -0
  189. package/dist/view/directives/index.d.ts +12 -0
  190. package/dist/view/directives/index.d.ts.map +1 -0
  191. package/dist/view/directives/model.d.ts +7 -0
  192. package/dist/view/directives/model.d.ts.map +1 -0
  193. package/dist/view/directives/on.d.ts +7 -0
  194. package/dist/view/directives/on.d.ts.map +1 -0
  195. package/dist/view/directives/ref.d.ts +7 -0
  196. package/dist/view/directives/ref.d.ts.map +1 -0
  197. package/dist/view/directives/show.d.ts +7 -0
  198. package/dist/view/directives/show.d.ts.map +1 -0
  199. package/dist/view/directives/style.d.ts +7 -0
  200. package/dist/view/directives/style.d.ts.map +1 -0
  201. package/dist/view/directives/text.d.ts +7 -0
  202. package/dist/view/directives/text.d.ts.map +1 -0
  203. package/dist/view/evaluate.d.ts +43 -0
  204. package/dist/view/evaluate.d.ts.map +1 -0
  205. package/dist/view/index.d.ts +3 -93
  206. package/dist/view/index.d.ts.map +1 -1
  207. package/dist/view/mount.d.ts +69 -0
  208. package/dist/view/mount.d.ts.map +1 -0
  209. package/dist/view/process.d.ts +26 -0
  210. package/dist/view/process.d.ts.map +1 -0
  211. package/dist/view/types.d.ts +36 -0
  212. package/dist/view/types.d.ts.map +1 -0
  213. package/dist/view.es.mjs +358 -251
  214. package/dist/view.es.mjs.map +1 -1
  215. package/dist/watch-DXXv3iAI.js +58 -0
  216. package/dist/watch-DXXv3iAI.js.map +1 -0
  217. package/package.json +14 -14
  218. package/src/component/component.ts +289 -0
  219. package/src/component/html.ts +53 -0
  220. package/src/component/index.ts +40 -414
  221. package/src/component/props.ts +116 -0
  222. package/src/component/types.ts +85 -0
  223. package/src/core/collection.ts +181 -7
  224. package/src/core/dom.ts +38 -0
  225. package/src/core/element.ts +59 -25
  226. package/src/core/index.ts +48 -4
  227. package/src/core/utils/array.ts +102 -0
  228. package/src/core/utils/function.ts +151 -0
  229. package/src/core/utils/index.ts +83 -0
  230. package/src/core/utils/misc.ts +82 -0
  231. package/src/core/utils/number.ts +78 -0
  232. package/src/core/utils/object.ts +206 -0
  233. package/src/core/utils/string.ts +112 -0
  234. package/src/core/utils/type-guards.ts +112 -0
  235. package/src/full.ts +187 -150
  236. package/src/index.ts +36 -36
  237. package/src/motion/animate.ts +113 -0
  238. package/src/motion/easing.ts +40 -0
  239. package/src/motion/flip.ts +176 -0
  240. package/src/motion/index.ts +41 -358
  241. package/src/motion/keyframes.ts +46 -0
  242. package/src/motion/reduced-motion.ts +17 -0
  243. package/src/motion/scroll.ts +57 -0
  244. package/src/motion/spring.ts +150 -0
  245. package/src/motion/stagger.ts +43 -0
  246. package/src/motion/timeline.ts +246 -0
  247. package/src/motion/transition.ts +51 -0
  248. package/src/motion/types.ts +198 -0
  249. package/src/platform/storage.ts +215 -208
  250. package/src/reactive/batch.ts +22 -0
  251. package/src/reactive/computed.ts +92 -0
  252. package/src/reactive/core.ts +114 -0
  253. package/src/reactive/effect.ts +54 -0
  254. package/src/reactive/index.ts +23 -22
  255. package/src/reactive/internals.ts +122 -0
  256. package/src/reactive/linked.ts +56 -0
  257. package/src/reactive/persisted.ts +74 -0
  258. package/src/reactive/readonly.ts +35 -0
  259. package/src/reactive/signal.ts +20 -520
  260. package/src/reactive/type-guards.ts +22 -0
  261. package/src/reactive/untrack.ts +31 -0
  262. package/src/reactive/watch.ts +73 -0
  263. package/src/router/index.ts +41 -718
  264. package/src/router/links.ts +130 -0
  265. package/src/router/match.ts +106 -0
  266. package/src/router/navigation.ts +71 -0
  267. package/src/router/query.ts +35 -0
  268. package/src/router/router.ts +211 -0
  269. package/src/router/state.ts +46 -0
  270. package/src/router/types.ts +93 -0
  271. package/src/router/utils.ts +116 -0
  272. package/src/security/constants.ts +209 -0
  273. package/src/security/csp.ts +77 -0
  274. package/src/security/index.ts +4 -12
  275. package/src/security/sanitize-core.ts +364 -0
  276. package/src/security/sanitize.ts +66 -625
  277. package/src/security/trusted-types.ts +69 -0
  278. package/src/security/types.ts +40 -0
  279. package/src/store/create-store.ts +329 -0
  280. package/src/store/define-store.ts +48 -0
  281. package/src/store/devtools.ts +45 -0
  282. package/src/store/index.ts +22 -848
  283. package/src/store/mapping.ts +73 -0
  284. package/src/store/persisted.ts +61 -0
  285. package/src/store/plugins.ts +32 -0
  286. package/src/store/registry.ts +51 -0
  287. package/src/store/types.ts +94 -0
  288. package/src/store/utils.ts +141 -0
  289. package/src/store/watch.ts +52 -0
  290. package/src/view/directives/bind.ts +23 -0
  291. package/src/view/directives/class.ts +70 -0
  292. package/src/view/directives/for.ts +275 -0
  293. package/src/view/directives/html.ts +19 -0
  294. package/src/view/directives/if.ts +30 -0
  295. package/src/view/directives/index.ts +11 -0
  296. package/src/view/directives/model.ts +56 -0
  297. package/src/view/directives/on.ts +41 -0
  298. package/src/view/directives/ref.ts +41 -0
  299. package/src/view/directives/show.ts +26 -0
  300. package/src/view/directives/style.ts +47 -0
  301. package/src/view/directives/text.ts +15 -0
  302. package/src/view/evaluate.ts +290 -0
  303. package/src/view/index.ts +112 -1041
  304. package/src/view/mount.ts +200 -0
  305. package/src/view/process.ts +92 -0
  306. package/src/view/types.ts +44 -0
  307. package/dist/core/utils.d.ts +0 -313
  308. package/dist/core/utils.d.ts.map +0 -1
  309. package/src/core/utils.ts +0 -444
@@ -0,0 +1,176 @@
1
+ /**
2
+ * FLIP animation helpers.
3
+ *
4
+ * @module bquery/motion
5
+ */
6
+
7
+ import type { ElementBounds, FlipGroupOptions, FlipOptions } from './types';
8
+
9
+ /**
10
+ * Capture the current bounds of an element for FLIP animation.
11
+ *
12
+ * @param element - The DOM element to measure
13
+ * @returns The element's current position and size
14
+ */
15
+ export const capturePosition = (element: Element): ElementBounds => {
16
+ const rect = element.getBoundingClientRect();
17
+ return {
18
+ top: rect.top,
19
+ left: rect.left,
20
+ width: rect.width,
21
+ height: rect.height,
22
+ };
23
+ };
24
+
25
+ /**
26
+ * Perform a FLIP (First, Last, Invert, Play) animation.
27
+ * Animates an element from its captured position to its current position.
28
+ *
29
+ * @param element - The element to animate
30
+ * @param firstBounds - The previously captured bounds
31
+ * @param options - Animation configuration
32
+ * @returns Promise that resolves when animation completes
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * const first = capturePosition(element);
37
+ * // ... DOM changes that move the element ...
38
+ * await flip(element, first, { duration: 300 });
39
+ * ```
40
+ */
41
+ export const flip = (
42
+ element: Element,
43
+ firstBounds: ElementBounds,
44
+ options: FlipOptions = {}
45
+ ): Promise<void> => {
46
+ const { duration = 300, easing = 'ease-out', onComplete } = options;
47
+
48
+ // Last: Get current position
49
+ const lastBounds = capturePosition(element);
50
+
51
+ // Skip animation if element has zero dimensions (avoid division by zero)
52
+ if (lastBounds.width === 0 || lastBounds.height === 0) {
53
+ onComplete?.();
54
+ return Promise.resolve();
55
+ }
56
+
57
+ // Invert: Calculate the delta
58
+ const deltaX = firstBounds.left - lastBounds.left;
59
+ const deltaY = firstBounds.top - lastBounds.top;
60
+ const deltaW = firstBounds.width / lastBounds.width;
61
+ const deltaH = firstBounds.height / lastBounds.height;
62
+
63
+ // Skip animation if no change
64
+ if (deltaX === 0 && deltaY === 0 && deltaW === 1 && deltaH === 1) {
65
+ onComplete?.();
66
+ return Promise.resolve();
67
+ }
68
+
69
+ const htmlElement = element as HTMLElement;
70
+
71
+ // Feature check: fallback if Web Animations API is unavailable
72
+ if (typeof htmlElement.animate !== 'function') {
73
+ onComplete?.();
74
+ return Promise.resolve();
75
+ }
76
+
77
+ // Apply inverted transform
78
+ htmlElement.style.transform = `translate(${deltaX}px, ${deltaY}px) scale(${deltaW}, ${deltaH})`;
79
+ htmlElement.style.transformOrigin = 'top left';
80
+
81
+ // Force reflow
82
+ void htmlElement.offsetHeight;
83
+
84
+ // Play: Animate back to current position
85
+ return new Promise((resolve) => {
86
+ const animation = htmlElement.animate(
87
+ [
88
+ {
89
+ transform: `translate(${deltaX}px, ${deltaY}px) scale(${deltaW}, ${deltaH})`,
90
+ },
91
+ { transform: 'translate(0, 0) scale(1, 1)' },
92
+ ],
93
+ { duration, easing, fill: 'forwards' }
94
+ );
95
+
96
+ let finalized = false;
97
+ const finalize = () => {
98
+ if (finalized) return;
99
+ finalized = true;
100
+ htmlElement.style.transform = '';
101
+ htmlElement.style.transformOrigin = '';
102
+ onComplete?.();
103
+ resolve();
104
+ };
105
+
106
+ animation.onfinish = finalize;
107
+ // Handle cancel/rejection via the finished promise
108
+ if (animation.finished) {
109
+ animation.finished.then(finalize).catch(finalize);
110
+ }
111
+ });
112
+ };
113
+
114
+ /**
115
+ * FLIP helper for animating a list of elements.
116
+ * Useful for reordering lists with smooth animations.
117
+ *
118
+ * @param elements - Array of elements to animate
119
+ * @param performUpdate - Function that performs the DOM update
120
+ * @param options - Animation configuration
121
+ *
122
+ * @example
123
+ * ```ts
124
+ * await flipList(listItems, () => {
125
+ * container.appendChild(container.firstChild); // Move first to last
126
+ * });
127
+ * ```
128
+ */
129
+ export const flipList = async (
130
+ elements: Element[],
131
+ performUpdate: () => void,
132
+ options: FlipOptions = {}
133
+ ): Promise<void> => {
134
+ await flipElements(elements, performUpdate, options);
135
+ };
136
+
137
+ /**
138
+ * FLIP helper with optional stagger support.
139
+ *
140
+ * @param elements - Array of elements to animate
141
+ * @param performUpdate - Function that performs the DOM update
142
+ * @param options - Animation configuration
143
+ */
144
+ export const flipElements = async (
145
+ elements: Element[],
146
+ performUpdate: () => void,
147
+ options: FlipGroupOptions = {}
148
+ ): Promise<void> => {
149
+ const { stagger, ...flipOptions } = options;
150
+
151
+ // First: Capture all positions
152
+ const positions = new Map<Element, ElementBounds>();
153
+ for (const el of elements) {
154
+ positions.set(el, capturePosition(el));
155
+ }
156
+
157
+ // Perform DOM update
158
+ performUpdate();
159
+
160
+ const total = elements.length;
161
+
162
+ // Animate each element
163
+ const animations = elements.map((el, index) => {
164
+ const first = positions.get(el);
165
+ if (!first) return Promise.resolve();
166
+ const delay = stagger ? stagger(index, total) : 0;
167
+ if (delay > 0) {
168
+ return new Promise((resolve) => setTimeout(resolve, delay)).then(() =>
169
+ flip(el, first, flipOptions)
170
+ );
171
+ }
172
+ return flip(el, first, flipOptions);
173
+ });
174
+
175
+ await Promise.all(animations);
176
+ };
@@ -5,361 +5,44 @@
5
5
  * @module bquery/motion
6
6
  */
7
7
 
8
- // ============================================================================
9
- // Types
10
- // ============================================================================
11
-
12
- /**
13
- * Options for view transitions.
14
- */
15
- export interface TransitionOptions {
16
- /** The DOM update function to execute during transition */
17
- update: () => void;
18
- }
19
-
20
- /**
21
- * Captured element bounds for FLIP animations.
22
- */
23
- export interface ElementBounds {
24
- top: number;
25
- left: number;
26
- width: number;
27
- height: number;
28
- }
29
-
30
- /**
31
- * FLIP animation configuration options.
32
- */
33
- export interface FlipOptions {
34
- /** Animation duration in milliseconds */
35
- duration?: number;
36
- /** CSS easing function */
37
- easing?: string;
38
- /** Callback when animation completes */
39
- onComplete?: () => void;
40
- }
41
-
42
- /**
43
- * Spring physics configuration.
44
- */
45
- export interface SpringConfig {
46
- /** Spring stiffness (default: 100) */
47
- stiffness?: number;
48
- /** Damping coefficient (default: 10) */
49
- damping?: number;
50
- /** Mass of the object (default: 1) */
51
- mass?: number;
52
- /** Velocity threshold for completion (default: 0.01) */
53
- precision?: number;
54
- }
55
-
56
- /**
57
- * Spring instance for animating values.
58
- */
59
- export interface Spring {
60
- /** Start animating to target value */
61
- to(target: number): Promise<void>;
62
- /** Get current animated value */
63
- current(): number;
64
- /** Stop the animation */
65
- stop(): void;
66
- /** Subscribe to value changes */
67
- onChange(callback: (value: number) => void): () => void;
68
- }
69
-
70
- // ============================================================================
71
- // View Transitions
72
- // ============================================================================
73
-
74
- /** Extended document type with View Transitions API */
75
- type DocumentWithTransition = Document & {
76
- startViewTransition?: (callback: () => void) => {
77
- finished: Promise<void>;
78
- ready: Promise<void>;
79
- updateCallbackDone: Promise<void>;
80
- };
81
- };
82
-
83
- /**
84
- * Execute a DOM update with view transition animation.
85
- * Falls back to immediate update when View Transitions API is unavailable.
86
- *
87
- * @param updateOrOptions - Update function or options object
88
- * @returns Promise that resolves when transition completes
89
- *
90
- * @example
91
- * ```ts
92
- * await transition(() => {
93
- * $('#content').text('Updated');
94
- * });
95
- * ```
96
- */
97
- export const transition = async (
98
- updateOrOptions: (() => void) | TransitionOptions
99
- ): Promise<void> => {
100
- const update = typeof updateOrOptions === 'function' ? updateOrOptions : updateOrOptions.update;
101
-
102
- const doc = document as DocumentWithTransition;
103
-
104
- if (doc.startViewTransition) {
105
- await doc.startViewTransition(() => update()).finished;
106
- return;
107
- }
108
-
109
- update();
110
- };
111
-
112
- // ============================================================================
113
- // FLIP Animations
114
- // ============================================================================
115
-
116
- /**
117
- * Capture the current bounds of an element for FLIP animation.
118
- *
119
- * @param element - The DOM element to measure
120
- * @returns The element's current position and size
121
- */
122
- export const capturePosition = (element: Element): ElementBounds => {
123
- const rect = element.getBoundingClientRect();
124
- return {
125
- top: rect.top,
126
- left: rect.left,
127
- width: rect.width,
128
- height: rect.height,
129
- };
130
- };
131
-
132
- /**
133
- * Perform a FLIP (First, Last, Invert, Play) animation.
134
- * Animates an element from its captured position to its current position.
135
- *
136
- * @param element - The element to animate
137
- * @param firstBounds - The previously captured bounds
138
- * @param options - Animation configuration
139
- * @returns Promise that resolves when animation completes
140
- *
141
- * @example
142
- * ```ts
143
- * const first = capturePosition(element);
144
- * // ... DOM changes that move the element ...
145
- * await flip(element, first, { duration: 300 });
146
- * ```
147
- */
148
- export const flip = (
149
- element: Element,
150
- firstBounds: ElementBounds,
151
- options: FlipOptions = {}
152
- ): Promise<void> => {
153
- const { duration = 300, easing = 'ease-out', onComplete } = options;
154
-
155
- // Last: Get current position
156
- const lastBounds = capturePosition(element);
157
-
158
- // Skip animation if element has zero dimensions (avoid division by zero)
159
- if (lastBounds.width === 0 || lastBounds.height === 0) {
160
- return Promise.resolve();
161
- }
162
-
163
- // Invert: Calculate the delta
164
- const deltaX = firstBounds.left - lastBounds.left;
165
- const deltaY = firstBounds.top - lastBounds.top;
166
- const deltaW = firstBounds.width / lastBounds.width;
167
- const deltaH = firstBounds.height / lastBounds.height;
168
-
169
- // Skip animation if no change
170
- if (deltaX === 0 && deltaY === 0 && deltaW === 1 && deltaH === 1) {
171
- return Promise.resolve();
172
- }
173
-
174
- const htmlElement = element as HTMLElement;
175
-
176
- // Apply inverted transform
177
- htmlElement.style.transform = `translate(${deltaX}px, ${deltaY}px) scale(${deltaW}, ${deltaH})`;
178
- htmlElement.style.transformOrigin = 'top left';
179
-
180
- // Force reflow
181
- void htmlElement.offsetHeight;
182
-
183
- // Play: Animate back to current position
184
- return new Promise((resolve) => {
185
- const animation = htmlElement.animate(
186
- [
187
- {
188
- transform: `translate(${deltaX}px, ${deltaY}px) scale(${deltaW}, ${deltaH})`,
189
- },
190
- { transform: 'translate(0, 0) scale(1, 1)' },
191
- ],
192
- { duration, easing, fill: 'forwards' }
193
- );
194
-
195
- animation.onfinish = () => {
196
- htmlElement.style.transform = '';
197
- htmlElement.style.transformOrigin = '';
198
- onComplete?.();
199
- resolve();
200
- };
201
- });
202
- };
203
-
204
- /**
205
- * FLIP helper for animating a list of elements.
206
- * Useful for reordering lists with smooth animations.
207
- *
208
- * @param elements - Array of elements to animate
209
- * @param performUpdate - Function that performs the DOM update
210
- * @param options - Animation configuration
211
- *
212
- * @example
213
- * ```ts
214
- * await flipList(listItems, () => {
215
- * container.appendChild(container.firstChild); // Move first to last
216
- * });
217
- * ```
218
- */
219
- export const flipList = async (
220
- elements: Element[],
221
- performUpdate: () => void,
222
- options: FlipOptions = {}
223
- ): Promise<void> => {
224
- // First: Capture all positions
225
- const positions = new Map<Element, ElementBounds>();
226
- for (const el of elements) {
227
- positions.set(el, capturePosition(el));
228
- }
229
-
230
- // Perform DOM update
231
- performUpdate();
232
-
233
- // Animate each element
234
- const animations = elements.map((el) => {
235
- const first = positions.get(el);
236
- if (!first) return Promise.resolve();
237
- return flip(el, first, options);
238
- });
239
-
240
- await Promise.all(animations);
241
- };
242
-
243
- // ============================================================================
244
- // Spring Physics
245
- // ============================================================================
246
-
247
- /**
248
- * Default spring configuration values.
249
- */
250
- const DEFAULT_SPRING_CONFIG: Required<SpringConfig> = {
251
- stiffness: 100,
252
- damping: 10,
253
- mass: 1,
254
- precision: 0.01,
255
- };
256
-
257
- /**
258
- * Create a spring-based animation for smooth, physics-based motion.
259
- *
260
- * @param initialValue - Starting value for the spring
261
- * @param config - Spring physics configuration
262
- * @returns Spring instance for controlling the animation
263
- *
264
- * @example
265
- * ```ts
266
- * const x = spring(0, { stiffness: 120, damping: 14 });
267
- * x.onChange((value) => {
268
- * element.style.transform = `translateX(${value}px)`;
269
- * });
270
- * await x.to(100);
271
- * ```
272
- */
273
- export const spring = (initialValue: number, config: SpringConfig = {}): Spring => {
274
- const { stiffness, damping, mass, precision } = {
275
- ...DEFAULT_SPRING_CONFIG,
276
- ...config,
277
- };
278
-
279
- let current = initialValue;
280
- let velocity = 0;
281
- let target = initialValue;
282
- let animationFrame: number | null = null;
283
- let resolvePromise: (() => void) | null = null;
284
- const listeners = new Set<(value: number) => void>();
285
-
286
- const notifyListeners = () => {
287
- for (const listener of listeners) {
288
- listener(current);
289
- }
290
- };
291
-
292
- const step = () => {
293
- // Spring physics calculation
294
- const displacement = current - target;
295
- const springForce = -stiffness * displacement;
296
- const dampingForce = -damping * velocity;
297
- const acceleration = (springForce + dampingForce) / mass;
298
-
299
- velocity += acceleration * (1 / 60); // Assuming 60fps
300
- current += velocity * (1 / 60);
301
-
302
- notifyListeners();
303
-
304
- // Check if spring has settled
305
- if (Math.abs(velocity) < precision && Math.abs(displacement) < precision) {
306
- current = target;
307
- velocity = 0;
308
- animationFrame = null;
309
- notifyListeners();
310
- resolvePromise?.();
311
- resolvePromise = null;
312
- return;
313
- }
314
-
315
- animationFrame = requestAnimationFrame(step);
316
- };
317
-
318
- return {
319
- to(newTarget: number): Promise<void> {
320
- target = newTarget;
321
-
322
- if (animationFrame !== null) {
323
- cancelAnimationFrame(animationFrame);
324
- }
325
-
326
- return new Promise((resolve) => {
327
- resolvePromise = resolve;
328
- animationFrame = requestAnimationFrame(step);
329
- });
330
- },
331
-
332
- current(): number {
333
- return current;
334
- },
335
-
336
- stop(): void {
337
- if (animationFrame !== null) {
338
- cancelAnimationFrame(animationFrame);
339
- animationFrame = null;
340
- }
341
- velocity = 0;
342
- resolvePromise?.();
343
- resolvePromise = null;
344
- },
345
-
346
- onChange(callback: (value: number) => void): () => void {
347
- listeners.add(callback);
348
- return () => listeners.delete(callback);
349
- },
350
- };
351
- };
352
-
353
- /**
354
- * Preset spring configurations for common use cases.
355
- */
356
- export const springPresets = {
357
- /** Gentle, slow-settling spring */
358
- gentle: { stiffness: 80, damping: 15 } as SpringConfig,
359
- /** Responsive, snappy spring */
360
- snappy: { stiffness: 200, damping: 20 } as SpringConfig,
361
- /** Bouncy, playful spring */
362
- bouncy: { stiffness: 300, damping: 8 } as SpringConfig,
363
- /** Stiff, quick spring with minimal overshoot */
364
- stiff: { stiffness: 400, damping: 30 } as SpringConfig,
365
- };
8
+ export type {
9
+ AnimateOptions,
10
+ EasingFunction,
11
+ ElementBounds,
12
+ FlipGroupOptions,
13
+ FlipOptions,
14
+ ScrollAnimateCleanup,
15
+ ScrollAnimateOptions,
16
+ SequenceOptions,
17
+ SequenceStep,
18
+ Spring,
19
+ SpringConfig,
20
+ StaggerFunction,
21
+ StaggerOptions,
22
+ TimelineConfig,
23
+ TimelineControls,
24
+ TimelineStep,
25
+ TransitionOptions,
26
+ } from './types';
27
+
28
+ export { animate } from './animate';
29
+ export {
30
+ easeInCubic,
31
+ easeInOutCubic,
32
+ easeInOutQuad,
33
+ easeInQuad,
34
+ easeOutBack,
35
+ easeOutCubic,
36
+ easeOutExpo,
37
+ easeOutQuad,
38
+ easingPresets,
39
+ linear,
40
+ } from './easing';
41
+ export { capturePosition, flip, flipElements, flipList } from './flip';
42
+ export { keyframePresets } from './keyframes';
43
+ export { prefersReducedMotion } from './reduced-motion';
44
+ export { scrollAnimate } from './scroll';
45
+ export { spring, springPresets } from './spring';
46
+ export { stagger } from './stagger';
47
+ export { sequence, timeline } from './timeline';
48
+ export { transition } from './transition';
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Keyframe presets.
3
+ *
4
+ * @module bquery/motion
5
+ */
6
+
7
+ /**
8
+ * Common keyframe presets for quick animations.
9
+ */
10
+ export const keyframePresets = {
11
+ fadeIn: (from = 0, to = 1): Keyframe[] => [{ opacity: from }, { opacity: to }],
12
+ fadeOut: (from = 1, to = 0): Keyframe[] => [{ opacity: from }, { opacity: to }],
13
+ slideInUp: (distance = 16): Keyframe[] => [
14
+ { opacity: 0, transform: `translateY(${distance}px)` },
15
+ { opacity: 1, transform: 'translateY(0)' },
16
+ ],
17
+ slideInDown: (distance = 16): Keyframe[] => [
18
+ { opacity: 0, transform: `translateY(-${distance}px)` },
19
+ { opacity: 1, transform: 'translateY(0)' },
20
+ ],
21
+ slideInLeft: (distance = 16): Keyframe[] => [
22
+ { opacity: 0, transform: `translateX(${distance}px)` },
23
+ { opacity: 1, transform: 'translateX(0)' },
24
+ ],
25
+ slideInRight: (distance = 16): Keyframe[] => [
26
+ { opacity: 0, transform: `translateX(-${distance}px)` },
27
+ { opacity: 1, transform: 'translateX(0)' },
28
+ ],
29
+ scaleIn: (from = 0.95, to = 1): Keyframe[] => [
30
+ { opacity: 0, transform: `scale(${from})` },
31
+ { opacity: 1, transform: `scale(${to})` },
32
+ ],
33
+ scaleOut: (from = 1, to = 0.95): Keyframe[] => [
34
+ { opacity: 1, transform: `scale(${from})` },
35
+ { opacity: 0, transform: `scale(${to})` },
36
+ ],
37
+ pop: (from = 0.9, mid = 1.02, to = 1): Keyframe[] => [
38
+ { opacity: 0, transform: `scale(${from})` },
39
+ { opacity: 1, transform: `scale(${mid})`, offset: 0.6 },
40
+ { opacity: 1, transform: `scale(${to})` },
41
+ ],
42
+ rotateIn: (degrees = 6): Keyframe[] => [
43
+ { opacity: 0, transform: `rotate(${degrees}deg) scale(0.98)` },
44
+ { opacity: 1, transform: 'rotate(0deg) scale(1)' },
45
+ ],
46
+ };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Reduced motion detection helpers.
3
+ *
4
+ * @module bquery/motion
5
+ */
6
+
7
+ /**
8
+ * Check whether the user prefers reduced motion.
9
+ *
10
+ * @returns true if the user prefers reduced motion, otherwise false
11
+ */
12
+ export const prefersReducedMotion = (): boolean => {
13
+ if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {
14
+ return false;
15
+ }
16
+ return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
17
+ };
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Scroll-triggered animation helpers.
3
+ *
4
+ * @module bquery/motion
5
+ */
6
+
7
+ import { animate } from './animate';
8
+ import type { ScrollAnimateCleanup, ScrollAnimateOptions } from './types';
9
+
10
+ const resolveElements = (elements: Element | Iterable<Element> | ArrayLike<Element>): Element[] => {
11
+ if (typeof Element !== 'undefined' && elements instanceof Element) return [elements];
12
+ return Array.from(elements as Iterable<Element>);
13
+ };
14
+
15
+ /**
16
+ * Animate elements when they enter the viewport.
17
+ *
18
+ * @param elements - Target element(s)
19
+ * @param options - Scroll animation configuration
20
+ * @returns Cleanup function to disconnect observers
21
+ */
22
+ export const scrollAnimate = (
23
+ elements: Element | Iterable<Element> | ArrayLike<Element>,
24
+ options: ScrollAnimateOptions
25
+ ): ScrollAnimateCleanup => {
26
+ const targets = resolveElements(elements);
27
+ if (!targets.length) return () => undefined;
28
+
29
+ const { root = null, rootMargin, threshold, once = true, onEnter, ...animationConfig } = options;
30
+
31
+ if (typeof IntersectionObserver === 'undefined') {
32
+ targets.forEach((element) => {
33
+ onEnter?.(element);
34
+ void animate(element, animationConfig);
35
+ });
36
+ return () => undefined;
37
+ }
38
+
39
+ const observer = new IntersectionObserver(
40
+ (entries) => {
41
+ entries.forEach((entry) => {
42
+ if (!entry.isIntersecting) return;
43
+ const element = entry.target as Element;
44
+ onEnter?.(element);
45
+ void animate(element, animationConfig);
46
+ if (once) {
47
+ observer.unobserve(element);
48
+ }
49
+ });
50
+ },
51
+ { root, rootMargin, threshold }
52
+ );
53
+
54
+ targets.forEach((element) => observer.observe(element));
55
+
56
+ return () => observer.disconnect();
57
+ };