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,472 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useCallback Hook Implementation
|
|
3
|
+
* ElementDrawing Framework - Callback memoization with dependency comparison,
|
|
4
|
+
* stable references, debouncing, throttling, profiling, and callback composition.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
// ─── Internal State ───────────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
let currentlyRenderingComponent = null;
|
|
12
|
+
let hookIndex = 0;
|
|
13
|
+
|
|
14
|
+
// ─── Callback Profiling ───────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
const callbackProfiles = new WeakMap();
|
|
17
|
+
let profilingEnabled = false;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Enable or disable callback profiling.
|
|
21
|
+
* @param {boolean} enabled
|
|
22
|
+
*/
|
|
23
|
+
function setCallbackProfiling(enabled) {
|
|
24
|
+
profilingEnabled = enabled;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get profiling data for a component's callbacks.
|
|
29
|
+
* @param {Object} component
|
|
30
|
+
* @returns {Object|null}
|
|
31
|
+
*/
|
|
32
|
+
function getCallbackProfile(component) {
|
|
33
|
+
return callbackProfiles.get(component) || null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ─── Dependency Comparison ────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Compare two dependency arrays for shallow equality.
|
|
40
|
+
* @param {Array|null} prevDeps
|
|
41
|
+
* @param {Array|null} nextDeps
|
|
42
|
+
* @returns {boolean}
|
|
43
|
+
*/
|
|
44
|
+
function areDepsEqual(prevDeps, nextDeps) {
|
|
45
|
+
if (prevDeps === null || nextDeps === null) return false;
|
|
46
|
+
if (prevDeps.length !== nextDeps.length) return false;
|
|
47
|
+
for (let i = 0; i < prevDeps.length; i++) {
|
|
48
|
+
if (Object.is(prevDeps[i], nextDeps[i])) continue;
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Deep dependency comparison for complex dependency values.
|
|
56
|
+
*/
|
|
57
|
+
function areDepsDeepEqual(prevDeps, nextDeps) {
|
|
58
|
+
if (prevDeps === null || nextDeps === null) return false;
|
|
59
|
+
if (prevDeps.length !== nextDeps.length) return false;
|
|
60
|
+
for (let i = 0; i < prevDeps.length; i++) {
|
|
61
|
+
if (deepEqualVal(prevDeps[i], nextDeps[i])) continue;
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function deepEqualVal(a, b) {
|
|
68
|
+
if (Object.is(a, b)) return true;
|
|
69
|
+
if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) return false;
|
|
70
|
+
const keysA = Object.keys(a);
|
|
71
|
+
const keysB = Object.keys(b);
|
|
72
|
+
if (keysA.length !== keysB.length) return false;
|
|
73
|
+
for (const key of keysA) {
|
|
74
|
+
if (!deepEqualVal(a[key], b[key])) return false;
|
|
75
|
+
}
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Validate dependency array.
|
|
81
|
+
* @param {*} deps
|
|
82
|
+
*/
|
|
83
|
+
function validateDeps(deps) {
|
|
84
|
+
if (deps !== undefined && !Array.isArray(deps)) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
'[useCallback] Dependencies must be an array or undefined. ' +
|
|
87
|
+
`Received: ${typeof deps}`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ─── Hook Context Management ──────────────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
function setCurrentComponent(component) {
|
|
95
|
+
currentlyRenderingComponent = component;
|
|
96
|
+
hookIndex = 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function resetHookIndex() {
|
|
100
|
+
hookIndex = 0;
|
|
101
|
+
currentlyRenderingComponent = null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ─── useCallback Hook ─────────────────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* useCallback - Memoize a callback function based on dependencies.
|
|
108
|
+
* Returns a stable reference that only changes when dependencies change.
|
|
109
|
+
*
|
|
110
|
+
* @param {Function} callback - The callback function to memoize
|
|
111
|
+
* @param {Array} deps - Dependency array; callback updates when deps change
|
|
112
|
+
* @returns {Function} Memoized callback with stable reference
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* const handleClick = useCallback((event) => {
|
|
116
|
+
* setCount(prev => prev + 1);
|
|
117
|
+
* }, [setCount]);
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* // Preventing unnecessary child re-renders
|
|
121
|
+
* const handleSubmit = useCallback((data) => {
|
|
122
|
+
* onSubmit(data);
|
|
123
|
+
* }, [onSubmit]);
|
|
124
|
+
*
|
|
125
|
+
* <ChildComponent onSubmit={handleSubmit} />
|
|
126
|
+
*/
|
|
127
|
+
function useCallback(callback, deps) {
|
|
128
|
+
const component = currentlyRenderingComponent;
|
|
129
|
+
if (!component) {
|
|
130
|
+
throw new Error('[useCallback] Must be called within a component render phase');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (typeof callback !== 'function') {
|
|
134
|
+
throw new Error(
|
|
135
|
+
'[useCallback] First argument must be a function. ' +
|
|
136
|
+
`Received: ${typeof callback}`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
validateDeps(deps);
|
|
141
|
+
|
|
142
|
+
// Initialize hooks array
|
|
143
|
+
if (!component._hooks) {
|
|
144
|
+
component._hooks = [];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const currentHookIndex = hookIndex;
|
|
148
|
+
const resolvedDeps = deps !== undefined ? deps : null;
|
|
149
|
+
let hook;
|
|
150
|
+
|
|
151
|
+
if (component._hooks[currentHookIndex]) {
|
|
152
|
+
// Re-render: check if dependencies changed
|
|
153
|
+
hook = component._hooks[currentHookIndex];
|
|
154
|
+
|
|
155
|
+
const prevDeps = hook.deps;
|
|
156
|
+
const depsChanged = !areDepsEqual(prevDeps, resolvedDeps);
|
|
157
|
+
|
|
158
|
+
if (depsChanged) {
|
|
159
|
+
// Dependencies changed - update the callback
|
|
160
|
+
hook.callback = callback;
|
|
161
|
+
hook.deps = resolvedDeps;
|
|
162
|
+
hook.memoizedCallback = createStableCallback(callback, component, currentHookIndex);
|
|
163
|
+
hook._updateCount++;
|
|
164
|
+
}
|
|
165
|
+
// If deps unchanged, keep the existing memoizedCallback
|
|
166
|
+
} else {
|
|
167
|
+
// First render: create the hook
|
|
168
|
+
hook = {
|
|
169
|
+
type: 'callback',
|
|
170
|
+
callback,
|
|
171
|
+
deps: resolvedDeps,
|
|
172
|
+
memoizedCallback: null,
|
|
173
|
+
_callCount: 0,
|
|
174
|
+
_lastCalledAt: null,
|
|
175
|
+
_updateCount: 0,
|
|
176
|
+
_createdAt: Date.now(),
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
hook.memoizedCallback = createStableCallback(callback, component, currentHookIndex);
|
|
180
|
+
component._hooks[currentHookIndex] = hook;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Update profiling data
|
|
184
|
+
if (profilingEnabled) {
|
|
185
|
+
if (!callbackProfiles.has(component)) {
|
|
186
|
+
callbackProfiles.set(component, {});
|
|
187
|
+
}
|
|
188
|
+
const profile = callbackProfiles.get(component);
|
|
189
|
+
profile[currentHookIndex] = {
|
|
190
|
+
callCount: hook._callCount,
|
|
191
|
+
updateCount: hook._updateCount,
|
|
192
|
+
lastCalledAt: hook._lastCalledAt,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
hookIndex++;
|
|
197
|
+
return hook.memoizedCallback;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ─── Stable Callback Creation ─────────────────────────────────────────────────
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Create a stable callback wrapper that delegates to the current callback.
|
|
204
|
+
* The wrapper function reference stays the same across renders while
|
|
205
|
+
* always calling the latest callback version.
|
|
206
|
+
*
|
|
207
|
+
* @param {Function} initialCallback - The initial callback
|
|
208
|
+
* @param {Object} component - The owning component
|
|
209
|
+
* @param {number} hookIdx - The hook index
|
|
210
|
+
* @returns {Function} Stable callback reference
|
|
211
|
+
*/
|
|
212
|
+
function createStableCallback(initialCallback, component, hookIdx) {
|
|
213
|
+
// The stable wrapper always reads the latest callback from the hook
|
|
214
|
+
const stableCallback = function stableCallbackWrapper() {
|
|
215
|
+
const hook = component._hooks?.[hookIdx];
|
|
216
|
+
if (!hook) {
|
|
217
|
+
console.warn('[useCallback] Callback called after component unmount');
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const currentCallback = hook.callback;
|
|
222
|
+
hook._callCount++;
|
|
223
|
+
hook._lastCalledAt = Date.now();
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
return currentCallback.apply(this, arguments);
|
|
227
|
+
} catch (error) {
|
|
228
|
+
console.error('[useCallback] Callback execution error:', error);
|
|
229
|
+
if (component._handleError) {
|
|
230
|
+
component._handleError(error);
|
|
231
|
+
}
|
|
232
|
+
throw error;
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// Attach metadata for debugging and introspection
|
|
237
|
+
Object.defineProperty(stableCallback, '_isStable', { value: true });
|
|
238
|
+
Object.defineProperty(stableCallback, '_hookIndex', { value: hookIdx });
|
|
239
|
+
Object.defineProperty(stableCallback, '_component', { value: component });
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Get the number of times this callback has been invoked.
|
|
243
|
+
* @returns {number}
|
|
244
|
+
*/
|
|
245
|
+
stableCallback.getCallCount = function getCallCount() {
|
|
246
|
+
const hook = component._hooks?.[hookIdx];
|
|
247
|
+
return hook ? hook._callCount : 0;
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Get the timestamp of the last invocation.
|
|
252
|
+
* @returns {number|null}
|
|
253
|
+
*/
|
|
254
|
+
stableCallback.getLastCalledAt = function getLastCalledAt() {
|
|
255
|
+
const hook = component._hooks?.[hookIdx];
|
|
256
|
+
return hook ? hook._lastCalledAt : null;
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Get the current underlying callback without calling it.
|
|
261
|
+
* @returns {Function}
|
|
262
|
+
*/
|
|
263
|
+
stableCallback.getCurrentCallback = function getCurrentCallback() {
|
|
264
|
+
const hook = component._hooks?.[hookIdx];
|
|
265
|
+
return hook ? hook.callback : null;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Check if this is the same stable reference as another callback.
|
|
270
|
+
* @param {Function} other - Another callback to compare
|
|
271
|
+
* @returns {boolean}
|
|
272
|
+
*/
|
|
273
|
+
stableCallback.isSameRef = function isSameRef(other) {
|
|
274
|
+
return other === stableCallback;
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
return stableCallback;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ─── Extended useCallback Features ────────────────────────────────────────────
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Create a debounced version of useCallback.
|
|
284
|
+
* The returned callback will only execute after the specified delay
|
|
285
|
+
* since the last invocation.
|
|
286
|
+
*
|
|
287
|
+
* @param {Function} callback - The callback to debounce
|
|
288
|
+
* @param {Array} deps - Dependency array
|
|
289
|
+
* @param {number} delay - Debounce delay in milliseconds
|
|
290
|
+
* @returns {Function} Debounced memoized callback
|
|
291
|
+
*/
|
|
292
|
+
function useDebouncedCallback(callback, deps, delay) {
|
|
293
|
+
const memoizedCallback = useCallback(callback, deps);
|
|
294
|
+
|
|
295
|
+
// Simple debounce wrapper
|
|
296
|
+
let timeoutId = null;
|
|
297
|
+
const debouncedCallback = function debouncedWrapper() {
|
|
298
|
+
const args = arguments;
|
|
299
|
+
const context = this;
|
|
300
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
301
|
+
timeoutId = setTimeout(() => {
|
|
302
|
+
memoizedCallback.apply(context, args);
|
|
303
|
+
}, delay);
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
debouncedCallback.cancel = function cancel() {
|
|
307
|
+
if (timeoutId) {
|
|
308
|
+
clearTimeout(timeoutId);
|
|
309
|
+
timeoutId = null;
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
debouncedCallback.flush = function flush() {
|
|
314
|
+
if (timeoutId) {
|
|
315
|
+
clearTimeout(timeoutId);
|
|
316
|
+
timeoutId = null;
|
|
317
|
+
memoizedCallback();
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
return debouncedCallback;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Create a throttled version of useCallback.
|
|
326
|
+
* The returned callback will execute at most once per specified interval.
|
|
327
|
+
*
|
|
328
|
+
* @param {Function} callback - The callback to throttle
|
|
329
|
+
* @param {Array} deps - Dependency array
|
|
330
|
+
* @param {number} interval - Throttle interval in milliseconds
|
|
331
|
+
* @returns {Function} Throttled memoized callback
|
|
332
|
+
*/
|
|
333
|
+
function useThrottledCallback(callback, deps, interval) {
|
|
334
|
+
const memoizedCallback = useCallback(callback, deps);
|
|
335
|
+
|
|
336
|
+
let lastExecTime = 0;
|
|
337
|
+
let timeoutId = null;
|
|
338
|
+
|
|
339
|
+
const throttledCallback = function throttledWrapper() {
|
|
340
|
+
const args = arguments;
|
|
341
|
+
const context = this;
|
|
342
|
+
const now = Date.now();
|
|
343
|
+
const elapsed = now - lastExecTime;
|
|
344
|
+
|
|
345
|
+
if (elapsed >= interval) {
|
|
346
|
+
lastExecTime = now;
|
|
347
|
+
memoizedCallback.apply(context, args);
|
|
348
|
+
} else if (!timeoutId) {
|
|
349
|
+
timeoutId = setTimeout(() => {
|
|
350
|
+
lastExecTime = Date.now();
|
|
351
|
+
timeoutId = null;
|
|
352
|
+
memoizedCallback.apply(context, args);
|
|
353
|
+
}, interval - elapsed);
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
throttledCallback.cancel = function cancel() {
|
|
358
|
+
if (timeoutId) {
|
|
359
|
+
clearTimeout(timeoutId);
|
|
360
|
+
timeoutId = null;
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
return throttledCallback;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Create a callback that only executes once and then becomes a no-op.
|
|
369
|
+
*
|
|
370
|
+
* @param {Function} callback - The callback
|
|
371
|
+
* @param {Array} deps - Dependency array
|
|
372
|
+
* @returns {Function} One-time callback
|
|
373
|
+
*/
|
|
374
|
+
function useOnceCallback(callback, deps) {
|
|
375
|
+
let called = false;
|
|
376
|
+
return useCallback(function onceWrapper() {
|
|
377
|
+
if (called) return;
|
|
378
|
+
called = true;
|
|
379
|
+
return callback.apply(this, arguments);
|
|
380
|
+
}, deps);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Create a callback with deep dependency comparison.
|
|
385
|
+
* Useful when dependencies are objects or arrays that may be recreated.
|
|
386
|
+
*
|
|
387
|
+
* @param {Function} callback - The callback function
|
|
388
|
+
* @param {Array} deps - Dependency array (compared deeply)
|
|
389
|
+
* @returns {Function} Memoized callback
|
|
390
|
+
*/
|
|
391
|
+
function useDeepCompareCallback(callback, deps) {
|
|
392
|
+
const component = currentlyRenderingComponent;
|
|
393
|
+
if (!component) {
|
|
394
|
+
throw new Error('[useDeepCompareCallback] Must be called within a component render phase');
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (!component._hooks) {
|
|
398
|
+
component._hooks = [];
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const currentHookIndex = hookIndex;
|
|
402
|
+
const resolvedDeps = deps !== undefined ? deps : null;
|
|
403
|
+
let hook;
|
|
404
|
+
|
|
405
|
+
if (component._hooks[currentHookIndex]) {
|
|
406
|
+
hook = component._hooks[currentHookIndex];
|
|
407
|
+
|
|
408
|
+
const prevDeps = hook.deps;
|
|
409
|
+
const depsChanged = !areDepsDeepEqual(prevDeps, resolvedDeps);
|
|
410
|
+
|
|
411
|
+
if (depsChanged) {
|
|
412
|
+
hook.callback = callback;
|
|
413
|
+
hook.deps = resolvedDeps;
|
|
414
|
+
hook.memoizedCallback = createStableCallback(callback, component, currentHookIndex);
|
|
415
|
+
hook._updateCount++;
|
|
416
|
+
}
|
|
417
|
+
} else {
|
|
418
|
+
hook = {
|
|
419
|
+
type: 'callback',
|
|
420
|
+
callback,
|
|
421
|
+
deps: resolvedDeps,
|
|
422
|
+
memoizedCallback: null,
|
|
423
|
+
_callCount: 0,
|
|
424
|
+
_lastCalledAt: null,
|
|
425
|
+
_updateCount: 0,
|
|
426
|
+
_createdAt: Date.now(),
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
hook.memoizedCallback = createStableCallback(callback, component, currentHookIndex);
|
|
430
|
+
component._hooks[currentHookIndex] = hook;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
hookIndex++;
|
|
434
|
+
return hook.memoizedCallback;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Compose multiple callbacks into a single callback.
|
|
439
|
+
* Each callback receives the same arguments and results are collected.
|
|
440
|
+
*
|
|
441
|
+
* @param {Function[]} callbacks - Array of callbacks to compose
|
|
442
|
+
* @param {Array} deps - Dependency array
|
|
443
|
+
* @returns {Function} Composed callback
|
|
444
|
+
*/
|
|
445
|
+
function useComposedCallback(callbacks, deps) {
|
|
446
|
+
return useCallback(function composedCallback() {
|
|
447
|
+
const args = arguments;
|
|
448
|
+
return callbacks.map((cb) => {
|
|
449
|
+
if (typeof cb === 'function') {
|
|
450
|
+
return cb.apply(this, args);
|
|
451
|
+
}
|
|
452
|
+
return undefined;
|
|
453
|
+
});
|
|
454
|
+
}, deps);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// ─── Exports ──────────────────────────────────────────────────────────────────
|
|
458
|
+
|
|
459
|
+
module.exports = {
|
|
460
|
+
useCallback,
|
|
461
|
+
useDebouncedCallback,
|
|
462
|
+
useThrottledCallback,
|
|
463
|
+
useOnceCallback,
|
|
464
|
+
useDeepCompareCallback,
|
|
465
|
+
useComposedCallback,
|
|
466
|
+
setCurrentComponent,
|
|
467
|
+
resetHookIndex,
|
|
468
|
+
areDepsEqual,
|
|
469
|
+
areDepsDeepEqual,
|
|
470
|
+
setCallbackProfiling,
|
|
471
|
+
getCallbackProfile,
|
|
472
|
+
};
|