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,704 @@
1
+ /**
2
+ * useEffect Hook Implementation
3
+ * ElementDrawing Framework - Effect hooks with dependency tracking,
4
+ * cleanup, scheduling, batching, passive/layout/insertion effects,
5
+ * strict mode support, effect grouping, and cancellation.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ // ─── Effect Types ─────────────────────────────────────────────────────────────
11
+
12
+ const EFFECT_TYPES = {
13
+ PASSIVE: 'passive', // useEffect - runs after paint
14
+ LAYOUT: 'layout', // useLayoutEffect - runs before paint
15
+ INSERTION: 'insertion', // useInsertionEffect - runs before DOM mutations
16
+ };
17
+
18
+ // ─── Internal State ───────────────────────────────────────────────────────────
19
+
20
+ let currentlyRenderingComponent = null;
21
+ let hookIndex = 0;
22
+ const effectQueue = [];
23
+ const layoutEffectQueue = [];
24
+ const insertionEffectQueue = [];
25
+ let isFlushingEffects = false;
26
+ let isFlushingLayoutEffects = false;
27
+ let isFlushingInsertionEffects = false;
28
+
29
+ // ─── Strict Mode ──────────────────────────────────────────────────────────────
30
+
31
+ let isStrictMode = false;
32
+
33
+ /**
34
+ * Enable or disable strict mode. In strict mode, effects are double-invoked
35
+ * on mount to help find side-effect cleanup issues.
36
+ * @param {boolean} enabled
37
+ */
38
+ function setStrictMode(enabled) {
39
+ isStrictMode = enabled;
40
+ }
41
+
42
+ /**
43
+ * Check if strict mode is enabled.
44
+ * @returns {boolean}
45
+ */
46
+ function isInStrictMode() {
47
+ return isStrictMode;
48
+ }
49
+
50
+ // ─── Dependency Comparison ────────────────────────────────────────────────────
51
+
52
+ /**
53
+ * Compare two dependency arrays for equality.
54
+ * @param {Array|null} prevDeps - Previous dependencies
55
+ * @param {Array|null} nextDeps - Next dependencies
56
+ * @returns {boolean} True if dependencies are the same
57
+ */
58
+ function areDepsEqual(prevDeps, nextDeps) {
59
+ if (prevDeps === null || nextDeps === null) return false;
60
+ if (prevDeps.length !== nextDeps.length) return false;
61
+
62
+ for (let i = 0; i < prevDeps.length; i++) {
63
+ if (Object.is(prevDeps[i], nextDeps[i])) continue;
64
+ return false;
65
+ }
66
+ return true;
67
+ }
68
+
69
+ /**
70
+ * Deep dependency comparison for complex dependency values.
71
+ * @param {Array|null} prevDeps
72
+ * @param {Array|null} nextDeps
73
+ * @returns {boolean}
74
+ */
75
+ function areDepsDeepEqual(prevDeps, nextDeps) {
76
+ if (prevDeps === null || nextDeps === null) return false;
77
+ if (prevDeps.length !== nextDeps.length) return false;
78
+
79
+ for (let i = 0; i < prevDeps.length; i++) {
80
+ if (deepEqualValue(prevDeps[i], nextDeps[i])) continue;
81
+ return false;
82
+ }
83
+ return true;
84
+ }
85
+
86
+ /**
87
+ * Deep equal comparison for individual values.
88
+ */
89
+ function deepEqualValue(a, b) {
90
+ if (Object.is(a, b)) return true;
91
+ if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) {
92
+ return false;
93
+ }
94
+ const keysA = Object.keys(a);
95
+ const keysB = Object.keys(b);
96
+ if (keysA.length !== keysB.length) return false;
97
+ for (const key of keysA) {
98
+ if (!deepEqualValue(a[key], b[key])) return false;
99
+ }
100
+ return true;
101
+ }
102
+
103
+ /**
104
+ * Validate that dependencies is an array or undefined.
105
+ * @param {*} deps - Dependencies to validate
106
+ */
107
+ function validateDeps(deps) {
108
+ if (deps !== undefined && !Array.isArray(deps)) {
109
+ throw new Error(
110
+ '[useEffect] Dependencies must be an array or undefined. ' +
111
+ `Received: ${typeof deps}`
112
+ );
113
+ }
114
+ }
115
+
116
+ // ─── Effect Execution ─────────────────────────────────────────────────────────
117
+
118
+ /**
119
+ * Execute an effect, capturing its cleanup function.
120
+ * @param {Object} effect - Effect object with create function
121
+ * @returns {Function|null} Cleanup function
122
+ */
123
+ function executeEffect(effect) {
124
+ try {
125
+ const cleanup = effect.create();
126
+ if (cleanup !== undefined && typeof cleanup !== 'function') {
127
+ console.warn(
128
+ '[useEffect] Effect cleanup must be a function or undefined. ' +
129
+ `Received: ${typeof cleanup}`
130
+ );
131
+ effect.cleanup = null;
132
+ } else {
133
+ effect.cleanup = cleanup;
134
+ }
135
+ } catch (error) {
136
+ console.error('[useEffect] Effect execution error:', error);
137
+ effect.cleanup = null;
138
+
139
+ // Call error boundary if available
140
+ if (effect.component && effect.component._handleError) {
141
+ effect.component._handleError(error);
142
+ }
143
+ }
144
+ return effect.cleanup;
145
+ }
146
+
147
+ /**
148
+ * Run cleanup for an effect.
149
+ * @param {Object} effect - Effect object with cleanup function
150
+ */
151
+ function runCleanup(effect) {
152
+ if (typeof effect.cleanup === 'function') {
153
+ try {
154
+ effect.cleanup();
155
+ } catch (error) {
156
+ console.error('[useEffect] Cleanup error:', error);
157
+ if (effect.component && effect.component._handleError) {
158
+ effect.component._handleError(error);
159
+ }
160
+ }
161
+ effect.cleanup = null;
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Execute an effect in strict mode (double-invoke on mount).
167
+ * @param {Object} effect - Effect to execute
168
+ */
169
+ function executeEffectStrictMode(effect) {
170
+ // First invocation
171
+ executeEffect(effect);
172
+
173
+ // Immediately cleanup and re-invoke in strict mode
174
+ runCleanup(effect);
175
+ executeEffect(effect);
176
+ }
177
+
178
+ // ─── Effect Scheduling ────────────────────────────────────────────────────────
179
+
180
+ /**
181
+ * Schedule passive effects to run after the browser paint.
182
+ */
183
+ function schedulePassiveEffects() {
184
+ if (effectQueue.length === 0) return;
185
+
186
+ // Use requestAnimationFrame then setTimeout to ensure effects run after paint
187
+ requestAnimationFrame(() => {
188
+ setTimeout(() => {
189
+ flushPassiveEffects();
190
+ }, 0);
191
+ });
192
+ }
193
+
194
+ /**
195
+ * Flush all queued passive effects.
196
+ */
197
+ function flushPassiveEffects() {
198
+ if (isFlushingEffects) return;
199
+ isFlushingEffects = true;
200
+
201
+ try {
202
+ // Process all queued passive effects
203
+ while (effectQueue.length > 0) {
204
+ const effect = effectQueue.shift();
205
+
206
+ // Run cleanup from previous render first
207
+ runCleanup(effect);
208
+
209
+ // Execute the new effect
210
+ if (isStrictMode && !effect._hasRunBefore) {
211
+ executeEffectStrictMode(effect);
212
+ effect._hasRunBefore = true;
213
+ } else {
214
+ executeEffect(effect);
215
+ }
216
+ }
217
+ } finally {
218
+ isFlushingEffects = false;
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Flush all queued layout effects (synchronous, before paint).
224
+ */
225
+ function flushLayoutEffects() {
226
+ if (isFlushingLayoutEffects) return;
227
+ isFlushingLayoutEffects = true;
228
+
229
+ try {
230
+ while (layoutEffectQueue.length > 0) {
231
+ const effect = layoutEffectQueue.shift();
232
+ runCleanup(effect);
233
+
234
+ if (isStrictMode && !effect._hasRunBefore) {
235
+ executeEffectStrictMode(effect);
236
+ effect._hasRunBefore = true;
237
+ } else {
238
+ executeEffect(effect);
239
+ }
240
+ }
241
+ } finally {
242
+ isFlushingLayoutEffects = false;
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Flush all queued insertion effects (synchronous, before DOM mutations).
248
+ */
249
+ function flushInsertionEffects() {
250
+ if (isFlushingInsertionEffects) return;
251
+ isFlushingInsertionEffects = true;
252
+
253
+ try {
254
+ while (insertionEffectQueue.length > 0) {
255
+ const effect = insertionEffectQueue.shift();
256
+ runCleanup(effect);
257
+ executeEffect(effect);
258
+ }
259
+ } finally {
260
+ isFlushingInsertionEffects = false;
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Flush effects in the correct order: insertion -> layout -> passive.
266
+ */
267
+ function flushAllEffects() {
268
+ flushInsertionEffects();
269
+ flushLayoutEffects();
270
+ schedulePassiveEffects();
271
+ }
272
+
273
+ // ─── Effect Cancellation ──────────────────────────────────────────────────────
274
+
275
+ /**
276
+ * Cancel a pending effect by reference.
277
+ * @param {Object} effect - The effect to cancel
278
+ * @returns {boolean} True if successfully cancelled
279
+ */
280
+ function cancelEffect(effect) {
281
+ if (!effect) return false;
282
+
283
+ let removed = false;
284
+
285
+ // Try to remove from passive queue
286
+ const passiveIdx = effectQueue.indexOf(effect);
287
+ if (passiveIdx !== -1) {
288
+ effectQueue.splice(passiveIdx, 1);
289
+ removed = true;
290
+ }
291
+
292
+ // Try to remove from layout queue
293
+ const layoutIdx = layoutEffectQueue.indexOf(effect);
294
+ if (layoutIdx !== -1) {
295
+ layoutEffectQueue.splice(layoutIdx, 1);
296
+ removed = true;
297
+ }
298
+
299
+ // Try to remove from insertion queue
300
+ const insertionIdx = insertionEffectQueue.indexOf(effect);
301
+ if (insertionIdx !== -1) {
302
+ insertionEffectQueue.splice(insertionIdx, 1);
303
+ removed = true;
304
+ }
305
+
306
+ // Run cleanup if the effect had been previously executed
307
+ if (removed) {
308
+ runCleanup(effect);
309
+ }
310
+
311
+ return removed;
312
+ }
313
+
314
+ // ─── Effect Grouping ──────────────────────────────────────────────────────────
315
+
316
+ /**
317
+ * Group effects together so they execute as a batch.
318
+ * @param {Function} callback - Function containing effectful operations
319
+ * @returns {Array} Array of effect objects created in the group
320
+ */
321
+ const activeEffectGroup = { current: null };
322
+
323
+ function beginEffectGroup() {
324
+ activeEffectGroup.current = [];
325
+ }
326
+
327
+ function endEffectGroup() {
328
+ const group = activeEffectGroup.current;
329
+ activeEffectGroup.current = null;
330
+ return group;
331
+ }
332
+
333
+ // ─── Hook Context Management ──────────────────────────────────────────────────
334
+
335
+ function setCurrentComponent(component) {
336
+ currentlyRenderingComponent = component;
337
+ hookIndex = 0;
338
+ }
339
+
340
+ function resetHookIndex() {
341
+ hookIndex = 0;
342
+ currentlyRenderingComponent = null;
343
+ }
344
+
345
+ // ─── Core Effect Hook ─────────────────────────────────────────────────────────
346
+
347
+ /**
348
+ * Internal implementation shared by all effect hooks.
349
+ *
350
+ * @param {Function} create - Effect callback (returns cleanup or undefined)
351
+ * @param {Array|undefined} deps - Dependency array
352
+ * @param {string} effectType - Type of effect (passive, layout, insertion)
353
+ * @returns {void}
354
+ */
355
+ function useEffectImpl(create, deps, effectType) {
356
+ const component = currentlyRenderingComponent;
357
+ if (!component) {
358
+ throw new Error(
359
+ `[use${effectType}Effect] Must be called within a component render phase`
360
+ );
361
+ }
362
+
363
+ if (typeof create !== 'function') {
364
+ throw new Error(
365
+ `[use${effectType}Effect] First argument must be a function. ` +
366
+ `Received: ${typeof create}`
367
+ );
368
+ }
369
+
370
+ validateDeps(deps);
371
+
372
+ // Initialize hooks array
373
+ if (!component._hooks) {
374
+ component._hooks = [];
375
+ }
376
+
377
+ const currentHookIndex = hookIndex;
378
+ const resolvedDeps = deps !== undefined ? deps : null;
379
+ let hook;
380
+
381
+ if (component._hooks[currentHookIndex]) {
382
+ // Re-render: check if dependencies changed
383
+ hook = component._hooks[currentHookIndex];
384
+
385
+ const prevDeps = hook.deps;
386
+ const depsChanged = !areDepsEqual(prevDeps, resolvedDeps);
387
+
388
+ if (depsChanged) {
389
+ // Dependencies changed - queue the effect
390
+ hook.deps = resolvedDeps;
391
+ hook.create = create;
392
+ hook.type = effectType;
393
+ hook.component = component;
394
+ hook._shouldRun = true;
395
+ } else {
396
+ // Dependencies unchanged - skip the effect
397
+ hook._shouldRun = false;
398
+ }
399
+ } else {
400
+ // First render - always run the effect
401
+ hook = {
402
+ type: effectType,
403
+ create,
404
+ cleanup: null,
405
+ deps: resolvedDeps,
406
+ component,
407
+ _shouldRun: true,
408
+ _isMounted: false,
409
+ _hasRunBefore: false,
410
+ _createdAt: Date.now(),
411
+ _effectId: ++effectIdCounter,
412
+ };
413
+ component._hooks[currentHookIndex] = hook;
414
+ }
415
+
416
+ // Queue the effect if it should run
417
+ if (hook._shouldRun) {
418
+ switch (effectType) {
419
+ case EFFECT_TYPES.PASSIVE:
420
+ effectQueue.push(hook);
421
+ break;
422
+ case EFFECT_TYPES.LAYOUT:
423
+ layoutEffectQueue.push(hook);
424
+ break;
425
+ case EFFECT_TYPES.INSERTION:
426
+ insertionEffectQueue.push(hook);
427
+ break;
428
+ }
429
+
430
+ // Track in active effect group if one exists
431
+ if (activeEffectGroup.current) {
432
+ activeEffectGroup.current.push(hook);
433
+ }
434
+ }
435
+
436
+ hookIndex++;
437
+ }
438
+
439
+ let effectIdCounter = 0;
440
+
441
+ // ─── Public Hooks ─────────────────────────────────────────────────────────────
442
+
443
+ /**
444
+ * useEffect - Schedule a passive effect that runs after the browser paint.
445
+ * Ideal for side effects that don't need to block visual updates.
446
+ *
447
+ * @param {Function} create - Effect callback, may return a cleanup function
448
+ * @param {Array} [deps] - Dependency array; effect re-runs when deps change
449
+ *
450
+ * @example
451
+ * useEffect(() => {
452
+ * const subscription = api.subscribe(data => setData(data));
453
+ * return () => subscription.unsubscribe();
454
+ * }, [api]);
455
+ */
456
+ function useEffect(create, deps) {
457
+ useEffectImpl(create, deps, EFFECT_TYPES.PASSIVE);
458
+ }
459
+
460
+ /**
461
+ * useLayoutEffect - Schedule a layout effect that runs synchronously after
462
+ * all DOM mutations but before the browser paint.
463
+ *
464
+ * @param {Function} create - Effect callback, may return a cleanup function
465
+ * @param {Array} [deps] - Dependency array
466
+ *
467
+ * @example
468
+ * useLayoutEffect(() => {
469
+ * const height = ref.current.clientHeight;
470
+ * setTooltipPosition(calculatePosition(height));
471
+ * }, [ref.current]);
472
+ */
473
+ function useLayoutEffect(create, deps) {
474
+ useEffectImpl(create, deps, EFFECT_TYPES.LAYOUT);
475
+ }
476
+
477
+ /**
478
+ * useInsertionEffect - Schedule an insertion effect that runs before DOM
479
+ * mutations. Primarily for CSS-in-JS libraries.
480
+ *
481
+ * @param {Function} create - Effect callback, may return a cleanup function
482
+ * @param {Array} [deps] - Dependency array
483
+ */
484
+ function useInsertionEffect(create, deps) {
485
+ useEffectImpl(create, deps, EFFECT_TYPES.INSERTION);
486
+ }
487
+
488
+ // ─── Custom Effect Hooks ──────────────────────────────────────────────────────
489
+
490
+ /**
491
+ * useDeepCompareEffect - Like useEffect but uses deep comparison for deps.
492
+ * Useful when dependencies are objects or arrays that may be recreated.
493
+ *
494
+ * @param {Function} create - Effect callback
495
+ * @param {Array} deps - Dependency array (compared deeply)
496
+ */
497
+ function useDeepCompareEffect(create, deps) {
498
+ const component = currentlyRenderingComponent;
499
+ if (!component) {
500
+ throw new Error('[useDeepCompareEffect] Must be called within a component render phase');
501
+ }
502
+
503
+ if (!component._hooks) {
504
+ component._hooks = [];
505
+ }
506
+
507
+ const currentHookIndex = hookIndex;
508
+ const resolvedDeps = deps !== undefined ? deps : null;
509
+ let hook;
510
+
511
+ if (component._hooks[currentHookIndex]) {
512
+ hook = component._hooks[currentHookIndex];
513
+
514
+ const prevDeps = hook.deps;
515
+ const depsChanged = !areDepsDeepEqual(prevDeps, resolvedDeps);
516
+
517
+ if (depsChanged) {
518
+ hook.deps = resolvedDeps;
519
+ hook.create = create;
520
+ hook._shouldRun = true;
521
+ } else {
522
+ hook._shouldRun = false;
523
+ }
524
+ } else {
525
+ hook = {
526
+ type: EFFECT_TYPES.PASSIVE,
527
+ create,
528
+ cleanup: null,
529
+ deps: resolvedDeps,
530
+ component,
531
+ _shouldRun: true,
532
+ _isMounted: false,
533
+ _hasRunBefore: false,
534
+ _createdAt: Date.now(),
535
+ _effectId: ++effectIdCounter,
536
+ };
537
+ component._hooks[currentHookIndex] = hook;
538
+ }
539
+
540
+ if (hook._shouldRun) {
541
+ effectQueue.push(hook);
542
+ }
543
+
544
+ hookIndex++;
545
+ }
546
+
547
+ /**
548
+ * useEffectOnce - Effect that only runs once on mount, like componentDidMount.
549
+ * @param {Function} create - Effect callback
550
+ */
551
+ function useEffectOnce(create) {
552
+ useEffect(create, []);
553
+ }
554
+
555
+ /**
556
+ * useUpdateEffect - Effect that skips the first render (only runs on updates).
557
+ * @param {Function} create - Effect callback
558
+ * @param {Array} deps - Dependency array
559
+ */
560
+ function useUpdateEffect(create, deps) {
561
+ const component = currentlyRenderingComponent;
562
+ if (!component) {
563
+ throw new Error('[useUpdateEffect] Must be called within a component render phase');
564
+ }
565
+
566
+ if (!component._hooks) {
567
+ component._hooks = [];
568
+ }
569
+
570
+ const currentHookIndex = hookIndex;
571
+ let hook;
572
+
573
+ if (component._hooks[currentHookIndex]) {
574
+ hook = component._hooks[currentHookIndex];
575
+ } else {
576
+ hook = {
577
+ _isFirstRender: true,
578
+ };
579
+ component._hooks[currentHookIndex] = hook;
580
+ }
581
+
582
+ const isFirst = hook._isFirstRender;
583
+ hook._isFirstRender = false;
584
+
585
+ if (isFirst) {
586
+ // Skip the first render, but still track deps
587
+ const resolvedDeps = deps !== undefined ? deps : null;
588
+ hook.deps = resolvedDeps;
589
+ hookIndex++;
590
+ return;
591
+ }
592
+
593
+ useEffectImpl(create, deps, EFFECT_TYPES.PASSIVE);
594
+ }
595
+
596
+ // ─── Effect Batching ──────────────────────────────────────────────────────────
597
+
598
+ /**
599
+ * Batch multiple effectful operations. All effects created within the
600
+ * callback will be collected and flushed together.
601
+ *
602
+ * @param {Function} callback - Function containing effectful operations
603
+ * @returns {*} Return value of the callback
604
+ */
605
+ function batchEffects(callback) {
606
+ const prevFlushingState = isFlushingEffects;
607
+ isFlushingEffects = true;
608
+
609
+ try {
610
+ return callback();
611
+ } finally {
612
+ isFlushingEffects = prevFlushingState;
613
+ }
614
+ }
615
+
616
+ // ─── Effect Debug & Inspection ────────────────────────────────────────────────
617
+
618
+ /**
619
+ * Get the count of pending effects across all queues.
620
+ * @returns {Object} Counts for each effect type
621
+ */
622
+ function getPendingEffectCounts() {
623
+ return {
624
+ passive: effectQueue.length,
625
+ layout: layoutEffectQueue.length,
626
+ insertion: insertionEffectQueue.length,
627
+ total: effectQueue.length + layoutEffectQueue.length + insertionEffectQueue.length,
628
+ };
629
+ }
630
+
631
+ /**
632
+ * Get all effects for a specific component.
633
+ * @param {Object} component
634
+ * @returns {Array} Array of effect objects
635
+ */
636
+ function getComponentEffects(component) {
637
+ if (!component._hooks) return [];
638
+
639
+ return component._hooks.filter((hook) =>
640
+ hook && (hook.type === EFFECT_TYPES.PASSIVE ||
641
+ hook.type === EFFECT_TYPES.LAYOUT ||
642
+ hook.type === EFFECT_TYPES.INSERTION)
643
+ );
644
+ }
645
+
646
+ // ─── Cleanup on Unmount ──────────────────────────────────────────────────────
647
+
648
+ /**
649
+ * Clean up all effects for a component when it unmounts.
650
+ * @param {Object} component - The unmounting component
651
+ */
652
+ function cleanupComponentEffects(component) {
653
+ if (!component._hooks) return;
654
+
655
+ component._hooks.forEach((hook) => {
656
+ if (hook && typeof hook.cleanup === 'function') {
657
+ runCleanup(hook);
658
+ }
659
+ });
660
+
661
+ // Remove effects from queues
662
+ for (let i = effectQueue.length - 1; i >= 0; i--) {
663
+ if (effectQueue[i].component === component) {
664
+ effectQueue.splice(i, 1);
665
+ }
666
+ }
667
+ for (let i = layoutEffectQueue.length - 1; i >= 0; i--) {
668
+ if (layoutEffectQueue[i].component === component) {
669
+ layoutEffectQueue.splice(i, 1);
670
+ }
671
+ }
672
+ for (let i = insertionEffectQueue.length - 1; i >= 0; i--) {
673
+ if (insertionEffectQueue[i].component === component) {
674
+ insertionEffectQueue.splice(i, 1);
675
+ }
676
+ }
677
+ }
678
+
679
+ // ─── Exports ──────────────────────────────────────────────────────────────────
680
+
681
+ module.exports = {
682
+ useEffect,
683
+ useLayoutEffect,
684
+ useInsertionEffect,
685
+ useDeepCompareEffect,
686
+ useEffectOnce,
687
+ useUpdateEffect,
688
+ setCurrentComponent,
689
+ resetHookIndex,
690
+ flushPassiveEffects,
691
+ flushLayoutEffects,
692
+ flushInsertionEffects,
693
+ flushAllEffects,
694
+ batchEffects,
695
+ areDepsEqual,
696
+ areDepsDeepEqual,
697
+ cancelEffect,
698
+ setStrictMode,
699
+ isInStrictMode,
700
+ getPendingEffectCounts,
701
+ getComponentEffects,
702
+ cleanupComponentEffects,
703
+ EFFECT_TYPES,
704
+ };