@bquery/bquery 1.2.0 → 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 (305) hide show
  1. package/README.md +501 -427
  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 +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-Dz_ryNuC.js +278 -0
  81. package/dist/persisted-Dz_ryNuC.js.map +1 -0
  82. package/dist/reactive/batch.d.ts +13 -0
  83. package/dist/reactive/batch.d.ts.map +1 -0
  84. package/dist/reactive/computed.d.ts +50 -0
  85. package/dist/reactive/computed.d.ts.map +1 -0
  86. package/dist/reactive/core.d.ts +60 -0
  87. package/dist/reactive/core.d.ts.map +1 -0
  88. package/dist/reactive/effect.d.ts +15 -0
  89. package/dist/reactive/effect.d.ts.map +1 -0
  90. package/dist/reactive/index.d.ts +2 -2
  91. package/dist/reactive/index.d.ts.map +1 -1
  92. package/dist/reactive/internals.d.ts +36 -0
  93. package/dist/reactive/internals.d.ts.map +1 -0
  94. package/dist/reactive/linked.d.ts +36 -0
  95. package/dist/reactive/linked.d.ts.map +1 -0
  96. package/dist/reactive/persisted.d.ts +14 -0
  97. package/dist/reactive/persisted.d.ts.map +1 -0
  98. package/dist/reactive/readonly.d.ts +26 -0
  99. package/dist/reactive/readonly.d.ts.map +1 -0
  100. package/dist/reactive/signal.d.ts +13 -312
  101. package/dist/reactive/signal.d.ts.map +1 -1
  102. package/dist/reactive/type-guards.d.ts +20 -0
  103. package/dist/reactive/type-guards.d.ts.map +1 -0
  104. package/dist/reactive/untrack.d.ts +29 -0
  105. package/dist/reactive/untrack.d.ts.map +1 -0
  106. package/dist/reactive/watch.d.ts +42 -0
  107. package/dist/reactive/watch.d.ts.map +1 -0
  108. package/dist/reactive.es.mjs +30 -163
  109. package/dist/reactive.es.mjs.map +1 -1
  110. package/dist/router/index.d.ts +6 -252
  111. package/dist/router/index.d.ts.map +1 -1
  112. package/dist/router/links.d.ts +44 -0
  113. package/dist/router/links.d.ts.map +1 -0
  114. package/dist/router/match.d.ts +20 -0
  115. package/dist/router/match.d.ts.map +1 -0
  116. package/dist/router/navigation.d.ts +45 -0
  117. package/dist/router/navigation.d.ts.map +1 -0
  118. package/dist/router/query.d.ts +16 -0
  119. package/dist/router/query.d.ts.map +1 -0
  120. package/dist/router/router.d.ts +34 -0
  121. package/dist/router/router.d.ts.map +1 -0
  122. package/dist/router/state.d.ts +27 -0
  123. package/dist/router/state.d.ts.map +1 -0
  124. package/dist/router/types.d.ts +88 -0
  125. package/dist/router/types.d.ts.map +1 -0
  126. package/dist/router/utils.d.ts +65 -0
  127. package/dist/router/utils.d.ts.map +1 -0
  128. package/dist/router.es.mjs +168 -132
  129. package/dist/router.es.mjs.map +1 -1
  130. package/dist/sanitize-1FBEPAFH.js +272 -0
  131. package/dist/sanitize-1FBEPAFH.js.map +1 -0
  132. package/dist/security/constants.d.ts +42 -0
  133. package/dist/security/constants.d.ts.map +1 -0
  134. package/dist/security/csp.d.ts +24 -0
  135. package/dist/security/csp.d.ts.map +1 -0
  136. package/dist/security/index.d.ts +4 -2
  137. package/dist/security/index.d.ts.map +1 -1
  138. package/dist/security/sanitize-core.d.ts +13 -0
  139. package/dist/security/sanitize-core.d.ts.map +1 -0
  140. package/dist/security/sanitize.d.ts +5 -57
  141. package/dist/security/sanitize.d.ts.map +1 -1
  142. package/dist/security/trusted-types.d.ts +25 -0
  143. package/dist/security/trusted-types.d.ts.map +1 -0
  144. package/dist/security/types.d.ts +36 -0
  145. package/dist/security/types.d.ts.map +1 -0
  146. package/dist/security.es.mjs +50 -277
  147. package/dist/security.es.mjs.map +1 -1
  148. package/dist/store/create-store.d.ts +15 -0
  149. package/dist/store/create-store.d.ts.map +1 -0
  150. package/dist/store/define-store.d.ts +28 -0
  151. package/dist/store/define-store.d.ts.map +1 -0
  152. package/dist/store/devtools.d.ts +22 -0
  153. package/dist/store/devtools.d.ts.map +1 -0
  154. package/dist/store/index.d.ts +10 -286
  155. package/dist/store/index.d.ts.map +1 -1
  156. package/dist/store/mapping.d.ts +28 -0
  157. package/dist/store/mapping.d.ts.map +1 -0
  158. package/dist/store/persisted.d.ts +13 -0
  159. package/dist/store/persisted.d.ts.map +1 -0
  160. package/dist/store/plugins.d.ts +13 -0
  161. package/dist/store/plugins.d.ts.map +1 -0
  162. package/dist/store/registry.d.ts +28 -0
  163. package/dist/store/registry.d.ts.map +1 -0
  164. package/dist/store/types.d.ts +71 -0
  165. package/dist/store/types.d.ts.map +1 -0
  166. package/dist/store/utils.d.ts +28 -0
  167. package/dist/store/utils.d.ts.map +1 -0
  168. package/dist/store/watch.d.ts +23 -0
  169. package/dist/store/watch.d.ts.map +1 -0
  170. package/dist/store.es.mjs +22 -224
  171. package/dist/store.es.mjs.map +1 -1
  172. package/dist/type-guards-DRma3-Kc.js +16 -0
  173. package/dist/type-guards-DRma3-Kc.js.map +1 -0
  174. package/dist/untrack-BuEQKH7_.js +6 -0
  175. package/dist/untrack-BuEQKH7_.js.map +1 -0
  176. package/dist/view/directives/bind.d.ts +7 -0
  177. package/dist/view/directives/bind.d.ts.map +1 -0
  178. package/dist/view/directives/class.d.ts +8 -0
  179. package/dist/view/directives/class.d.ts.map +1 -0
  180. package/dist/view/directives/for.d.ts +23 -0
  181. package/dist/view/directives/for.d.ts.map +1 -0
  182. package/dist/view/directives/html.d.ts +7 -0
  183. package/dist/view/directives/html.d.ts.map +1 -0
  184. package/dist/view/directives/if.d.ts +7 -0
  185. package/dist/view/directives/if.d.ts.map +1 -0
  186. package/dist/view/directives/index.d.ts +12 -0
  187. package/dist/view/directives/index.d.ts.map +1 -0
  188. package/dist/view/directives/model.d.ts +7 -0
  189. package/dist/view/directives/model.d.ts.map +1 -0
  190. package/dist/view/directives/on.d.ts +7 -0
  191. package/dist/view/directives/on.d.ts.map +1 -0
  192. package/dist/view/directives/ref.d.ts +7 -0
  193. package/dist/view/directives/ref.d.ts.map +1 -0
  194. package/dist/view/directives/show.d.ts +7 -0
  195. package/dist/view/directives/show.d.ts.map +1 -0
  196. package/dist/view/directives/style.d.ts +7 -0
  197. package/dist/view/directives/style.d.ts.map +1 -0
  198. package/dist/view/directives/text.d.ts +7 -0
  199. package/dist/view/directives/text.d.ts.map +1 -0
  200. package/dist/view/evaluate.d.ts +43 -0
  201. package/dist/view/evaluate.d.ts.map +1 -0
  202. package/dist/view/index.d.ts +3 -93
  203. package/dist/view/index.d.ts.map +1 -1
  204. package/dist/view/mount.d.ts +69 -0
  205. package/dist/view/mount.d.ts.map +1 -0
  206. package/dist/view/process.d.ts +26 -0
  207. package/dist/view/process.d.ts.map +1 -0
  208. package/dist/view/types.d.ts +36 -0
  209. package/dist/view/types.d.ts.map +1 -0
  210. package/dist/view.es.mjs +368 -267
  211. package/dist/view.es.mjs.map +1 -1
  212. package/dist/watch-CXyaBC_9.js +58 -0
  213. package/dist/watch-CXyaBC_9.js.map +1 -0
  214. package/package.json +132 -132
  215. package/src/component/component.ts +289 -0
  216. package/src/component/html.ts +53 -0
  217. package/src/component/index.ts +40 -414
  218. package/src/component/props.ts +116 -0
  219. package/src/component/types.ts +85 -0
  220. package/src/core/collection.ts +588 -454
  221. package/src/core/dom.ts +38 -0
  222. package/src/core/element.ts +746 -740
  223. package/src/core/index.ts +43 -0
  224. package/src/core/utils/array.ts +102 -0
  225. package/src/core/utils/function.ts +110 -0
  226. package/src/core/utils/index.ts +83 -0
  227. package/src/core/utils/misc.ts +82 -0
  228. package/src/core/utils/number.ts +78 -0
  229. package/src/core/utils/object.ts +206 -0
  230. package/src/core/utils/string.ts +112 -0
  231. package/src/core/utils/type-guards.ts +112 -0
  232. package/src/full.ts +187 -150
  233. package/src/index.ts +36 -36
  234. package/src/motion/animate.ts +113 -0
  235. package/src/motion/easing.ts +40 -0
  236. package/src/motion/flip.ts +176 -0
  237. package/src/motion/index.ts +41 -358
  238. package/src/motion/keyframes.ts +46 -0
  239. package/src/motion/reduced-motion.ts +17 -0
  240. package/src/motion/scroll.ts +57 -0
  241. package/src/motion/spring.ts +150 -0
  242. package/src/motion/stagger.ts +43 -0
  243. package/src/motion/timeline.ts +246 -0
  244. package/src/motion/transition.ts +51 -0
  245. package/src/motion/types.ts +198 -0
  246. package/src/reactive/batch.ts +22 -0
  247. package/src/reactive/computed.ts +92 -0
  248. package/src/reactive/core.ts +93 -0
  249. package/src/reactive/effect.ts +43 -0
  250. package/src/reactive/index.ts +23 -22
  251. package/src/reactive/internals.ts +105 -0
  252. package/src/reactive/linked.ts +56 -0
  253. package/src/reactive/persisted.ts +74 -0
  254. package/src/reactive/readonly.ts +35 -0
  255. package/src/reactive/signal.ts +20 -520
  256. package/src/reactive/type-guards.ts +22 -0
  257. package/src/reactive/untrack.ts +31 -0
  258. package/src/reactive/watch.ts +73 -0
  259. package/src/router/index.ts +41 -718
  260. package/src/router/links.ts +130 -0
  261. package/src/router/match.ts +106 -0
  262. package/src/router/navigation.ts +71 -0
  263. package/src/router/query.ts +35 -0
  264. package/src/router/router.ts +211 -0
  265. package/src/router/state.ts +46 -0
  266. package/src/router/types.ts +93 -0
  267. package/src/router/utils.ts +116 -0
  268. package/src/security/constants.ts +209 -0
  269. package/src/security/csp.ts +77 -0
  270. package/src/security/index.ts +4 -12
  271. package/src/security/sanitize-core.ts +343 -0
  272. package/src/security/sanitize.ts +66 -625
  273. package/src/security/trusted-types.ts +69 -0
  274. package/src/security/types.ts +40 -0
  275. package/src/store/create-store.ts +329 -0
  276. package/src/store/define-store.ts +48 -0
  277. package/src/store/devtools.ts +45 -0
  278. package/src/store/index.ts +22 -848
  279. package/src/store/mapping.ts +73 -0
  280. package/src/store/persisted.ts +61 -0
  281. package/src/store/plugins.ts +32 -0
  282. package/src/store/registry.ts +51 -0
  283. package/src/store/types.ts +94 -0
  284. package/src/store/utils.ts +141 -0
  285. package/src/store/watch.ts +52 -0
  286. package/src/view/directives/bind.ts +23 -0
  287. package/src/view/directives/class.ts +70 -0
  288. package/src/view/directives/for.ts +275 -0
  289. package/src/view/directives/html.ts +19 -0
  290. package/src/view/directives/if.ts +30 -0
  291. package/src/view/directives/index.ts +11 -0
  292. package/src/view/directives/model.ts +56 -0
  293. package/src/view/directives/on.ts +41 -0
  294. package/src/view/directives/ref.ts +41 -0
  295. package/src/view/directives/show.ts +26 -0
  296. package/src/view/directives/style.ts +47 -0
  297. package/src/view/directives/text.ts +15 -0
  298. package/src/view/evaluate.ts +274 -0
  299. package/src/view/index.ts +112 -1041
  300. package/src/view/mount.ts +200 -0
  301. package/src/view/process.ts +92 -0
  302. package/src/view/types.ts +44 -0
  303. package/dist/core/utils.d.ts +0 -313
  304. package/dist/core/utils.d.ts.map +0 -1
  305. package/src/core/utils.ts +0 -444
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Web Animations helpers.
3
+ *
4
+ * @module bquery/motion
5
+ */
6
+
7
+ import { prefersReducedMotion } from './reduced-motion';
8
+ import type { AnimateOptions } from './types';
9
+
10
+ /** @internal */
11
+ const isStyleValue = (value: unknown): value is string | number =>
12
+ typeof value === 'string' || typeof value === 'number';
13
+
14
+ /**
15
+ * Convert camelCase property names to kebab-case for CSS.
16
+ * @internal
17
+ */
18
+ const toKebabCase = (str: string): string => {
19
+ return str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
20
+ };
21
+
22
+ /** @internal */
23
+ export const applyFinalKeyframeStyles = (
24
+ element: Element,
25
+ keyframes: Keyframe[] | PropertyIndexedKeyframes
26
+ ): void => {
27
+ const htmlElement = element as HTMLElement;
28
+ const style = htmlElement.style;
29
+
30
+ if (Array.isArray(keyframes)) {
31
+ const last = keyframes[keyframes.length - 1];
32
+ if (!last) return;
33
+ for (const [prop, value] of Object.entries(last)) {
34
+ if (prop === 'offset' || prop === 'easing' || prop === 'composite') continue;
35
+ if (isStyleValue(value)) {
36
+ // Convert camelCase to kebab-case for CSS properties
37
+ const cssProp = prop.startsWith('--') ? prop : toKebabCase(prop);
38
+ style.setProperty(cssProp, String(value));
39
+ }
40
+ }
41
+ return;
42
+ }
43
+
44
+ for (const [prop, value] of Object.entries(keyframes)) {
45
+ if (prop === 'offset' || prop === 'easing' || prop === 'composite') continue;
46
+ const finalValue = Array.isArray(value) ? value[value.length - 1] : value;
47
+ if (isStyleValue(finalValue)) {
48
+ // Convert camelCase to kebab-case for CSS properties
49
+ const cssProp = prop.startsWith('--') ? prop : toKebabCase(prop);
50
+ style.setProperty(cssProp, String(finalValue));
51
+ }
52
+ }
53
+ };
54
+
55
+ /**
56
+ * Animate an element using the Web Animations API with reduced-motion fallback.
57
+ *
58
+ * @param element - Element to animate
59
+ * @param config - Animation configuration
60
+ * @returns Promise that resolves when animation completes
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * await animate(element, {
65
+ * keyframes: [{ opacity: 0 }, { opacity: 1 }],
66
+ * options: { duration: 200, easing: 'ease-out' },
67
+ * });
68
+ * ```
69
+ */
70
+ export const animate = (element: Element, config: AnimateOptions): Promise<void> => {
71
+ const { keyframes, options, commitStyles = true, respectReducedMotion = true, onFinish } = config;
72
+
73
+ if (respectReducedMotion && prefersReducedMotion()) {
74
+ if (commitStyles) {
75
+ applyFinalKeyframeStyles(element, keyframes);
76
+ }
77
+ onFinish?.();
78
+ return Promise.resolve();
79
+ }
80
+
81
+ const htmlElement = element as HTMLElement;
82
+ if (typeof htmlElement.animate !== 'function') {
83
+ if (commitStyles) {
84
+ applyFinalKeyframeStyles(element, keyframes);
85
+ }
86
+ onFinish?.();
87
+ return Promise.resolve();
88
+ }
89
+
90
+ return new Promise((resolve) => {
91
+ const animation = htmlElement.animate(keyframes, options);
92
+ let finalized = false;
93
+ const finalize = () => {
94
+ if (finalized) return;
95
+ finalized = true;
96
+ if (commitStyles) {
97
+ if (typeof animation.commitStyles === 'function') {
98
+ animation.commitStyles();
99
+ } else {
100
+ applyFinalKeyframeStyles(element, keyframes);
101
+ }
102
+ }
103
+ animation.cancel();
104
+ onFinish?.();
105
+ resolve();
106
+ };
107
+
108
+ animation.onfinish = finalize;
109
+ if (animation.finished) {
110
+ animation.finished.then(finalize).catch(finalize);
111
+ }
112
+ });
113
+ };
@@ -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
+ };