@bquery/bquery 1.1.2 → 1.3.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 (307) hide show
  1. package/README.md +501 -323
  2. package/dist/batch-4LAvfLE7.js +13 -0
  3. package/dist/batch-4LAvfLE7.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 +36 -0
  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 +8 -0
  21. package/dist/core/element.d.ts.map +1 -1
  22. package/dist/core/index.d.ts +1 -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 +70 -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-COenAZjD.js +145 -0
  41. package/dist/core-COenAZjD.js.map +1 -0
  42. package/dist/core.es.mjs +411 -448
  43. package/dist/core.es.mjs.map +1 -1
  44. package/dist/full.d.ts +8 -2
  45. package/dist/full.d.ts.map +1 -1
  46. package/dist/full.es.mjs +86 -40
  47. package/dist/full.es.mjs.map +1 -1
  48. package/dist/full.iife.js +6 -1
  49. package/dist/full.iife.js.map +1 -1
  50. package/dist/full.umd.js +6 -1
  51. package/dist/full.umd.js.map +1 -1
  52. package/dist/index.d.ts +3 -0
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.es.mjs +137 -44
  55. package/dist/index.es.mjs.map +1 -1
  56. package/dist/motion/animate.d.ts +25 -0
  57. package/dist/motion/animate.d.ts.map +1 -0
  58. package/dist/motion/easing.d.ts +30 -0
  59. package/dist/motion/easing.d.ts.map +1 -0
  60. package/dist/motion/flip.d.ts +55 -0
  61. package/dist/motion/flip.d.ts.map +1 -0
  62. package/dist/motion/index.d.ts +11 -138
  63. package/dist/motion/index.d.ts.map +1 -1
  64. package/dist/motion/keyframes.d.ts +21 -0
  65. package/dist/motion/keyframes.d.ts.map +1 -0
  66. package/dist/motion/reduced-motion.d.ts +12 -0
  67. package/dist/motion/reduced-motion.d.ts.map +1 -0
  68. package/dist/motion/scroll.d.ts +15 -0
  69. package/dist/motion/scroll.d.ts.map +1 -0
  70. package/dist/motion/spring.d.ts +42 -0
  71. package/dist/motion/spring.d.ts.map +1 -0
  72. package/dist/motion/stagger.d.ts +22 -0
  73. package/dist/motion/stagger.d.ts.map +1 -0
  74. package/dist/motion/timeline.d.ts +21 -0
  75. package/dist/motion/timeline.d.ts.map +1 -0
  76. package/dist/motion/transition.d.ts +22 -0
  77. package/dist/motion/transition.d.ts.map +1 -0
  78. package/dist/motion/types.d.ts +182 -0
  79. package/dist/motion/types.d.ts.map +1 -0
  80. package/dist/motion.es.mjs +320 -61
  81. package/dist/motion.es.mjs.map +1 -1
  82. package/dist/persisted-Dz_ryNuC.js +278 -0
  83. package/dist/persisted-Dz_ryNuC.js.map +1 -0
  84. package/dist/reactive/batch.d.ts +13 -0
  85. package/dist/reactive/batch.d.ts.map +1 -0
  86. package/dist/reactive/computed.d.ts +50 -0
  87. package/dist/reactive/computed.d.ts.map +1 -0
  88. package/dist/reactive/core.d.ts +60 -0
  89. package/dist/reactive/core.d.ts.map +1 -0
  90. package/dist/reactive/effect.d.ts +15 -0
  91. package/dist/reactive/effect.d.ts.map +1 -0
  92. package/dist/reactive/index.d.ts +2 -2
  93. package/dist/reactive/index.d.ts.map +1 -1
  94. package/dist/reactive/internals.d.ts +36 -0
  95. package/dist/reactive/internals.d.ts.map +1 -0
  96. package/dist/reactive/linked.d.ts +36 -0
  97. package/dist/reactive/linked.d.ts.map +1 -0
  98. package/dist/reactive/persisted.d.ts +14 -0
  99. package/dist/reactive/persisted.d.ts.map +1 -0
  100. package/dist/reactive/readonly.d.ts +26 -0
  101. package/dist/reactive/readonly.d.ts.map +1 -0
  102. package/dist/reactive/signal.d.ts +13 -305
  103. package/dist/reactive/signal.d.ts.map +1 -1
  104. package/dist/reactive/type-guards.d.ts +20 -0
  105. package/dist/reactive/type-guards.d.ts.map +1 -0
  106. package/dist/reactive/untrack.d.ts +29 -0
  107. package/dist/reactive/untrack.d.ts.map +1 -0
  108. package/dist/reactive/watch.d.ts +42 -0
  109. package/dist/reactive/watch.d.ts.map +1 -0
  110. package/dist/reactive.es.mjs +30 -154
  111. package/dist/reactive.es.mjs.map +1 -1
  112. package/dist/router/index.d.ts +41 -0
  113. package/dist/router/index.d.ts.map +1 -0
  114. package/dist/router/links.d.ts +44 -0
  115. package/dist/router/links.d.ts.map +1 -0
  116. package/dist/router/match.d.ts +20 -0
  117. package/dist/router/match.d.ts.map +1 -0
  118. package/dist/router/navigation.d.ts +45 -0
  119. package/dist/router/navigation.d.ts.map +1 -0
  120. package/dist/router/query.d.ts +16 -0
  121. package/dist/router/query.d.ts.map +1 -0
  122. package/dist/router/router.d.ts +34 -0
  123. package/dist/router/router.d.ts.map +1 -0
  124. package/dist/router/state.d.ts +27 -0
  125. package/dist/router/state.d.ts.map +1 -0
  126. package/dist/router/types.d.ts +88 -0
  127. package/dist/router/types.d.ts.map +1 -0
  128. package/dist/router/utils.d.ts +65 -0
  129. package/dist/router/utils.d.ts.map +1 -0
  130. package/dist/router.es.mjs +202 -0
  131. package/dist/router.es.mjs.map +1 -0
  132. package/dist/sanitize-1FBEPAFH.js +272 -0
  133. package/dist/sanitize-1FBEPAFH.js.map +1 -0
  134. package/dist/security/constants.d.ts +42 -0
  135. package/dist/security/constants.d.ts.map +1 -0
  136. package/dist/security/csp.d.ts +24 -0
  137. package/dist/security/csp.d.ts.map +1 -0
  138. package/dist/security/index.d.ts +4 -2
  139. package/dist/security/index.d.ts.map +1 -1
  140. package/dist/security/sanitize-core.d.ts +13 -0
  141. package/dist/security/sanitize-core.d.ts.map +1 -0
  142. package/dist/security/sanitize.d.ts +5 -57
  143. package/dist/security/sanitize.d.ts.map +1 -1
  144. package/dist/security/trusted-types.d.ts +25 -0
  145. package/dist/security/trusted-types.d.ts.map +1 -0
  146. package/dist/security/types.d.ts +36 -0
  147. package/dist/security/types.d.ts.map +1 -0
  148. package/dist/security.es.mjs +50 -277
  149. package/dist/security.es.mjs.map +1 -1
  150. package/dist/store/create-store.d.ts +15 -0
  151. package/dist/store/create-store.d.ts.map +1 -0
  152. package/dist/store/define-store.d.ts +28 -0
  153. package/dist/store/define-store.d.ts.map +1 -0
  154. package/dist/store/devtools.d.ts +22 -0
  155. package/dist/store/devtools.d.ts.map +1 -0
  156. package/dist/store/index.d.ts +12 -0
  157. package/dist/store/index.d.ts.map +1 -0
  158. package/dist/store/mapping.d.ts +28 -0
  159. package/dist/store/mapping.d.ts.map +1 -0
  160. package/dist/store/persisted.d.ts +13 -0
  161. package/dist/store/persisted.d.ts.map +1 -0
  162. package/dist/store/plugins.d.ts +13 -0
  163. package/dist/store/plugins.d.ts.map +1 -0
  164. package/dist/store/registry.d.ts +28 -0
  165. package/dist/store/registry.d.ts.map +1 -0
  166. package/dist/store/types.d.ts +71 -0
  167. package/dist/store/types.d.ts.map +1 -0
  168. package/dist/store/utils.d.ts +28 -0
  169. package/dist/store/utils.d.ts.map +1 -0
  170. package/dist/store/watch.d.ts +23 -0
  171. package/dist/store/watch.d.ts.map +1 -0
  172. package/dist/store.es.mjs +27 -0
  173. package/dist/store.es.mjs.map +1 -0
  174. package/dist/type-guards-DRma3-Kc.js +16 -0
  175. package/dist/type-guards-DRma3-Kc.js.map +1 -0
  176. package/dist/untrack-BuEQKH7_.js +6 -0
  177. package/dist/untrack-BuEQKH7_.js.map +1 -0
  178. package/dist/view/directives/bind.d.ts +7 -0
  179. package/dist/view/directives/bind.d.ts.map +1 -0
  180. package/dist/view/directives/class.d.ts +8 -0
  181. package/dist/view/directives/class.d.ts.map +1 -0
  182. package/dist/view/directives/for.d.ts +23 -0
  183. package/dist/view/directives/for.d.ts.map +1 -0
  184. package/dist/view/directives/html.d.ts +7 -0
  185. package/dist/view/directives/html.d.ts.map +1 -0
  186. package/dist/view/directives/if.d.ts +7 -0
  187. package/dist/view/directives/if.d.ts.map +1 -0
  188. package/dist/view/directives/index.d.ts +12 -0
  189. package/dist/view/directives/index.d.ts.map +1 -0
  190. package/dist/view/directives/model.d.ts +7 -0
  191. package/dist/view/directives/model.d.ts.map +1 -0
  192. package/dist/view/directives/on.d.ts +7 -0
  193. package/dist/view/directives/on.d.ts.map +1 -0
  194. package/dist/view/directives/ref.d.ts +7 -0
  195. package/dist/view/directives/ref.d.ts.map +1 -0
  196. package/dist/view/directives/show.d.ts +7 -0
  197. package/dist/view/directives/show.d.ts.map +1 -0
  198. package/dist/view/directives/style.d.ts +7 -0
  199. package/dist/view/directives/style.d.ts.map +1 -0
  200. package/dist/view/directives/text.d.ts +7 -0
  201. package/dist/view/directives/text.d.ts.map +1 -0
  202. package/dist/view/evaluate.d.ts +43 -0
  203. package/dist/view/evaluate.d.ts.map +1 -0
  204. package/dist/view/index.d.ts +111 -0
  205. package/dist/view/index.d.ts.map +1 -0
  206. package/dist/view/mount.d.ts +69 -0
  207. package/dist/view/mount.d.ts.map +1 -0
  208. package/dist/view/process.d.ts +26 -0
  209. package/dist/view/process.d.ts.map +1 -0
  210. package/dist/view/types.d.ts +36 -0
  211. package/dist/view/types.d.ts.map +1 -0
  212. package/dist/view.es.mjs +426 -0
  213. package/dist/view.es.mjs.map +1 -0
  214. package/dist/watch-CXyaBC_9.js +58 -0
  215. package/dist/watch-CXyaBC_9.js.map +1 -0
  216. package/package.json +26 -14
  217. package/src/component/component.ts +289 -0
  218. package/src/component/html.ts +53 -0
  219. package/src/component/index.ts +40 -414
  220. package/src/component/props.ts +116 -0
  221. package/src/component/types.ts +85 -0
  222. package/src/core/collection.ts +588 -454
  223. package/src/core/dom.ts +38 -0
  224. package/src/core/element.ts +746 -740
  225. package/src/core/index.ts +43 -0
  226. package/src/core/utils/array.ts +102 -0
  227. package/src/core/utils/function.ts +110 -0
  228. package/src/core/utils/index.ts +83 -0
  229. package/src/core/utils/misc.ts +82 -0
  230. package/src/core/utils/number.ts +78 -0
  231. package/src/core/utils/object.ts +206 -0
  232. package/src/core/utils/string.ts +112 -0
  233. package/src/core/utils/type-guards.ts +112 -0
  234. package/src/full.ts +187 -106
  235. package/src/index.ts +36 -27
  236. package/src/motion/animate.ts +113 -0
  237. package/src/motion/easing.ts +40 -0
  238. package/src/motion/flip.ts +176 -0
  239. package/src/motion/index.ts +41 -358
  240. package/src/motion/keyframes.ts +46 -0
  241. package/src/motion/reduced-motion.ts +17 -0
  242. package/src/motion/scroll.ts +57 -0
  243. package/src/motion/spring.ts +150 -0
  244. package/src/motion/stagger.ts +43 -0
  245. package/src/motion/timeline.ts +246 -0
  246. package/src/motion/transition.ts +51 -0
  247. package/src/motion/types.ts +198 -0
  248. package/src/reactive/batch.ts +22 -0
  249. package/src/reactive/computed.ts +92 -0
  250. package/src/reactive/core.ts +93 -0
  251. package/src/reactive/effect.ts +43 -0
  252. package/src/reactive/index.ts +23 -22
  253. package/src/reactive/internals.ts +105 -0
  254. package/src/reactive/linked.ts +56 -0
  255. package/src/reactive/persisted.ts +74 -0
  256. package/src/reactive/readonly.ts +35 -0
  257. package/src/reactive/signal.ts +20 -506
  258. package/src/reactive/type-guards.ts +22 -0
  259. package/src/reactive/untrack.ts +31 -0
  260. package/src/reactive/watch.ts +73 -0
  261. package/src/router/index.ts +41 -0
  262. package/src/router/links.ts +130 -0
  263. package/src/router/match.ts +106 -0
  264. package/src/router/navigation.ts +71 -0
  265. package/src/router/query.ts +35 -0
  266. package/src/router/router.ts +211 -0
  267. package/src/router/state.ts +46 -0
  268. package/src/router/types.ts +93 -0
  269. package/src/router/utils.ts +116 -0
  270. package/src/security/constants.ts +209 -0
  271. package/src/security/csp.ts +77 -0
  272. package/src/security/index.ts +4 -12
  273. package/src/security/sanitize-core.ts +343 -0
  274. package/src/security/sanitize.ts +66 -625
  275. package/src/security/trusted-types.ts +69 -0
  276. package/src/security/types.ts +40 -0
  277. package/src/store/create-store.ts +329 -0
  278. package/src/store/define-store.ts +48 -0
  279. package/src/store/devtools.ts +45 -0
  280. package/src/store/index.ts +22 -0
  281. package/src/store/mapping.ts +73 -0
  282. package/src/store/persisted.ts +61 -0
  283. package/src/store/plugins.ts +32 -0
  284. package/src/store/registry.ts +51 -0
  285. package/src/store/types.ts +94 -0
  286. package/src/store/utils.ts +141 -0
  287. package/src/store/watch.ts +52 -0
  288. package/src/view/directives/bind.ts +23 -0
  289. package/src/view/directives/class.ts +70 -0
  290. package/src/view/directives/for.ts +275 -0
  291. package/src/view/directives/html.ts +19 -0
  292. package/src/view/directives/if.ts +30 -0
  293. package/src/view/directives/index.ts +11 -0
  294. package/src/view/directives/model.ts +56 -0
  295. package/src/view/directives/on.ts +41 -0
  296. package/src/view/directives/ref.ts +41 -0
  297. package/src/view/directives/show.ts +26 -0
  298. package/src/view/directives/style.ts +47 -0
  299. package/src/view/directives/text.ts +15 -0
  300. package/src/view/evaluate.ts +274 -0
  301. package/src/view/index.ts +112 -0
  302. package/src/view/mount.ts +200 -0
  303. package/src/view/process.ts +92 -0
  304. package/src/view/types.ts +44 -0
  305. package/dist/core/utils.d.ts +0 -313
  306. package/dist/core/utils.d.ts.map +0 -1
  307. package/src/core/utils.ts +0 -444
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Easing helpers.
3
+ *
4
+ * @module bquery/motion
5
+ */
6
+
7
+ import type { EasingFunction } from './types';
8
+
9
+ const clamp = (value: number) => Math.min(1, Math.max(0, value));
10
+
11
+ export const linear: EasingFunction = (t) => clamp(t);
12
+ export const easeInQuad: EasingFunction = (t) => clamp(t * t);
13
+ export const easeOutQuad: EasingFunction = (t) => clamp(1 - (1 - t) * (1 - t));
14
+ export const easeInOutQuad: EasingFunction = (t) =>
15
+ clamp(t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2);
16
+ export const easeInCubic: EasingFunction = (t) => clamp(t * t * t);
17
+ export const easeOutCubic: EasingFunction = (t) => clamp(1 - Math.pow(1 - t, 3));
18
+ export const easeInOutCubic: EasingFunction = (t) =>
19
+ clamp(t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2);
20
+ export const easeOutBack: EasingFunction = (t) => {
21
+ const c1 = 1.70158;
22
+ const c3 = c1 + 1;
23
+ return clamp(1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2));
24
+ };
25
+ export const easeOutExpo: EasingFunction = (t) => clamp(t === 1 ? 1 : 1 - Math.pow(2, -10 * t));
26
+
27
+ /**
28
+ * Named easing presets.
29
+ */
30
+ export const easingPresets = {
31
+ linear,
32
+ easeInQuad,
33
+ easeOutQuad,
34
+ easeInOutQuad,
35
+ easeInCubic,
36
+ easeOutCubic,
37
+ easeInOutCubic,
38
+ easeOutBack,
39
+ easeOutExpo,
40
+ };
@@ -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
+ };