elementdrawing 1.0.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 (78) hide show
  1. package/LICENSE +21 -0
  2. package/dist/elementdrawing.min.js +3 -0
  3. package/dist/elementdrawing.min.js.LICENSE.txt +8 -0
  4. package/dist/elementdrawing.min.js.map +1 -0
  5. package/dist/index.html +1 -0
  6. package/package.json +127 -0
  7. package/src/core/bridge.h +855 -0
  8. package/src/core/diff.c +900 -0
  9. package/src/core/element.c +1078 -0
  10. package/src/core/event.c +813 -0
  11. package/src/core/fiber.c +1027 -0
  12. package/src/core/hooks.c +919 -0
  13. package/src/core/renderer.c +963 -0
  14. package/src/core/scheduler.c +702 -0
  15. package/src/core/state.c +803 -0
  16. package/src/css/animations.css +779 -0
  17. package/src/css/base.css +615 -0
  18. package/src/css/components.css +1311 -0
  19. package/src/css/tailwind.css +370 -0
  20. package/src/css/themes.css +517 -0
  21. package/src/css/utilities.css +475 -0
  22. package/src/index.js +746 -0
  23. package/src/js/animation.js +655 -0
  24. package/src/js/dom.js +665 -0
  25. package/src/js/events.js +585 -0
  26. package/src/js/http.js +446 -0
  27. package/src/js/index.js +26 -0
  28. package/src/js/router.js +483 -0
  29. package/src/js/store.js +539 -0
  30. package/src/js/utils.js +593 -0
  31. package/src/js/validator.js +529 -0
  32. package/src/jsx/components/Accordion.jsx +210 -0
  33. package/src/jsx/components/Alert.jsx +169 -0
  34. package/src/jsx/components/Avatar.jsx +214 -0
  35. package/src/jsx/components/Badge.jsx +136 -0
  36. package/src/jsx/components/Breadcrumb.jsx +200 -0
  37. package/src/jsx/components/Button.jsx +188 -0
  38. package/src/jsx/components/Card.jsx +192 -0
  39. package/src/jsx/components/Carousel.jsx +278 -0
  40. package/src/jsx/components/Checkbox.jsx +215 -0
  41. package/src/jsx/components/Dialog.jsx +242 -0
  42. package/src/jsx/components/Drawer.jsx +190 -0
  43. package/src/jsx/components/Dropdown.jsx +268 -0
  44. package/src/jsx/components/Form.jsx +274 -0
  45. package/src/jsx/components/Input.jsx +285 -0
  46. package/src/jsx/components/Menu.jsx +276 -0
  47. package/src/jsx/components/Modal.jsx +274 -0
  48. package/src/jsx/components/Navbar.jsx +292 -0
  49. package/src/jsx/components/Pagination.jsx +268 -0
  50. package/src/jsx/components/Progress.jsx +252 -0
  51. package/src/jsx/components/Radio.jsx +208 -0
  52. package/src/jsx/components/Select.jsx +397 -0
  53. package/src/jsx/components/Sidebar.jsx +250 -0
  54. package/src/jsx/components/Slider.jsx +310 -0
  55. package/src/jsx/components/Spinner.jsx +198 -0
  56. package/src/jsx/components/Switch.jsx +201 -0
  57. package/src/jsx/components/Table.jsx +332 -0
  58. package/src/jsx/components/Tabs.jsx +227 -0
  59. package/src/jsx/components/Textarea.jsx +212 -0
  60. package/src/jsx/components/Toast.jsx +270 -0
  61. package/src/jsx/components/Tooltip.jsx +178 -0
  62. package/src/jsx/components/Typography.jsx +299 -0
  63. package/src/jsx/components/index.jsx +70 -0
  64. package/src/jsx/core/element.js +3 -0
  65. package/src/jsx/hooks/index.js +356 -0
  66. package/src/jsx/hooks/useCallback.js +472 -0
  67. package/src/jsx/hooks/useContext.js +586 -0
  68. package/src/jsx/hooks/useEffect.js +704 -0
  69. package/src/jsx/hooks/useLayoutEffect.js +508 -0
  70. package/src/jsx/hooks/useMemo.js +689 -0
  71. package/src/jsx/hooks/useReducer.js +729 -0
  72. package/src/jsx/hooks/useRef.js +542 -0
  73. package/src/jsx/hooks/useState.js +854 -0
  74. package/src/jsx/runtime/commit.js +903 -0
  75. package/src/jsx/runtime/createElement.js +860 -0
  76. package/src/jsx/runtime/index.js +356 -0
  77. package/src/jsx/runtime/reconcile.js +687 -0
  78. package/src/jsx/runtime/render.js +914 -0
