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,854 @@
1
+ /**
2
+ * useState Hook Implementation
3
+ * ElementDrawing Framework - State management hook with batched updates,
4
+ * functional updates, lazy initialization, state comparison, subscriptions,
5
+ * priority-based updates, transition support, and debug tracing.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ // ─── Internal State ───────────────────────────────────────────────────────────
11
+
12
+ let currentlyRenderingComponent = null;
13
+ let hookIndex = 0;
14
+ let isBatchingUpdates = false;
15
+ const pendingStateUpdates = new Map();
16
+ const stateSubscribers = new Map();
17
+ const statePersistenceKeys = new WeakMap();
18
+ let scheduleUpdateCallback = null;
19
+
20
+ // ─── Update Priority Levels ──────────────────────────────────────────────────
21
+
22
+ const PRIORITY = {
23
+ IMMEDIATE: 0, // Synchronous, user-initiated (click, keypress)
24
+ NORMAL: 1, // Standard state update
25
+ TRANSITION: 2, // Non-urgent, can be interrupted (useTransition)
26
+ IDLE: 3, // Lowest priority, runs when idle
27
+ };
28
+
29
+ let currentUpdatePriority = PRIORITY.NORMAL;
30
+
31
+ /**
32
+ * Get the current update priority level.
33
+ * @returns {number}
34
+ */
35
+ function getCurrentPriority() {
36
+ return currentUpdatePriority;
37
+ }
38
+
39
+ /**
40
+ * Set the current update priority level.
41
+ * @param {number} priority
42
+ */
43
+ function setCurrentPriority(priority) {
44
+ if (!Object.values(PRIORITY).includes(priority)) {
45
+ console.warn('[useState] Invalid priority level:', priority);
46
+ return;
47
+ }
48
+ currentUpdatePriority = priority;
49
+ }
50
+
51
+ // ─── Update Queue ─────────────────────────────────────────────────────────────
52
+
53
+ /**
54
+ * Represents a pending state update in the queue.
55
+ * @param {*} updateFnOrValue - New value or updater function
56
+ * @param {number} priority - Update priority
57
+ * @param {number} lane - Lane for concurrent rendering
58
+ */
59
+ function createStateUpdate(updateFnOrValue, priority, lane) {
60
+ return {
61
+ updateFnOrValue,
62
+ priority: priority !== undefined ? priority : currentUpdatePriority,
63
+ lane: lane || 0,
64
+ timestamp: Date.now(),
65
+ id: ++updateIdCounter,
66
+ };
67
+ }
68
+
69
+ let updateIdCounter = 0;
70
+
71
+ // ─── Batching System ─────────────────────────────────────────────────────────
72
+
73
+ /**
74
+ * Start batching state updates. All setState calls within the batch
75
+ * will be deferred until endBatchUpdates is called.
76
+ */
77
+ function startBatchUpdates() {
78
+ isBatchingUpdates = true;
79
+ }
80
+
81
+ /**
82
+ * Flush all pending batched updates and re-render affected components.
83
+ */
84
+ function endBatchUpdates() {
85
+ isBatchingUpdates = false;
86
+ flushPendingUpdates();
87
+ }
88
+
89
+ /**
90
+ * Flush all pending state updates, merging multiple updates for the same hook.
91
+ * Processes updates by priority order (IMMEDIATE first).
92
+ */
93
+ function flushPendingUpdates() {
94
+ if (pendingStateUpdates.size === 0) return;
95
+
96
+ const updates = new Map(pendingStateUpdates);
97
+ pendingStateUpdates.clear();
98
+
99
+ updates.forEach((stateQueue, component) => {
100
+ if (stateQueue.length === 0) return;
101
+
102
+ // Sort updates by priority (lower number = higher priority)
103
+ stateQueue.sort((a, b) => a.priority - b.priority);
104
+
105
+ const hooks = component._hooks;
106
+ stateQueue.forEach(({ hookIdx, update }) => {
107
+ const hook = hooks[hookIdx];
108
+ if (!hook) return;
109
+
110
+ let newValue;
111
+ if (typeof update.updateFnOrValue === 'function') {
112
+ newValue = update.updateFnOrValue(hook.state);
113
+ } else {
114
+ newValue = update.updateFnOrValue;
115
+ }
116
+
117
+ // State comparison - skip update if value is the same
118
+ const areEqual = hook._areEqual || Object.is;
119
+ if (areEqual(hook.state, newValue)) return;
120
+
121
+ const prevState = hook.state;
122
+ hook.state = newValue;
123
+
124
+ // Track update in debug history
125
+ if (hook._debug) {
126
+ hook._updateHistory.push({
127
+ prev: prevState,
128
+ next: newValue,
129
+ timestamp: Date.now(),
130
+ priority: update.priority,
131
+ });
132
+ // Keep only last 50 entries
133
+ if (hook._updateHistory.length > 50) {
134
+ hook._updateHistory.shift();
135
+ }
136
+ }
137
+
138
+ // Notify subscribers of state change
139
+ notifySubscribers(component, hookIdx, newValue, prevState);
140
+
141
+ // Persist state if persistence is configured
142
+ persistState(component, hookIdx, newValue);
143
+ });
144
+
145
+ // Schedule re-render for the component
146
+ if (scheduleUpdateCallback) {
147
+ scheduleUpdateCallback(component);
148
+ } else if (component._scheduleReRender) {
149
+ component._scheduleReRender();
150
+ }
151
+ });
152
+ }
153
+
154
+ /**
155
+ * Execute a function with batched updates enabled.
156
+ * @param {Function} fn - Function to execute within the batch
157
+ * @returns {*} Return value of the executed function
158
+ */
159
+ function batchedUpdates(fn) {
160
+ startBatchUpdates();
161
+ try {
162
+ return fn();
163
+ } finally {
164
+ endBatchUpdates();
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Execute a function at a specific priority level.
170
+ * @param {number} priority - PRIORITY.IMMEDIATE | NORMAL | TRANSITION | IDLE
171
+ * @param {Function} fn - Function to execute
172
+ * @returns {*} Return value of the executed function
173
+ */
174
+ function runWithPriority(priority, fn) {
175
+ const prevPriority = currentUpdatePriority;
176
+ setCurrentPriority(priority);
177
+ try {
178
+ return fn();
179
+ } finally {
180
+ setCurrentPriority(prevPriority);
181
+ }
182
+ }
183
+
184
+ // ─── Transition Support ───────────────────────────────────────────────────────
185
+
186
+ let isTransitionActive = false;
187
+ const pendingTransitions = [];
188
+
189
+ /**
190
+ * Start a transition - marks updates as non-urgent.
191
+ * @param {Function} fn - Function containing transition updates
192
+ */
193
+ function startTransition(fn) {
194
+ isTransitionActive = true;
195
+ const prevPriority = currentUpdatePriority;
196
+ currentUpdatePriority = PRIORITY.TRANSITION;
197
+
198
+ try {
199
+ fn();
200
+ } finally {
201
+ isTransitionActive = false;
202
+ currentUpdatePriority = prevPriority;
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Check if a transition is currently active.
208
+ * @returns {boolean}
209
+ */
210
+ function isTransitionInProgress() {
211
+ return isTransitionActive;
212
+ }
213
+
214
+ // ─── Subscription System ──────────────────────────────────────────────────────
215
+
216
+ /**
217
+ * Subscribe to state changes for a specific component's hook.
218
+ * @param {Object} component - The component instance
219
+ * @param {number} hookIdx - The hook index
220
+ * @param {Function} callback - Called with (newValue, oldValue)
221
+ * @returns {Function} Unsubscribe function
222
+ */
223
+ function subscribeToState(component, hookIdx, callback) {
224
+ if (!stateSubscribers.has(component)) {
225
+ stateSubscribers.set(component, new Map());
226
+ }
227
+ const componentSubs = stateSubscribers.get(component);
228
+
229
+ if (!componentSubs.has(hookIdx)) {
230
+ componentSubs.set(hookIdx, new Set());
231
+ }
232
+ const subs = componentSubs.get(hookIdx);
233
+ subs.add(callback);
234
+
235
+ return function unsubscribe() {
236
+ subs.delete(callback);
237
+ if (subs.size === 0) {
238
+ componentSubs.delete(hookIdx);
239
+ }
240
+ if (componentSubs.size === 0) {
241
+ stateSubscribers.delete(component);
242
+ }
243
+ };
244
+ }
245
+
246
+ /**
247
+ * Notify all subscribers of a state change.
248
+ */
249
+ function notifySubscribers(component, hookIdx, newValue, oldValue) {
250
+ const componentSubs = stateSubscribers.get(component);
251
+ if (!componentSubs) return;
252
+
253
+ const subs = componentSubs.get(hookIdx);
254
+ if (!subs) return;
255
+
256
+ subs.forEach((callback) => {
257
+ try {
258
+ callback(newValue, oldValue);
259
+ } catch (error) {
260
+ console.error('[useState] Subscriber error:', error);
261
+ }
262
+ });
263
+ }
264
+
265
+ // ─── State Persistence ────────────────────────────────────────────────────────
266
+
267
+ /**
268
+ * Configure state persistence for a hook.
269
+ * @param {string} key - The localStorage/sessionStorage key
270
+ * @param {Object} options - Persistence options
271
+ * @param {string} options.storage - 'localStorage' | 'sessionStorage'
272
+ * @param {Function} [options.serialize] - Custom serialize function
273
+ * @param {Function} [options.deserialize] - Custom deserialize function
274
+ * @param {number} [options.debounceMs] - Debounce persistence writes
275
+ */
276
+ function configurePersistence(key, options = {}) {
277
+ const config = {
278
+ key,
279
+ storage: options.storage || 'localStorage',
280
+ serialize: options.serialize || JSON.stringify,
281
+ deserialize: options.deserialize || JSON.parse,
282
+ debounceMs: options.debounceMs || 0,
283
+ };
284
+ return config;
285
+ }
286
+
287
+ /**
288
+ * Persist state to storage.
289
+ */
290
+ let persistenceTimers = new Map();
291
+
292
+ function persistState(component, hookIdx, value) {
293
+ const persistenceConfig = component._hooks[hookIdx]?._persistence;
294
+ if (!persistenceConfig) return;
295
+
296
+ const writeFn = () => {
297
+ try {
298
+ const storage = window[persistenceConfig.storage];
299
+ const serialized = persistenceConfig.serialize(value);
300
+ storage.setItem(persistenceConfig.key, serialized);
301
+ } catch (error) {
302
+ console.error('[useState] Persistence error:', error);
303
+ }
304
+ };
305
+
306
+ // Debounced persistence
307
+ if (persistenceConfig.debounceMs > 0) {
308
+ const timerKey = component._hooks[hookIdx]._persistenceTimer;
309
+ if (timerKey) clearTimeout(timerKey);
310
+ component._hooks[hookIdx]._persistenceTimer = setTimeout(writeFn, persistenceConfig.debounceMs);
311
+ } else {
312
+ writeFn();
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Load persisted state from storage.
318
+ */
319
+ function loadPersistedState(persistenceConfig) {
320
+ if (!persistenceConfig) return undefined;
321
+
322
+ try {
323
+ const storage = window[persistenceConfig.storage];
324
+ const serialized = storage.getItem(persistenceConfig.key);
325
+ if (serialized === null) return undefined;
326
+ return persistenceConfig.deserialize(serialized);
327
+ } catch (error) {
328
+ console.error('[useState] Load persistence error:', error);
329
+ return undefined;
330
+ }
331
+ }
332
+
333
+ // ─── State Comparison Utilities ───────────────────────────────────────────────
334
+
335
+ /**
336
+ * Deep comparison function for state values.
337
+ * @param {*} a - First value
338
+ * @param {*} b - Second value
339
+ * @returns {boolean} True if deeply equal
340
+ */
341
+ function deepEqual(a, b) {
342
+ if (Object.is(a, b)) return true;
343
+
344
+ if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) {
345
+ return false;
346
+ }
347
+
348
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
349
+
350
+ const keysA = Object.keys(a);
351
+ const keysB = Object.keys(b);
352
+
353
+ if (keysA.length !== keysB.length) return false;
354
+
355
+ for (const key of keysA) {
356
+ if (!keysB.includes(key)) return false;
357
+ if (!deepEqual(a[key], b[key])) return false;
358
+ }
359
+
360
+ return true;
361
+ }
362
+
363
+ /**
364
+ * Shallow comparison function for state values.
365
+ * @param {*} a - First value
366
+ * @param {*} b - Second value
367
+ * @returns {boolean}
368
+ */
369
+ function shallowEqual(a, b) {
370
+ if (Object.is(a, b)) return true;
371
+
372
+ if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) {
373
+ return false;
374
+ }
375
+
376
+ const keysA = Object.keys(a);
377
+ const keysB = Object.keys(b);
378
+
379
+ if (keysA.length !== keysB.length) return false;
380
+
381
+ for (const key of keysA) {
382
+ if (!Object.is(a[key], b[key])) return false;
383
+ }
384
+
385
+ return true;
386
+ }
387
+
388
+ // ─── Hook Context Management ──────────────────────────────────────────────────
389
+
390
+ /**
391
+ * Set the currently rendering component (called by the framework before render).
392
+ * @param {Object} component - The component being rendered
393
+ */
394
+ function setCurrentComponent(component) {
395
+ currentlyRenderingComponent = component;
396
+ hookIndex = 0;
397
+ }
398
+
399
+ /**
400
+ * Get the currently rendering component.
401
+ * @returns {Object|null}
402
+ */
403
+ function getCurrentComponent() {
404
+ return currentlyRenderingComponent;
405
+ }
406
+
407
+ /**
408
+ * Reset the hook index after rendering is complete.
409
+ */
410
+ function resetHookIndex() {
411
+ hookIndex = 0;
412
+ currentlyRenderingComponent = null;
413
+ }
414
+
415
+ /**
416
+ * Set the framework's update scheduling callback.
417
+ * @param {Function} callback - Called with (component) to schedule re-render
418
+ */
419
+ function setScheduleUpdateCallback(callback) {
420
+ scheduleUpdateCallback = callback;
421
+ }
422
+
423
+ // ─── useState Hook ────────────────────────────────────────────────────────────
424
+
425
+ /**
426
+ * useState - State hook with initial value, lazy initializer, batched updates,
427
+ * functional updates, state comparison, subscriptions, persistence, and
428
+ * priority-based updates.
429
+ *
430
+ * @param {*} initialState - Initial state value or lazy initializer function
431
+ * @param {Object} [options] - Additional options
432
+ * @param {Object} [options.persistence] - Persistence configuration
433
+ * @param {Function} [options.areEqual] - Custom equality comparison function
434
+ * @param {boolean} [options.debug] - Enable debug tracing for this state
435
+ * @returns {[*, Function]} Tuple of [currentState, setState]
436
+ *
437
+ * @example
438
+ * // Simple state
439
+ * const [count, setCount] = useState(0);
440
+ *
441
+ * @example
442
+ * // Lazy initializer (expensive computation runs once)
443
+ * const [data, setData] = useState(() => computeExpensiveValue());
444
+ *
445
+ * @example
446
+ * // Functional update
447
+ * setCount(prev => prev + 1);
448
+ *
449
+ * @example
450
+ * // With persistence
451
+ * const [theme, setTheme] = useState('light', {
452
+ * persistence: configurePersistence('app-theme', { storage: 'localStorage' })
453
+ * });
454
+ *
455
+ * @example
456
+ * // With deep comparison
457
+ * const [form, setForm] = useState({}, { areEqual: deepEqual });
458
+ */
459
+ function useState(initialState, options = {}) {
460
+ const component = currentlyRenderingComponent;
461
+ if (!component) {
462
+ throw new Error('[useState] Must be called within a component render phase');
463
+ }
464
+
465
+ // Initialize hooks array on first render
466
+ if (!component._hooks) {
467
+ component._hooks = [];
468
+ }
469
+
470
+ // Get or create the hook state
471
+ const currentHookIndex = hookIndex;
472
+ let hook;
473
+
474
+ if (component._hooks[currentHookIndex]) {
475
+ // Re-render: use existing hook state
476
+ hook = component._hooks[currentHookIndex];
477
+ } else {
478
+ // First render: initialize hook state
479
+ let resolvedInitialState;
480
+
481
+ // Check for persisted state first
482
+ if (options.persistence) {
483
+ const persistedValue = loadPersistedState(options.persistence);
484
+ if (persistedValue !== undefined) {
485
+ resolvedInitialState = persistedValue;
486
+ }
487
+ }
488
+
489
+ // If no persisted value, resolve initial state
490
+ if (resolvedInitialState === undefined) {
491
+ if (typeof initialState === 'function') {
492
+ // Lazy initializer - only called once on mount
493
+ try {
494
+ resolvedInitialState = initialState();
495
+ } catch (error) {
496
+ console.error('[useState] Lazy initializer error:', error);
497
+ resolvedInitialState = undefined;
498
+ }
499
+ } else {
500
+ resolvedInitialState = initialState;
501
+ }
502
+ }
503
+
504
+ hook = {
505
+ state: resolvedInitialState,
506
+ _persistence: options.persistence || null,
507
+ _areEqual: options.areEqual || Object.is,
508
+ _isMounted: false,
509
+ _pendingUpdates: [],
510
+ _queue: [],
511
+ _debug: options.debug || false,
512
+ _updateHistory: options.debug ? [] : null,
513
+ _persistenceTimer: null,
514
+ };
515
+
516
+ component._hooks[currentHookIndex] = hook;
517
+ }
518
+
519
+ // Process any pending functional updates that were queued
520
+ if (hook._pendingUpdates.length > 0) {
521
+ const updates = hook._pendingUpdates.splice(0);
522
+ updates.forEach((update) => {
523
+ if (typeof update === 'function') {
524
+ hook.state = update(hook.state);
525
+ } else {
526
+ hook.state = update;
527
+ }
528
+ });
529
+ }
530
+
531
+ // Increment hook index for the next hook call
532
+ hookIndex++;
533
+
534
+ // ─── setState Implementation ────────────────────────────────────────────
535
+
536
+ /**
537
+ * Update state with a new value or updater function.
538
+ * Supports batching, functional updates, priority, and state comparison.
539
+ *
540
+ * @param {*} updateFnOrValue - New state value or updater function
541
+ */
542
+ const setState = function setState(updateFnOrValue) {
543
+ if (!component._hooks) {
544
+ console.warn('[useState] setState called on unmounted component');
545
+ return;
546
+ }
547
+
548
+ const targetHook = component._hooks[currentHookIndex];
549
+ if (!targetHook) {
550
+ console.warn('[useState] Hook index out of bounds');
551
+ return;
552
+ }
553
+
554
+ const update = createStateUpdate(updateFnOrValue, currentUpdatePriority);
555
+
556
+ if (isBatchingUpdates) {
557
+ // Queue the update for batch processing
558
+ if (!pendingStateUpdates.has(component)) {
559
+ pendingStateUpdates.set(component, []);
560
+ }
561
+ pendingStateUpdates.get(component).push({
562
+ hookIdx: currentHookIndex,
563
+ update,
564
+ });
565
+ } else {
566
+ // Immediate update
567
+ let newValue;
568
+ if (typeof updateFnOrValue === 'function') {
569
+ newValue = updateFnOrValue(targetHook.state);
570
+ } else {
571
+ newValue = updateFnOrValue;
572
+ }
573
+
574
+ // Use custom equality check or Object.is
575
+ const areEqual = targetHook._areEqual;
576
+ if (areEqual(targetHook.state, newValue)) {
577
+ return; // Skip update - state is the same
578
+ }
579
+
580
+ const prevState = targetHook.state;
581
+ targetHook.state = newValue;
582
+
583
+ // Track update in debug history
584
+ if (targetHook._debug && targetHook._updateHistory) {
585
+ targetHook._updateHistory.push({
586
+ prev: prevState,
587
+ next: newValue,
588
+ timestamp: Date.now(),
589
+ priority: update.priority,
590
+ });
591
+ if (targetHook._updateHistory.length > 50) {
592
+ targetHook._updateHistory.shift();
593
+ }
594
+ }
595
+
596
+ // Notify subscribers
597
+ notifySubscribers(component, currentHookIndex, newValue, prevState);
598
+
599
+ // Persist state if configured
600
+ persistState(component, currentHookIndex, newValue);
601
+
602
+ // Schedule re-render
603
+ if (scheduleUpdateCallback) {
604
+ scheduleUpdateCallback(component);
605
+ } else if (component._scheduleReRender) {
606
+ component._scheduleReRender();
607
+ }
608
+ }
609
+ };
610
+
611
+ // ─── Extended setState Methods ──────────────────────────────────────────
612
+
613
+ /**
614
+ * Reset state to initial value.
615
+ */
616
+ setState.reset = function reset() {
617
+ let resetValue;
618
+ if (typeof initialState === 'function') {
619
+ resetValue = initialState();
620
+ } else {
621
+ resetValue = initialState;
622
+ }
623
+ setState(resetValue);
624
+ };
625
+
626
+ /**
627
+ * Subscribe to state changes.
628
+ * @param {Function} callback - Called with (newValue, oldValue)
629
+ * @returns {Function} Unsubscribe function
630
+ */
631
+ setState.subscribe = function subscribe(callback) {
632
+ return subscribeToState(component, currentHookIndex, callback);
633
+ };
634
+
635
+ /**
636
+ * Get current state without triggering a re-render.
637
+ * @returns {*} Current state value
638
+ */
639
+ setState.getCurrent = function getCurrent() {
640
+ const targetHook = component._hooks?.[currentHookIndex];
641
+ return targetHook ? targetHook.state : undefined;
642
+ };
643
+
644
+ /**
645
+ * Check if the component is mounted.
646
+ * @returns {boolean}
647
+ */
648
+ setState.isMounted = function isMounted() {
649
+ const targetHook = component._hooks?.[currentHookIndex];
650
+ return targetHook ? targetHook._isMounted : false;
651
+ };
652
+
653
+ /**
654
+ * Set state with a specific priority level.
655
+ * @param {*} updateFnOrValue - New state value or updater function
656
+ * @param {number} priority - PRIORITY level
657
+ */
658
+ setState.withPriority = function withPriority(updateFnOrValue, priority) {
659
+ const prevPriority = currentUpdatePriority;
660
+ setCurrentPriority(priority);
661
+ try {
662
+ setState(updateFnOrValue);
663
+ } finally {
664
+ setCurrentPriority(prevPriority);
665
+ }
666
+ };
667
+
668
+ /**
669
+ * Set state as a transition (non-urgent update).
670
+ * @param {*} updateFnOrValue - New state value or updater function
671
+ */
672
+ setState.asTransition = function asTransition(updateFnOrValue) {
673
+ startTransition(() => {
674
+ setState(updateFnOrValue);
675
+ });
676
+ };
677
+
678
+ /**
679
+ * Get the update history for debugging (only if debug mode is enabled).
680
+ * @returns {Array|null} Update history array or null
681
+ */
682
+ setState.getHistory = function getHistory() {
683
+ const targetHook = component._hooks?.[currentHookIndex];
684
+ return targetHook?._updateHistory || null;
685
+ };
686
+
687
+ return [hook.state, setState];
688
+ }
689
+
690
+ // ─── useTransition Hook ───────────────────────────────────────────────────────
691
+
692
+ /**
693
+ * useTransition - Hook for marking updates as transitions (non-urgent).
694
+ * Returns a pending flag and a startTransition function.
695
+ *
696
+ * @returns {[boolean, Function]} Tuple of [isPending, startTransition]
697
+ *
698
+ * @example
699
+ * const [isPending, startTransition] = useTransition();
700
+ * startTransition(() => {
701
+ * setSearchResults(filteredData);
702
+ * });
703
+ */
704
+ function useTransition() {
705
+ const component = currentlyRenderingComponent;
706
+ if (!component) {
707
+ throw new Error('[useTransition] Must be called within a component render phase');
708
+ }
709
+
710
+ if (!component._hooks) {
711
+ component._hooks = [];
712
+ }
713
+
714
+ const currentHookIndex = hookIndex;
715
+ let hook;
716
+
717
+ if (component._hooks[currentHookIndex]) {
718
+ hook = component._hooks[currentHookIndex];
719
+ } else {
720
+ hook = {
721
+ type: 'transition',
722
+ isPending: false,
723
+ _pendingCount: 0,
724
+ };
725
+ component._hooks[currentHookIndex] = hook;
726
+ }
727
+
728
+ hookIndex++;
729
+
730
+ const startTransitionFn = function startTransitionFn(callback) {
731
+ hook._pendingCount++;
732
+ hook.isPending = true;
733
+
734
+ startTransition(() => {
735
+ try {
736
+ callback();
737
+ } finally {
738
+ hook._pendingCount--;
739
+ if (hook._pendingCount <= 0) {
740
+ hook.isPending = false;
741
+ hook._pendingCount = 0;
742
+ }
743
+ }
744
+ });
745
+ };
746
+
747
+ return [hook.isPending, startTransitionFn];
748
+ }
749
+
750
+ // ─── useDeferredValue Hook ────────────────────────────────────────────────────
751
+
752
+ /**
753
+ * useDeferredValue - Returns a deferred version of the value that may lag
754
+ * behind the current value. Useful for optimizing renders of expensive lists.
755
+ *
756
+ * @param {*} value - The value to defer
757
+ * @param {number} [timeoutMs] - Maximum time to defer (optional)
758
+ * @returns {*} Deferred value
759
+ *
760
+ * @example
761
+ * const deferredQuery = useDeferredValue(query);
762
+ * const filteredItems = useMemo(() => items.filter(i => i.name.includes(deferredQuery)), [deferredQuery, items]);
763
+ */
764
+ function useDeferredValue(value, timeoutMs) {
765
+ const component = currentlyRenderingComponent;
766
+ if (!component) {
767
+ throw new Error('[useDeferredValue] Must be called within a component render phase');
768
+ }
769
+
770
+ if (!component._hooks) {
771
+ component._hooks = [];
772
+ }
773
+
774
+ const currentHookIndex = hookIndex;
775
+ let hook;
776
+
777
+ if (component._hooks[currentHookIndex]) {
778
+ hook = component._hooks[currentHookIndex];
779
+
780
+ // Update the current value immediately
781
+ hook.currentValue = value;
782
+
783
+ // If no timeout or no deferred update pending, use current value
784
+ if (!hook._deferredUpdatePending) {
785
+ hook.deferredValue = value;
786
+ }
787
+ } else {
788
+ hook = {
789
+ type: 'deferred-value',
790
+ currentValue: value,
791
+ deferredValue: value,
792
+ _deferredUpdatePending: false,
793
+ _timeoutId: null,
794
+ };
795
+ component._hooks[currentHookIndex] = hook;
796
+ }
797
+
798
+ hookIndex++;
799
+ return hook.deferredValue;
800
+ }
801
+
802
+ // ─── Cleanup ──────────────────────────────────────────────────────────────────
803
+
804
+ /**
805
+ * Clean up hook state when a component unmounts.
806
+ * @param {Object} component - The unmounting component
807
+ */
808
+ function cleanupComponentState(component) {
809
+ // Remove all subscribers for this component
810
+ stateSubscribers.delete(component);
811
+
812
+ // Remove any pending updates
813
+ pendingStateUpdates.delete(component);
814
+
815
+ // Clean up persistence timers and references
816
+ if (component._hooks) {
817
+ component._hooks.forEach((hook) => {
818
+ hook._pendingUpdates = [];
819
+ hook._queue = [];
820
+ if (hook._persistenceTimer) {
821
+ clearTimeout(hook._persistenceTimer);
822
+ }
823
+ if (hook._updateHistory) {
824
+ hook._updateHistory = null;
825
+ }
826
+ });
827
+ }
828
+ }
829
+
830
+ // ─── Exports ──────────────────────────────────────────────────────────────────
831
+
832
+ module.exports = {
833
+ useState,
834
+ useTransition,
835
+ useDeferredValue,
836
+ setCurrentComponent,
837
+ getCurrentComponent,
838
+ resetHookIndex,
839
+ setScheduleUpdateCallback,
840
+ startBatchUpdates,
841
+ endBatchUpdates,
842
+ batchedUpdates,
843
+ configurePersistence,
844
+ subscribeToState,
845
+ cleanupComponentState,
846
+ deepEqual,
847
+ shallowEqual,
848
+ runWithPriority,
849
+ startTransition,
850
+ isTransitionInProgress,
851
+ getCurrentPriority,
852
+ setCurrentPriority,
853
+ PRIORITY,
854
+ };