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.
- package/LICENSE +21 -0
- package/dist/elementdrawing.min.js +3 -0
- package/dist/elementdrawing.min.js.LICENSE.txt +8 -0
- package/dist/elementdrawing.min.js.map +1 -0
- package/dist/index.html +1 -0
- package/package.json +127 -0
- package/src/core/bridge.h +855 -0
- package/src/core/diff.c +900 -0
- package/src/core/element.c +1078 -0
- package/src/core/event.c +813 -0
- package/src/core/fiber.c +1027 -0
- package/src/core/hooks.c +919 -0
- package/src/core/renderer.c +963 -0
- package/src/core/scheduler.c +702 -0
- package/src/core/state.c +803 -0
- package/src/css/animations.css +779 -0
- package/src/css/base.css +615 -0
- package/src/css/components.css +1311 -0
- package/src/css/tailwind.css +370 -0
- package/src/css/themes.css +517 -0
- package/src/css/utilities.css +475 -0
- package/src/index.js +746 -0
- package/src/js/animation.js +655 -0
- package/src/js/dom.js +665 -0
- package/src/js/events.js +585 -0
- package/src/js/http.js +446 -0
- package/src/js/index.js +26 -0
- package/src/js/router.js +483 -0
- package/src/js/store.js +539 -0
- package/src/js/utils.js +593 -0
- package/src/js/validator.js +529 -0
- package/src/jsx/components/Accordion.jsx +210 -0
- package/src/jsx/components/Alert.jsx +169 -0
- package/src/jsx/components/Avatar.jsx +214 -0
- package/src/jsx/components/Badge.jsx +136 -0
- package/src/jsx/components/Breadcrumb.jsx +200 -0
- package/src/jsx/components/Button.jsx +188 -0
- package/src/jsx/components/Card.jsx +192 -0
- package/src/jsx/components/Carousel.jsx +278 -0
- package/src/jsx/components/Checkbox.jsx +215 -0
- package/src/jsx/components/Dialog.jsx +242 -0
- package/src/jsx/components/Drawer.jsx +190 -0
- package/src/jsx/components/Dropdown.jsx +268 -0
- package/src/jsx/components/Form.jsx +274 -0
- package/src/jsx/components/Input.jsx +285 -0
- package/src/jsx/components/Menu.jsx +276 -0
- package/src/jsx/components/Modal.jsx +274 -0
- package/src/jsx/components/Navbar.jsx +292 -0
- package/src/jsx/components/Pagination.jsx +268 -0
- package/src/jsx/components/Progress.jsx +252 -0
- package/src/jsx/components/Radio.jsx +208 -0
- package/src/jsx/components/Select.jsx +397 -0
- package/src/jsx/components/Sidebar.jsx +250 -0
- package/src/jsx/components/Slider.jsx +310 -0
- package/src/jsx/components/Spinner.jsx +198 -0
- package/src/jsx/components/Switch.jsx +201 -0
- package/src/jsx/components/Table.jsx +332 -0
- package/src/jsx/components/Tabs.jsx +227 -0
- package/src/jsx/components/Textarea.jsx +212 -0
- package/src/jsx/components/Toast.jsx +270 -0
- package/src/jsx/components/Tooltip.jsx +178 -0
- package/src/jsx/components/Typography.jsx +299 -0
- package/src/jsx/components/index.jsx +70 -0
- package/src/jsx/core/element.js +3 -0
- package/src/jsx/hooks/index.js +356 -0
- package/src/jsx/hooks/useCallback.js +472 -0
- package/src/jsx/hooks/useContext.js +586 -0
- package/src/jsx/hooks/useEffect.js +704 -0
- package/src/jsx/hooks/useLayoutEffect.js +508 -0
- package/src/jsx/hooks/useMemo.js +689 -0
- package/src/jsx/hooks/useReducer.js +729 -0
- package/src/jsx/hooks/useRef.js +542 -0
- package/src/jsx/hooks/useState.js +854 -0
- package/src/jsx/runtime/commit.js +903 -0
- package/src/jsx/runtime/createElement.js +860 -0
- package/src/jsx/runtime/index.js +356 -0
- package/src/jsx/runtime/reconcile.js +687 -0
- 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
|
+
};
|