@@ -0,0 +1,655 @@
1
+ /**
2
+ * Animation Utilities
3
+ * ElementDrawing Framework - requestAnimationFrame wrapper, easing functions,
4
+ * tween engine, spring physics, keyframes, scroll/intersection animation,
5
+ * sequencing, and CSS animation helpers.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ // ─── requestAnimationFrame Wrapper ────────────────────────────────────────────
11
+
12
+ let rafId = 0;
13
+ const rafCallbacks = new Map();
14
+
15
+ /**
16
+ * Request an animation frame with a cancel function.
17
+ * @param {Function} callback
18
+ * @returns {number} RAF ID
19
+ */
20
+ function requestFrame(callback) {
21
+ const id = ++rafId;
22
+ const wrapped = (time) => {
23
+ rafCallbacks.delete(id);
24
+ callback(time);
25
+ };
26
+ rafCallbacks.set(id, wrapped);
27
+ return window.requestAnimationFrame(wrapped);
28
+ }
29
+
30
+ /**
31
+ * Cancel a requested animation frame.
32
+ * @param {number} id
33
+ */
34
+ function cancelFrame(id) {
35
+ window.cancelAnimationFrame(id);
36
+ rafCallbacks.delete(id);
37
+ }
38
+
39
+ /**
40
+ * Cancel all pending animation frames.
41
+ */
42
+ function cancelAllFrames() {
43
+ rafCallbacks.forEach((cb, id) => window.cancelAnimationFrame(id));
44
+ rafCallbacks.clear();
45
+ }
46
+
47
+ // ─── Easing Functions ─────────────────────────────────────────────────────────
48
+
49
+ const easing = {
50
+ linear: (t) => t,
51
+
52
+ easeIn: (t) => t * t * t,
53
+ easeOut: (t) => 1 - Math.pow(1 - t, 3),
54
+ easeInOut: (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2,
55
+
56
+ easeInQuad: (t) => t * t,
57
+ easeOutQuad: (t) => 1 - (1 - t) * (1 - t),
58
+ easeInOutQuad: (t) => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2,
59
+
60
+ easeInCubic: (t) => t * t * t,
61
+ easeOutCubic: (t) => 1 - Math.pow(1 - t, 3),
62
+ easeInOutCubic: (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2,
63
+
64
+ easeInQuart: (t) => t * t * t * t,
65
+ easeOutQuart: (t) => 1 - Math.pow(1 - t, 4),
66
+ easeInOutQuart: (t) => t < 0.5 ? 8 * t * t * t * t : 1 - Math.pow(-2 * t + 2, 4) / 2,
67
+
68
+ bounce: (t) => {
69
+ const n1 = 7.5625, d1 = 2.75;
70
+ if (t < 1 / d1) return n1 * t * t;
71
+ if (t < 2 / d1) return n1 * (t -= 1.5 / d1) * t + 0.75;
72
+ if (t < 2.5 / d1) return n1 * (t -= 2.25 / d1) * t + 0.9375;
73
+ return n1 * (t -= 2.625 / d1) * t + 0.984375;
74
+ },
75
+
76
+ elastic: (t) => {
77
+ if (t === 0 || t === 1) return t;
78
+ return -Math.pow(2, 10 * t - 10) * Math.sin((t * 10 - 10.75) * ((2 * Math.PI) / 3));
79
+ },
80
+
81
+ back: (t) => {
82
+ const c1 = 1.70158, c3 = c1 + 1;
83
+ return c3 * t * t * t - c1 * t * t;
84
+ },
85
+
86
+ expo: (t) => t === 0 ? 0 : Math.pow(2, 10 * t - 10),
87
+ circ: (t) => 1 - Math.sqrt(1 - t * t),
88
+
89
+ spring: (t, stiffness, damping) => {
90
+ stiffness = stiffness || 100;
91
+ damping = damping || 10;
92
+ const omega = Math.sqrt(stiffness);
93
+ const zeta = damping / (2 * omega);
94
+ if (zeta < 1) {
95
+ const omegaD = omega * Math.sqrt(1 - zeta * zeta);
96
+ return 1 - Math.exp(-zeta * omega * t) * (Math.cos(omegaD * t) + (zeta * omega / omegaD) * Math.sin(omegaD * t));
97
+ }
98
+ return 1 - (1 + omega * t) * Math.exp(-omega * t);
99
+ },
100
+ };
101
+
102
+ // ─── Animation Timeline ───────────────────────────────────────────────────────
103
+
104
+ /**
105
+ * Create an animation timeline for sequencing animations.
106
+ * @returns {Object} Timeline controller
107
+ */
108
+ function createTimeline() {
109
+ const animations = [];
110
+ let startTime = null;
111
+ let isPlaying = false;
112
+ let rafId = null;
113
+
114
+ return {
115
+ /**
116
+ * Add an animation to the timeline.
117
+ * @param {Object} anim
118
+ * @param {number} anim.delay - Start delay in ms
119
+ * @param {number} anim.duration - Duration in ms
120
+ * @param {Function} anim.onUpdate - Called with (progress) each frame
121
+ * @param {Function} [anim.onComplete] - Called when animation completes
122
+ * @param {string} [anim.easing='linear'] - Easing function name
123
+ */
124
+ add: function (anim) {
125
+ animations.push(Object.assign({ delay: 0, duration: 1000, easing: 'linear' }, anim));
126
+ return this;
127
+ },
128
+
129
+ /**
130
+ * Start playing the timeline.
131
+ */
132
+ play: function () {
133
+ if (isPlaying) return;
134
+ isPlaying = true;
135
+ startTime = performance.now();
136
+
137
+ function tick(now) {
138
+ if (!isPlaying) return;
139
+ const elapsed = now - startTime;
140
+
141
+ let allComplete = true;
142
+ animations.forEach((anim) => {
143
+ const localTime = elapsed - anim.delay;
144
+ if (localTime < 0) { allComplete = false; return; }
145
+
146
+ const rawProgress = Math.min(localTime / anim.duration, 1);
147
+ if (rawProgress < 1) allComplete = false;
148
+
149
+ const easeFn = easing[anim.easing] || easing.linear;
150
+ const progress = easeFn(rawProgress);
151
+ anim.onUpdate(progress, rawProgress);
152
+
153
+ if (rawProgress >= 1 && anim.onComplete) {
154
+ anim.onComplete();
155
+ anim.onComplete = null;
156
+ }
157
+ });
158
+
159
+ if (!allComplete) {
160
+ rafId = requestFrame(tick);
161
+ } else {
162
+ isPlaying = false;
163
+ }
164
+ }
165
+
166
+ rafId = requestFrame(tick);
167
+ return this;
168
+ },
169
+
170
+ /**
171
+ * Pause the timeline.
172
+ */
173
+ pause: function () {
174
+ isPlaying = false;
175
+ if (rafId) cancelFrame(rafId);
176
+ return this;
177
+ },
178
+
179
+ /**
180
+ * Reset the timeline.
181
+ */
182
+ reset: function () {
183
+ this.pause();
184
+ startTime = null;
185
+ return this;
186
+ },
187
+ };
188
+ }
189
+
190
+ // ─── Tween Engine ─────────────────────────────────────────────────────────────
191
+
192
+ /**
193
+ * Create a tween animation from a start value to an end value.
194
+ * @param {Object} options
195
+ * @param {*} options.from - Starting value
196
+ * @param {*} options.to - Ending value
197
+ * @param {number} options.duration - Duration in ms
198
+ * @param {string} [options.easing='easeInOut'] - Easing function
199
+ * @param {Function} options.onUpdate - Called with (currentValue) each frame
200
+ * @param {Function} [options.onComplete] - Called when complete
201
+ * @returns {Object} Tween controller with play, pause, reset
202
+ */
203
+ function tween(options) {
204
+ const from = options.from;
205
+ const to = options.to;
206
+ const duration = options.duration || 1000;
207
+ const easeFn = easing[options.easing || 'easeInOut'] || easing.easeInOut;
208
+ const onUpdate = options.onUpdate;
209
+ const onComplete = options.onComplete;
210
+
211
+ let startTime = null;
212
+ let isPlaying = false;
213
+ let rafId = null;
214
+ let currentValue = from;
215
+
216
+ function tick(now) {
217
+ if (!isPlaying) return;
218
+ if (!startTime) startTime = now;
219
+
220
+ const elapsed = now - startTime;
221
+ const rawProgress = Math.min(elapsed / duration, 1);
222
+ const progress = easeFn(rawProgress);
223
+
224
+ currentValue = from + (to - from) * progress;
225
+ onUpdate(currentValue, progress, rawProgress);
226
+
227
+ if (rawProgress < 1) {
228
+ rafId = requestFrame(tick);
229
+ } else {
230
+ isPlaying = false;
231
+ if (onComplete) onComplete(currentValue);
232
+ }
233
+ }
234
+
235
+ return {
236
+ play: function () {
237
+ if (isPlaying) return this;
238
+ isPlaying = true;
239
+ startTime = null;
240
+ rafId = requestFrame(tick);
241
+ return this;
242
+ },
243
+ pause: function () {
244
+ isPlaying = false;
245
+ if (rafId) cancelFrame(rafId);
246
+ return this;
247
+ },
248
+ reset: function () {
249
+ this.pause();
250
+ startTime = null;
251
+ currentValue = from;
252
+ onUpdate(from, 0, 0);
253
+ return this;
254
+ },
255
+ getCurrentValue: function () { return currentValue; },
256
+ isPlaying: function () { return isPlaying; },
257
+ };
258
+ }
259
+
260
+ /**
261
+ * Create a tween from a starting value.
262
+ * @param {*} from
263
+ * @param {*} to
264
+ * @param {number} duration
265
+ * @returns {Object} Tween builder
266
+ */
267
+ function fromTo(from, to, duration) {
268
+ return tween({ from, to, duration });
269
+ }
270
+
271
+ /**
272
+ * Create a tween from current value to a target.
273
+ * @param {*} from
274
+ * @param {*} to
275
+ * @param {number} duration
276
+ * @returns {Object} Tween builder
277
+ */
278
+ function to(from, to, duration) {
279
+ return tween({ from, to, duration });
280
+ }
281
+
282
+ /**
283
+ * Create staggered tweens for multiple targets.
284
+ * @param {Array} targets
285
+ * @param {Object} options
286
+ * @param {number} options.stagger - Delay between each target in ms
287
+ * @param {*} options.from
288
+ * @param {*} options.to
289
+ * @param {number} options.duration
290
+ * @param {Function} options.onUpdate
291
+ * @returns {Array} Array of tween controllers
292
+ */
293
+ function stagger(targets, options) {
294
+ const staggerDelay = options.stagger || 50;
295
+ return targets.map((target, i) => {
296
+ return tween({
297
+ from: options.from,
298
+ to: options.to,
299
+ duration: options.duration || 1000,
300
+ easing: options.easing || 'easeInOut',
301
+ onUpdate: function (value) {
302
+ if (options.onUpdate) options.onUpdate(value, target, i);
303
+ },
304
+ onComplete: options.onComplete ? () => options.onComplete(target, i) : undefined,
305
+ });
306
+ });
307
+ }
308
+
309
+ // ─── Spring Physics ───────────────────────────────────────────────────────────
310
+
311
+ /**
312
+ * Create a spring-based animation.
313
+ * @param {Object} options
314
+ * @param {number} [options.stiffness=100]
315
+ * @param {number} [options.damping=10]
316
+ * @param {number} [options.mass=1]
317
+ * @param {number} [options.initialVelocity=0]
318
+ * @param {number} options.from
319
+ * @param {number} options.to
320
+ * @param {Function} options.onUpdate
321
+ * @param {Function} [options.onComplete]
322
+ * @returns {Object} Spring controller
323
+ */
324
+ function spring(options) {
325
+ const stiffness = options.stiffness || 100;
326
+ const damping = options.damping || 10;
327
+ const mass = options.mass || 1;
328
+ let velocity = options.initialVelocity || 0;
329
+ const from = options.from;
330
+ const to = options.to;
331
+ const onUpdate = options.onUpdate;
332
+ const onComplete = options.onComplete;
333
+
334
+ let currentValue = from;
335
+ let isPlaying = false;
336
+ let rafId = null;
337
+ let lastTime = null;
338
+ const precision = 0.01;
339
+
340
+ function step(now) {
341
+ if (!isPlaying) return;
342
+ if (!lastTime) lastTime = now;
343
+ const dt = Math.min((now - lastTime) / 1000, 0.064);
344
+ lastTime = now;
345
+
346
+ const displacement = currentValue - to;
347
+ const springForce = -stiffness * displacement;
348
+ const dampingForce = -damping * velocity;
349
+ const acceleration = (springForce + dampingForce) / mass;
350
+
351
+ velocity += acceleration * dt;
352
+ currentValue += velocity * dt;
353
+
354
+ onUpdate(currentValue);
355
+
356
+ // Check if settled
357
+ if (Math.abs(velocity) < precision && Math.abs(displacement) < precision) {
358
+ currentValue = to;
359
+ onUpdate(to);
360
+ isPlaying = false;
361
+ if (onComplete) onComplete(to);
362
+ return;
363
+ }
364
+
365
+ rafId = requestFrame(step);
366
+ }
367
+
368
+ return {
369
+ play: function () {
370
+ if (isPlaying) return this;
371
+ isPlaying = true;
372
+ lastTime = null;
373
+ rafId = requestFrame(step);
374
+ return this;
375
+ },
376
+ pause: function () {
377
+ isPlaying = false;
378
+ if (rafId) cancelFrame(rafId);
379
+ return this;
380
+ },
381
+ reset: function () {
382
+ this.pause();
383
+ currentValue = from;
384
+ velocity = options.initialVelocity || 0;
385
+ lastTime = null;
386
+ onUpdate(from);
387
+ return this;
388
+ },
389
+ setVelocity: function (v) { velocity = v; return this; },
390
+ getCurrentValue: function () { return currentValue; },
391
+ isPlaying: function () { return isPlaying; },
392
+ };
393
+ }
394
+
395
+ // ─── Keyframe Animation ───────────────────────────────────────────────────────
396
+
397
+ /**
398
+ * Create a keyframe animation with multiple steps.
399
+ * @param {Object} options
400
+ * @param {Array<{offset: number, value: *}>} options.keyframes
401
+ * @param {number} options.duration
402
+ * @param {string} [options.easing]
403
+ * @param {Function} options.onUpdate
404
+ * @param {Function} [options.onComplete]
405
+ * @returns {Object} Keyframe controller
406
+ */
407
+ function keyframe(options) {
408
+ const frames = options.keyframes.sort((a, b) => a.offset - b.offset);
409
+ const duration = options.duration || 1000;
410
+ const easeFn = easing[options.easing || 'linear'] || easing.linear;
411
+ const onUpdate = options.onUpdate;
412
+ const onComplete = options.onComplete;
413
+
414
+ let startTime = null;
415
+ let isPlaying = false;
416
+ let rafId = null;
417
+
418
+ function interpolate(progress) {
419
+ if (frames.length === 0) return 0;
420
+ if (frames.length === 1) return frames[0].value;
421
+
422
+ // Find surrounding keyframes
423
+ let lower = frames[0], upper = frames[frames.length - 1];
424
+ for (let i = 0; i < frames.length - 1; i++) {
425
+ if (progress >= frames[i].offset && progress <= frames[i + 1].offset) {
426
+ lower = frames[i];
427
+ upper = frames[i + 1];
428
+ break;
429
+ }
430
+ }
431
+
432
+ const range = upper.offset - lower.offset;
433
+ const localProgress = range === 0 ? 1 : (progress - lower.offset) / range;
434
+ return lower.value + (upper.value - lower.value) * localProgress;
435
+ }
436
+
437
+ function tick(now) {
438
+ if (!isPlaying) return;
439
+ if (!startTime) startTime = now;
440
+
441
+ const elapsed = now - startTime;
442
+ const rawProgress = Math.min(elapsed / duration, 1);
443
+ const easedProgress = easeFn(rawProgress);
444
+ const value = interpolate(easedProgress);
445
+
446
+ onUpdate(value, easedProgress, rawProgress);
447
+
448
+ if (rawProgress < 1) {
449
+ rafId = requestFrame(tick);
450
+ } else {
451
+ isPlaying = false;
452
+ if (onComplete) onComplete(value);
453
+ }
454
+ }
455
+
456
+ return {
457
+ play: function () { isPlaying = true; startTime = null; rafId = requestFrame(tick); return this; },
458
+ pause: function () { isPlaying = false; if (rafId) cancelFrame(rafId); return this; },
459
+ reset: function () { this.pause(); startTime = null; onUpdate(interpolate(0), 0, 0); return this; },
460
+ };
461
+ }
462
+
463
+ // ─── Scroll Animation ─────────────────────────────────────────────────────────
464
+
465
+ /**
466
+ * Animate elements based on scroll position.
467
+ * @param {HTMLElement} el - Element to observe
468
+ * @param {Object} options
469
+ * @param {Function} options.onProgress - Called with (progress: 0-1) on scroll
470
+ * @param {number} [options.start=0] - Start offset in viewport (0=top, 1=bottom)
471
+ * @param {number} [options.end=1] - End offset
472
+ * @returns {Function} Cleanup function
473
+ */
474
+ function scrollAnimate(el, options) {
475
+ const onProgress = options.onProgress;
476
+ const start = options.start || 0;
477
+ const end = options.end || 1;
478
+
479
+ function update() {
480
+ const rect = el.getBoundingClientRect();
481
+ const viewportHeight = window.innerHeight;
482
+ const elementProgress = (viewportHeight - rect.top) / (viewportHeight + rect.height);
483
+ const progress = Math.max(0, Math.min(1, (elementProgress - start) / (end - start)));
484
+ onProgress(progress);
485
+ }
486
+
487
+ window.addEventListener('scroll', update, { passive: true });
488
+ update();
489
+
490
+ return function cleanup() {
491
+ window.removeEventListener('scroll', update);
492
+ };
493
+ }
494
+
495
+ // ─── Intersection Observer Animation ──────────────────────────────────────────
496
+
497
+ /**
498
+ * Trigger animation when element enters viewport.
499
+ * @param {HTMLElement} el
500
+ * @param {Object} options
501
+ * @param {Function} options.onEnter - Called when element enters viewport
502
+ * @param {Function} [options.onExit] - Called when element exits viewport
503
+ * @param {number} [options.threshold=0.1]
504
+ * @returns {Function} Cleanup function
505
+ */
506
+ function intersectionAnimate(el, options) {
507
+ if (typeof IntersectionObserver === 'undefined') {
508
+ options.onEnter(el);
509
+ return function () {};
510
+ }
511
+
512
+ const observer = new IntersectionObserver(
513
+ (entries) => {
514
+ entries.forEach((entry) => {
515
+ if (entry.isIntersecting) {
516
+ options.onEnter(el, entry);
517
+ } else if (options.onExit) {
518
+ options.onExit(el, entry);
519
+ }
520
+ });
521
+ },
522
+ { threshold: options.threshold || 0.1 }
523
+ );
524
+
525
+ observer.observe(el);
526
+ return function cleanup() { observer.disconnect(); };
527
+ }
528
+
529
+ // ─── Animation Queue & Sequencing ─────────────────────────────────────────────
530
+
531
+ /**
532
+ * Create an animation queue that runs animations sequentially.
533
+ * @returns {Object} Queue controller
534
+ */
535
+ function createQueue() {
536
+ const queue = [];
537
+ let isRunning = false;
538
+
539
+ function runNext() {
540
+ if (queue.length === 0) { isRunning = false; return; }
541
+ isRunning = true;
542
+ const anim = queue.shift();
543
+ anim(function onComplete() {
544
+ runNext();
545
+ });
546
+ }
547
+
548
+ return {
549
+ add: function (animFn) { queue.push(animFn); if (!isRunning) runNext(); return this; },
550
+ clear: function () { queue.length = 0; isRunning = false; return this; },
551
+ isRunning: function () { return isRunning; },
552
+ length: function () { return queue.length; },
553
+ };
554
+ }
555
+
556
+ /**
557
+ * Run animations in sequence.
558
+ * @param {...Object} anims - Tween/keyframe controllers
559
+ * @returns {Promise} Resolves when all animations complete
560
+ */
561
+ function sequence(anims) {
562
+ return anims.reduce((promise, anim) => {
563
+ return promise.then(() => new Promise((resolve) => {
564
+ anim.play();
565
+ const originalComplete = anim.onComplete;
566
+ anim.onComplete = function () {
567
+ if (originalComplete) originalComplete();
568
+ resolve();
569
+ };
570
+ }));
571
+ }, Promise.resolve());
572
+ }
573
+
574
+ /**
575
+ * Run animations in parallel.
576
+ * @param {Array} anims
577
+ * @returns {Promise} Resolves when all animations complete
578
+ */
579
+ function parallel(anims) {
580
+ return Promise.all(anims.map((anim) => new Promise((resolve) => {
581
+ anim.play();
582
+ const originalComplete = anim.onComplete;
583
+ anim.onComplete = function () {
584
+ if (originalComplete) originalComplete();
585
+ resolve();
586
+ };
587
+ })));
588
+ }
589
+
590
+ // ─── CSS Animation Helpers ────────────────────────────────────────────────────
591
+
592
+ /**
593
+ * Apply a CSS transition to an element.
594
+ * @param {HTMLElement} el
595
+ * @param {string} property
596
+ * @param {number} [duration=300]
597
+ * @param {string} [easing='ease']
598
+ * @param {number} [delay=0]
599
+ */
600
+ function cssTransition(el, property, duration, easingName, delay) {
601
+ duration = duration || 300;
602
+ easingName = easingName || 'ease';
603
+ delay = delay || 0;
604
+ el.style.transition = property + ' ' + duration + 'ms ' + easingName + ' ' + delay + 'ms';
605
+ }
606
+
607
+ /**
608
+ * Apply a CSS keyframe animation.
609
+ * @param {HTMLElement} el
610
+ * @param {string} name - Animation name
611
+ * @param {number} [duration=300]
612
+ * @param {string} [easing='ease']
613
+ * @param {string} [fill='forwards']
614
+ * @returns {Promise}
615
+ */
616
+ function cssAnimate(el, name, duration, easingName, fill) {
617
+ duration = duration || 300;
618
+ easingName = easingName || 'ease';
619
+ fill = fill || 'forwards';
620
+
621
+ el.style.animation = name + ' ' + duration + 'ms ' + easingName + ' ' + fill;
622
+
623
+ return new Promise((resolve) => {
624
+ function handler(e) {
625
+ if (e.animationName === name) {
626
+ el.removeEventListener('animationend', handler);
627
+ resolve(el);
628
+ }
629
+ }
630
+ el.addEventListener('animationend', handler);
631
+ });
632
+ }
633
+
634
+ /**
635
+ * Remove CSS animation from element.
636
+ * @param {HTMLElement} el
637
+ */
638
+ function cssAnimateStop(el) {
639
+ el.style.animation = '';
640
+ }
641
+
642
+ // ─── Exports ──────────────────────────────────────────────────────────────────
643
+
644
+ module.exports = {
645
+ requestFrame, cancelFrame, cancelAllFrames,
646
+ easing,
647
+ createTimeline,
648
+ tween, fromTo, to, stagger,
649
+ spring,
650
+ keyframe,
651
+ scrollAnimate,
652
+ intersectionAnimate,
653
+ createQueue, sequence, parallel,
654
+ cssTransition, cssAnimate, cssAnimateStop,
655
+ };