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,729 @@
1
+ /**
2
+ * useReducer Hook Implementation
3
+ * ElementDrawing Framework - Reducer-based state management with lazy
4
+ * initialization, dispatch, action batching, middleware support,
5
+ * action creators, undo/redo, optimistic updates, and action history.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ // ─── Internal State ───────────────────────────────────────────────────────────
11
+
12
+ let currentlyRenderingComponent = null;
13
+ let hookIndex = 0;
14
+ let isBatchingDispatches = false;
15
+ const pendingDispatches = new Map();
16
+ let scheduleUpdateCallback = null;
17
+
18
+ // ─── Batching System ─────────────────────────────────────────────────────────
19
+
20
+ /**
21
+ * Start batching dispatches. All dispatched actions within the batch
22
+ * will be deferred until endBatchDispatches is called.
23
+ */
24
+ function startBatchDispatches() {
25
+ isBatchingDispatches = true;
26
+ }
27
+
28
+ /**
29
+ * Flush all pending batched dispatches.
30
+ */
31
+ function endBatchDispatches() {
32
+ isBatchingDispatches = false;
33
+ flushPendingDispatches();
34
+ }
35
+
36
+ /**
37
+ * Flush all pending dispatch actions, processing them in order.
38
+ */
39
+ function flushPendingDispatches() {
40
+ if (pendingDispatches.size === 0) return;
41
+
42
+ const dispatches = new Map(pendingDispatches);
43
+ pendingDispatches.clear();
44
+
45
+ dispatches.forEach((actions, component) => {
46
+ if (actions.length === 0) return;
47
+
48
+ // Process all queued actions through the reducer
49
+ actions.forEach(({ hookIdx, action, reducer }) => {
50
+ const hook = component._hooks[hookIdx];
51
+ if (!hook) return;
52
+
53
+ // Apply middleware chain before reducing
54
+ let processedAction = action;
55
+ if (hook._middleware && hook._middleware.length > 0) {
56
+ processedAction = applyMiddlewareChain(hook._middleware, action, hook.state);
57
+ if (processedAction === null) return; // Action cancelled
58
+ }
59
+
60
+ // Run the reducer
61
+ const newState = reducer(hook.state, processedAction);
62
+
63
+ // State comparison - skip update if unchanged
64
+ if (Object.is(hook.state, newState)) return;
65
+
66
+ const prevState = hook.state;
67
+ hook.state = newState;
68
+
69
+ // Track in action history
70
+ if (hook._actionHistory) {
71
+ hook._actionHistory.push({
72
+ action: processedAction,
73
+ prevState,
74
+ nextState: newState,
75
+ timestamp: Date.now(),
76
+ });
77
+ if (hook._actionHistory.length > (hook._maxHistorySize || 100)) {
78
+ hook._actionHistory.shift();
79
+ }
80
+ }
81
+
82
+ // Notify state change subscribers
83
+ if (hook._subscribers) {
84
+ hook._subscribers.forEach((callback) => {
85
+ try {
86
+ callback(newState, prevState, processedAction);
87
+ } catch (error) {
88
+ console.error('[useReducer] Subscriber error:', error);
89
+ }
90
+ });
91
+ }
92
+
93
+ // Persist state if configured
94
+ if (hook._persistence) {
95
+ try {
96
+ const storage = window[hook._persistence.storage];
97
+ storage.setItem(hook._persistence.key, hook._persistence.serialize(newState));
98
+ } catch (error) {
99
+ console.error('[useReducer] Persistence error:', error);
100
+ }
101
+ }
102
+ });
103
+
104
+ // Schedule re-render
105
+ if (scheduleUpdateCallback) {
106
+ scheduleUpdateCallback(component);
107
+ } else if (component._scheduleReRender) {
108
+ component._scheduleReRender();
109
+ }
110
+ });
111
+ }
112
+
113
+ /**
114
+ * Execute a function with batched dispatches enabled.
115
+ * @param {Function} fn
116
+ * @returns {*}
117
+ */
118
+ function batchDispatches(fn) {
119
+ startBatchDispatches();
120
+ try {
121
+ return fn();
122
+ } finally {
123
+ endBatchDispatches();
124
+ }
125
+ }
126
+
127
+ // ─── Middleware System ─────────────────────────────────────────────────────────
128
+
129
+ /**
130
+ * Apply a chain of middleware to an action before it reaches the reducer.
131
+ * Middleware signature: (action, state) => action (or throw to cancel)
132
+ *
133
+ * @param {Function[]} middleware - Array of middleware functions
134
+ * @param {*} action - The dispatched action
135
+ * @param {*} state - Current state
136
+ * @returns {*} Processed action
137
+ */
138
+ function applyMiddlewareChain(middleware, action, state) {
139
+ let processedAction = action;
140
+
141
+ for (let i = 0; i < middleware.length; i++) {
142
+ try {
143
+ const result = middleware[i](processedAction, state);
144
+ if (result === null) return null; // Middleware cancelled the action
145
+ processedAction = result;
146
+ } catch (error) {
147
+ console.error('[useReducer] Middleware error:', error);
148
+ return action; // Fall back to original action
149
+ }
150
+ }
151
+
152
+ return processedAction;
153
+ }
154
+
155
+ /**
156
+ * Create a logging middleware that logs all dispatched actions.
157
+ * @param {string} [prefix] - Log prefix
158
+ * @returns {Function} Middleware function
159
+ */
160
+ function createLoggerMiddleware(prefix = 'Reducer') {
161
+ return function loggerMiddleware(action, state) {
162
+ console.log(`[${prefix}] Action:`, action, '| State:', state);
163
+ return action;
164
+ };
165
+ }
166
+
167
+ /**
168
+ * Create a middleware that validates actions against a schema.
169
+ * @param {Object} schema - Action type to validator mapping
170
+ * @returns {Function} Middleware function
171
+ */
172
+ function createValidationMiddleware(schema) {
173
+ return function validationMiddleware(action) {
174
+ if (action && action.type && schema[action.type]) {
175
+ const validator = schema[action.type];
176
+ if (typeof validator === 'function') {
177
+ const isValid = validator(action);
178
+ if (!isValid) {
179
+ console.warn('[useReducer] Action validation failed:', action);
180
+ return null; // Cancel invalid action
181
+ }
182
+ }
183
+ }
184
+ return action;
185
+ };
186
+ }
187
+
188
+ /**
189
+ * Create a middleware that transforms actions.
190
+ * @param {Function} transformer - (action, state) => newAction
191
+ * @returns {Function} Middleware function
192
+ */
193
+ function createTransformMiddleware(transformer) {
194
+ return function transformMiddleware(action, state) {
195
+ return transformer(action, state);
196
+ };
197
+ }
198
+
199
+ /**
200
+ * Create a middleware that throttles actions of a given type.
201
+ * @param {string} actionType - Action type to throttle
202
+ * @param {number} intervalMs - Throttle interval
203
+ * @returns {Function} Middleware function
204
+ */
205
+ function createThrottleMiddleware(actionType, intervalMs) {
206
+ let lastDispatch = 0;
207
+ return function throttleMiddleware(action) {
208
+ if (action && action.type === actionType) {
209
+ const now = Date.now();
210
+ if (now - lastDispatch < intervalMs) {
211
+ return null; // Cancel throttled action
212
+ }
213
+ lastDispatch = now;
214
+ }
215
+ return action;
216
+ };
217
+ }
218
+
219
+ // ─── Action Creator Factory ───────────────────────────────────────────────────
220
+
221
+ /**
222
+ * Create action creator functions from an action definition map.
223
+ *
224
+ * @param {Object} actionMap - Map of action type to payload creator function
225
+ * @returns {Object} Object of action creator functions
226
+ *
227
+ * @example
228
+ * const actions = createActionCreators({
229
+ * increment: () => ({ type: 'INCREMENT' }),
230
+ * add: (amount) => ({ type: 'ADD', payload: amount }),
231
+ * reset: () => ({ type: 'RESET' }),
232
+ * });
233
+ *
234
+ * dispatch(actions.increment());
235
+ * dispatch(actions.add(5));
236
+ */
237
+ function createActionCreators(actionMap) {
238
+ const creators = {};
239
+ for (const type in actionMap) {
240
+ if (!actionMap.hasOwnProperty(type)) continue;
241
+ const payloadCreator = actionMap[type];
242
+ creators[type] = function actionCreator() {
243
+ return payloadCreator.apply(null, arguments);
244
+ };
245
+ }
246
+ return creators;
247
+ }
248
+
249
+ /**
250
+ * Create a simple action creator for a type.
251
+ * @param {string} type - Action type
252
+ * @returns {Function} Action creator
253
+ */
254
+ function createAction(type) {
255
+ return function actionCreator(payload) {
256
+ return { type, payload };
257
+ };
258
+ }
259
+
260
+ // ─── Hook Context Management ──────────────────────────────────────────────────
261
+
262
+ function setCurrentComponent(component) {
263
+ currentlyRenderingComponent = component;
264
+ hookIndex = 0;
265
+ }
266
+
267
+ function resetHookIndex() {
268
+ hookIndex = 0;
269
+ currentlyRenderingComponent = null;
270
+ }
271
+
272
+ function setScheduleUpdateCallback(callback) {
273
+ scheduleUpdateCallback = callback;
274
+ }
275
+
276
+ // ─── useReducer Hook ──────────────────────────────────────────────────────────
277
+
278
+ /**
279
+ * useReducer - Reducer-based state management hook.
280
+ *
281
+ * @param {Function} reducer - Pure function: (state, action) => newState
282
+ * @param {*} initialArg - Initial state value or argument for lazy initializer
283
+ * @param {Function} [init] - Lazy initializer: (initialArg) => initialState
284
+ * @param {Object} [options] - Additional options
285
+ * @param {Function[]} [options.middleware] - Middleware chain for actions
286
+ * @param {Object} [options.persistence] - State persistence configuration
287
+ * @param {boolean} [options.trackHistory] - Track action history for debugging
288
+ * @param {number} [options.maxHistorySize] - Maximum history entries
289
+ * @returns {[*, Function]} Tuple of [state, dispatch]
290
+ *
291
+ * @example
292
+ * function counterReducer(state, action) {
293
+ * switch (action.type) {
294
+ * case 'increment': return { count: state.count + 1 };
295
+ * case 'decrement': return { count: state.count - 1 };
296
+ * case 'reset': return { count: action.payload };
297
+ * default: return state;
298
+ * }
299
+ * }
300
+ *
301
+ * const [state, dispatch] = useReducer(counterReducer, { count: 0 });
302
+ *
303
+ * @example
304
+ * // Lazy initialization
305
+ * const [state, dispatch] = useReducer(reducer, initialCount, (count) => {
306
+ * return { count: count * 2 };
307
+ * });
308
+ *
309
+ * @example
310
+ * // With middleware and history
311
+ * const [state, dispatch] = useReducer(reducer, initialState, undefined, {
312
+ * middleware: [createLoggerMiddleware('Counter')],
313
+ * trackHistory: true,
314
+ * });
315
+ */
316
+ function useReducer(reducer, initialArg, init, options = {}) {
317
+ const component = currentlyRenderingComponent;
318
+ if (!component) {
319
+ throw new Error('[useReducer] Must be called within a component render phase');
320
+ }
321
+
322
+ if (typeof reducer !== 'function') {
323
+ throw new Error(
324
+ '[useReducer] First argument must be a reducer function. ' +
325
+ `Received: ${typeof reducer}`
326
+ );
327
+ }
328
+
329
+ // Initialize hooks array
330
+ if (!component._hooks) {
331
+ component._hooks = [];
332
+ }
333
+
334
+ const currentHookIndex = hookIndex;
335
+ let hook;
336
+
337
+ if (component._hooks[currentHookIndex]) {
338
+ // Re-render: use existing hook state
339
+ hook = component._hooks[currentHookIndex];
340
+ } else {
341
+ // First render: compute initial state
342
+ let initialState;
343
+
344
+ // Check for persisted state first
345
+ if (options.persistence) {
346
+ try {
347
+ const storage = window[options.persistence.storage || 'localStorage'];
348
+ const persisted = storage.getItem(options.persistence.key);
349
+ if (persisted !== null) {
350
+ initialState = (options.persistence.deserialize || JSON.parse)(persisted);
351
+ }
352
+ } catch (error) {
353
+ console.error('[useReducer] Load persistence error:', error);
354
+ }
355
+ }
356
+
357
+ // If no persisted state, compute initial state
358
+ if (initialState === undefined) {
359
+ if (typeof init === 'function') {
360
+ // Lazy initialization
361
+ try {
362
+ initialState = init(initialArg);
363
+ } catch (error) {
364
+ console.error('[useReducer] Lazy initializer error:', error);
365
+ initialState = initialArg;
366
+ }
367
+ } else {
368
+ initialState = initialArg;
369
+ }
370
+ }
371
+
372
+ hook = {
373
+ type: 'reducer',
374
+ state: initialState,
375
+ reducer,
376
+ _middleware: options.middleware || [],
377
+ _persistence: options.persistence || null,
378
+ _subscribers: new Set(),
379
+ _isDispatching: false,
380
+ _actionHistory: options.trackHistory ? [] : null,
381
+ _maxHistorySize: options.maxHistorySize || 100,
382
+ _undoStack: [],
383
+ _redoStack: [],
384
+ _optimisticState: null,
385
+ _isOptimistic: false,
386
+ };
387
+
388
+ component._hooks[currentHookIndex] = hook;
389
+ }
390
+
391
+ // Update reducer reference in case it changes
392
+ hook.reducer = reducer;
393
+
394
+ hookIndex++;
395
+
396
+ // ─── Dispatch Implementation ────────────────────────────────────────────
397
+
398
+ /**
399
+ * Dispatch an action to the reducer.
400
+ *
401
+ * @param {*} action - The action to dispatch (typically { type, payload })
402
+ */
403
+ const dispatch = function dispatch(action) {
404
+ if (!component._hooks) {
405
+ console.warn('[useReducer] Dispatch called on unmounted component');
406
+ return;
407
+ }
408
+
409
+ const targetHook = component._hooks[currentHookIndex];
410
+ if (!targetHook) {
411
+ console.warn('[useReducer] Hook index out of bounds');
412
+ return;
413
+ }
414
+
415
+ // Prevent dispatching during reducer execution
416
+ if (targetHook._isDispatching) {
417
+ throw new Error(
418
+ '[useReducer] Cannot dispatch an action while the reducer is executing. ' +
419
+ 'This is not allowed to prevent infinite loops.'
420
+ );
421
+ }
422
+
423
+ if (isBatchingDispatches) {
424
+ // Queue the dispatch for batch processing
425
+ if (!pendingDispatches.has(component)) {
426
+ pendingDispatches.set(component, []);
427
+ }
428
+ pendingDispatches.get(component).push({
429
+ hookIdx: currentHookIndex,
430
+ action,
431
+ reducer: targetHook.reducer,
432
+ });
433
+ return;
434
+ }
435
+
436
+ // Immediate dispatch
437
+ let processedAction = action;
438
+
439
+ // Apply middleware chain
440
+ if (targetHook._middleware && targetHook._middleware.length > 0) {
441
+ processedAction = applyMiddlewareChain(targetHook._middleware, action, targetHook.state);
442
+ if (processedAction === null) return; // Action was cancelled by middleware
443
+ }
444
+
445
+ // Run the reducer
446
+ targetHook._isDispatching = true;
447
+ let newState;
448
+ try {
449
+ newState = targetHook.reducer(targetHook.state, processedAction);
450
+ } catch (error) {
451
+ targetHook._isDispatching = false;
452
+ console.error('[useReducer] Reducer error:', error);
453
+ if (component._handleError) {
454
+ component._handleError(error);
455
+ }
456
+ return;
457
+ }
458
+ targetHook._isDispatching = false;
459
+
460
+ // State comparison
461
+ if (Object.is(targetHook.state, newState)) return;
462
+
463
+ const prevState = targetHook.state;
464
+ targetHook.state = newState;
465
+
466
+ // Track in action history
467
+ if (targetHook._actionHistory) {
468
+ targetHook._actionHistory.push({
469
+ action: processedAction,
470
+ prevState,
471
+ nextState: newState,
472
+ timestamp: Date.now(),
473
+ });
474
+ if (targetHook._actionHistory.length > targetHook._maxHistorySize) {
475
+ targetHook._actionHistory.shift();
476
+ }
477
+ }
478
+
479
+ // Notify subscribers
480
+ targetHook._subscribers.forEach((callback) => {
481
+ try {
482
+ callback(newState, prevState, processedAction);
483
+ } catch (error) {
484
+ console.error('[useReducer] Subscriber error:', error);
485
+ }
486
+ });
487
+
488
+ // Persist state
489
+ if (targetHook._persistence) {
490
+ try {
491
+ const storage = window[targetHook._persistence.storage || 'localStorage'];
492
+ const serialized = (targetHook._persistence.serialize || JSON.stringify)(newState);
493
+ storage.setItem(targetHook._persistence.key, serialized);
494
+ } catch (error) {
495
+ console.error('[useReducer] Persistence error:', error);
496
+ }
497
+ }
498
+
499
+ // Schedule re-render
500
+ if (scheduleUpdateCallback) {
501
+ scheduleUpdateCallback(component);
502
+ } else if (component._scheduleReRender) {
503
+ component._scheduleReRender();
504
+ }
505
+ };
506
+
507
+ // ─── Extended Dispatch Methods ──────────────────────────────────────────
508
+
509
+ /**
510
+ * Get the current state without triggering a re-render.
511
+ * @returns {*} Current state
512
+ */
513
+ dispatch.getState = function getState() {
514
+ const targetHook = component._hooks?.[currentHookIndex];
515
+ return targetHook ? targetHook.state : undefined;
516
+ };
517
+
518
+ /**
519
+ * Subscribe to state changes from this reducer.
520
+ * @param {Function} callback - Called with (newState, prevState, action)
521
+ * @returns {Function} Unsubscribe function
522
+ */
523
+ dispatch.subscribe = function subscribe(callback) {
524
+ const targetHook = component._hooks?.[currentHookIndex];
525
+ if (!targetHook) return function noop() {};
526
+
527
+ targetHook._subscribers.add(callback);
528
+ return function unsubscribe() {
529
+ targetHook._subscribers.delete(callback);
530
+ };
531
+ };
532
+
533
+ /**
534
+ * Add middleware to this reducer's middleware chain.
535
+ * @param {Function} middleware - Middleware function
536
+ * @returns {Function} Removal function
537
+ */
538
+ dispatch.addMiddleware = function addMiddleware(middleware) {
539
+ const targetHook = component._hooks?.[currentHookIndex];
540
+ if (!targetHook) return function noop() {};
541
+
542
+ targetHook._middleware.push(middleware);
543
+ return function removeMiddleware() {
544
+ const idx = targetHook._middleware.indexOf(middleware);
545
+ if (idx !== -1) targetHook._middleware.splice(idx, 1);
546
+ };
547
+ };
548
+
549
+ /**
550
+ * Dispatch multiple actions at once (batched).
551
+ * @param {Array} actions - Array of actions
552
+ */
553
+ dispatch.batch = function batch(actions) {
554
+ if (!Array.isArray(actions)) {
555
+ console.warn('[useReducer] dispatch.batch expects an array of actions');
556
+ return;
557
+ }
558
+ startBatchDispatches();
559
+ try {
560
+ actions.forEach((action) => dispatch(action));
561
+ } finally {
562
+ endBatchDispatches();
563
+ }
564
+ };
565
+
566
+ /**
567
+ * Reset state to the initial value.
568
+ */
569
+ dispatch.reset = function reset() {
570
+ let resetState;
571
+ if (typeof init === 'function') {
572
+ resetState = init(initialArg);
573
+ } else {
574
+ resetState = initialArg;
575
+ }
576
+ dispatch({ type: '__RESET__', payload: resetState });
577
+ };
578
+
579
+ /**
580
+ * Get the action history (if tracking is enabled).
581
+ * @returns {Array|null}
582
+ */
583
+ dispatch.getHistory = function getHistory() {
584
+ const targetHook = component._hooks?.[currentHookIndex];
585
+ return targetHook?._actionHistory || null;
586
+ };
587
+
588
+ // ─── Undo/Redo Support ──────────────────────────────────────────────────
589
+
590
+ /**
591
+ * Undo the last action. Pushes the current state to the redo stack.
592
+ * @returns {boolean} True if undo was successful
593
+ */
594
+ dispatch.undo = function undo() {
595
+ const targetHook = component._hooks?.[currentHookIndex];
596
+ if (!targetHook || targetHook._undoStack.length === 0) return false;
597
+
598
+ const prev = targetHook._undoStack.pop();
599
+ targetHook._redoStack.push(targetHook.state);
600
+ targetHook.state = prev;
601
+
602
+ if (component._scheduleReRender) component._scheduleReRender();
603
+ return true;
604
+ };
605
+
606
+ /**
607
+ * Redo the last undone action.
608
+ * @returns {boolean} True if redo was successful
609
+ */
610
+ dispatch.redo = function redo() {
611
+ const targetHook = component._hooks?.[currentHookIndex];
612
+ if (!targetHook || targetHook._redoStack.length === 0) return false;
613
+
614
+ const next = targetHook._redoStack.pop();
615
+ targetHook._undoStack.push(targetHook.state);
616
+ targetHook.state = next;
617
+
618
+ if (component._scheduleReRender) component._scheduleReRender();
619
+ return true;
620
+ };
621
+
622
+ /**
623
+ * Check if undo is available.
624
+ * @returns {boolean}
625
+ */
626
+ dispatch.canUndo = function canUndo() {
627
+ const targetHook = component._hooks?.[currentHookIndex];
628
+ return targetHook ? targetHook._undoStack.length > 0 : false;
629
+ };
630
+
631
+ /**
632
+ * Check if redo is available.
633
+ * @returns {boolean}
634
+ */
635
+ dispatch.canRedo = function canRedo() {
636
+ const targetHook = component._hooks?.[currentHookIndex];
637
+ return targetHook ? targetHook._redoStack.length > 0 : false;
638
+ };
639
+
640
+ // ─── Optimistic Updates ─────────────────────────────────────────────────
641
+
642
+ /**
643
+ * Dispatch an optimistic update. The state is immediately updated
644
+ * with the optimistic value, then rolled back if the async action fails.
645
+ *
646
+ * @param {*} optimisticState - The optimistic state to apply immediately
647
+ * @param {Promise} asyncAction - Promise that resolves to an action
648
+ */
649
+ dispatch.optimistic = function optimistic(optimisticState, asyncAction) {
650
+ const targetHook = component._hooks?.[currentHookIndex];
651
+ if (!targetHook) return;
652
+
653
+ // Save current state for rollback
654
+ const realState = targetHook.state;
655
+ targetHook._undoStack.push(realState);
656
+ targetHook._optimisticState = realState;
657
+ targetHook._isOptimistic = true;
658
+
659
+ // Apply optimistic state immediately
660
+ targetHook.state = optimisticState;
661
+ if (component._scheduleReRender) component._scheduleReRender();
662
+
663
+ // Handle async resolution
664
+ if (asyncAction && typeof asyncAction.then === 'function') {
665
+ asyncAction.then(
666
+ function resolve(action) {
667
+ targetHook._isOptimistic = false;
668
+ targetHook._optimisticState = null;
669
+ dispatch(action);
670
+ },
671
+ function reject(error) {
672
+ // Roll back to the real state
673
+ targetHook._isOptimistic = false;
674
+ targetHook.state = targetHook._optimisticState || realState;
675
+ targetHook._optimisticState = null;
676
+ if (component._scheduleReRender) component._scheduleReRender();
677
+ console.error('[useReducer] Optimistic update failed:', error);
678
+ }
679
+ );
680
+ }
681
+ };
682
+
683
+ return [hook.state, dispatch];
684
+ }
685
+
686
+ // ─── Cleanup ──────────────────────────────────────────────────────────────────
687
+
688
+ /**
689
+ * Clean up reducer state when a component unmounts.
690
+ * @param {Object} component - The unmounting component
691
+ */
692
+ function cleanupReducerState(component) {
693
+ if (!component._hooks) return;
694
+
695
+ component._hooks.forEach((hook) => {
696
+ if (hook && hook.type === 'reducer') {
697
+ hook._subscribers.clear();
698
+ hook._middleware = [];
699
+ hook._isDispatching = false;
700
+ if (hook._actionHistory) {
701
+ hook._actionHistory = null;
702
+ }
703
+ hook._undoStack = [];
704
+ hook._redoStack = [];
705
+ }
706
+ });
707
+
708
+ pendingDispatches.delete(component);
709
+ }
710
+
711
+ // ─── Exports ──────────────────────────────────────────────────────────────────
712
+
713
+ module.exports = {
714
+ useReducer,
715
+ setCurrentComponent,
716
+ resetHookIndex,
717
+ setScheduleUpdateCallback,
718
+ startBatchDispatches,
719
+ endBatchDispatches,
720
+ batchDispatches,
721
+ createLoggerMiddleware,
722
+ createValidationMiddleware,
723
+ createTransformMiddleware,
724
+ createThrottleMiddleware,
725
+ createActionCreators,
726
+ createAction,
727
+ applyMiddlewareChain,
728
+ cleanupReducerState,
729
+ };