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,689 @@
1
+ /**
2
+ * useMemo Hook Implementation
3
+ * ElementDrawing Framework - Value memoization with dependency comparison,
4
+ * cache invalidation, LRU caching, deep comparison, TTL, and profiling.
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ // ─── Internal State ───────────────────────────────────────────────────────────
10
+
11
+ let currentlyRenderingComponent = null;
12
+ let hookIndex = 0;
13
+ const memoCache = new WeakMap();
14
+
15
+ // ─── Memo Profiling ───────────────────────────────────────────────────────────
16
+
17
+ const memoProfiles = new WeakMap();
18
+ let memoProfilingEnabled = false;
19
+
20
+ /**
21
+ * Enable or disable memo profiling.
22
+ * @param {boolean} enabled
23
+ */
24
+ function setMemoProfiling(enabled) {
25
+ memoProfilingEnabled = enabled;
26
+ }
27
+
28
+ /**
29
+ * Get profiling data for a component's memo hooks.
30
+ * @param {Object} component
31
+ * @returns {Object|null}
32
+ */
33
+ function getMemoProfile(component) {
34
+ return memoProfiles.get(component) || null;
35
+ }
36
+
37
+ // ─── LRU Cache ────────────────────────────────────────────────────────────────
38
+
39
+ /**
40
+ * Simple LRU cache for memo values.
41
+ * @param {number} capacity - Maximum cache entries
42
+ */
43
+ class LRUMemoCache {
44
+ constructor(capacity) {
45
+ this.capacity = capacity || 10;
46
+ this.cache = new Map();
47
+ this.hits = 0;
48
+ this.misses = 0;
49
+ }
50
+
51
+ get(key) {
52
+ if (!this.cache.has(key)) {
53
+ this.misses++;
54
+ return undefined;
55
+ }
56
+ // Move to end (most recently used)
57
+ const value = this.cache.get(key);
58
+ this.cache.delete(key);
59
+ this.cache.set(key, value);
60
+ this.hits++;
61
+ return value;
62
+ }
63
+
64
+ set(key, value) {
65
+ if (this.cache.has(key)) {
66
+ this.cache.delete(key);
67
+ } else if (this.cache.size >= this.capacity) {
68
+ // Delete least recently used (first entry)
69
+ const firstKey = this.cache.keys().next().value;
70
+ this.cache.delete(firstKey);
71
+ }
72
+ this.cache.set(key, value);
73
+ }
74
+
75
+ has(key) {
76
+ return this.cache.has(key);
77
+ }
78
+
79
+ clear() {
80
+ this.cache.clear();
81
+ this.hits = 0;
82
+ this.misses = 0;
83
+ }
84
+
85
+ getStats() {
86
+ return {
87
+ size: this.cache.size,
88
+ capacity: this.capacity,
89
+ hits: this.hits,
90
+ misses: this.misses,
91
+ hitRate: this.hits + this.misses > 0 ? this.hits / (this.hits + this.misses) : 0,
92
+ };
93
+ }
94
+ }
95
+
96
+ // ─── Dependency Comparison ────────────────────────────────────────────────────
97
+
98
+ /**
99
+ * Compare two dependency arrays for shallow equality.
100
+ * @param {Array|null} prevDeps
101
+ * @param {Array|null} nextDeps
102
+ * @returns {boolean}
103
+ */
104
+ function areDepsEqual(prevDeps, nextDeps) {
105
+ if (prevDeps === null || nextDeps === null) return false;
106
+ if (prevDeps.length !== nextDeps.length) return false;
107
+ for (let i = 0; i < prevDeps.length; i++) {
108
+ if (Object.is(prevDeps[i], nextDeps[i])) continue;
109
+ return false;
110
+ }
111
+ return true;
112
+ }
113
+
114
+ /**
115
+ * Deep dependency comparison.
116
+ */
117
+ function areDepsDeepEqual(prevDeps, nextDeps) {
118
+ if (prevDeps === null || nextDeps === null) return false;
119
+ if (prevDeps.length !== nextDeps.length) return false;
120
+ for (let i = 0; i < prevDeps.length; i++) {
121
+ if (deepEqualVal(prevDeps[i], nextDeps[i])) continue;
122
+ return false;
123
+ }
124
+ return true;
125
+ }
126
+
127
+ function deepEqualVal(a, b) {
128
+ if (Object.is(a, b)) return true;
129
+ if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) return false;
130
+ const keysA = Object.keys(a);
131
+ const keysB = Object.keys(b);
132
+ if (keysA.length !== keysB.length) return false;
133
+ for (const key of keysA) {
134
+ if (!deepEqualVal(a[key], b[key])) return false;
135
+ }
136
+ return true;
137
+ }
138
+
139
+ /**
140
+ * Validate dependency array.
141
+ * @param {*} deps
142
+ */
143
+ function validateDeps(deps) {
144
+ if (deps !== undefined && !Array.isArray(deps)) {
145
+ throw new Error(
146
+ '[useMemo] Dependencies must be an array or undefined. ' +
147
+ `Received: ${typeof deps}`
148
+ );
149
+ }
150
+ }
151
+
152
+ // ─── Hook Context Management ──────────────────────────────────────────────────
153
+
154
+ function setCurrentComponent(component) {
155
+ currentlyRenderingComponent = component;
156
+ hookIndex = 0;
157
+ }
158
+
159
+ function resetHookIndex() {
160
+ hookIndex = 0;
161
+ currentlyRenderingComponent = null;
162
+ }
163
+
164
+ // ─── useMemo Hook ──────────────────────────────────────────────────────────────
165
+
166
+ /**
167
+ * useMemo - Memoize a computed value based on dependencies.
168
+ * Only recomputes when dependencies change.
169
+ *
170
+ * @param {Function} factory - Function that returns the memoized value
171
+ * @param {Array} deps - Dependency array; value recomputes when deps change
172
+ * @returns {*} Memoized value
173
+ *
174
+ * @example
175
+ * const expensiveValue = useMemo(() => computeExpensive(a, b), [a, b]);
176
+ *
177
+ * @example
178
+ * const sortedList = useMemo(() => {
179
+ * return items.sort((a, b) => a.name.localeCompare(b.name));
180
+ * }, [items]);
181
+ */
182
+ function useMemo(factory, deps) {
183
+ const component = currentlyRenderingComponent;
184
+ if (!component) {
185
+ throw new Error('[useMemo] Must be called within a component render phase');
186
+ }
187
+
188
+ if (typeof factory !== 'function') {
189
+ throw new Error(
190
+ '[useMemo] First argument must be a function. ' +
191
+ `Received: ${typeof factory}`
192
+ );
193
+ }
194
+
195
+ validateDeps(deps);
196
+
197
+ // Initialize hooks array
198
+ if (!component._hooks) {
199
+ component._hooks = [];
200
+ }
201
+
202
+ const currentHookIndex = hookIndex;
203
+ const resolvedDeps = deps !== undefined ? deps : null;
204
+ let hook;
205
+
206
+ if (component._hooks[currentHookIndex]) {
207
+ // Re-render: check if dependencies changed
208
+ hook = component._hooks[currentHookIndex];
209
+
210
+ const prevDeps = hook.deps;
211
+ const depsChanged = !areDepsEqual(prevDeps, resolvedDeps);
212
+
213
+ if (depsChanged) {
214
+ // Dependencies changed - recompute the value
215
+ hook.deps = resolvedDeps;
216
+
217
+ try {
218
+ const prevValue = hook.value;
219
+ hook.value = factory();
220
+ hook._computeCount++;
221
+
222
+ // Track value change for debugging
223
+ if (!Object.is(prevValue, hook.value)) {
224
+ hook._valueChangedCount++;
225
+ }
226
+ } catch (error) {
227
+ console.error('[useMemo] Factory function error:', error);
228
+ if (component._handleError) {
229
+ component._handleError(error);
230
+ }
231
+ // Keep previous value on error
232
+ }
233
+ }
234
+ // If deps unchanged, keep the existing memoized value
235
+ } else {
236
+ // First render: compute initial value
237
+ let initialValue;
238
+ try {
239
+ initialValue = factory();
240
+ } catch (error) {
241
+ console.error('[useMemo] Factory function error on mount:', error);
242
+ if (component._handleError) {
243
+ component._handleError(error);
244
+ }
245
+ initialValue = undefined;
246
+ }
247
+
248
+ hook = {
249
+ type: 'memo',
250
+ value: initialValue,
251
+ deps: resolvedDeps,
252
+ _computeCount: 1,
253
+ _valueChangedCount: 1,
254
+ _createdAt: Date.now(),
255
+ _lastComputedAt: Date.now(),
256
+ _onInvalidate: null,
257
+ _lruCache: null,
258
+ };
259
+
260
+ component._hooks[currentHookIndex] = hook;
261
+
262
+ // Register in the global memo cache for invalidation support
263
+ if (!memoCache.has(component)) {
264
+ memoCache.set(component, new Set());
265
+ }
266
+ memoCache.get(component).add(currentHookIndex);
267
+ }
268
+
269
+ // Update profiling data
270
+ if (memoProfilingEnabled) {
271
+ if (!memoProfiles.has(component)) {
272
+ memoProfiles.set(component, {});
273
+ }
274
+ const profile = memoProfiles.get(component);
275
+ profile[currentHookIndex] = {
276
+ computeCount: hook._computeCount,
277
+ valueChangedCount: hook._valueChangedCount,
278
+ lastComputedAt: hook._lastComputedAt,
279
+ };
280
+ }
281
+
282
+ hookIndex++;
283
+ return hook.value;
284
+ }
285
+
286
+ // ─── Cache Invalidation ───────────────────────────────────────────────────────
287
+
288
+ /**
289
+ * Invalidate a specific memoized value, forcing recomputation on next render.
290
+ *
291
+ * @param {Object} component - The component owning the memo
292
+ * @param {number} hookIdx - The hook index to invalidate
293
+ */
294
+ function invalidateMemo(component, hookIdx) {
295
+ if (!component._hooks) return;
296
+ const hook = component._hooks[hookIdx];
297
+ if (!hook || hook.type !== 'memo') return;
298
+
299
+ // Clear deps to force recomputation
300
+ hook.deps = null;
301
+
302
+ // Call invalidation callback if registered
303
+ if (typeof hook._onInvalidate === 'function') {
304
+ try {
305
+ hook._onInvalidate(hook.value);
306
+ } catch (error) {
307
+ console.error('[useMemo] Invalidation callback error:', error);
308
+ }
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Invalidate all memoized values for a component.
314
+ * @param {Object} component
315
+ */
316
+ function invalidateAllMemos(component) {
317
+ if (!component._hooks) return;
318
+
319
+ component._hooks.forEach((hook, idx) => {
320
+ if (hook && hook.type === 'memo') {
321
+ invalidateMemo(component, idx);
322
+ }
323
+ });
324
+ }
325
+
326
+ /**
327
+ * Register a callback to be called when a memoized value is invalidated.
328
+ * Must be called during the same render as the useMemo.
329
+ *
330
+ * @param {Function} callback - Called with (oldValue) when invalidated
331
+ */
332
+ function useMemoOnInvalidate(callback) {
333
+ const component = currentlyRenderingComponent;
334
+ if (!component) return;
335
+
336
+ // Attach to the previous memo hook
337
+ const prevHookIdx = hookIndex - 1;
338
+ const hook = component._hooks?.[prevHookIdx];
339
+ if (hook && hook.type === 'memo') {
340
+ hook._onInvalidate = callback;
341
+ }
342
+ }
343
+
344
+ // ─── Extended useMemo Features ────────────────────────────────────────────────
345
+
346
+ /**
347
+ * Memoize with a custom equality function instead of Object.is.
348
+ *
349
+ * @param {Function} factory - Factory function
350
+ * @param {Array} deps - Dependency array
351
+ * @param {Function} areEqual - Custom equality: (prev, next) => boolean
352
+ * @returns {*} Memoized value
353
+ */
354
+ function useMemoWith(factory, deps, areEqual) {
355
+ const component = currentlyRenderingComponent;
356
+ if (!component) {
357
+ throw new Error('[useMemoWith] Must be called within a component render phase');
358
+ }
359
+
360
+ if (!component._hooks) {
361
+ component._hooks = [];
362
+ }
363
+
364
+ const currentHookIndex = hookIndex;
365
+ const resolvedDeps = deps !== undefined ? deps : null;
366
+ let hook;
367
+
368
+ if (component._hooks[currentHookIndex]) {
369
+ hook = component._hooks[currentHookIndex];
370
+
371
+ const prevDeps = hook.deps;
372
+ let depsChanged = !areDepsEqual(prevDeps, resolvedDeps);
373
+
374
+ // Even if deps changed by reference, use custom equality to compare values
375
+ if (depsChanged && typeof areEqual === 'function' && hook.value !== undefined) {
376
+ try {
377
+ const newValue = factory();
378
+ if (areEqual(hook.value, newValue)) {
379
+ // Values are equal despite dep reference changes
380
+ hook.deps = resolvedDeps;
381
+ hook.value = newValue;
382
+ } else {
383
+ hook.deps = resolvedDeps;
384
+ hook.value = newValue;
385
+ hook._computeCount++;
386
+ hook._valueChangedCount++;
387
+ }
388
+ } catch (error) {
389
+ console.error('[useMemoWith] Factory error:', error);
390
+ }
391
+ } else if (depsChanged) {
392
+ try {
393
+ hook.deps = resolvedDeps;
394
+ hook.value = factory();
395
+ hook._computeCount++;
396
+ hook._valueChangedCount++;
397
+ } catch (error) {
398
+ console.error('[useMemoWith] Factory error:', error);
399
+ }
400
+ }
401
+ } else {
402
+ let initialValue;
403
+ try {
404
+ initialValue = factory();
405
+ } catch (error) {
406
+ console.error('[useMemoWith] Factory error on mount:', error);
407
+ initialValue = undefined;
408
+ }
409
+
410
+ hook = {
411
+ type: 'memo',
412
+ value: initialValue,
413
+ deps: resolvedDeps,
414
+ _computeCount: 1,
415
+ _valueChangedCount: 1,
416
+ _createdAt: Date.now(),
417
+ _lastComputedAt: Date.now(),
418
+ _onInvalidate: null,
419
+ _lruCache: null,
420
+ };
421
+
422
+ component._hooks[currentHookIndex] = hook;
423
+ }
424
+
425
+ hookIndex++;
426
+ return hook.value;
427
+ }
428
+
429
+ /**
430
+ * Memoize with a time-to-live (TTL). The cached value expires after
431
+ * the specified duration and will be recomputed.
432
+ *
433
+ * @param {Function} factory - Factory function
434
+ * @param {Array} deps - Dependency array
435
+ * @param {number} ttlMs - Time-to-live in milliseconds
436
+ * @returns {*} Memoized value
437
+ */
438
+ function useMemoTTL(factory, deps, ttlMs) {
439
+ const component = currentlyRenderingComponent;
440
+ if (!component) {
441
+ throw new Error('[useMemoTTL] Must be called within a component render phase');
442
+ }
443
+
444
+ if (!component._hooks) {
445
+ component._hooks = [];
446
+ }
447
+
448
+ const currentHookIndex = hookIndex;
449
+ const resolvedDeps = deps !== undefined ? deps : null;
450
+ let hook;
451
+
452
+ if (component._hooks[currentHookIndex]) {
453
+ hook = component._hooks[currentHookIndex];
454
+
455
+ const now = Date.now();
456
+ const expired = (now - hook._lastComputedAt) > ttlMs;
457
+ const prevDeps = hook.deps;
458
+ const depsChanged = !areDepsEqual(prevDeps, resolvedDeps);
459
+
460
+ if (expired || depsChanged) {
461
+ try {
462
+ hook.deps = resolvedDeps;
463
+ hook.value = factory();
464
+ hook._computeCount++;
465
+ hook._valueChangedCount++;
466
+ hook._lastComputedAt = now;
467
+ } catch (error) {
468
+ console.error('[useMemoTTL] Factory error:', error);
469
+ }
470
+ }
471
+ } else {
472
+ let initialValue;
473
+ try {
474
+ initialValue = factory();
475
+ } catch (error) {
476
+ console.error('[useMemoTTL] Factory error on mount:', error);
477
+ initialValue = undefined;
478
+ }
479
+
480
+ hook = {
481
+ type: 'memo',
482
+ value: initialValue,
483
+ deps: resolvedDeps,
484
+ _computeCount: 1,
485
+ _valueChangedCount: 1,
486
+ _createdAt: Date.now(),
487
+ _lastComputedAt: Date.now(),
488
+ _onInvalidate: null,
489
+ _lruCache: null,
490
+ };
491
+
492
+ component._hooks[currentHookIndex] = hook;
493
+ }
494
+
495
+ hookIndex++;
496
+ return hook.value;
497
+ }
498
+
499
+ /**
500
+ * Memoize with deep comparison of dependencies.
501
+ *
502
+ * @param {Function} factory - Factory function
503
+ * @param {Array} deps - Dependency array (compared deeply)
504
+ * @returns {*} Memoized value
505
+ */
506
+ function useDeepCompareMemo(factory, deps) {
507
+ const component = currentlyRenderingComponent;
508
+ if (!component) {
509
+ throw new Error('[useDeepCompareMemo] Must be called within a component render phase');
510
+ }
511
+
512
+ if (!component._hooks) {
513
+ component._hooks = [];
514
+ }
515
+
516
+ const currentHookIndex = hookIndex;
517
+ const resolvedDeps = deps !== undefined ? deps : null;
518
+ let hook;
519
+
520
+ if (component._hooks[currentHookIndex]) {
521
+ hook = component._hooks[currentHookIndex];
522
+
523
+ const prevDeps = hook.deps;
524
+ const depsChanged = !areDepsDeepEqual(prevDeps, resolvedDeps);
525
+
526
+ if (depsChanged) {
527
+ hook.deps = resolvedDeps;
528
+ try {
529
+ hook.value = factory();
530
+ hook._computeCount++;
531
+ hook._valueChangedCount++;
532
+ hook._lastComputedAt = Date.now();
533
+ } catch (error) {
534
+ console.error('[useDeepCompareMemo] Factory error:', error);
535
+ }
536
+ }
537
+ } else {
538
+ let initialValue;
539
+ try {
540
+ initialValue = factory();
541
+ } catch (error) {
542
+ console.error('[useDeepCompareMemo] Factory error on mount:', error);
543
+ initialValue = undefined;
544
+ }
545
+
546
+ hook = {
547
+ type: 'memo',
548
+ value: initialValue,
549
+ deps: resolvedDeps,
550
+ _computeCount: 1,
551
+ _valueChangedCount: 1,
552
+ _createdAt: Date.now(),
553
+ _lastComputedAt: Date.now(),
554
+ _onInvalidate: null,
555
+ _lruCache: null,
556
+ };
557
+
558
+ component._hooks[currentHookIndex] = hook;
559
+ if (!memoCache.has(component)) {
560
+ memoCache.set(component, new Set());
561
+ }
562
+ memoCache.get(component).add(currentHookIndex);
563
+ }
564
+
565
+ hookIndex++;
566
+ return hook.value;
567
+ }
568
+
569
+ /**
570
+ * Memoize with LRU cache - keeps multiple cached values for different dep sets.
571
+ *
572
+ * @param {Function} factory - Factory function
573
+ * @param {Array} deps - Dependency array
574
+ * @param {number} capacity - LRU cache capacity (default 10)
575
+ * @returns {*} Memoized value
576
+ */
577
+ function useMemoLRU(factory, deps, capacity) {
578
+ const component = currentlyRenderingComponent;
579
+ if (!component) {
580
+ throw new Error('[useMemoLRU] Must be called within a component render phase');
581
+ }
582
+
583
+ if (!component._hooks) {
584
+ component._hooks = [];
585
+ }
586
+
587
+ const currentHookIndex = hookIndex;
588
+ const resolvedDeps = deps !== undefined ? deps : null;
589
+ const depKey = resolvedDeps ? JSON.stringify(resolvedDeps) : '__no_deps__';
590
+ let hook;
591
+
592
+ if (component._hooks[currentHookIndex]) {
593
+ hook = component._hooks[currentHookIndex];
594
+
595
+ // Initialize LRU cache if not present
596
+ if (!hook._lruCache) {
597
+ hook._lruCache = new LRUMemoCache(capacity || 10);
598
+ }
599
+
600
+ // Check LRU cache
601
+ const cached = hook._lruCache.get(depKey);
602
+ if (cached !== undefined) {
603
+ hook.value = cached;
604
+ } else {
605
+ try {
606
+ const newValue = factory();
607
+ hook._lruCache.set(depKey, newValue);
608
+ hook.value = newValue;
609
+ hook._computeCount++;
610
+ hook._valueChangedCount++;
611
+ hook._lastComputedAt = Date.now();
612
+ } catch (error) {
613
+ console.error('[useMemoLRU] Factory error:', error);
614
+ }
615
+ }
616
+ } else {
617
+ let initialValue;
618
+ try {
619
+ initialValue = factory();
620
+ } catch (error) {
621
+ console.error('[useMemoLRU] Factory error on mount:', error);
622
+ initialValue = undefined;
623
+ }
624
+
625
+ const lru = new LRUMemoCache(capacity || 10);
626
+ lru.set(depKey, initialValue);
627
+
628
+ hook = {
629
+ type: 'memo',
630
+ value: initialValue,
631
+ deps: resolvedDeps,
632
+ _computeCount: 1,
633
+ _valueChangedCount: 1,
634
+ _createdAt: Date.now(),
635
+ _lastComputedAt: Date.now(),
636
+ _onInvalidate: null,
637
+ _lruCache: lru,
638
+ };
639
+
640
+ component._hooks[currentHookIndex] = hook;
641
+ }
642
+
643
+ hookIndex++;
644
+ return hook.value;
645
+ }
646
+
647
+ // ─── Cleanup ──────────────────────────────────────────────────────────────────
648
+
649
+ /**
650
+ * Clean up memo cache when a component unmounts.
651
+ * @param {Object} component
652
+ */
653
+ function cleanupMemoCache(component) {
654
+ const indices = memoCache.get(component);
655
+ if (indices) {
656
+ indices.forEach((idx) => {
657
+ const hook = component._hooks?.[idx];
658
+ if (hook) {
659
+ hook._onInvalidate = null;
660
+ if (hook._lruCache) {
661
+ hook._lruCache.clear();
662
+ }
663
+ }
664
+ });
665
+ memoCache.delete(component);
666
+ }
667
+ memoProfiles.delete(component);
668
+ }
669
+
670
+ // ─── Exports ──────────────────────────────────────────────────────────────────
671
+
672
+ module.exports = {
673
+ useMemo,
674
+ useMemoWith,
675
+ useMemoTTL,
676
+ useDeepCompareMemo,
677
+ useMemoLRU,
678
+ useMemoOnInvalidate,
679
+ invalidateMemo,
680
+ invalidateAllMemos,
681
+ setCurrentComponent,
682
+ resetHookIndex,
683
+ areDepsEqual,
684
+ areDepsDeepEqual,
685
+ cleanupMemoCache,
686
+ setMemoProfiling,
687
+ getMemoProfile,
688
+ LRUMemoCache,
689
+ };