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,508 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useLayoutEffect Hook Implementation
|
|
3
|
+
* ElementDrawing Framework - Synchronous effect after DOM mutations,
|
|
4
|
+
* executed during the commit phase before the browser paints.
|
|
5
|
+
* Includes priority-based ordering, isomorphic layout effects,
|
|
6
|
+
* mutation observation, resize observation, and batch processing.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
// ─── Internal State ───────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
let currentlyRenderingComponent = null;
|
|
14
|
+
let hookIndex = 0;
|
|
15
|
+
const layoutEffectQueue = [];
|
|
16
|
+
let isFlushingLayoutEffects = false;
|
|
17
|
+
|
|
18
|
+
// ─── Layout Effect Priority ───────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Priority levels for layout effects.
|
|
22
|
+
* Lower number = higher priority = executes first.
|
|
23
|
+
*/
|
|
24
|
+
const LAYOUT_PRIORITY = {
|
|
25
|
+
HIGHEST: 0, // Insertion effects, critical DOM measurements
|
|
26
|
+
HIGH: 1, // Layout calculations
|
|
27
|
+
NORMAL: 2, // Standard layout effects
|
|
28
|
+
LOW: 3, // Non-critical layout adjustments
|
|
29
|
+
LOWEST: 4, // Post-layout cleanup
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// ─── Dependency Comparison ────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Compare two dependency arrays for shallow equality.
|
|
36
|
+
* @param {Array|null} prevDeps
|
|
37
|
+
* @param {Array|null} nextDeps
|
|
38
|
+
* @returns {boolean}
|
|
39
|
+
*/
|
|
40
|
+
function areDepsEqual(prevDeps, nextDeps) {
|
|
41
|
+
if (prevDeps === null || nextDeps === null) return false;
|
|
42
|
+
if (prevDeps.length !== nextDeps.length) return false;
|
|
43
|
+
for (let i = 0; i < prevDeps.length; i++) {
|
|
44
|
+
if (Object.is(prevDeps[i], nextDeps[i])) continue;
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Validate dependency array.
|
|
52
|
+
* @param {*} deps
|
|
53
|
+
*/
|
|
54
|
+
function validateDeps(deps) {
|
|
55
|
+
if (deps !== undefined && !Array.isArray(deps)) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
'[useLayoutEffect] Dependencies must be an array or undefined. ' +
|
|
58
|
+
`Received: ${typeof deps}`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ─── Effect Execution ─────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Execute a layout effect, capturing its cleanup function.
|
|
67
|
+
* @param {Object} effect - Effect object
|
|
68
|
+
*/
|
|
69
|
+
function executeLayoutEffect(effect) {
|
|
70
|
+
try {
|
|
71
|
+
const cleanup = effect.create();
|
|
72
|
+
if (cleanup !== undefined && typeof cleanup !== 'function') {
|
|
73
|
+
console.warn(
|
|
74
|
+
'[useLayoutEffect] Effect cleanup must be a function or undefined. ' +
|
|
75
|
+
`Received: ${typeof cleanup}`
|
|
76
|
+
);
|
|
77
|
+
effect.cleanup = null;
|
|
78
|
+
} else {
|
|
79
|
+
effect.cleanup = cleanup;
|
|
80
|
+
}
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error('[useLayoutEffect] Effect execution error:', error);
|
|
83
|
+
effect.cleanup = null;
|
|
84
|
+
if (effect.component && effect.component._handleError) {
|
|
85
|
+
effect.component._handleError(error);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Run cleanup for a layout effect.
|
|
92
|
+
* @param {Object} effect
|
|
93
|
+
*/
|
|
94
|
+
function runLayoutCleanup(effect) {
|
|
95
|
+
if (typeof effect.cleanup === 'function') {
|
|
96
|
+
try {
|
|
97
|
+
effect.cleanup();
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.error('[useLayoutEffect] Cleanup error:', error);
|
|
100
|
+
if (effect.component && effect.component._handleError) {
|
|
101
|
+
effect.component._handleError(error);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
effect.cleanup = null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ─── Layout Effect Queue Management ───────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Flush all queued layout effects synchronously.
|
|
112
|
+
* Called during the commit phase, before the browser paints.
|
|
113
|
+
* Effects are sorted by priority before execution.
|
|
114
|
+
*/
|
|
115
|
+
function flushLayoutEffects() {
|
|
116
|
+
if (isFlushingLayoutEffects) return;
|
|
117
|
+
isFlushingLayoutEffects = true;
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
// Sort by priority (lower = higher priority = executes first)
|
|
121
|
+
const sortedEffects = layoutEffectQueue.slice().sort((a, b) => {
|
|
122
|
+
return (a._priority || LAYOUT_PRIORITY.NORMAL) - (b._priority || LAYOUT_PRIORITY.NORMAL);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Clear the queue before execution to prevent double-execution
|
|
126
|
+
layoutEffectQueue.length = 0;
|
|
127
|
+
|
|
128
|
+
while (sortedEffects.length > 0) {
|
|
129
|
+
const effect = sortedEffects.shift();
|
|
130
|
+
|
|
131
|
+
// Run cleanup from previous render
|
|
132
|
+
runLayoutCleanup(effect);
|
|
133
|
+
|
|
134
|
+
// Execute the new layout effect
|
|
135
|
+
executeLayoutEffect(effect);
|
|
136
|
+
|
|
137
|
+
// Track execution time for profiling
|
|
138
|
+
effect._executedAt = Date.now();
|
|
139
|
+
}
|
|
140
|
+
} finally {
|
|
141
|
+
isFlushingLayoutEffects = false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Check if there are pending layout effects.
|
|
147
|
+
* @returns {boolean}
|
|
148
|
+
*/
|
|
149
|
+
function hasPendingLayoutEffects() {
|
|
150
|
+
return layoutEffectQueue.length > 0;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get the count of pending layout effects.
|
|
155
|
+
* @returns {number}
|
|
156
|
+
*/
|
|
157
|
+
function getPendingLayoutEffectCount() {
|
|
158
|
+
return layoutEffectQueue.length;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Get pending layout effects grouped by priority.
|
|
163
|
+
* @returns {Object}
|
|
164
|
+
*/
|
|
165
|
+
function getPendingLayoutEffectsByPriority() {
|
|
166
|
+
const grouped = {};
|
|
167
|
+
layoutEffectQueue.forEach((effect) => {
|
|
168
|
+
const priority = effect._priority || LAYOUT_PRIORITY.NORMAL;
|
|
169
|
+
if (!grouped[priority]) grouped[priority] = 0;
|
|
170
|
+
grouped[priority]++;
|
|
171
|
+
});
|
|
172
|
+
return grouped;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ─── Hook Context Management ──────────────────────────────────────────────────
|
|
176
|
+
|
|
177
|
+
function setCurrentComponent(component) {
|
|
178
|
+
currentlyRenderingComponent = component;
|
|
179
|
+
hookIndex = 0;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function resetHookIndex() {
|
|
183
|
+
hookIndex = 0;
|
|
184
|
+
currentlyRenderingComponent = null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ─── useLayoutEffect Hook ─────────────────────────────────────────────────────
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* useLayoutEffect - Synchronous effect that runs after DOM mutations
|
|
191
|
+
* but before the browser paints. Use for:
|
|
192
|
+
* - DOM measurements (getBoundingClientRect, etc.)
|
|
193
|
+
* - Synchronous DOM mutations that must happen before paint
|
|
194
|
+
* - Preventing visual flickering
|
|
195
|
+
*
|
|
196
|
+
* @param {Function} create - Effect callback; may return a cleanup function
|
|
197
|
+
* @param {Array} [deps] - Dependency array; effect re-runs when deps change
|
|
198
|
+
* @returns {void}
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* useLayoutEffect(() => {
|
|
202
|
+
* const height = ref.current.getBoundingClientRect().height;
|
|
203
|
+
* if (height > maxHeight) {
|
|
204
|
+
* ref.current.style.overflow = 'auto';
|
|
205
|
+
* ref.current.style.maxHeight = maxHeight + 'px';
|
|
206
|
+
* }
|
|
207
|
+
* }, [maxHeight]);
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* // Auto-resize textarea
|
|
211
|
+
* useLayoutEffect(() => {
|
|
212
|
+
* const textarea = ref.current;
|
|
213
|
+
* textarea.style.height = 'auto';
|
|
214
|
+
* textarea.style.height = textarea.scrollHeight + 'px';
|
|
215
|
+
* }, [value]);
|
|
216
|
+
*/
|
|
217
|
+
function useLayoutEffect(create, deps) {
|
|
218
|
+
const component = currentlyRenderingComponent;
|
|
219
|
+
if (!component) {
|
|
220
|
+
throw new Error(
|
|
221
|
+
'[useLayoutEffect] Must be called within a component render phase'
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (typeof create !== 'function') {
|
|
226
|
+
throw new Error(
|
|
227
|
+
'[useLayoutEffect] First argument must be a function. ' +
|
|
228
|
+
`Received: ${typeof create}`
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
validateDeps(deps);
|
|
233
|
+
|
|
234
|
+
// Initialize hooks array
|
|
235
|
+
if (!component._hooks) {
|
|
236
|
+
component._hooks = [];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const currentHookIndex = hookIndex;
|
|
240
|
+
const resolvedDeps = deps !== undefined ? deps : null;
|
|
241
|
+
let hook;
|
|
242
|
+
|
|
243
|
+
if (component._hooks[currentHookIndex]) {
|
|
244
|
+
// Re-render: check if dependencies changed
|
|
245
|
+
hook = component._hooks[currentHookIndex];
|
|
246
|
+
|
|
247
|
+
const prevDeps = hook.deps;
|
|
248
|
+
const depsChanged = !areDepsEqual(prevDeps, resolvedDeps);
|
|
249
|
+
|
|
250
|
+
if (depsChanged) {
|
|
251
|
+
hook.deps = resolvedDeps;
|
|
252
|
+
hook.create = create;
|
|
253
|
+
hook._shouldRun = true;
|
|
254
|
+
} else {
|
|
255
|
+
hook._shouldRun = false;
|
|
256
|
+
}
|
|
257
|
+
} else {
|
|
258
|
+
// First render - always run
|
|
259
|
+
hook = {
|
|
260
|
+
type: 'layout-effect',
|
|
261
|
+
create,
|
|
262
|
+
cleanup: null,
|
|
263
|
+
deps: resolvedDeps,
|
|
264
|
+
component,
|
|
265
|
+
_shouldRun: true,
|
|
266
|
+
_priority: LAYOUT_PRIORITY.NORMAL,
|
|
267
|
+
_isMounted: false,
|
|
268
|
+
_executedAt: null,
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
component._hooks[currentHookIndex] = hook;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Queue the effect for synchronous execution during commit phase
|
|
275
|
+
if (hook._shouldRun) {
|
|
276
|
+
layoutEffectQueue.push(hook);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
hookIndex++;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ─── Priority Layout Effect ───────────────────────────────────────────────────
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Create a layout effect with a priority level.
|
|
286
|
+
* Higher priority effects execute first during the commit phase.
|
|
287
|
+
*
|
|
288
|
+
* @param {number} priority - Priority level (use LAYOUT_PRIORITY constants)
|
|
289
|
+
* @param {Function} create - Effect callback
|
|
290
|
+
* @param {Array} [deps] - Dependency array
|
|
291
|
+
*/
|
|
292
|
+
function usePriorityLayoutEffect(priority, create, deps) {
|
|
293
|
+
const component = currentlyRenderingComponent;
|
|
294
|
+
if (!component) {
|
|
295
|
+
throw new Error(
|
|
296
|
+
'[usePriorityLayoutEffect] Must be called within a component render phase'
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (typeof create !== 'function') {
|
|
301
|
+
throw new Error(
|
|
302
|
+
'[usePriorityLayoutEffect] Create must be a function. ' +
|
|
303
|
+
`Received: ${typeof create}`
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
validateDeps(deps);
|
|
308
|
+
|
|
309
|
+
if (!component._hooks) {
|
|
310
|
+
component._hooks = [];
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const currentHookIndex = hookIndex;
|
|
314
|
+
const resolvedDeps = deps !== undefined ? deps : null;
|
|
315
|
+
let hook;
|
|
316
|
+
|
|
317
|
+
if (component._hooks[currentHookIndex]) {
|
|
318
|
+
hook = component._hooks[currentHookIndex];
|
|
319
|
+
const prevDeps = hook.deps;
|
|
320
|
+
const depsChanged = !areDepsEqual(prevDeps, resolvedDeps);
|
|
321
|
+
|
|
322
|
+
if (depsChanged) {
|
|
323
|
+
hook.deps = resolvedDeps;
|
|
324
|
+
hook.create = create;
|
|
325
|
+
hook._priority = priority;
|
|
326
|
+
hook._shouldRun = true;
|
|
327
|
+
} else {
|
|
328
|
+
hook._shouldRun = false;
|
|
329
|
+
}
|
|
330
|
+
} else {
|
|
331
|
+
hook = {
|
|
332
|
+
type: 'layout-effect',
|
|
333
|
+
create,
|
|
334
|
+
cleanup: null,
|
|
335
|
+
deps: resolvedDeps,
|
|
336
|
+
component,
|
|
337
|
+
_shouldRun: true,
|
|
338
|
+
_priority: priority,
|
|
339
|
+
_isMounted: false,
|
|
340
|
+
_executedAt: null,
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
component._hooks[currentHookIndex] = hook;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (hook._shouldRun) {
|
|
347
|
+
layoutEffectQueue.push(hook);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
hookIndex++;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// ─── Isomorphic Layout Effect ─────────────────────────────────────────────────
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* useIsomorphicLayoutEffect - Uses useLayoutEffect on the client and useEffect
|
|
357
|
+
* on the server to avoid SSR warnings.
|
|
358
|
+
*
|
|
359
|
+
* @param {Function} create - Effect callback
|
|
360
|
+
* @param {Array} [deps] - Dependency array
|
|
361
|
+
*/
|
|
362
|
+
const useIsomorphicLayoutEffect = typeof window !== 'undefined'
|
|
363
|
+
? useLayoutEffect
|
|
364
|
+
: function useLayoutEffectNoop(create, deps) {
|
|
365
|
+
// On server, fall back to no-op to avoid warnings
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
// ─── Mutation Observer Effect ─────────────────────────────────────────────────
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* useMutationObserver - Observe DOM mutations on a ref's element.
|
|
372
|
+
*
|
|
373
|
+
* @param {Object} ref - Ref to the target element
|
|
374
|
+
* @param {Object} options - MutationObserver options
|
|
375
|
+
* @param {Function} callback - Called with mutation records
|
|
376
|
+
*
|
|
377
|
+
* @example
|
|
378
|
+
* const divRef = useRef(null);
|
|
379
|
+
* useMutationObserver(divRef, { childList: true, subtree: true }, (mutations) => {
|
|
380
|
+
* console.log('DOM changed:', mutations);
|
|
381
|
+
* });
|
|
382
|
+
*/
|
|
383
|
+
function useMutationObserver(ref, options, callback) {
|
|
384
|
+
const component = currentlyRenderingComponent;
|
|
385
|
+
if (!component) {
|
|
386
|
+
throw new Error('[useMutationObserver] Must be called within a component render phase');
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
useLayoutEffect(() => {
|
|
390
|
+
const node = ref.current;
|
|
391
|
+
if (!node || typeof MutationObserver === 'undefined') return;
|
|
392
|
+
|
|
393
|
+
const observer = new MutationObserver((mutations) => {
|
|
394
|
+
try {
|
|
395
|
+
callback(mutations);
|
|
396
|
+
} catch (error) {
|
|
397
|
+
console.error('[useMutationObserver] Callback error:', error);
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
observer.observe(node, options);
|
|
402
|
+
|
|
403
|
+
return () => {
|
|
404
|
+
observer.disconnect();
|
|
405
|
+
};
|
|
406
|
+
}, [ref.current]);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// ─── Resize Observer Effect ───────────────────────────────────────────────────
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* useResizeObserver - Observe element size changes.
|
|
413
|
+
*
|
|
414
|
+
* @param {Object} ref - Ref to the target element
|
|
415
|
+
* @param {Function} callback - Called with ResizeObserver entries
|
|
416
|
+
*
|
|
417
|
+
* @example
|
|
418
|
+
* const divRef = useRef(null);
|
|
419
|
+
* useResizeObserver(divRef, (entries) => {
|
|
420
|
+
* const { width, height } = entries[0].contentRect;
|
|
421
|
+
* setSize({ width, height });
|
|
422
|
+
* });
|
|
423
|
+
*/
|
|
424
|
+
function useResizeObserver(ref, callback) {
|
|
425
|
+
const component = currentlyRenderingComponent;
|
|
426
|
+
if (!component) {
|
|
427
|
+
throw new Error('[useResizeObserver] Must be called within a component render phase');
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
useLayoutEffect(() => {
|
|
431
|
+
const node = ref.current;
|
|
432
|
+
if (!node || typeof ResizeObserver === 'undefined') return;
|
|
433
|
+
|
|
434
|
+
const observer = new ResizeObserver((entries) => {
|
|
435
|
+
try {
|
|
436
|
+
callback(entries);
|
|
437
|
+
} catch (error) {
|
|
438
|
+
console.error('[useResizeObserver] Callback error:', error);
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
observer.observe(node);
|
|
443
|
+
|
|
444
|
+
return () => {
|
|
445
|
+
observer.disconnect();
|
|
446
|
+
};
|
|
447
|
+
}, [ref.current]);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// ─── Auto-focus Effect ────────────────────────────────────────────────────────
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* useAutoFocus - Auto-focus an element on mount.
|
|
454
|
+
*
|
|
455
|
+
* @param {Object} ref - Ref to the element to focus
|
|
456
|
+
* @param {boolean} [shouldFocus=true] - Whether to auto-focus
|
|
457
|
+
*/
|
|
458
|
+
function useAutoFocus(ref, shouldFocus) {
|
|
459
|
+
useLayoutEffect(() => {
|
|
460
|
+
if (shouldFocus === false) return;
|
|
461
|
+
const node = ref.current;
|
|
462
|
+
if (node && typeof node.focus === 'function') {
|
|
463
|
+
node.focus();
|
|
464
|
+
}
|
|
465
|
+
}, []);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// ─── Cleanup ──────────────────────────────────────────────────────────────────
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Clean up layout effects when a component unmounts.
|
|
472
|
+
* @param {Object} component
|
|
473
|
+
*/
|
|
474
|
+
function cleanupLayoutEffects(component) {
|
|
475
|
+
if (!component._hooks) return;
|
|
476
|
+
|
|
477
|
+
component._hooks.forEach((hook) => {
|
|
478
|
+
if (hook && hook.type === 'layout-effect' && typeof hook.cleanup === 'function') {
|
|
479
|
+
runLayoutCleanup(hook);
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
// Remove from queue
|
|
484
|
+
for (let i = layoutEffectQueue.length - 1; i >= 0; i--) {
|
|
485
|
+
if (layoutEffectQueue[i].component === component) {
|
|
486
|
+
layoutEffectQueue.splice(i, 1);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// ─── Exports ──────────────────────────────────────────────────────────────────
|
|
492
|
+
|
|
493
|
+
module.exports = {
|
|
494
|
+
useLayoutEffect,
|
|
495
|
+
useIsomorphicLayoutEffect,
|
|
496
|
+
usePriorityLayoutEffect,
|
|
497
|
+
useMutationObserver,
|
|
498
|
+
useResizeObserver,
|
|
499
|
+
useAutoFocus,
|
|
500
|
+
setCurrentComponent,
|
|
501
|
+
resetHookIndex,
|
|
502
|
+
flushLayoutEffects,
|
|
503
|
+
hasPendingLayoutEffects,
|
|
504
|
+
getPendingLayoutEffectCount,
|
|
505
|
+
getPendingLayoutEffectsByPriority,
|
|
506
|
+
cleanupLayoutEffects,
|
|
507
|
+
LAYOUT_PRIORITY,
|
|
508
|
+
};
|