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,542 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useRef Hook Implementation
|
|
3
|
+
* ElementDrawing Framework - Mutable ref objects, DOM refs, callback refs,
|
|
4
|
+
* ref forwarding, ref merging, previous value tracking, ref lifecycle,
|
|
5
|
+
* and DOM measurement helpers.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
// ─── Internal State ───────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
let currentlyRenderingComponent = null;
|
|
13
|
+
let hookIndex = 0;
|
|
14
|
+
const refRegistry = new WeakMap();
|
|
15
|
+
|
|
16
|
+
// ─── Ref Lifecycle Tracking ───────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
const refLifecycle = new WeakMap();
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get the lifecycle state of a ref (mounted/unmounted).
|
|
22
|
+
* @param {Object} ref - Ref object
|
|
23
|
+
* @returns {string} 'mounted' | 'unmounted' | 'unknown'
|
|
24
|
+
*/
|
|
25
|
+
function getRefLifecycle(ref) {
|
|
26
|
+
if (!ref || !ref._isRef) return 'unknown';
|
|
27
|
+
return refLifecycle.get(ref) || 'unknown';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ─── Hook Context Management ──────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
function setCurrentComponent(component) {
|
|
33
|
+
currentlyRenderingComponent = component;
|
|
34
|
+
hookIndex = 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function resetHookIndex() {
|
|
38
|
+
hookIndex = 0;
|
|
39
|
+
currentlyRenderingComponent = null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ─── Ref Object Creation ──────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Create a mutable ref object with a current property.
|
|
46
|
+
*
|
|
47
|
+
* @param {*} initialValue - Initial value for current
|
|
48
|
+
* @returns {Object} Ref object with { current } property
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* const countRef = useRef(0);
|
|
52
|
+
* countRef.current++; // Mutable without re-renders
|
|
53
|
+
*/
|
|
54
|
+
function createRef(initialValue) {
|
|
55
|
+
const ref = {
|
|
56
|
+
current: initialValue,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
Object.defineProperty(ref, '_isRef', {
|
|
60
|
+
enumerable: false,
|
|
61
|
+
configurable: false,
|
|
62
|
+
writable: false,
|
|
63
|
+
value: true,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
Object.defineProperty(ref, '_createdAt', {
|
|
67
|
+
enumerable: false,
|
|
68
|
+
configurable: false,
|
|
69
|
+
writable: false,
|
|
70
|
+
value: Date.now(),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return ref;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ─── useRef Hook ───────────────────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* useRef - Create a mutable ref object that persists across renders.
|
|
80
|
+
* Mutating .current does NOT trigger a re-render.
|
|
81
|
+
*
|
|
82
|
+
* @param {*} initialValue - Initial value for the ref's current property
|
|
83
|
+
* @returns {Object} Ref object { current: initialValue }
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* // DOM ref
|
|
87
|
+
* const inputRef = useRef(null);
|
|
88
|
+
* <input ref={inputRef} />
|
|
89
|
+
* inputRef.current.focus();
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* // Instance variable
|
|
93
|
+
* const timerRef = useRef(null);
|
|
94
|
+
* timerRef.current = setInterval(() => {}, 1000);
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* // Previous value tracking
|
|
98
|
+
* const prevCountRef = useRef(count);
|
|
99
|
+
* useEffect(() => { prevCountRef.current = count; }, [count]);
|
|
100
|
+
*/
|
|
101
|
+
function useRef(initialValue) {
|
|
102
|
+
const component = currentlyRenderingComponent;
|
|
103
|
+
if (!component) {
|
|
104
|
+
throw new Error('[useRef] Must be called within a component render phase');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Initialize hooks array
|
|
108
|
+
if (!component._hooks) {
|
|
109
|
+
component._hooks = [];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const currentHookIndex = hookIndex;
|
|
113
|
+
let hook;
|
|
114
|
+
|
|
115
|
+
if (component._hooks[currentHookIndex]) {
|
|
116
|
+
// Re-render: return existing ref (never changes identity)
|
|
117
|
+
hook = component._hooks[currentHookIndex];
|
|
118
|
+
} else {
|
|
119
|
+
// First render: create the ref object
|
|
120
|
+
const ref = createRef(initialValue);
|
|
121
|
+
|
|
122
|
+
hook = {
|
|
123
|
+
type: 'ref',
|
|
124
|
+
ref,
|
|
125
|
+
_isDOMRef: false,
|
|
126
|
+
_cleanup: null,
|
|
127
|
+
_prevValues: [],
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
component._hooks[currentHookIndex] = hook;
|
|
131
|
+
|
|
132
|
+
// Register in the global ref registry
|
|
133
|
+
if (!refRegistry.has(component)) {
|
|
134
|
+
refRegistry.set(component, new Set());
|
|
135
|
+
}
|
|
136
|
+
refRegistry.get(component).add(currentHookIndex);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
hookIndex++;
|
|
140
|
+
return hook.ref;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ─── Previous Value Tracking ──────────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* usePrevious - Track the previous value of a variable across renders.
|
|
147
|
+
*
|
|
148
|
+
* @param {*} value - Current value
|
|
149
|
+
* @returns {*} Previous value (undefined on first render)
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* const [count, setCount] = useState(0);
|
|
153
|
+
* const prevCount = usePrevious(count);
|
|
154
|
+
* console.log(`Current: ${count}, Previous: ${prevCount}`);
|
|
155
|
+
*/
|
|
156
|
+
function usePrevious(value) {
|
|
157
|
+
const ref = useRef(undefined);
|
|
158
|
+
|
|
159
|
+
// Store the previous value
|
|
160
|
+
const previousValue = ref.current;
|
|
161
|
+
|
|
162
|
+
// Schedule the update for next render (not immediate)
|
|
163
|
+
if (currentlyRenderingComponent) {
|
|
164
|
+
const component = currentlyRenderingComponent;
|
|
165
|
+
// Queue the update to happen after render
|
|
166
|
+
if (!component._pendingPrevUpdates) {
|
|
167
|
+
component._pendingPrevUpdates = [];
|
|
168
|
+
}
|
|
169
|
+
component._pendingPrevUpdates.push(() => {
|
|
170
|
+
ref.current = value;
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return previousValue;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* usePreviousWithInit - Like usePrevious but with an explicit initial previous value.
|
|
179
|
+
*
|
|
180
|
+
* @param {*} value - Current value
|
|
181
|
+
* @param {*} initPrev - Initial "previous" value for first render
|
|
182
|
+
* @returns {*} Previous value
|
|
183
|
+
*/
|
|
184
|
+
function usePreviousWithInit(value, initPrev) {
|
|
185
|
+
const ref = useRef(initPrev);
|
|
186
|
+
const previousValue = ref.current;
|
|
187
|
+
|
|
188
|
+
if (currentlyRenderingComponent) {
|
|
189
|
+
const component = currentlyRenderingComponent;
|
|
190
|
+
if (!component._pendingPrevUpdates) {
|
|
191
|
+
component._pendingPrevUpdates = [];
|
|
192
|
+
}
|
|
193
|
+
component._pendingPrevUpdates.push(() => {
|
|
194
|
+
ref.current = value;
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return previousValue;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ─── Callback Refs ────────────────────────────────────────────────────────────
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Create a callback ref that invokes a function when the DOM node changes.
|
|
205
|
+
*
|
|
206
|
+
* @param {Function} callback - Called with (node) when the ref value changes
|
|
207
|
+
* @returns {Function} Callback ref function
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* const measureRef = useCallbackRef((node) => {
|
|
211
|
+
* if (node) {
|
|
212
|
+
* const { width, height } = node.getBoundingClientRect();
|
|
213
|
+
* setSize({ width, height });
|
|
214
|
+
* }
|
|
215
|
+
* });
|
|
216
|
+
*/
|
|
217
|
+
function useCallbackRef(callback) {
|
|
218
|
+
if (typeof callback !== 'function') {
|
|
219
|
+
throw new Error('[useCallbackRef] Argument must be a function');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
let lastNode = null;
|
|
223
|
+
|
|
224
|
+
const callbackRef = function refFunction(node) {
|
|
225
|
+
// Clean up previous node
|
|
226
|
+
if (lastNode && typeof callbackRef._cleanup === 'function') {
|
|
227
|
+
try {
|
|
228
|
+
callbackRef._cleanup(lastNode);
|
|
229
|
+
} catch (error) {
|
|
230
|
+
console.error('[useCallbackRef] Cleanup error:', error);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Invoke callback with new node
|
|
235
|
+
if (node !== null) {
|
|
236
|
+
try {
|
|
237
|
+
const result = callback(node);
|
|
238
|
+
if (typeof result === 'function') {
|
|
239
|
+
callbackRef._cleanup = result;
|
|
240
|
+
}
|
|
241
|
+
} catch (error) {
|
|
242
|
+
console.error('[useCallbackRef] Callback error:', error);
|
|
243
|
+
}
|
|
244
|
+
} else {
|
|
245
|
+
callbackRef._cleanup = null;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
lastNode = node;
|
|
249
|
+
|
|
250
|
+
// Track lifecycle
|
|
251
|
+
refLifecycle.set(callbackRef, node ? 'mounted' : 'unmounted');
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
callbackRef._isCallbackRef = true;
|
|
255
|
+
callbackRef._cleanup = null;
|
|
256
|
+
|
|
257
|
+
return callbackRef;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ─── Ref Forwarding ───────────────────────────────────────────────────────────
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Create a forwarded ref component that passes refs through to child components.
|
|
264
|
+
*
|
|
265
|
+
* @param {Function} render - Function (props, ref) => VNode
|
|
266
|
+
* @returns {Function} Forwarded ref component
|
|
267
|
+
*
|
|
268
|
+
* @example
|
|
269
|
+
* const FancyInput = forwardRef((props, ref) => (
|
|
270
|
+
* <input ref={ref} className="fancy-input" {...props} />
|
|
271
|
+
* ));
|
|
272
|
+
*
|
|
273
|
+
* <FancyInput ref={inputRef} />
|
|
274
|
+
*/
|
|
275
|
+
function forwardRef(render) {
|
|
276
|
+
if (typeof render !== 'function') {
|
|
277
|
+
throw new Error(
|
|
278
|
+
'[forwardRef] Argument must be a render function. ' +
|
|
279
|
+
`Received: ${typeof render}`
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const ForwardedComponent = function ForwardRefComponent(props) {
|
|
284
|
+
const ref = props.ref;
|
|
285
|
+
const restProps = Object.assign({}, props);
|
|
286
|
+
delete restProps.ref;
|
|
287
|
+
|
|
288
|
+
return render(restProps, ref);
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
ForwardedComponent._isForwardRef = true;
|
|
292
|
+
ForwardedComponent.displayName = render.displayName || render.name || 'ForwardRef';
|
|
293
|
+
ForwardedComponent.render = render;
|
|
294
|
+
|
|
295
|
+
return ForwardedComponent;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Check if a component was created with forwardRef.
|
|
300
|
+
* @param {Function} component
|
|
301
|
+
* @returns {boolean}
|
|
302
|
+
*/
|
|
303
|
+
function isForwardRef(component) {
|
|
304
|
+
return component && component._isForwardRef === true;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ─── Ref Merging ──────────────────────────────────────────────────────────────
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Merge multiple refs into a single callback ref.
|
|
311
|
+
* Supports object refs, callback refs, and forwarded refs.
|
|
312
|
+
*
|
|
313
|
+
* @param {...(Object|Function|null)} refs - Refs to merge
|
|
314
|
+
* @returns {Function} Callback ref that sets all provided refs
|
|
315
|
+
*
|
|
316
|
+
* @example
|
|
317
|
+
* const mergedRef = useMergedRefs(externalRef, localRef);
|
|
318
|
+
* <div ref={mergedRef} />
|
|
319
|
+
*/
|
|
320
|
+
function useMergedRefs() {
|
|
321
|
+
const refs = Array.prototype.slice.call(arguments);
|
|
322
|
+
|
|
323
|
+
const mergedRef = function mergedRefCallback(node) {
|
|
324
|
+
refs.forEach((ref) => {
|
|
325
|
+
if (!ref) return;
|
|
326
|
+
|
|
327
|
+
if (typeof ref === 'function') {
|
|
328
|
+
// Callback ref
|
|
329
|
+
ref(node);
|
|
330
|
+
} else if (typeof ref === 'object' && ref._isRef) {
|
|
331
|
+
// Object ref
|
|
332
|
+
ref.current = node;
|
|
333
|
+
} else if (typeof ref === 'object' && ref !== null) {
|
|
334
|
+
// Plain object ref
|
|
335
|
+
ref.current = node;
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
mergedRef._isMergedRef = true;
|
|
341
|
+
return mergedRef;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// ─── DOM Ref Helpers ──────────────────────────────────────────────────────────
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Create a ref that measures the DOM node dimensions.
|
|
348
|
+
*
|
|
349
|
+
* @returns {Array} [ref, dimensions] where dimensions is { width, height, x, y }
|
|
350
|
+
*
|
|
351
|
+
* @example
|
|
352
|
+
* const [measureRef, dimensions] = useMeasureRef();
|
|
353
|
+
* <div ref={measureRef} />
|
|
354
|
+
* console.log(dimensions.width, dimensions.height);
|
|
355
|
+
*/
|
|
356
|
+
function useMeasureRef() {
|
|
357
|
+
const component = currentlyRenderingComponent;
|
|
358
|
+
if (!component) {
|
|
359
|
+
throw new Error('[useMeasureRef] Must be called within a component render phase');
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const dimensionsRef = useRef({ width: 0, height: 0, x: 0, y: 0 });
|
|
363
|
+
const observerRef = useRef(null);
|
|
364
|
+
|
|
365
|
+
const callbackRef = useCallbackRef((node) => {
|
|
366
|
+
if (observerRef.current) {
|
|
367
|
+
observerRef.current.disconnect();
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (node) {
|
|
371
|
+
// Measure initial dimensions
|
|
372
|
+
const rect = node.getBoundingClientRect();
|
|
373
|
+
dimensionsRef.current = {
|
|
374
|
+
width: rect.width,
|
|
375
|
+
height: rect.height,
|
|
376
|
+
x: rect.x,
|
|
377
|
+
y: rect.y,
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
// Observe size changes
|
|
381
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
382
|
+
observerRef.current = new ResizeObserver((entries) => {
|
|
383
|
+
const entry = entries[0];
|
|
384
|
+
if (entry) {
|
|
385
|
+
const { width, height } = entry.contentRect;
|
|
386
|
+
const { x, y } = entry.target.getBoundingClientRect();
|
|
387
|
+
dimensionsRef.current = { width, height, x, y };
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
observerRef.current.observe(node);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
return [callbackRef, dimensionsRef.current];
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Create a ref that tracks whether the element is in the viewport.
|
|
400
|
+
*
|
|
401
|
+
* @param {Object} [options] - IntersectionObserver options
|
|
402
|
+
* @returns {Array} [ref, isIntersecting]
|
|
403
|
+
*/
|
|
404
|
+
function useInViewRef(options) {
|
|
405
|
+
const component = currentlyRenderingComponent;
|
|
406
|
+
if (!component) {
|
|
407
|
+
throw new Error('[useInViewRef] Must be called within a component render phase');
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const inViewRef = useRef(false);
|
|
411
|
+
const observerRef = useRef(null);
|
|
412
|
+
|
|
413
|
+
const callbackRef = useCallbackRef((node) => {
|
|
414
|
+
if (observerRef.current) {
|
|
415
|
+
observerRef.current.disconnect();
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (node && typeof IntersectionObserver !== 'undefined') {
|
|
419
|
+
observerRef.current = new IntersectionObserver((entries) => {
|
|
420
|
+
const entry = entries[0];
|
|
421
|
+
if (entry) {
|
|
422
|
+
inViewRef.current = entry.isIntersecting;
|
|
423
|
+
}
|
|
424
|
+
}, options);
|
|
425
|
+
observerRef.current.observe(node);
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
return [callbackRef, inViewRef.current];
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Create a ref that tracks focus state.
|
|
434
|
+
*
|
|
435
|
+
* @returns {Array} [ref, isFocused]
|
|
436
|
+
*/
|
|
437
|
+
function useFocusRef() {
|
|
438
|
+
const component = currentlyRenderingComponent;
|
|
439
|
+
if (!component) {
|
|
440
|
+
throw new Error('[useFocusRef] Must be called within a component render phase');
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const focusRef = useRef(false);
|
|
444
|
+
|
|
445
|
+
const callbackRef = useCallbackRef((node) => {
|
|
446
|
+
if (node) {
|
|
447
|
+
node.addEventListener('focus', () => { focusRef.current = true; });
|
|
448
|
+
node.addEventListener('blur', () => { focusRef.current = false; });
|
|
449
|
+
focusRef.current = document.activeElement === node;
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
return [callbackRef, focusRef.current];
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Create a ref that auto-scrolls to the element when dependencies change.
|
|
458
|
+
*
|
|
459
|
+
* @param {Array} deps - Dependencies that trigger scroll
|
|
460
|
+
* @returns {Function} Ref callback
|
|
461
|
+
*/
|
|
462
|
+
function useScrollRef(deps) {
|
|
463
|
+
const scrolledRef = useRef(false);
|
|
464
|
+
|
|
465
|
+
const callbackRef = useCallbackRef((node) => {
|
|
466
|
+
if (node) {
|
|
467
|
+
node.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
468
|
+
scrolledRef.current = true;
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
return callbackRef;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// ─── Cleanup ──────────────────────────────────────────────────────────────────
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Process pending previous value updates after render.
|
|
479
|
+
* @param {Object} component
|
|
480
|
+
*/
|
|
481
|
+
function processPendingPrevUpdates(component) {
|
|
482
|
+
if (component._pendingPrevUpdates && component._pendingPrevUpdates.length > 0) {
|
|
483
|
+
component._pendingPrevUpdates.forEach((fn) => fn());
|
|
484
|
+
component._pendingPrevUpdates = [];
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Clean up refs when a component unmounts.
|
|
490
|
+
* @param {Object} component
|
|
491
|
+
*/
|
|
492
|
+
function cleanupRefs(component) {
|
|
493
|
+
if (!component._hooks) return;
|
|
494
|
+
|
|
495
|
+
component._hooks.forEach((hook) => {
|
|
496
|
+
if (hook && hook.type === 'ref') {
|
|
497
|
+
// Run cleanup for callback refs
|
|
498
|
+
if (typeof hook._cleanup === 'function') {
|
|
499
|
+
try {
|
|
500
|
+
hook._cleanup(null);
|
|
501
|
+
} catch (error) {
|
|
502
|
+
console.error('[useRef] Cleanup error:', error);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Clear DOM refs
|
|
507
|
+
if (hook._isDOMRef && hook.ref) {
|
|
508
|
+
hook.ref.current = null;
|
|
509
|
+
refLifecycle.set(hook.ref, 'unmounted');
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
refRegistry.delete(component);
|
|
515
|
+
|
|
516
|
+
// Process any remaining pending updates
|
|
517
|
+
if (component._pendingPrevUpdates) {
|
|
518
|
+
component._pendingPrevUpdates = [];
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// ─── Exports ──────────────────────────────────────────────────────────────────
|
|
523
|
+
|
|
524
|
+
module.exports = {
|
|
525
|
+
useRef,
|
|
526
|
+
usePrevious,
|
|
527
|
+
usePreviousWithInit,
|
|
528
|
+
createRef,
|
|
529
|
+
useCallbackRef,
|
|
530
|
+
forwardRef,
|
|
531
|
+
isForwardRef,
|
|
532
|
+
useMergedRefs,
|
|
533
|
+
useMeasureRef,
|
|
534
|
+
useInViewRef,
|
|
535
|
+
useFocusRef,
|
|
536
|
+
useScrollRef,
|
|
537
|
+
setCurrentComponent,
|
|
538
|
+
resetHookIndex,
|
|
539
|
+
cleanupRefs,
|
|
540
|
+
processPendingPrevUpdates,
|
|
541
|
+
getRefLifecycle,
|
|
542
|
+
};
|