@reause/shared 0.1.2

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/dist/index.js ADDED
@@ -0,0 +1,3091 @@
1
+ import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState, useSyncExternalStore } from "react";
2
+ import { jsx } from "react/jsx-runtime";
3
+ //#region createEventHook/index.tsx
4
+ /**
5
+ * Utility for creating event hooks
6
+ *
7
+ * @see https://vueuse.org/createEventHook
8
+ *
9
+ * @__NO_SIDE_EFFECTS__
10
+ */
11
+ function createEventHook() {
12
+ const fns = /* @__PURE__ */ new Set();
13
+ const off = (fn) => {
14
+ fns.delete(fn);
15
+ };
16
+ const clear = () => {
17
+ fns.clear();
18
+ };
19
+ const on = (fn) => {
20
+ fns.add(fn);
21
+ const offFn = () => off(fn);
22
+ return { off: offFn };
23
+ };
24
+ const trigger = (...args) => {
25
+ return Promise.all(Array.from(fns).map((fn) => fn(...args)));
26
+ };
27
+ return {
28
+ on,
29
+ off,
30
+ trigger,
31
+ clear
32
+ };
33
+ }
34
+ //#endregion
35
+ //#region createGlobalState/index.tsx
36
+ /* @__NO_SIDE_EFFECTS__ */
37
+ function createGlobalState(initialState) {
38
+ let state = typeof initialState === "function" ? initialState() : initialState;
39
+ const listeners = /* @__PURE__ */ new Set();
40
+ const subscribe = (listener) => {
41
+ listeners.add(listener);
42
+ return () => {
43
+ listeners.delete(listener);
44
+ };
45
+ };
46
+ const getSnapshot = () => state;
47
+ const setState = (update) => {
48
+ state = typeof update === "function" ? update(state) : update;
49
+ for (const listener of listeners) listener();
50
+ };
51
+ return function useGlobalState() {
52
+ const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
53
+ return useMemo(() => [snapshot, setState], [snapshot, setState]);
54
+ };
55
+ }
56
+ //#endregion
57
+ //#region createInjectionState/index.tsx
58
+ function createInjectionState(composable, options) {
59
+ var _options$injectionKey;
60
+ const InjectionContext = (_options$injectionKey = options === null || options === void 0 ? void 0 : options.injectionKey) !== null && _options$injectionKey !== void 0 ? _options$injectionKey : createContext(options === null || options === void 0 ? void 0 : options.defaultValue);
61
+ function Provider(props) {
62
+ const { children, ...rest } = props;
63
+ const state = composable(rest);
64
+ return /* @__PURE__ */ jsx(InjectionContext.Provider, {
65
+ value: state,
66
+ children
67
+ });
68
+ }
69
+ Provider.displayName = composable.name ? `${composable.name}Provider` : "InjectionStateProvider";
70
+ const useInjectedState = () => useContext(InjectionContext);
71
+ return [Provider, useInjectedState];
72
+ }
73
+ //#endregion
74
+ //#region createSharedHook/index.tsx
75
+ /**
76
+ * Make a composable function usable with multiple React components.
77
+ *
78
+ * Map from @vueuse/shared `createSharedComposable`
79
+ * Mapping: upstream runs the composable once inside a detached
80
+ * `effectScope(true)`, counts the subscribers and stops the scope when the
81
+ * last consumer leaves. React has no `effectScope`, so the same lifetime is
82
+ * expressed by an external store held in the closure of one
83
+ * `createSharedHook` call — the `state` snapshot, a `Set` of `listeners`, the
84
+ * `refCount` and the optional `cleanup` — which every consumer reads through
85
+ * `useSyncExternalStore`.
86
+ *
87
+ * The shared instance is created by the **first consumer to render** (the
88
+ * "creator"): it runs the wrapped hook on every one of its renders — the
89
+ * wrapped hook is therefore free to use React hooks internally — assigning
90
+ * the result to `state`, and `useLayoutEffect` publishes the latest value to
91
+ * every other consumer after commit. Every later consumer never calls the
92
+ * wrapped hook (both call patterns are stable per consumer, so the hook order
93
+ * never changes across renders); it just reads the published snapshot. The
94
+ * creator assigns `state` *before* `useSyncExternalStore` reads its snapshot
95
+ * in the same render, so every consumer — creator included — receives the
96
+ * shared value on its very first render, never `undefined`.
97
+ *
98
+ * The creator deliberately **does not register a store listener**: it already
99
+ * re-renders on its own state changes (the wrapped hook's setters belong to
100
+ * its component) and re-publishes afterwards, so a notification would only
101
+ * re-render it from its own publish. `useSyncExternalStore` requires the
102
+ * snapshot to stay reference-stable between real changes ("the result of
103
+ * getSnapshot should be cached"): every creator render assigns a fresh
104
+ * reference, so a subscribed creator would see "the store changed" forever and
105
+ * loop. The creator still counts toward `refCount`, so teardown timing is
106
+ * exact — it just never receives notifications.
107
+ *
108
+ * Deviations from upstream:
109
+ * - upstream runs the composable exactly once, with the first caller's
110
+ * arguments; here the creator re-runs the wrapped hook on every one of its
111
+ * renders (React hooks cannot be called outside a render), so while the
112
+ * creator stays mounted the shared value keeps tracking its latest render.
113
+ * - **frozen after the creator unmounts**: the wrapped hook's setters belong
114
+ * to the creator's component, so if the creator unmounts while other
115
+ * consumers remain mounted, the shared value stops updating — it freezes at
116
+ * the last published value. The instance itself lives on until the last
117
+ * consumer unmounts (upstream lifetime parity).
118
+ * - `getServerSnapshot` returns the same snapshot as the client: server
119
+ * rendering yields the uninitialized value, the client fills it in after
120
+ * hydration, and `useSyncExternalStore` handles the mismatch.
121
+ * - Teardown has no `tryOnScopeDispose` to hook into, so the optional
122
+ * `cleanup` argument is called — and the state dropped — when the last
123
+ * consumer unmounts; a later mount starts a fresh instance, exactly like
124
+ * upstream's `scope.stop()` followed by `state = undefined`.
125
+ *
126
+ * ```tsx
127
+ * const useSharedMouse = createSharedHook(useMouse)
128
+ *
129
+ * // CompA — const { x, y } = useSharedMouse()
130
+ * // CompB — const { x, y } = useSharedMouse() // same state, no new listeners
131
+ * ```
132
+ *
133
+ * @see https://vueuse.org/createSharedComposable
134
+ * @param hook The composable to share across every consumer of the returned
135
+ * hook. It runs on every render of the first consumer (the creator).
136
+ * @param cleanup Called when the last consumer unmounts, before the shared
137
+ * state is dropped — the place to undo whatever `hook` set up outside React.
138
+ */
139
+ /* @__NO_SIDE_EFFECTS__ */
140
+ function createSharedHook(hook, cleanup) {
141
+ let state;
142
+ const listeners = /* @__PURE__ */ new Set();
143
+ let refCount = 0;
144
+ let teardown = cleanup;
145
+ let creatorId;
146
+ const notify = () => {
147
+ for (const listener of listeners) listener();
148
+ };
149
+ const subscribe = (listener) => {
150
+ listeners.add(listener);
151
+ refCount += 1;
152
+ return () => {
153
+ listeners.delete(listener);
154
+ refCount -= 1;
155
+ if (refCount <= 0) {
156
+ state = void 0;
157
+ refCount = 0;
158
+ creatorId = void 0;
159
+ teardown === null || teardown === void 0 || teardown();
160
+ teardown = void 0;
161
+ }
162
+ };
163
+ };
164
+ const creatorSubscribe = (_listener) => {
165
+ refCount += 1;
166
+ return () => {
167
+ refCount -= 1;
168
+ if (refCount <= 0) {
169
+ state = void 0;
170
+ refCount = 0;
171
+ creatorId = void 0;
172
+ teardown === null || teardown === void 0 || teardown();
173
+ teardown = void 0;
174
+ }
175
+ };
176
+ };
177
+ const getSnapshot = () => state;
178
+ const getServerSnapshot = getSnapshot;
179
+ return function useSharedHook(...args) {
180
+ const id = useRef(Symbol("createSharedHook")).current;
181
+ if (creatorId === void 0) creatorId = id;
182
+ const isCreator = creatorId === id;
183
+ if (isCreator) state = hook(...args);
184
+ useLayoutEffect(() => {
185
+ if (isCreator) notify();
186
+ });
187
+ return useSyncExternalStore(isCreator ? creatorSubscribe : subscribe, getSnapshot, getServerSnapshot);
188
+ };
189
+ }
190
+ //#endregion
191
+ //#region utils/index.tsx
192
+ function promiseTimeout(ms, throwOnTimeout = false, reason = "Timeout") {
193
+ return new Promise((resolve, reject) => {
194
+ if (throwOnTimeout) setTimeout(reject, ms, reason);
195
+ else setTimeout(resolve, ms);
196
+ });
197
+ }
198
+ /**
199
+ * Create singleton promise function
200
+ *
201
+ * @example
202
+ * ```
203
+ * const promise = createSingletonPromise(async () => { ... })
204
+ *
205
+ * await promise()
206
+ * await promise() // all of them will be bind to a single promise instance
207
+ * await promise() // and be resolved together
208
+ * ```
209
+ */
210
+ function createSingletonPromise(fn) {
211
+ let _promise;
212
+ function wrapper() {
213
+ if (!_promise) _promise = fn();
214
+ return _promise;
215
+ }
216
+ wrapper.reset = async () => {
217
+ const _prev = _promise;
218
+ _promise = void 0;
219
+ if (_prev) await _prev;
220
+ };
221
+ return wrapper;
222
+ }
223
+ function increaseWithUnit(target, delta) {
224
+ var _target$match;
225
+ if (typeof target === "number") return target + delta;
226
+ const value = ((_target$match = target.match(/^-?\d+\.?\d*/)) === null || _target$match === void 0 ? void 0 : _target$match[0]) || "";
227
+ const unit = target.slice(value.length);
228
+ const result = Number.parseFloat(value) + delta;
229
+ if (Number.isNaN(result)) return target;
230
+ return result + unit;
231
+ }
232
+ /**
233
+ * Get a px value for SSR use, do not rely on this method outside of SSR as REM
234
+ * unit is assumed at 16px, which might not be the case on the client
235
+ *
236
+ * @example pxValue('37rem') // 592
237
+ * @example pxValue('500px') // 500
238
+ */
239
+ function pxValue(px) {
240
+ return px.endsWith("rem") ? Number.parseFloat(px) * 16 : Number.parseFloat(px);
241
+ }
242
+ /**
243
+ * Create a new subset object by giving keys
244
+ */
245
+ function objectPick(obj, keys, omitUndefined = false) {
246
+ return keys.reduce((n, k) => {
247
+ if (k in obj) {
248
+ if (!omitUndefined || obj[k] !== void 0) n[k] = obj[k];
249
+ }
250
+ return n;
251
+ }, {});
252
+ }
253
+ /**
254
+ * Create a new subset object by omit giving keys
255
+ */
256
+ function objectOmit(obj, keys, omitUndefined = false) {
257
+ return Object.fromEntries(Object.entries(obj).filter(([key, value]) => {
258
+ return (!omitUndefined || value !== void 0) && !keys.includes(key);
259
+ }));
260
+ }
261
+ function toArray(value) {
262
+ return Array.isArray(value) ? value : [value];
263
+ }
264
+ const isClient$1 = typeof window !== "undefined" && typeof document !== "undefined";
265
+ const isDef = (val) => typeof val !== "undefined";
266
+ const assert = (condition, ...infos) => {
267
+ if (!condition) console.warn(...infos);
268
+ };
269
+ const toString$1 = Object.prototype.toString;
270
+ const isObject = (val) => toString$1.call(val) === "[object Object]";
271
+ const now = () => Date.now();
272
+ const timestamp = () => +Date.now();
273
+ const clamp = (n, min, max) => Math.min(max, Math.max(min, n));
274
+ const rand = (min, max) => {
275
+ min = Math.ceil(min);
276
+ max = Math.floor(max);
277
+ return Math.floor(Math.random() * (max - min + 1)) + min;
278
+ };
279
+ const hasOwn = (val, key) => Object.hasOwn(val, key);
280
+ const isIOS = /* #__PURE__ */ getIsIOS();
281
+ function getIsIOS() {
282
+ var _window, _window2, _window3;
283
+ return isClient$1 && !!((_window = window) === null || _window === void 0 || (_window = _window.navigator) === null || _window === void 0 ? void 0 : _window.userAgent) && (/iP(?:ad|hone|od)/.test(window.navigator.userAgent) || ((_window2 = window) === null || _window2 === void 0 || (_window2 = _window2.navigator) === null || _window2 === void 0 ? void 0 : _window2.maxTouchPoints) > 2 && /iPad|Macintosh/.test((_window3 = window) === null || _window3 === void 0 ? void 0 : _window3.navigator.userAgent));
284
+ }
285
+ function cacheStringFunction(fn) {
286
+ const cache = Object.create(null);
287
+ return ((str) => {
288
+ return cache[str] || (cache[str] = fn(str));
289
+ });
290
+ }
291
+ const hyphenateRE = /\B([A-Z])/g;
292
+ const hyphenate = cacheStringFunction((str) => str.replace(hyphenateRE, "-$1").toLowerCase());
293
+ /**
294
+ * Type guard for React ref objects (`RefObject` — `{ current }` holders).
295
+ * Callback refs are functions and cannot be read synchronously, so they are
296
+ * not ref-like.
297
+ */
298
+ function isRefLike(value) {
299
+ return value !== null && value !== void 0 && typeof value === "object" && "current" in value;
300
+ }
301
+ function toValue(value) {
302
+ if (Array.isArray(value) && value.length === 2 && typeof value[1] === "function") return value[0];
303
+ if (value !== null && value !== void 0 && typeof value === "object" && "value" in value && !("addEventListener" in value)) return value.value;
304
+ if (typeof value === "function") return value();
305
+ if (isRefLike(value)) return value.current;
306
+ return value;
307
+ }
308
+ /**
309
+ * Write a value back through a writable `State<T>` source — a ref-like
310
+ * `.current`, a `[value, setter]` tuple or a `{ value, onChange }` pair.
311
+ * Plain values and getters have no write path and are skipped. This is the
312
+ * write-side counterpart of `toValue`; hooks that push values into a
313
+ * `State<T>` import it from here rather than re-implementing the branches.
314
+ */
315
+ function writeState(source, value) {
316
+ if (source === null || source === void 0) return;
317
+ if (Array.isArray(source) && source.length === 2 && typeof source[1] === "function") {
318
+ source[1](value);
319
+ return;
320
+ }
321
+ if (isRefLike(source)) {
322
+ source.current = value;
323
+ return;
324
+ }
325
+ if (typeof source === "object" && !Array.isArray(source) && "value" in source && !("addEventListener" in source)) {
326
+ var _onChange;
327
+ (_onChange = source.onChange) === null || _onChange === void 0 || _onChange.call(source, value);
328
+ }
329
+ }
330
+ //#endregion
331
+ //#region isDefined/index.tsx
332
+ function isDefined(v) {
333
+ return (isRefLike(v) ? v.current : v) != null;
334
+ }
335
+ //#endregion
336
+ //#region makeDestructurable/index.tsx
337
+ /**
338
+ * Make isomorphic destructurable for object and array at the same time —
339
+ * React port of VueUse's `makeDestructurable` (a pure utility function, so it
340
+ * maps 1:1 with no React adaptation). See this blog for the underlying idea:
341
+ * https://antfu.me/posts/destructuring-with-object-or-array/
342
+ *
343
+ * Map from @vueuse/shared `makeDestructurable`
344
+ * Upstream semantics are kept verbatim: given `(obj, arr)` the returned value
345
+ * can be destructured as an object (`const { foo, bar } = obj`) or as an array
346
+ * (`const [foo, bar] = obj`) — the array mode is backed by a non-enumerable
347
+ * `Symbol.iterator` defined on a shallow clone of `obj` (spread
348
+ * `{ ...obj }`); `Object.assign` appears only in the no-Symbol SSR fallback.
349
+ *
350
+ * @example
351
+ * const foo = { name: 'foo' }
352
+ * const bar = 1024
353
+ * const obj = makeDestructurable({ foo, bar } as const, [foo, bar] as const)
354
+ * const { foo: f1, bar: b1 } = obj // object destructuring
355
+ * const [f2, b2] = obj // array destructuring
356
+ */
357
+ /* @__NO_SIDE_EFFECTS__ */
358
+ function makeDestructurable(obj, arr) {
359
+ if (typeof Symbol !== "undefined") {
360
+ const clone = { ...obj };
361
+ Object.defineProperty(clone, Symbol.iterator, {
362
+ enumerable: false,
363
+ value() {
364
+ let index = 0;
365
+ return { next: () => ({
366
+ value: arr[index++],
367
+ done: index > arr.length
368
+ }) };
369
+ }
370
+ });
371
+ return clone;
372
+ } else return Object.assign([...arr], obj);
373
+ }
374
+ //#endregion
375
+ //#region syncState/index.tsx
376
+ const neverObserved$1 = Symbol("reause.syncState.neverObserved");
377
+ function classifyWritable(source) {
378
+ if (source === null || source === void 0) return "readonly";
379
+ if (isRefLike(source)) return "sync";
380
+ if (Array.isArray(source) && source.length === 2 && typeof source[1] === "function") return "async";
381
+ if (typeof source === "object" && !Array.isArray(source) && "value" in source && !("addEventListener" in source)) return "async";
382
+ return "readonly";
383
+ }
384
+ /**
385
+ * Two-way state synchronization — keeps two writable `State<T>` sources in
386
+ * sync, with optional direction and value transforms.
387
+ *
388
+ * Map from @vueuse/shared `syncRef`
389
+ * (`source/vueuse/packages/shared/syncRef/`), renamed `syncState` for the
390
+ * React port: the two sides are `State<T>` sources — a `[value, setter]`
391
+ * tuple, a `{ value, onChange }` pair, a ref-like `{ current }`, a getter or
392
+ * a plain value — instead of Vue refs. Each side is read with `toValue` and
393
+ * written back through its writable form (tuple setter / `onChange` /
394
+ * `.current`); plain values and getters have no write path, so that side is
395
+ * treated as read-only (the sync becomes one-way for it).
396
+ *
397
+ * React Hook adaptation: upstream drives both sides through Vue's reactive
398
+ * `watchPausable`, pausing all watchers while writing so a side never echoes
399
+ * its own write back. React has no reactive system, so `syncState` is
400
+ * implemented as a hook (call it unconditionally at the top of a component).
401
+ * A `useEffect` that runs after every commit compares each side's resolved
402
+ * value with the last observed one via `Object.is` and mirrors the changed
403
+ * side into the other — through the optional `transform` convertors when
404
+ * given — recording the value it just wrote as already observed on the
405
+ * receiving side (the React analogue of upstream's pause/resume). Ref-like
406
+ * `.current` writes are synchronous and need no absorption; writes through a
407
+ * setter / `onChange` are asynchronous, so until the target's value reflects
408
+ * the write the stale pre-write value is absorbed and never mistaken for an
409
+ * external change. Read-only sides (plain values / getters) are never marked
410
+ * as written, so a changing source keeps propagating. The initial sync
411
+ * (upstream default `immediate: true`) runs in the mount effect and cascades
412
+ * ltr before rtl,
413
+ * matching upstream's watcher creation order. Because the observation happens
414
+ * post-commit, an external mutation is only adopted on the render that
415
+ * follows it — the mutation itself never schedules a render, so a bare
416
+ * `.current` write outside of React is not observed (see the maintainer
417
+ * notes on reause #40 / #41). The returned `stop` function tears the
418
+ * synchronization down; the effect also stops doing any work once the owning
419
+ * component unmounts.
420
+ *
421
+ * @example
422
+ * const [a, setA] = useState('a')
423
+ * const [b, setB] = useState('b')
424
+ *
425
+ * const stop = syncState([a, setA], [b, setB])
426
+ *
427
+ * console.log(a) // a
428
+ *
429
+ * setB('foo') // then the component re-renders
430
+ * console.log(a) // foo
431
+ *
432
+ * setA('bar') // then the component re-renders
433
+ * console.log(b) // bar
434
+ *
435
+ * stop()
436
+ */
437
+ function syncState(left, right, options = {}) {
438
+ var _transform$ltr, _transform$rtl;
439
+ const { immediate = true, direction = "both", transform = {} } = options;
440
+ const leftRef = useRef(left);
441
+ leftRef.current = left;
442
+ const rightRef = useRef(right);
443
+ rightRef.current = right;
444
+ const transformLTR = (_transform$ltr = transform.ltr) !== null && _transform$ltr !== void 0 ? _transform$ltr : ((v) => v);
445
+ const transformRTL = (_transform$rtl = transform.rtl) !== null && _transform$rtl !== void 0 ? _transform$rtl : ((v) => v);
446
+ const lastLeftRef = useRef(neverObserved$1);
447
+ const lastRightRef = useRef(neverObserved$1);
448
+ const pendingLeftRef = useRef(null);
449
+ const pendingRightRef = useRef(null);
450
+ const stoppedRef = useRef(false);
451
+ useEffect(() => {
452
+ if (stoppedRef.current) return;
453
+ const leftState = leftRef.current;
454
+ const rightState = rightRef.current;
455
+ const l = toValue(leftState);
456
+ const r = toValue(rightState);
457
+ const ltrActive = direction === "both" || direction === "ltr";
458
+ const rtlActive = direction === "both" || direction === "rtl";
459
+ if (lastLeftRef.current === neverObserved$1 && lastRightRef.current === neverObserved$1) {
460
+ let lastLeft = l;
461
+ let lastRight = r;
462
+ if (immediate) {
463
+ if (ltrActive) {
464
+ const newRight = transformLTR(l);
465
+ if (!Object.is(r, newRight)) {
466
+ const rightKind = classifyWritable(rightState);
467
+ if (rightKind !== "readonly") {
468
+ writeState(rightState, newRight);
469
+ lastRight = newRight;
470
+ if (rightKind === "async") pendingRightRef.current = {
471
+ before: r,
472
+ after: newRight
473
+ };
474
+ }
475
+ }
476
+ if (rtlActive) {
477
+ const newLeft = transformRTL(newRight);
478
+ if (!Object.is(l, newLeft)) {
479
+ const leftKind = classifyWritable(leftState);
480
+ if (leftKind !== "readonly") {
481
+ writeState(leftState, newLeft);
482
+ lastLeft = newLeft;
483
+ if (leftKind === "async") pendingLeftRef.current = {
484
+ before: l,
485
+ after: newLeft
486
+ };
487
+ }
488
+ }
489
+ }
490
+ } else {
491
+ const newLeft = transformRTL(r);
492
+ if (!Object.is(l, newLeft)) {
493
+ const leftKind = classifyWritable(leftState);
494
+ if (leftKind !== "readonly") {
495
+ writeState(leftState, newLeft);
496
+ lastLeft = newLeft;
497
+ if (leftKind === "async") pendingLeftRef.current = {
498
+ before: l,
499
+ after: newLeft
500
+ };
501
+ }
502
+ }
503
+ }
504
+ }
505
+ lastLeftRef.current = lastLeft;
506
+ lastRightRef.current = lastRight;
507
+ return;
508
+ }
509
+ const leftPending = pendingLeftRef.current;
510
+ const rightPending = pendingRightRef.current;
511
+ const leftChanged = !Object.is(lastLeftRef.current, l) && !(leftPending && Object.is(leftPending.before, l));
512
+ const rightChanged = !Object.is(lastRightRef.current, r) && !(rightPending && Object.is(rightPending.before, r));
513
+ if (leftPending && !Object.is(leftPending.before, l)) pendingLeftRef.current = null;
514
+ if (rightPending && !Object.is(rightPending.before, r)) pendingRightRef.current = null;
515
+ if (ltrActive && leftChanged) {
516
+ lastLeftRef.current = l;
517
+ const converted = transformLTR(l);
518
+ if (!Object.is(r, converted)) {
519
+ const rightKind = classifyWritable(rightState);
520
+ if (rightKind !== "readonly") {
521
+ writeState(rightState, converted);
522
+ lastRightRef.current = converted;
523
+ if (rightKind === "async") pendingRightRef.current = {
524
+ before: r,
525
+ after: converted
526
+ };
527
+ }
528
+ }
529
+ }
530
+ if (rtlActive && rightChanged) {
531
+ lastRightRef.current = r;
532
+ const converted = transformRTL(r);
533
+ if (!Object.is(l, converted)) {
534
+ const leftKind = classifyWritable(leftState);
535
+ if (leftKind !== "readonly") {
536
+ writeState(leftState, converted);
537
+ lastLeftRef.current = converted;
538
+ if (leftKind === "async") pendingLeftRef.current = {
539
+ before: l,
540
+ after: converted
541
+ };
542
+ }
543
+ }
544
+ }
545
+ });
546
+ return () => {
547
+ stoppedRef.current = true;
548
+ };
549
+ }
550
+ //#endregion
551
+ //#region syncStates/index.tsx
552
+ const neverObserved = Symbol("reause.syncStates.neverObserved");
553
+ /**
554
+ * Keep target state(s) in sync with a source value — React port of VueUse's
555
+ * `syncRefs`.
556
+ *
557
+ * Map from @vueuse/shared `syncRefs`
558
+ * (`source/vueuse/packages/shared/syncRefs/`), renamed `syncStates` for the
559
+ * React port: the source is a `State<T>` — a plain value, getter, ref-like,
560
+ * `[value, setter]` tuple or `{ value, onChange }` pair (upstream:
561
+ * `WatchSource`) — resolved with `toValue`; the targets are writable
562
+ * `State<T>` sources written back through their writable form (tuple setter /
563
+ * `onChange` / `.current`); upstream's `flush` / `deep` / `immediate` options
564
+ * are kept for signature compatibility.
565
+ *
566
+ * React Hook adaptation: upstream syncs through Vue's reactive `watch`, and
567
+ * React has no reactive system — so `syncStates` is implemented as a hook
568
+ * (call it unconditionally at the top of a component). Internally a
569
+ * `useEffect` that runs after every commit compares the resolved source value
570
+ * with the last observed one via `Object.is`; a change is written through to
571
+ * all targets. Because the observation happens post-commit, the caller must
572
+ * re-render (e.g. `setState`) for a new source value to reach the targets —
573
+ * a bare mutation outside of React is never observed (see the maintainer
574
+ * notes on reause #40 / #41). The returned `stop` function tears the
575
+ * synchronization down; the effect also stops doing any work once the owning
576
+ * component unmounts.
577
+ *
578
+ * @example
579
+ * function Form() {
580
+ * const [source, setSource] = useState('hello')
581
+ * const [target, setTarget] = useState('target')
582
+ *
583
+ * const stop = syncStates(source, [target, setTarget])
584
+ *
585
+ * // during the first render `target` is still 'target' — the sync effect
586
+ * // runs after the commit, so the source reaches the target only once the
587
+ * // component has mounted (target === 'hello' afterwards).
588
+ * // Calling `setSource('foo')` re-renders and the effect then copies 'foo'
589
+ * // into the target state on the following commit.
590
+ *
591
+ * stop()
592
+ * }
593
+ */
594
+ function syncStates(source, targets, options = {}) {
595
+ const { immediate = true } = options;
596
+ const sourceRef = useRef(source);
597
+ sourceRef.current = source;
598
+ const isTupleTarget = Array.isArray(targets) && targets.length === 2 && typeof targets[1] === "function";
599
+ const targetsArray = Array.isArray(targets) && !isTupleTarget ? targets : [targets];
600
+ const targetsRef = useRef(targetsArray);
601
+ targetsRef.current = targetsArray;
602
+ const lastValueRef = useRef(neverObserved);
603
+ const stoppedRef = useRef(false);
604
+ useEffect(() => {
605
+ if (stoppedRef.current) return;
606
+ const value = toValue(sourceRef.current);
607
+ const last = lastValueRef.current;
608
+ lastValueRef.current = value;
609
+ if (last === neverObserved) {
610
+ if (!immediate) return;
611
+ } else if (Object.is(last, value)) return;
612
+ targetsRef.current.forEach((target) => {
613
+ if (!Object.is(toValue(target), value)) writeState(target, value);
614
+ });
615
+ });
616
+ return useCallback(() => {
617
+ stoppedRef.current = true;
618
+ }, []);
619
+ }
620
+ //#endregion
621
+ //#region until/index.tsx
622
+ /**
623
+ * Polling interval (ms) used to resolve `until` promises. React has no
624
+ * reactive watch, so the port re-reads the source at this fixed interval —
625
+ * the same polling approach `useFetch` uses for its `refetch` watch.
626
+ */
627
+ const UNTIL_POLL_INTERVAL = 50;
628
+ /**
629
+ * Minimal structural equality — `Object.is` for primitives (so `NaN` equals
630
+ * `NaN`), arrays compared by length and element, plain objects by own-key
631
+ * count and value. Used by `changedTimes` when `deep: true`.
632
+ */
633
+ function deepEquals(a, b) {
634
+ if (Object.is(a, b)) return true;
635
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
636
+ const aIsArray = Array.isArray(a);
637
+ const bIsArray = Array.isArray(b);
638
+ if (aIsArray !== bIsArray) return false;
639
+ if (aIsArray && bIsArray) {
640
+ const arrA = a;
641
+ const arrB = b;
642
+ if (arrA.length !== arrB.length) return false;
643
+ return arrA.every((item, index) => deepEquals(item, arrB[index]));
644
+ }
645
+ const keysA = Object.keys(a);
646
+ const keysB = Object.keys(b);
647
+ if (keysA.length !== keysB.length) return false;
648
+ return keysA.every((key) => deepEquals(a[key], b[key]));
649
+ }
650
+ /**
651
+ * Clone used to snapshot the source between polls when `changedTimes` runs
652
+ * with `deep: true` — the poller re-reads the same reference, so a reference
653
+ * copy could never see an in-place mutation.
654
+ */
655
+ function cloneDeep(value) {
656
+ if (value === null || typeof value !== "object") return value;
657
+ if (Array.isArray(value)) return value.map((item) => cloneDeep(item));
658
+ const result = {};
659
+ for (const key of Object.keys(value)) result[key] = cloneDeep(value[key]);
660
+ return result;
661
+ }
662
+ /**
663
+ * Resolve the accepted `until` source — a plain value or a zero-argument
664
+ * getter (the React-idiomatic live source; a `Ref` / `{ current }` object is
665
+ * not accepted — pass `() => ref.current`).
666
+ *
667
+ * NOTE: a source value that *is* a function is treated as a getter and
668
+ * invoked (the same ambiguity `toValue` has).
669
+ */
670
+ function resolveSource(source) {
671
+ return typeof source === "function" ? source() : source;
672
+ }
673
+ function createUntil(r, isNot = false) {
674
+ function toMatch(condition, { timeout, throwOnTimeout } = {}) {
675
+ let stop = null;
676
+ const promises = [new Promise((resolve) => {
677
+ let settled = false;
678
+ const check = () => {
679
+ if (settled) return;
680
+ const value = resolveSource(r);
681
+ if (condition(value) !== isNot) {
682
+ settled = true;
683
+ stop === null || stop === void 0 || stop();
684
+ resolve(value);
685
+ }
686
+ };
687
+ check();
688
+ if (!settled) {
689
+ const timer = setInterval(check, UNTIL_POLL_INTERVAL);
690
+ stop = () => clearInterval(timer);
691
+ }
692
+ })];
693
+ if (timeout != null) promises.push(promiseTimeout(timeout, throwOnTimeout).then(() => resolveSource(r)).finally(() => stop === null || stop === void 0 ? void 0 : stop()));
694
+ return Promise.race(promises);
695
+ }
696
+ function toBe(value, options) {
697
+ return toMatch((v) => v === value, options);
698
+ }
699
+ function toBeTruthy(options) {
700
+ return toMatch((v) => Boolean(v), options);
701
+ }
702
+ function toBeNull(options) {
703
+ return toBe(null, options);
704
+ }
705
+ function toBeUndefined(options) {
706
+ return toBe(void 0, options);
707
+ }
708
+ function toBeNaN(options) {
709
+ return toMatch(Number.isNaN, options);
710
+ }
711
+ function toContains(value, options) {
712
+ return toMatch((v) => {
713
+ return Array.from(v).includes(value);
714
+ }, options);
715
+ }
716
+ function changed(options) {
717
+ return changedTimes(1, options);
718
+ }
719
+ function changedTimes(n = 1, options) {
720
+ var _options$deep;
721
+ let count = 0;
722
+ let hasBaseline = false;
723
+ let lastValue;
724
+ const deep = (_options$deep = options === null || options === void 0 ? void 0 : options.deep) !== null && _options$deep !== void 0 ? _options$deep : false;
725
+ const snapshot = (value) => deep ? cloneDeep(value) : value;
726
+ return toMatch((v) => {
727
+ if (!hasBaseline) {
728
+ hasBaseline = true;
729
+ lastValue = snapshot(v);
730
+ return count >= n;
731
+ }
732
+ if (deep ? !deepEquals(lastValue, v) : !Object.is(lastValue, v)) {
733
+ count += 1;
734
+ lastValue = snapshot(v);
735
+ }
736
+ return count >= n;
737
+ }, options);
738
+ }
739
+ if (Array.isArray(resolveSource(r))) return {
740
+ toMatch,
741
+ toContains,
742
+ changed,
743
+ changedTimes,
744
+ get not() {
745
+ return createUntil(r, !isNot);
746
+ }
747
+ };
748
+ else return {
749
+ toMatch,
750
+ toBe,
751
+ toBeTruthy,
752
+ toBeNull,
753
+ toBeNaN,
754
+ toBeUndefined,
755
+ changed,
756
+ changedTimes,
757
+ get not() {
758
+ return createUntil(r, !isNot);
759
+ }
760
+ };
761
+ }
762
+ function until(r) {
763
+ return createUntil(r);
764
+ }
765
+ //#endregion
766
+ //#region useArrayDifference/index.tsx
767
+ function defaultComparator(value, othVal) {
768
+ return value === othVal;
769
+ }
770
+ /**
771
+ * React port of VueUse's `useArrayDifference`.
772
+ *
773
+ * Map from @vueuse/shared `useArrayDifference`
774
+ * Mapping: upstream wraps the diff passes in `computed(...)` and returns a
775
+ * `ComputedRef`; React has no reactive value tracking, so this is a plain
776
+ * function recomputed on every render over the plain `list` / `values` arrays
777
+ * the caller passes — pass state arrays and the difference is re-diffed on the
778
+ * next render, no `.value` on the result. The same three call shapes as
779
+ * upstream are supported: plain diff, diff by `key`, and diff by `compareFn`,
780
+ * plus the `{ symmetric }` option.
781
+ *
782
+ * @see https://vueuse.org/shared/useArrayDifference/
783
+ *
784
+ * @example
785
+ * const list = [{ id: 1 }, { id: 2 }, { id: 3 }]
786
+ * useArrayDifference(list, [{ id: 3 }]) // [{ id: 1 }, { id: 2 }]
787
+ * useArrayDifference(list, [{ id: 3 }], 'id') // diff by key
788
+ * useArrayDifference(list, [{ id: 3 }], (a, b) => a.id === b.id, { symmetric: true })
789
+ */
790
+ function useArrayDifference(...args) {
791
+ var _args$, _args$2;
792
+ const list = args[0];
793
+ const values = args[1];
794
+ let compareFn = (_args$ = args[2]) !== null && _args$ !== void 0 ? _args$ : defaultComparator;
795
+ const { symmetric = false } = (_args$2 = args[3]) !== null && _args$2 !== void 0 ? _args$2 : {};
796
+ if (typeof compareFn === "string") {
797
+ const key = compareFn;
798
+ compareFn = (value, othVal) => value[key] === othVal[key];
799
+ }
800
+ const diff1 = list.filter((x) => values.findIndex((y) => compareFn(x, y)) === -1);
801
+ if (symmetric) {
802
+ const diff2 = values.filter((x) => list.findIndex((y) => compareFn(x, y)) === -1);
803
+ return [...diff1, ...diff2];
804
+ } else return diff1;
805
+ }
806
+ //#endregion
807
+ //#region useArrayEvery/index.tsx
808
+ /**
809
+ * React port of VueUse's `useArrayEvery`.
810
+ *
811
+ * Map from @vueuse/shared `useArrayEvery`
812
+ * Mapping: upstream wraps `toValue(list).every(...)` in `computed(() => ...)`
813
+ * and returns a `ComputedRef`; React has no reactive value tracking, so this
814
+ * is a plain function recomputed on every render over the plain `list` array
815
+ * the caller passes. Hold the array in `useState` (or any render-scoped value)
816
+ * and pass a new array to observe a change — the result recomputes on the next
817
+ * render. The predicate may return any value (coerced by truthiness, like
818
+ * `Array.prototype.every`).
819
+ *
820
+ * @see https://vueuse.org/shared/useArrayEvery/
821
+ *
822
+ * @example
823
+ * const [list, setList] = useState([0, 2, 4])
824
+ * useArrayEvery(list, val => val % 2 === 0) // true
825
+ * setList([0, 2, 5]) // false on the next render
826
+ *
827
+ * @param list - the array was called upon.
828
+ * @param fn - a function to test each element.
829
+ *
830
+ * @returns **true** if the `fn` function returns a **truthy** value for every element from the array. Otherwise, **false**.
831
+ */
832
+ function useArrayEvery(list, fn) {
833
+ return list.every(fn);
834
+ }
835
+ //#endregion
836
+ //#region useArrayFilter/index.tsx
837
+ function useArrayFilter(list, fn) {
838
+ return list.filter(fn);
839
+ }
840
+ //#endregion
841
+ //#region useArrayFind/index.tsx
842
+ /**
843
+ * React port of VueUse's `useArrayFind`.
844
+ *
845
+ * Map from @vueuse/shared `useArrayFind`
846
+ * Mapping: upstream wraps `toValue(list).find(...)` in `computed(() => ...)`
847
+ * and returns a `ComputedRef`; React has no reactive value tracking, so this
848
+ * is a plain function recomputed on every render over the plain `list` array
849
+ * the caller passes. Hold the array in `useState` and pass a new array to
850
+ * observe a change — the first match is returned on the next render.
851
+ *
852
+ * @see https://vueuse.org/shared/useArrayFind/
853
+ *
854
+ * @example
855
+ * const [list, setList] = useState([1, -1, 2])
856
+ * useArrayFind(list, val => val > 0) // 1
857
+ * setList([3, -1, 2]) // 3 on the next render
858
+ */
859
+ function useArrayFind(list, fn) {
860
+ return list.find(fn);
861
+ }
862
+ //#endregion
863
+ //#region useArrayFindIndex/index.tsx
864
+ /**
865
+ * React port of VueUse's `useArrayFindIndex`.
866
+ *
867
+ * Map from @vueuse/shared `useArrayFindIndex`
868
+ * Mapping: upstream wraps `toValue(list).findIndex(...)` in `computed(...)`
869
+ * and accepts a `RefOrValue`; React has no reactive value tracking, so
870
+ * this is a plain function that recomputes the index on every render — pass
871
+ * a state array (upstream: reactive array) and re-render with new state to
872
+ * see the updated result. The return is a plain number, no `.value`.
873
+ *
874
+ * @example
875
+ * const [list, setList] = useState([0, 2, 4, 6, 8])
876
+ * useArrayFindIndex(list, i => i % 2 === 0) // 0
877
+ *
878
+ * setList([1, 3, 5, 7, 9]) // result === -1 on the next render
879
+ *
880
+ * @param list - the array was called upon.
881
+ * @param fn - a function to test each element.
882
+ *
883
+ * @returns the index of the first element in the array that passes the test. Otherwise, "-1".
884
+ */
885
+ function useArrayFindIndex(list, fn) {
886
+ return list.findIndex(fn);
887
+ }
888
+ //#endregion
889
+ //#region useArrayFindLast/index.tsx
890
+ /**
891
+ * Loop equivalent of `Array.prototype.findLast` — upstream ships the same
892
+ * fallback for runtimes without the native method (e.g. node < 18); the repo
893
+ * targets lib ES2022, where the native method is not available.
894
+ */
895
+ function findLast(array, fn) {
896
+ for (let index = array.length - 1; index >= 0; index--) if (fn(array[index], index, array)) return array[index];
897
+ }
898
+ /**
899
+ * React port of VueUse's `useArrayFindLast`.
900
+ *
901
+ * Map from @vueuse/shared `useArrayFindLast`
902
+ * Mapping: upstream wraps native `Array.prototype.findLast` (with a loop
903
+ * fallback for runtimes without it) in `computed(() => ...)` and returns a
904
+ * `ComputedRef`; React has no reactive value tracking, so this is a plain
905
+ * function recomputed on every render over the plain `list` array the caller
906
+ * passes — the loop helper stands in for the native method since the repo
907
+ * targets lib ES2022. Hold the array in `useState` and pass a new array to
908
+ * observe a change — the last match is returned on the next render.
909
+ *
910
+ * @see https://vueuse.org/shared/useArrayFindLast/
911
+ *
912
+ * @example
913
+ * const [list, setList] = useState([1, -1, 2])
914
+ * useArrayFindLast(list, val => val > 0) // 2
915
+ * setList([1, -1, -2]) // 1 on the next render
916
+ */
917
+ function useArrayFindLast(list, fn) {
918
+ return findLast(list, fn);
919
+ }
920
+ //#endregion
921
+ //#region useArrayIncludes/index.tsx
922
+ const toString = Object.prototype.toString;
923
+ function isObject$1(val) {
924
+ return toString.call(val) === "[object Object]";
925
+ }
926
+ function containsProp(obj, ...props) {
927
+ return props.some((k) => k in obj);
928
+ }
929
+ function isArrayIncludesOptions(obj) {
930
+ return isObject$1(obj) && containsProp(obj, "formIndex", "comparator");
931
+ }
932
+ function useArrayIncludes(...args) {
933
+ var _comparator;
934
+ const list = args[0];
935
+ const value = args[1];
936
+ let comparator = args[2];
937
+ let formIndex = 0;
938
+ if (isArrayIncludesOptions(comparator)) {
939
+ var _comparator$fromIndex;
940
+ formIndex = (_comparator$fromIndex = comparator.fromIndex) !== null && _comparator$fromIndex !== void 0 ? _comparator$fromIndex : 0;
941
+ comparator = comparator.comparator;
942
+ }
943
+ if (typeof comparator === "string") {
944
+ const key = comparator;
945
+ comparator = (element, value) => element[key] === value;
946
+ }
947
+ comparator = (_comparator = comparator) !== null && _comparator !== void 0 ? _comparator : ((element, value) => element === value);
948
+ return list.slice(formIndex).some((element, index, arr) => comparator(element, value, index, arr));
949
+ }
950
+ //#endregion
951
+ //#region useArrayJoin/index.tsx
952
+ /**
953
+ * React port of VueUse's `useArrayJoin`.
954
+ *
955
+ * Map from @vueuse/shared `useArrayJoin`
956
+ * Mapping: upstream wraps `toValue(list).map(i => toValue(i)).join(toValue(separator))`
957
+ * in `computed(...)` and accepts a `RefOrValue`; React has no reactive
958
+ * value tracking, so this is a plain function that recomputes the join on
959
+ * every render — pass a state array (upstream: reactive array) and re-render
960
+ * with new state to see the updated result. The return is a plain string,
961
+ * no `.value`.
962
+ *
963
+ * `list` holds plain values only: the elements are joined with
964
+ * `Array.prototype.join`, so no per-element unwrap happens (upstream
965
+ * `toValue`s each element). A function element would be stringified to its
966
+ * source instead of invoked.
967
+ *
968
+ * @example
969
+ * const [list, setList] = useState(['foo', 0, { prop: 'val' }])
970
+ * useArrayJoin(list) // 'foo,0,[object Object]'
971
+ * useArrayJoin(list, '--') // 'foo--0--[object Object]'
972
+ *
973
+ * setList([...list, 'bar']) // result === 'foo--0--[object Object]--bar' on the next render
974
+ *
975
+ * @param list - the array was called upon.
976
+ * @param separator - a string to separate each pair of adjacent elements of the array. If omitted, the array elements are separated with a comma (",").
977
+ *
978
+ * @returns a string with all array elements joined. If `list.length` is 0, the empty string is returned.
979
+ */
980
+ function useArrayJoin(list, separator) {
981
+ return list.join(separator);
982
+ }
983
+ //#endregion
984
+ //#region useArrayMap/index.tsx
985
+ /**
986
+ * Reactive `Array.map`
987
+ *
988
+ * Map from @vueuse/shared `useArrayMap`
989
+ * React port of VueUse's `useArrayMap`.
990
+ *
991
+ * Mapping: Vue's `computed` → recompute per render and return a plain array
992
+ * (no `.value`) over the plain `list` array the caller passes.
993
+ * Pass a `useState` array directly — the result updates on the next render.
994
+ *
995
+ * @example
996
+ * const [list, setList] = useState([0, 1, 2, 3, 4])
997
+ * const result = useArrayMap(list, i => i * 2) // [0, 2, 4, 6, 8]
998
+ * setList(list.slice(0, -1)) // result: [0, 2, 4, 6] on the next render
999
+ */
1000
+ function useArrayMap(list, fn) {
1001
+ return list.map(fn);
1002
+ }
1003
+ //#endregion
1004
+ //#region useArrayReduce/index.tsx
1005
+ function useArrayReduce(list, reducer, ...args) {
1006
+ const reduceCallback = (sum, value, index) => reducer(sum, value, index);
1007
+ const initial = typeof args[0] === "function" ? args[0]() : args[0];
1008
+ return args.length ? list.reduce(reduceCallback, initial) : list.reduce(reduceCallback);
1009
+ }
1010
+ //#endregion
1011
+ //#region useArraySome/index.tsx
1012
+ /**
1013
+ * React port of VueUse's `useArraySome`.
1014
+ *
1015
+ * Map from @vueuse/shared `useArraySome`
1016
+ * Mapping: `computed(() => ...)` → recompute on every render — the result is a
1017
+ * plain `boolean` (no `.value`, no caching) computed from the plain `list`
1018
+ * array the caller passes. Hold the array in `useState` and pass a new array
1019
+ * to observe a change; the result recomputes on the next render.
1020
+ *
1021
+ * @see https://vueuse.org/shared/useArraySome/
1022
+ * @param list - the array was called upon.
1023
+ * @param fn - a function to test each element.
1024
+ *
1025
+ * @returns **true** if the `fn` function returns a **truthy** value for any element from the array. Otherwise, **false**.
1026
+ *
1027
+ * @example
1028
+ * const [list, setList] = useState([0, 2, 4, 6, 8])
1029
+ * const result = useArraySome(list, i => i > 10) // false
1030
+ * setList([...list, 11]) // result === true on the next render
1031
+ */
1032
+ function useArraySome(list, fn) {
1033
+ return list.some(fn);
1034
+ }
1035
+ //#endregion
1036
+ //#region useArrayUnique/index.tsx
1037
+ /**
1038
+ * Reactive `Array.unique`
1039
+ *
1040
+ * Map from @vueuse/shared `useArrayUnique`
1041
+ * React port of VueUse's `useArrayUnique`.
1042
+ *
1043
+ * Mapping: upstream wraps `toValue(list)` in `computed(() => ...)` and returns
1044
+ * a `ComputedRef`; React has no reactive value tracking, so this is a plain
1045
+ * function recomputed on every render over the plain `list` array the caller
1046
+ * passes — the result is a deduped plain array (no `.value`, no caching).
1047
+ * Duplicate detection uses a `Set` of the values (reference identity for
1048
+ * objects) unless a custom `compareFn` is given — same as upstream. Hold the
1049
+ * array in `useState` and pass a new array to observe a change.
1050
+ *
1051
+ * @see https://vueuse.org/shared/useArrayUnique/
1052
+ *
1053
+ * @example
1054
+ * const [list, setList] = useState([0, 2, 2, 4, 4, 4])
1055
+ * const result = useArrayUnique(list) // [0, 2, 4]
1056
+ *
1057
+ * setList([0, 2, 4, 6, 6]) // result === [0, 2, 4, 6] on the next render
1058
+ */
1059
+ function useArrayUnique(list, compareFn) {
1060
+ return compareFn ? uniqueElementsBy(list, compareFn) : uniq(list);
1061
+ }
1062
+ function uniq(array) {
1063
+ return Array.from(new Set(array));
1064
+ }
1065
+ function uniqueElementsBy(array, fn) {
1066
+ return array.reduce((acc, v) => {
1067
+ if (!acc.some((x) => fn(v, x, array))) acc.push(v);
1068
+ return acc;
1069
+ }, []);
1070
+ }
1071
+ //#endregion
1072
+ //#region useControllableState/index.tsx
1073
+ function isTuple(state) {
1074
+ return Array.isArray(state) && state.length === 2 && typeof state[1] === "function";
1075
+ }
1076
+ function isObjectState$1(state) {
1077
+ return typeof state === "object" && state !== null && !Array.isArray(state) && "value" in state;
1078
+ }
1079
+ /**
1080
+ * Combine controlled and uncontrolled state sources.
1081
+ *
1082
+ * `state` is resolved with `toValue` on every render. A tuple
1083
+ * `[value, setter]` or a `{ value, onChange }` pair is always controlled: the
1084
+ * current value is the resolved source and `setValue` writes through to the
1085
+ * tuple setter / `onChange`. With `passive: true` a plain value, getter, or
1086
+ * ref source is uncontrolled — the hook initializes from the source and local
1087
+ * updates persist, and external source changes are synced back (honoring
1088
+ * `shouldUpdate`). With the default `passive: false` such a source is
1089
+ * controlled (the external value wins on every render); `setValue` then has
1090
+ * no channel back to the caller, so it warns instead of silently discarding
1091
+ * the update — pass a tuple, a `{ value, onChange }` pair, or use
1092
+ * `passive: true` to write. `defaultValue` (value or lazy initializer) seeds
1093
+ * the internal state of uncontrolled sources; `shouldUpdate(prev, next)`
1094
+ * guards every commit, including the passive sync.
1095
+ */
1096
+ function useControllableState(state, options = {}) {
1097
+ const { defaultValue, shouldUpdate = (prev, next) => !Object.is(prev, next), passive = false } = options;
1098
+ const controlled = isTuple(state) || isObjectState$1(state) || !passive;
1099
+ const externalValue = toValue(state);
1100
+ const initial = defaultValue === void 0 ? externalValue : typeof defaultValue === "function" ? defaultValue() : defaultValue;
1101
+ const [internal, setInternal] = useState(initial);
1102
+ const value = controlled ? externalValue : internal;
1103
+ const valueRef = useRef(value);
1104
+ const previousExternalRef = useRef(externalValue);
1105
+ valueRef.current = value;
1106
+ const stateRef = useRef(state);
1107
+ stateRef.current = state;
1108
+ useEffect(() => {
1109
+ const canSync = typeof state === "function" || isRefLike(state) || externalValue === null || typeof externalValue !== "object";
1110
+ if (passive && canSync && !isTuple(state) && !isObjectState$1(state) && !Object.is(previousExternalRef.current, externalValue) && shouldUpdate(valueRef.current, externalValue)) setInternal(externalValue);
1111
+ previousExternalRef.current = externalValue;
1112
+ }, [
1113
+ externalValue,
1114
+ passive,
1115
+ shouldUpdate
1116
+ ]);
1117
+ return [value, useCallback((action) => {
1118
+ var _currentState$onChang;
1119
+ const prev = valueRef.current;
1120
+ const next = typeof action === "function" ? action(prev) : action;
1121
+ if (!shouldUpdate(prev, next)) return;
1122
+ const currentState = stateRef.current;
1123
+ if (isTuple(currentState)) currentState[1](next);
1124
+ else if (isObjectState$1(currentState)) (_currentState$onChang = currentState.onChange) === null || _currentState$onChang === void 0 || _currentState$onChang.call(currentState, next);
1125
+ else if (!controlled) setInternal(next);
1126
+ else assert(false, "useControllableState: `setValue` on a controlled source without a write channel (a plain value, getter, or ref with `passive: false`) is ignored. Pass a [value, setter] tuple, a { value, onChange } pair, or use `passive: true`.");
1127
+ }, [controlled, shouldUpdate])];
1128
+ }
1129
+ //#endregion
1130
+ //#region useCounter/index.tsx
1131
+ /**
1132
+ * React port of VueUse's `useCounter`.
1133
+ *
1134
+ * Map from @vueuse/shared `useCounter`
1135
+ * Mapping: `ref(initialValue)` → `useState`, mutation functions become
1136
+ * stable `useCallback`s; options are kept in refs so callbacks stay stable.
1137
+ *
1138
+ * @example
1139
+ * const { count, inc, dec, set, reset } = useCounter(10, { min: 0, max: 100 })
1140
+ */
1141
+ function useCounter(initialValue = 0, options = {}) {
1142
+ const { min = Number.NEGATIVE_INFINITY, max = Number.POSITIVE_INFINITY } = options;
1143
+ const minRef = useRef(min);
1144
+ const maxRef = useRef(max);
1145
+ const initialRef = useRef(toValue(initialValue));
1146
+ const [count, setCount] = useControllableState(initialValue, { passive: true });
1147
+ const set = useCallback((value) => {
1148
+ setCount((current) => {
1149
+ const next = clamp(value, minRef.current, maxRef.current);
1150
+ return current === next ? current : next;
1151
+ });
1152
+ }, []);
1153
+ const get = useCallback(() => count, [count]);
1154
+ return {
1155
+ count,
1156
+ inc: useCallback((delta = 1) => {
1157
+ setCount((current) => {
1158
+ const next = clamp(current + delta, minRef.current, maxRef.current);
1159
+ return current === next ? current : next;
1160
+ });
1161
+ }, []),
1162
+ dec: useCallback((delta = 1) => {
1163
+ setCount((current) => {
1164
+ const next = clamp(current - delta, minRef.current, maxRef.current);
1165
+ return current === next ? current : next;
1166
+ });
1167
+ }, []),
1168
+ get,
1169
+ set,
1170
+ reset: useCallback((val) => {
1171
+ const target = val === void 0 ? initialRef.current : val;
1172
+ initialRef.current = target;
1173
+ const next = clamp(target, minRef.current, maxRef.current);
1174
+ setCount((current) => current === next ? current : next);
1175
+ return next;
1176
+ }, [])
1177
+ };
1178
+ }
1179
+ //#endregion
1180
+ //#region useDateFormat/index.tsx
1181
+ const REGEX_PARSE = /^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[T\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/i;
1182
+ const REGEX_FORMAT = /[YMDHhms]o|\[([^\]]+)\]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a{1,2}|A{1,2}|m{1,2}|s{1,2}|Z{1,2}|z{1,4}|SSS/g;
1183
+ function defaultMeridiem(hours, minutes, isLowercase, hasPeriod) {
1184
+ let m = hours < 12 ? "AM" : "PM";
1185
+ if (hasPeriod) m = m.split("").reduce((acc, curr) => acc += `${curr}.`, "");
1186
+ return isLowercase ? m.toLowerCase() : m;
1187
+ }
1188
+ function formatOrdinal(num) {
1189
+ const suffixes = [
1190
+ "th",
1191
+ "st",
1192
+ "nd",
1193
+ "rd"
1194
+ ];
1195
+ const v = num % 100;
1196
+ return num + (suffixes[(v - 20) % 10] || suffixes[v] || suffixes[0]);
1197
+ }
1198
+ /**
1199
+ * Unwrap the house input convention — a plain value, a ref-like `{ current }`
1200
+ * or a getter function (house replacement for Vue's `toValue` /
1201
+ * `RefOrValue<T>`).
1202
+ */
1203
+ function formatDate(date, formatStr, options = {}) {
1204
+ var _options$customMeridi;
1205
+ const years = date.getFullYear();
1206
+ const month = date.getMonth();
1207
+ const days = date.getDate();
1208
+ const hours = date.getHours();
1209
+ const minutes = date.getMinutes();
1210
+ const seconds = date.getSeconds();
1211
+ const milliseconds = date.getMilliseconds();
1212
+ const day = date.getDay();
1213
+ const meridiem = (_options$customMeridi = options.customMeridiem) !== null && _options$customMeridi !== void 0 ? _options$customMeridi : defaultMeridiem;
1214
+ const stripTimeZone = (dateString) => {
1215
+ var _dateString$split$;
1216
+ return (_dateString$split$ = dateString.split(" ")[1]) !== null && _dateString$split$ !== void 0 ? _dateString$split$ : "";
1217
+ };
1218
+ const matches = {
1219
+ Yo: () => formatOrdinal(years),
1220
+ YY: () => String(years).slice(-2),
1221
+ YYYY: () => years,
1222
+ M: () => month + 1,
1223
+ Mo: () => formatOrdinal(month + 1),
1224
+ MM: () => `${month + 1}`.padStart(2, "0"),
1225
+ MMM: () => date.toLocaleDateString(options.locales, { month: "short" }),
1226
+ MMMM: () => date.toLocaleDateString(options.locales, { month: "long" }),
1227
+ D: () => String(days),
1228
+ Do: () => formatOrdinal(days),
1229
+ DD: () => `${days}`.padStart(2, "0"),
1230
+ H: () => String(hours),
1231
+ Ho: () => formatOrdinal(hours),
1232
+ HH: () => `${hours}`.padStart(2, "0"),
1233
+ h: () => `${hours % 12 || 12}`.padStart(1, "0"),
1234
+ ho: () => formatOrdinal(hours % 12 || 12),
1235
+ hh: () => `${hours % 12 || 12}`.padStart(2, "0"),
1236
+ m: () => String(minutes),
1237
+ mo: () => formatOrdinal(minutes),
1238
+ mm: () => `${minutes}`.padStart(2, "0"),
1239
+ s: () => String(seconds),
1240
+ so: () => formatOrdinal(seconds),
1241
+ ss: () => `${seconds}`.padStart(2, "0"),
1242
+ SSS: () => `${milliseconds}`.padStart(3, "0"),
1243
+ d: () => day,
1244
+ dd: () => date.toLocaleDateString(options.locales, { weekday: "narrow" }),
1245
+ ddd: () => date.toLocaleDateString(options.locales, { weekday: "short" }),
1246
+ dddd: () => date.toLocaleDateString(options.locales, { weekday: "long" }),
1247
+ A: () => meridiem(hours, minutes),
1248
+ AA: () => meridiem(hours, minutes, false, true),
1249
+ a: () => meridiem(hours, minutes, true),
1250
+ aa: () => meridiem(hours, minutes, true, true),
1251
+ z: () => stripTimeZone(date.toLocaleDateString(options.locales, { timeZoneName: "shortOffset" })),
1252
+ zz: () => stripTimeZone(date.toLocaleDateString(options.locales, { timeZoneName: "shortOffset" })),
1253
+ zzz: () => stripTimeZone(date.toLocaleDateString(options.locales, { timeZoneName: "shortOffset" })),
1254
+ zzzz: () => stripTimeZone(date.toLocaleDateString(options.locales, { timeZoneName: "longOffset" }))
1255
+ };
1256
+ return formatStr.replace(REGEX_FORMAT, (match, $1) => {
1257
+ var _ref, _matches$match;
1258
+ return (_ref = $1 !== null && $1 !== void 0 ? $1 : (_matches$match = matches[match]) === null || _matches$match === void 0 ? void 0 : _matches$match.call(matches)) !== null && _ref !== void 0 ? _ref : match;
1259
+ });
1260
+ }
1261
+ function normalizeDate(date) {
1262
+ if (date === null) return /* @__PURE__ */ new Date(NaN);
1263
+ if (date === void 0) return /* @__PURE__ */ new Date();
1264
+ if (date instanceof Date) return new Date(date);
1265
+ if (typeof date === "string" && !/Z$/i.test(date)) {
1266
+ const d = date.match(REGEX_PARSE);
1267
+ if (d) {
1268
+ const m = Number(d[2]) - 1 || 0;
1269
+ const ms = Number((d[7] || "0").substring(0, 3));
1270
+ return new Date(Number(d[1]), m, Number(d[3]) || 1, Number(d[4]) || 0, Number(d[5]) || 0, Number(d[6]) || 0, ms);
1271
+ }
1272
+ }
1273
+ return new Date(date);
1274
+ }
1275
+ /**
1276
+ * Get the formatted date according to the string of tokens passed in.
1277
+ *
1278
+ * Map from @vueuse/shared `useDateFormat`.
1279
+ *
1280
+ * React divergence: upstream wraps the result in a Vue `computed` and returns
1281
+ * `ComputedRef<string>` — this port returns a PLAIN STRING. Call it during
1282
+ * render and pass plain values (e.g. your `useState` date); the string is
1283
+ * recomputed on every render with fresh inputs. Do not read `.value` from it.
1284
+ *
1285
+ * Inputs (`date`, `formatStr`, `options.locales`) are plain read-only values
1286
+ * — pass `ref.current` or the state value; a `MaybeRefOrGetter` source must be
1287
+ * resolved by the caller (upstream types them `MaybeRefOrGetter`).
1288
+ *
1289
+ * Supported tokens (mirroring upstream 1:1, default format `HH:mm:ss`):
1290
+ * `Yo YY YYYY` — year · `M Mo MM MMM MMMM` — month (locale-aware short/long
1291
+ * names via `Intl`) · `D Do DD` — day of month · `H Ho HH` — 24-hour clock ·
1292
+ * `h ho hh` — 12-hour clock · `m mo mm` — minutes · `s so ss` — seconds ·
1293
+ * `SSS` — milliseconds (3 digits) · `d dd ddd dddd` — weekday (locale-aware
1294
+ * via `Intl`) · `A AA a aa` — meridiem, customizable via
1295
+ * `options.customMeridiem` · `z zz zzz zzzz` — timezone offset names
1296
+ * (`shortOffset` / `longOffset` via `toLocaleString`). Text wrapped in
1297
+ * brackets (`[...]`) is output literally as an escape sequence.
1298
+ *
1299
+ * @see https://vueuse.org/useDateFormat
1300
+ * @param date - The date to format, can either be a `Date` object, a timestamp, or a string
1301
+ * @param formatStr - The combination of tokens to format the date
1302
+ * @param options - UseDateFormatOptions
1303
+ *
1304
+ * @__NO_SIDE_EFFECTS__
1305
+ */
1306
+ function useDateFormat(date, formatStr = "HH:mm:ss", options = {}) {
1307
+ return formatDate(normalizeDate(date), formatStr, options);
1308
+ }
1309
+ //#endregion
1310
+ //#region useDebounceFn/index.tsx
1311
+ /**
1312
+ * Debounce execution of a function — React port of VueUse's `useDebounceFn`.
1313
+ *
1314
+ * Map from @vueuse/shared `useDebounceFn`
1315
+ * Mapping: upstream builds `createFilterWrapper(debounceFilter(ms, options), fn)`
1316
+ * so every call returns a promise and the wrapper carries `cancel` / `flush` /
1317
+ * `isPending`. This port builds the same wrapper once (`useMemo`) so its
1318
+ * identity is stable across renders; the latest `fn` / `ms` / `options` are
1319
+ * mirrored into refs so every call sees fresh values. `ms` accepts a number or
1320
+ * a ref-like `{ current }` (upstream: `RefOrValue<number>`) and is re-read on
1321
+ * every call. `isPending` becomes a non-reactive getter (React has no reactive
1322
+ * refs); promise settlement mirrors upstream — a regular debounce resolves
1323
+ * with the result, a superseded/canceled call settles with `undefined` (or
1324
+ * rejects with `rejectOnCancel`), and the `maxWait` trailing edge runs the
1325
+ * latest invocation but settles the pending promise without its result.
1326
+ * Pending timers are cleared when the component unmounts (upstream leaves
1327
+ * disposal to the effect scope).
1328
+ *
1329
+ * @example
1330
+ * const debouncedFn = useDebounceFn(() => { ... }, 1000)
1331
+ * debouncedFn()
1332
+ * debouncedFn.cancel()
1333
+ * debouncedFn.flush()
1334
+ */
1335
+ function useDebounceFn(fn, ms = 200, options = {}) {
1336
+ const fnRef = useRef(fn);
1337
+ fnRef.current = fn;
1338
+ const msRef = useRef(ms);
1339
+ msRef.current = ms;
1340
+ const optionsRef = useRef(options);
1341
+ optionsRef.current = options;
1342
+ const timerRef = useRef(null);
1343
+ const maxTimerRef = useRef(null);
1344
+ const pendingRef = useRef(false);
1345
+ const onCancelRef = useRef(noop);
1346
+ const onFlushRef = useRef(noop);
1347
+ const lastInvokeRef = useRef(noop);
1348
+ const debounced = useMemo(() => {
1349
+ const clearTimers = () => {
1350
+ if (timerRef.current !== null) {
1351
+ clearTimeout(timerRef.current);
1352
+ timerRef.current = null;
1353
+ }
1354
+ if (maxTimerRef.current !== null) {
1355
+ clearTimeout(maxTimerRef.current);
1356
+ maxTimerRef.current = null;
1357
+ }
1358
+ };
1359
+ const settleCurrent = (settleRef) => {
1360
+ const callback = settleRef.current;
1361
+ onCancelRef.current = noop;
1362
+ onFlushRef.current = noop;
1363
+ pendingRef.current = false;
1364
+ callback();
1365
+ };
1366
+ const handler = (invoke) => {
1367
+ const duration = toValue(msRef.current);
1368
+ const maxDuration = toValue(optionsRef.current.maxWait);
1369
+ if (timerRef.current !== null) {
1370
+ clearTimeout(timerRef.current);
1371
+ timerRef.current = null;
1372
+ settleCurrent(onCancelRef);
1373
+ }
1374
+ if (duration === void 0 || duration <= 0 || maxDuration !== void 0 && maxDuration <= 0) {
1375
+ clearTimers();
1376
+ pendingRef.current = false;
1377
+ try {
1378
+ return Promise.resolve(invoke());
1379
+ } catch (error) {
1380
+ return Promise.reject(error);
1381
+ }
1382
+ }
1383
+ pendingRef.current = true;
1384
+ return new Promise((resolve, reject) => {
1385
+ onCancelRef.current = optionsRef.current.rejectOnCancel ? reject : () => resolve(void 0);
1386
+ onFlushRef.current = () => resolve(invoke());
1387
+ lastInvokeRef.current = invoke;
1388
+ if (maxDuration !== void 0 && maxTimerRef.current === null) maxTimerRef.current = setTimeout(() => {
1389
+ maxTimerRef.current = null;
1390
+ if (timerRef.current !== null) {
1391
+ clearTimeout(timerRef.current);
1392
+ timerRef.current = null;
1393
+ }
1394
+ const invoke = lastInvokeRef.current;
1395
+ settleCurrent(onCancelRef);
1396
+ invoke();
1397
+ }, maxDuration);
1398
+ timerRef.current = setTimeout(() => {
1399
+ timerRef.current = null;
1400
+ if (maxTimerRef.current !== null) {
1401
+ clearTimeout(maxTimerRef.current);
1402
+ maxTimerRef.current = null;
1403
+ }
1404
+ settleCurrent(onFlushRef);
1405
+ }, duration);
1406
+ });
1407
+ };
1408
+ const cancel = () => {
1409
+ clearTimers();
1410
+ settleCurrent(onCancelRef);
1411
+ };
1412
+ const flush = () => {
1413
+ if (!pendingRef.current) return;
1414
+ clearTimers();
1415
+ settleCurrent(onFlushRef);
1416
+ };
1417
+ const wrapped = ((...args) => {
1418
+ return handler(() => fnRef.current(...args));
1419
+ });
1420
+ wrapped.cancel = cancel;
1421
+ wrapped.flush = flush;
1422
+ Object.defineProperty(wrapped, "isPending", {
1423
+ enumerable: true,
1424
+ get: () => pendingRef.current
1425
+ });
1426
+ return wrapped;
1427
+ }, []);
1428
+ useEffect(() => debounced.cancel, [debounced]);
1429
+ return debounced;
1430
+ }
1431
+ //#endregion
1432
+ //#region useInterval/index.tsx
1433
+ function useInterval(interval = 1e3, options = {}) {
1434
+ const { controls: exposeControls = false, immediate = true, callback, immediateCallback = false } = options;
1435
+ const intervalRef = useRef(interval);
1436
+ intervalRef.current = interval;
1437
+ const callbackRef = useRef(callback);
1438
+ callbackRef.current = callback;
1439
+ const immediateCallbackRef = useRef(immediateCallback);
1440
+ immediateCallbackRef.current = immediateCallback;
1441
+ const [counter, setCounter] = useState(0);
1442
+ const counterRef = useRef(0);
1443
+ const [isActive, setIsActive] = useState(() => immediate && toValue(interval) > 0);
1444
+ const isActiveRef = useRef(false);
1445
+ const timerRef = useRef(null);
1446
+ const tick = useCallback(() => {
1447
+ var _callbackRef$current;
1448
+ counterRef.current += 1;
1449
+ setCounter(counterRef.current);
1450
+ (_callbackRef$current = callbackRef.current) === null || _callbackRef$current === void 0 || _callbackRef$current.call(callbackRef, counterRef.current);
1451
+ }, []);
1452
+ const reset = useCallback(() => {
1453
+ counterRef.current = 0;
1454
+ setCounter(0);
1455
+ }, []);
1456
+ const pause = useCallback(() => {
1457
+ isActiveRef.current = false;
1458
+ setIsActive(false);
1459
+ if (timerRef.current !== null) {
1460
+ clearInterval(timerRef.current);
1461
+ timerRef.current = null;
1462
+ }
1463
+ }, []);
1464
+ const resume = useCallback(() => {
1465
+ const ms = toValue(intervalRef.current);
1466
+ if (ms <= 0) return;
1467
+ isActiveRef.current = true;
1468
+ setIsActive(true);
1469
+ if (immediateCallbackRef.current) tick();
1470
+ if (timerRef.current !== null) {
1471
+ clearInterval(timerRef.current);
1472
+ timerRef.current = null;
1473
+ }
1474
+ timerRef.current = setInterval(tick, ms);
1475
+ }, [tick]);
1476
+ const startedRef = useRef(false);
1477
+ useEffect(() => {
1478
+ if (immediate && !startedRef.current) {
1479
+ startedRef.current = true;
1480
+ resume();
1481
+ }
1482
+ return pause;
1483
+ }, [immediate, resume]);
1484
+ const mountedRef = useRef(false);
1485
+ const intervalMs = toValue(interval);
1486
+ useEffect(() => {
1487
+ if (!mountedRef.current) {
1488
+ mountedRef.current = true;
1489
+ return;
1490
+ }
1491
+ if (isActiveRef.current) resume();
1492
+ }, [intervalMs, resume]);
1493
+ if (exposeControls) return {
1494
+ counter,
1495
+ reset,
1496
+ isActive,
1497
+ pause,
1498
+ resume
1499
+ };
1500
+ return counter;
1501
+ }
1502
+ //#endregion
1503
+ //#region useIntervalFn/index.tsx
1504
+ /**
1505
+ * React port of VueUse's `useIntervalFn` — wrapper for `setInterval` with
1506
+ * controls.
1507
+ *
1508
+ * Map from @vueuse/shared `useIntervalFn`
1509
+ * Mapping: upstream accepts `RefOrValue<number>` for the interval — this
1510
+ * port accepts a plain `number`. `isActive` is a boolean state (upstream: a
1511
+ * readonly shallow ref), also mirrored in a ref so `resume()` can check it
1512
+ * synchronously right after `immediateCallback` fires the callback — the
1513
+ * callback may `pause()` itself ("pause in callback"). The timer is scheduled
1514
+ * in a mount effect (upstream starts synchronously during setup) and cleared
1515
+ * on unmount via effect cleanup; changing the interval while active restarts
1516
+ * the timer (upstream: a `watch` on the interval calls `resume()`). The
1517
+ * callback, interval and options are kept in refs so every tick and restart
1518
+ * uses the newest ones.
1519
+ *
1520
+ * @example
1521
+ * const { isActive, pause, resume } = useIntervalFn(() => { ... }, 1000)
1522
+ */
1523
+ function useIntervalFn(cb, interval = 1e3, options = {}) {
1524
+ const { immediate = true, immediateCallback = false } = options;
1525
+ const [isActive, setIsActive] = useState(false);
1526
+ const isActiveRef = useRef(false);
1527
+ const cbRef = useRef(cb);
1528
+ const intervalRef = useRef(interval);
1529
+ const immediateCallbackRef = useRef(immediateCallback);
1530
+ const timerRef = useRef(null);
1531
+ cbRef.current = cb;
1532
+ intervalRef.current = interval;
1533
+ immediateCallbackRef.current = immediateCallback;
1534
+ const immediateRef = useRef(immediate);
1535
+ function clean() {
1536
+ if (timerRef.current) {
1537
+ clearInterval(timerRef.current);
1538
+ timerRef.current = null;
1539
+ }
1540
+ }
1541
+ function setActive(active) {
1542
+ isActiveRef.current = active;
1543
+ setIsActive(active);
1544
+ }
1545
+ const pause = useCallback(() => {
1546
+ setActive(false);
1547
+ clean();
1548
+ }, []);
1549
+ const resume = useCallback(() => {
1550
+ const intervalValue = intervalRef.current;
1551
+ if (intervalValue <= 0) return;
1552
+ setActive(true);
1553
+ if (immediateCallbackRef.current) cbRef.current();
1554
+ clean();
1555
+ if (isActiveRef.current) timerRef.current = setInterval(() => cbRef.current(), intervalValue);
1556
+ }, []);
1557
+ useEffect(() => {
1558
+ if (immediateRef.current) resume();
1559
+ return () => {
1560
+ clean();
1561
+ };
1562
+ }, [resume]);
1563
+ const mountedRef = useRef(false);
1564
+ useEffect(() => {
1565
+ if (!mountedRef.current) {
1566
+ mountedRef.current = true;
1567
+ return;
1568
+ }
1569
+ if (isActiveRef.current) resume();
1570
+ }, [interval, resume]);
1571
+ return {
1572
+ isActive,
1573
+ pause,
1574
+ resume
1575
+ };
1576
+ }
1577
+ //#endregion
1578
+ //#region useLastChanged/index.tsx
1579
+ function useLastChanged(value, options = {}) {
1580
+ const [lastChanged, setLastChanged] = useState(() => {
1581
+ var _options$initialValue;
1582
+ return (_options$initialValue = options.initialValue) !== null && _options$initialValue !== void 0 ? _options$initialValue : null;
1583
+ });
1584
+ const prevValue = useRef(value);
1585
+ useEffect(() => {
1586
+ if (!Object.is(prevValue.current, value)) {
1587
+ prevValue.current = value;
1588
+ setLastChanged(timestamp());
1589
+ }
1590
+ });
1591
+ return lastChanged;
1592
+ }
1593
+ //#endregion
1594
+ //#region useListener/index.tsx
1595
+ /**
1596
+ * React port of the `useListener` protocol — bind a callback to an event
1597
+ * registration function returned by a reause hook, with automatic cleanup
1598
+ * on unmount.
1599
+ *
1600
+ * Map from @reause/shared `useListener` (protocol: #129)
1601
+ * Motivation: hooks like `useFileDialog` return `onChange` / `onCancel`
1602
+ * registration functions (upstream `EventHookOn`). In Vue those auto-clean
1603
+ * via the effect scope; in React we need a hook to own that lifecycle.
1604
+ * `useListener` registers `cb` with `on` on mount and, when `on` returns an
1605
+ * `off` function, calls it on unmount, so listeners are cleaned up and
1606
+ * callbacks never fire after the component is gone. (An `on` that returns
1607
+ * nothing provides no cleanup — nothing can be released.) The callback is
1608
+ * kept in a ref, so changing `cb` across renders does not re-register — the
1609
+ * latest callback is used by the already-registered listener. If `on` itself
1610
+ * changes (a new hook instance), the effect re-runs and re-registers.
1611
+ *
1612
+ * @example
1613
+ * const { files, open, onChange } = useFileDialog()
1614
+ * useListener(onChange, (files) => { console.log(files) })
1615
+ */
1616
+ function useListener(on, cb) {
1617
+ const cbRef = useRef(cb);
1618
+ cbRef.current = cb;
1619
+ useEffect(() => {
1620
+ if (typeof on !== "function") return;
1621
+ const result = on(((...args) => cbRef.current(...args)));
1622
+ return () => {
1623
+ var _result$off;
1624
+ result === null || result === void 0 || (_result$off = result.off) === null || _result$off === void 0 || _result$off.call(result);
1625
+ };
1626
+ }, [on]);
1627
+ }
1628
+ //#endregion
1629
+ //#region useMount/index.tsx
1630
+ /**
1631
+ * React port of react-use's `useMount`.
1632
+ *
1633
+ * Map from react-use `useMount`.
1634
+ * Runs `fn` exactly once after the component mounts.
1635
+ *
1636
+ * @example
1637
+ * useMount(() => {
1638
+ * trackPageView()
1639
+ * })
1640
+ */
1641
+ function useMount(fn) {
1642
+ useEffect(() => {
1643
+ fn();
1644
+ }, []);
1645
+ }
1646
+ //#endregion
1647
+ //#region useStateAutoReset/index.tsx
1648
+ /**
1649
+ * A state which will be reset to the default value after some time.
1650
+ *
1651
+ * Map from @vueuse/shared `refAutoReset`
1652
+ * (`source/vueuse/packages/shared/refAutoReset/`). Upstream returns a single
1653
+ * writable Vue ref; per this repo's `useState*` family convention the return
1654
+ * is the React `[value, setValue]` tuple — `value` is the state, `setValue`
1655
+ * is a `useState`-style setter (value or updater form, `Dispatch<SetStateAction>`)
1656
+ * that also (re)schedules a timer to restore `defaultValue` after `afterMs`
1657
+ * milliseconds. `defaultValue` accepts the shared `State<T>` form (plain value,
1658
+ * lazy getter, ref-like object, state tuple, or controlled `{ value, onChange }` pair).
1659
+ * `afterMs` accepts the shared `RefOrValue<number>` form and is resolved with `toValue` at fire time
1660
+ * (upstream: `toValue`); the pending timer is cleared on unmount (upstream:
1661
+ * `tryOnScopeDispose`, timers in the effect scope). The deprecated `autoResetRef`
1662
+ * alias is not ported.
1663
+ *
1664
+ * @param defaultValue The value which will be set.
1665
+ * @param afterMs A zero-or-greater delay in milliseconds.
1666
+ * @example
1667
+ * const [message, setMessage] = useStateAutoReset('default message', 1000)
1668
+ *
1669
+ * function handleMessage() {
1670
+ * setMessage('message has set') // resets to 'default message' after 1000ms
1671
+ * }
1672
+ */
1673
+ function useStateAutoReset(defaultValue, afterMs = 1e4) {
1674
+ const [value, setValue] = useControllableState(defaultValue, { passive: true });
1675
+ const defaultValueRef = useRef(defaultValue);
1676
+ const afterMsRef = useRef(afterMs);
1677
+ const timerRef = useRef(null);
1678
+ defaultValueRef.current = defaultValue;
1679
+ afterMsRef.current = afterMs;
1680
+ const scheduleReset = useCallback(() => {
1681
+ if (timerRef.current) clearTimeout(timerRef.current);
1682
+ timerRef.current = setTimeout(() => {
1683
+ timerRef.current = null;
1684
+ setValue(toValue(defaultValueRef.current));
1685
+ }, toValue(afterMsRef.current));
1686
+ }, []);
1687
+ const setValueWithReset = useCallback((next) => {
1688
+ setValue(next);
1689
+ scheduleReset();
1690
+ }, [scheduleReset]);
1691
+ useEffect(() => () => {
1692
+ if (timerRef.current) clearTimeout(timerRef.current);
1693
+ }, []);
1694
+ return [value, setValueWithReset];
1695
+ }
1696
+ //#endregion
1697
+ //#region useStateDebounced/index.tsx
1698
+ /**
1699
+ * Debounce updates of a state value — React port of VueUse's `refDebounced`.
1700
+ *
1701
+ * Map from @vueuse/shared `refDebounced`
1702
+ * Mapping: upstream takes a Vue `Ref<T>` and returns a readonly ref that only
1703
+ * flips to the latest source value once it stops changing for `ms` (a watcher
1704
+ * hands every change to `useDebounceFn`). The naming follows this repo's
1705
+ * `ref* → useState*` rule (`refDebounced` → `useStateDebounced`), the Vue
1706
+ * `Ref<T>` input becomes a plain initial value, and the readonly ref becomes
1707
+ * an extra state slot — so the hook returns the tuple
1708
+ * `[value, setValue, debounced]`:
1709
+ *
1710
+ * ```ts
1711
+ * const [input, setInput, debounced] = useStateDebounced('foo', 1000)
1712
+ *
1713
+ * setInput('bar')
1714
+ * console.log(debounced) // 'foo' — flips to 'bar' once the debounce elapses
1715
+ * ```
1716
+ *
1717
+ * `value` is the source state, `setValue` its setter, and `debounced` lags
1718
+ * behind it by `ms`. Writes settle through a `useDebounceFn` updater, so a
1719
+ * burst of writes collapses into a single trailing update carrying the last
1720
+ * written value. `ms` (and `options.maxWait`) accept a plain number or a
1721
+ * ref-like `{ current }` (upstream: `RefOrValue<number>`) and are re-read on
1722
+ * every write; pending timers are cleared when the component unmounts
1723
+ * (upstream disposes with the effect scope). Note: a write only schedules the
1724
+ * debounce when the value actually changes — writing the same value is
1725
+ * skipped by `useControllableState`'s `Object.is` guard, so the pending timer
1726
+ * is not re-delayed (upstream's `watch` re-delays on every source write, even
1727
+ * unchanged ones).
1728
+ *
1729
+ * @example
1730
+ * ```ts
1731
+ * const [value, setValue, debounced] = useStateDebounced('foo', 1000)
1732
+ * ```
1733
+ */
1734
+ function useStateDebounced(value, ms = 200, options = {}) {
1735
+ const [state, setState] = useControllableState(value, { passive: true });
1736
+ const [debounced, setDebounced] = useState(state);
1737
+ const stateRef = useRef(state);
1738
+ stateRef.current = state;
1739
+ const updater = useDebounceFn(() => {
1740
+ setDebounced(stateRef.current);
1741
+ }, ms, options);
1742
+ const isFirstRunRef = useRef(true);
1743
+ useEffect(() => {
1744
+ if (isFirstRunRef.current) {
1745
+ isFirstRunRef.current = false;
1746
+ return;
1747
+ }
1748
+ updater();
1749
+ }, [state, updater]);
1750
+ return [
1751
+ state,
1752
+ setState,
1753
+ debounced
1754
+ ];
1755
+ }
1756
+ //#endregion
1757
+ //#region useStateDefault/index.tsx
1758
+ /**
1759
+ * A state tuple (`[value, setter]`) — mirrors `toValue`'s tuple branch.
1760
+ */
1761
+ function isStateTuple(source) {
1762
+ return Array.isArray(source) && source.length === 2 && typeof source[1] === "function";
1763
+ }
1764
+ /**
1765
+ * A `{ value, onChange }` source — mirrors `toValue`'s object branch (the
1766
+ * `addEventListener` guard keeps DOM-ish objects out, like `toValue`).
1767
+ */
1768
+ function isObjectState(source) {
1769
+ return typeof source === "object" && source !== null && !Array.isArray(source) && "value" in source && !("addEventListener" in source);
1770
+ }
1771
+ /**
1772
+ * A ref-like `{ current }` source.
1773
+ */
1774
+ function isRefState(source) {
1775
+ return source !== null && source !== void 0 && typeof source === "object" && "current" in source;
1776
+ }
1777
+ /**
1778
+ * Apply default value to a ref-like source — React port of VueUse's
1779
+ * `refDefault` renamed to `useStateDefault` (this repo's naming for the
1780
+ * `ref*` family; upstream's single writable computed ref becomes a tuple).
1781
+ *
1782
+ * Map from @vueuse/shared `refDefault`
1783
+ * Mapping: upstream derives a writable `computed` from a source
1784
+ * `Ref<T | undefined | null>` — it reads `source.value ?? defaultValue` and
1785
+ * writes back to `source.value`. This port accepts a `State<T | undefined |
1786
+ * null>` — a plain value, a ref-like object (`{ current }`, e.g. the first
1787
+ * tuple element of `useStorage`), a getter, a `[value, setter]` tuple or a
1788
+ * `{ value, onChange }` pair — and returns the React tuple
1789
+ * `const [value, setValue] = useStateDefault(raw, 'default')`. `value` is
1790
+ * derived on every render from the source through `toValue` (`source.current
1791
+ * ?? defaultValue`), so it always reflects the source's current value —
1792
+ * including writes made from outside the component; `setValue` resolves the
1793
+ * next value (value or updater form), writes it through to the source (its
1794
+ * `current`, its setter or its `onChange`) and bumps a local version counter
1795
+ * so the derived `value` re-renders. SSR-safe: nothing touches the DOM and the
1796
+ * first server render already shows the default.
1797
+ *
1798
+ * @param source The `State<T | undefined | null>` source holding the
1799
+ * value — read through `toValue` on every render and
1800
+ * written back to `current` / the tuple setter /
1801
+ * `onChange` on `setValue`.
1802
+ * @param defaultValue The value displayed while the source is `null` or
1803
+ * `undefined`.
1804
+ * @return A tuple `[value, setValue]` — the current value (source value or
1805
+ * `defaultValue`) and its setter.
1806
+ *
1807
+ * @example
1808
+ * const raw = { current: undefined as string | undefined }
1809
+ * const [value, setValue] = useStateDefault(raw, 'default')
1810
+ *
1811
+ * setValue('hello')
1812
+ * console.log(value) // 'hello' after the next render (React derives at render)
1813
+ *
1814
+ * setValue(undefined)
1815
+ * console.log(value) // 'default' after the next render
1816
+ */
1817
+ function useStateDefault(source, defaultValue) {
1818
+ var _toValue;
1819
+ const sourceRef = useRef(source);
1820
+ sourceRef.current = source;
1821
+ const [, setVersion] = useState(0);
1822
+ const bump = () => {
1823
+ setVersion((current) => current + 1);
1824
+ };
1825
+ const setValue = useCallback((next) => {
1826
+ const current = toValue(sourceRef.current);
1827
+ const resolved = typeof next === "function" ? next(current) : next;
1828
+ const currentSource = sourceRef.current;
1829
+ if (isStateTuple(currentSource)) {
1830
+ currentSource[1](resolved);
1831
+ bump();
1832
+ } else if (isObjectState(currentSource)) {
1833
+ var _currentSource$onChan;
1834
+ (_currentSource$onChan = currentSource.onChange) === null || _currentSource$onChan === void 0 || _currentSource$onChan.call(currentSource, resolved);
1835
+ bump();
1836
+ } else if (isRefState(currentSource)) {
1837
+ currentSource.current = resolved;
1838
+ bump();
1839
+ }
1840
+ }, []);
1841
+ return [(_toValue = toValue(source)) !== null && _toValue !== void 0 ? _toValue : defaultValue, setValue];
1842
+ }
1843
+ //#endregion
1844
+ //#region useStateManualReset/index.tsx
1845
+ /**
1846
+ * A controlled source — a `[value, setter]` tuple or a `{ value, onChange }`
1847
+ * object. These have no stored default of their own: `toValue` returns the
1848
+ * live value, so resetting to it would be a no-op. Reset therefore restores
1849
+ * the initial argument value instead.
1850
+ */
1851
+ function isControlledSource$1(state) {
1852
+ return Array.isArray(state) && state.length === 2 && typeof state[1] === "function" || typeof state === "object" && state !== null && !Array.isArray(state) && "value" in state;
1853
+ }
1854
+ /**
1855
+ * React port of VueUse's `refManualReset`.
1856
+ *
1857
+ * Map from @vueuse/shared `refManualReset`
1858
+ * (`source/vueuse/packages/shared/refManualReset/`). Create a state with
1859
+ * manual reset functionality — any update can be reverted back to the initial
1860
+ * value with the returned `reset` function.
1861
+ *
1862
+ * Upstream returns a writable Vue `Ref<T>` extended with a `reset` method
1863
+ * (built on `customRef`). Per this repo's naming rules the port is renamed to
1864
+ * `useStateManualReset` and the ref becomes a `[value, setValue, reset]`
1865
+ * tuple: the second element is the plain `useState` setter (value or updater
1866
+ * form), and `reset` restores the default value.
1867
+ *
1868
+ * The state input accepts the shared `State<T>` form: a value, getter, ref-like
1869
+ * object, state tuple, or controlled `{ value, onChange }` object. `reset`
1870
+ * re-reads the input on every call, so plain, getter and ref-like sources
1871
+ * reset to the latest source value (matching upstream's
1872
+ * `value = toValue(defaultValue)`); tuple / `{ value, onChange }` (controlled)
1873
+ * sources have no stored default, so they restore the initial argument value.
1874
+ *
1875
+ * @example
1876
+ * const [message, setMessage, resetMessage] = useStateManualReset('default message')
1877
+ * setMessage('message has set')
1878
+ * resetMessage()
1879
+ * console.log(message) // 'default message'
1880
+ */
1881
+ function useStateManualReset(value) {
1882
+ const valueRef = useRef(value);
1883
+ valueRef.current = value;
1884
+ const initialDefaultRef = useRef(toValue(value));
1885
+ const [state, setState] = useControllableState(value, { passive: true });
1886
+ return [
1887
+ state,
1888
+ setState,
1889
+ useCallback(() => {
1890
+ const current = valueRef.current;
1891
+ setState(isControlledSource$1(current) ? initialDefaultRef.current : toValue(current));
1892
+ }, [])
1893
+ ];
1894
+ }
1895
+ //#endregion
1896
+ //#region useThrottleFn/index.tsx
1897
+ /**
1898
+ * Throttle execution of a function — React port of VueUse's `useThrottleFn`.
1899
+ * Especially useful for rate limiting execution of handlers on events like
1900
+ * resize and scroll.
1901
+ *
1902
+ * Map from @vueuse/shared `useThrottleFn`
1903
+ * Mapping: upstream builds `createFilterWrapper(throttleFilter(ms, trailing,
1904
+ * leading, rejectOnCancel), fn)` and returns a plain `PromisifyFn<T>` — the
1905
+ * throttled wrapper carries no `cancel` / `flush` / `isPending` (unlike the
1906
+ * debounce filter, upstream's `throttleFilter` is not cancelable), so this
1907
+ * port mirrors that: the return value is the wrapped function and nothing
1908
+ * more. The wrapper is built once (`useMemo`) so its identity is stable
1909
+ * across renders — safe to add/remove in effects; the latest `fn` / `ms` /
1910
+ * `trailing` / `leading` / `rejectOnCancel` are mirrored into refs so every
1911
+ * call sees fresh values (upstream captures the flags once, at filter
1912
+ * creation). `ms` accepts a number or a ref-like `{ current: number }`
1913
+ * (upstream: `RefOrValue<number>`) and is re-read on every call. The
1914
+ * throttle filter logic is inlined (upstream: `utils/filters.ts`
1915
+ * `throttleFilter` — leading/trailing timestamps with a trailing invoke on
1916
+ * window end). The wrapper is cleaned up on unmount: any pending trailing
1917
+ * timer is cleared when the component unmounts — a React hygiene measure;
1918
+ * upstream registers no disposal at all (`@__NO_SIDE_EFFECTS__`), so a
1919
+ * pending call would still fire there after teardown.
1920
+ *
1921
+ * @param fn A function to be executed after delay milliseconds. The `this` context and all arguments are passed through, as-is,
1922
+ * to `callback` when the throttled-function is executed.
1923
+ * @param ms A zero-or-greater delay in milliseconds. For event callbacks, values around 100 or 250 (or even higher) are most useful.
1924
+ * (default value: 200)
1925
+ *
1926
+ * @param [trailing] if true, call fn again after the time is up (default value: true)
1927
+ *
1928
+ * @param [leading] if true, call fn on the leading edge of the ms timeout (default value: true)
1929
+ *
1930
+ * @param [rejectOnCancel] if true, reject the last call if it's been cancel (default value: false)
1931
+ *
1932
+ * @return A new, throttled, function.
1933
+ *
1934
+ * @example
1935
+ * const throttledFn = useThrottleFn(() => { ... }, 1000)
1936
+ * throttledFn()
1937
+ */
1938
+ function useThrottleFn(fn, ms = 200, trailing = true, leading = true, rejectOnCancel = false) {
1939
+ const fnRef = useRef(fn);
1940
+ fnRef.current = fn;
1941
+ const msRef = useRef(ms);
1942
+ msRef.current = ms;
1943
+ const trailingRef = useRef(trailing);
1944
+ trailingRef.current = trailing;
1945
+ const leadingRef = useRef(leading);
1946
+ leadingRef.current = leading;
1947
+ const rejectOnCancelRef = useRef(rejectOnCancel);
1948
+ rejectOnCancelRef.current = rejectOnCancel;
1949
+ const { throttled, clear } = useMemo(() => {
1950
+ let lastExec = 0;
1951
+ let timer;
1952
+ let isLeading = true;
1953
+ let lastRejector = noop;
1954
+ let lastValue;
1955
+ const clear = () => {
1956
+ if (timer !== void 0) {
1957
+ clearTimeout(timer);
1958
+ timer = void 0;
1959
+ lastRejector();
1960
+ lastRejector = noop;
1961
+ }
1962
+ };
1963
+ const handler = (_invoke) => {
1964
+ const duration = toValue(msRef.current);
1965
+ const elapsed = Date.now() - lastExec;
1966
+ const invoke = () => {
1967
+ return lastValue = _invoke();
1968
+ };
1969
+ clear();
1970
+ if (duration === void 0 || duration <= 0) {
1971
+ lastExec = Date.now();
1972
+ return invoke();
1973
+ }
1974
+ if (elapsed > duration) {
1975
+ lastExec = Date.now();
1976
+ if (leadingRef.current || !isLeading) invoke();
1977
+ } else if (trailingRef.current) lastValue = new Promise((resolve, reject) => {
1978
+ lastRejector = rejectOnCancelRef.current ? reject : resolve;
1979
+ timer = setTimeout(() => {
1980
+ lastExec = Date.now();
1981
+ isLeading = true;
1982
+ resolve(invoke());
1983
+ clear();
1984
+ }, Math.max(0, duration - elapsed));
1985
+ });
1986
+ if (!leadingRef.current && timer === void 0) timer = setTimeout(() => {
1987
+ isLeading = true;
1988
+ }, duration);
1989
+ isLeading = false;
1990
+ return lastValue;
1991
+ };
1992
+ const throttled = ((...args) => {
1993
+ return new Promise((resolve, reject) => {
1994
+ Promise.resolve(handler(() => fnRef.current(...args))).then(resolve).catch(reject);
1995
+ });
1996
+ });
1997
+ return {
1998
+ throttled,
1999
+ clear
2000
+ };
2001
+ }, []);
2002
+ useEffect(() => () => clear(), [clear]);
2003
+ return throttled;
2004
+ }
2005
+ //#endregion
2006
+ //#region useStateThrottled/index.tsx
2007
+ /**
2008
+ * Throttle changing of a state value — React port of VueUse's `refThrottled`.
2009
+ *
2010
+ * The `value` argument accepts any `State<T>` supported by
2011
+ * `useControllableState`: a plain value, lazy initializer, controlled tuple,
2012
+ * or `{ value, onChange }` source. The returned tuple contains the current
2013
+ * value, its setter, and a throttled mirror.
2014
+ *
2015
+ * A `delay <= 0` short-circuits like upstream (`if (delay <= 0) return value`):
2016
+ * the throttled element is the input itself — no throttling, no timers.
2017
+ *
2018
+ * @param value State source accepted by `useControllableState`.
2019
+ * @param delay Delay in milliseconds between commits (default: 200).
2020
+ * @param trailing Whether to commit the latest value after the window (default: true).
2021
+ * @param leading Whether to commit on the leading edge (default: true).
2022
+ */
2023
+ function useStateThrottled(value, delay = 200, trailing = true, leading = true) {
2024
+ const [input, setInput] = useControllableState(value, { passive: true });
2025
+ const [throttled, setThrottled] = useState(input);
2026
+ const inputRef = useRef(input);
2027
+ inputRef.current = input;
2028
+ const delayRef = useRef(delay);
2029
+ delayRef.current = delay;
2030
+ const throttledFn = useThrottleFn(() => {
2031
+ setThrottled(inputRef.current);
2032
+ }, delay, trailing, leading);
2033
+ const isFirstRunRef = useRef(true);
2034
+ useEffect(() => {
2035
+ if (isFirstRunRef.current) {
2036
+ isFirstRunRef.current = false;
2037
+ return;
2038
+ }
2039
+ if (delayRef.current > 0) throttledFn();
2040
+ }, [input, throttledFn]);
2041
+ if (delay <= 0) return [
2042
+ input,
2043
+ setInput,
2044
+ input
2045
+ ];
2046
+ return [
2047
+ input,
2048
+ setInput,
2049
+ throttled
2050
+ ];
2051
+ }
2052
+ //#endregion
2053
+ //#region useStateWithControl/index.tsx
2054
+ /**
2055
+ * A controlled source — a `[value, setter]` tuple or a `{ value, onChange }`
2056
+ * object — whose value is owned by the caller (re-rendered externally).
2057
+ */
2058
+ function isControlledSource(state) {
2059
+ return Array.isArray(state) && state.length === 2 && typeof state[1] === "function" || typeof state === "object" && state !== null && !Array.isArray(state) && "value" in state;
2060
+ }
2061
+ /**
2062
+ * Fine-grained controls over a state and its re-renders — React port of
2063
+ * VueUse's `refWithControl`.
2064
+ *
2065
+ * Map from @vueuse/shared `refWithControl`
2066
+ * (`source/vueuse/packages/shared/refWithControl/`). Upstream returns a single
2067
+ * writable Vue `Ref` extended with `get` / `set` / `untrackedGet` /
2068
+ * `silentSet` / `peek` / `lay`. This port owns the state like a `useState` and
2069
+ * returns the React tuple `const [num, setNum, control] = useStateWithControl(0)`
2070
+ * — the name follows this repo's `ref*` → `useState*` mapping rule. `setNum`
2071
+ * behaves like a normal `setState` (value or updater form — the updater base
2072
+ * is the current internal value, which may be ahead of the rendered value
2073
+ * after a silent write), while `control`
2074
+ * keeps the fine-grained get/set pair: `set(value, false)` (and `lay` /
2075
+ * `silentSet`) updates the value without re-rendering (upstream: without
2076
+ * triggering reactivity), and `peek` / `untrackedGet` read it back — in React
2077
+ * there is no dependency tracking during render, so those are plain aliases
2078
+ * for the current value. `reset()` (a small addition, upstream has no
2079
+ * equivalent) restores the initial value and participates in the change
2080
+ * callbacks (`onBeforeChange` can dismiss it, `onChanged` fires when
2081
+ * accepted). Option names are kept from upstream:
2082
+ * `onBeforeChange` can dismiss a change by returning `false`, and `onChanged`
2083
+ * fires synchronously after an accepted change.
2084
+ *
2085
+ * @param state State source: a plain value, getter, ref-like value, state
2086
+ * tuple, or `{ value, onChange }` controllable state.
2087
+ * @param options
2088
+ * @return A tuple `[value, setValue, control]` — the current value, a
2089
+ * `setState`-like setter and the fine-grained control object.
2090
+ *
2091
+ * @example
2092
+ * const [num, setNum, control] = useStateWithControl(0)
2093
+ *
2094
+ * setNum(42) // just like a normal useState setter
2095
+ * control.set(30, false) // set the value without re-rendering
2096
+ * control.peek() // get the value without tracking
2097
+ */
2098
+ function useStateWithControl(state, options = {}) {
2099
+ const { onBeforeChange, onChanged } = options;
2100
+ const initialRef = useRef(toValue(state));
2101
+ const sourceRef = useRef(initialRef.current);
2102
+ const [value, setState] = useControllableState(state, { passive: true });
2103
+ const lastTriggeredRef = useRef(void 0);
2104
+ if (isControlledSource(state) && !Object.is(value, lastTriggeredRef.current)) {
2105
+ sourceRef.current = value;
2106
+ lastTriggeredRef.current = value;
2107
+ }
2108
+ const callbacksRef = useRef({
2109
+ onBeforeChange,
2110
+ onChanged
2111
+ });
2112
+ callbacksRef.current = {
2113
+ onBeforeChange,
2114
+ onChanged
2115
+ };
2116
+ const set = useCallback((nextValue, triggering = true) => {
2117
+ var _callbacksRef$current, _callbacksRef$current2, _callbacksRef$current3, _callbacksRef$current4;
2118
+ if (nextValue === sourceRef.current) return;
2119
+ const old = sourceRef.current;
2120
+ if (((_callbacksRef$current = (_callbacksRef$current2 = callbacksRef.current).onBeforeChange) === null || _callbacksRef$current === void 0 ? void 0 : _callbacksRef$current.call(_callbacksRef$current2, nextValue, old)) === false) return;
2121
+ sourceRef.current = nextValue;
2122
+ (_callbacksRef$current3 = (_callbacksRef$current4 = callbacksRef.current).onChanged) === null || _callbacksRef$current3 === void 0 || _callbacksRef$current3.call(_callbacksRef$current4, nextValue, old);
2123
+ if (triggering) {
2124
+ lastTriggeredRef.current = nextValue;
2125
+ setState(nextValue);
2126
+ }
2127
+ }, []);
2128
+ const get = useCallback((_tracking = true) => {
2129
+ return sourceRef.current;
2130
+ }, []);
2131
+ const untrackedGet = useCallback(() => get(false), [get]);
2132
+ const silentSet = useCallback((nextValue) => set(nextValue, false), [set]);
2133
+ const peek = useCallback(() => get(false), [get]);
2134
+ const lay = useCallback((nextValue) => set(nextValue, false), [set]);
2135
+ const reset = useCallback(() => set(initialRef.current), [set]);
2136
+ return [
2137
+ value,
2138
+ useCallback((nextValue) => {
2139
+ const resolved = typeof nextValue === "function" ? nextValue(sourceRef.current) : nextValue;
2140
+ set(resolved);
2141
+ }, [set]),
2142
+ {
2143
+ get,
2144
+ set,
2145
+ untrackedGet,
2146
+ silentSet,
2147
+ peek,
2148
+ lay,
2149
+ reset
2150
+ }
2151
+ ];
2152
+ }
2153
+ //#endregion
2154
+ //#region useTimeout/index.tsx
2155
+ function useTimeout(interval = 1e3, options = {}) {
2156
+ const { controls: exposeControls = false, callback, immediate = true, immediateCallback = false } = options;
2157
+ const [isPending, setIsPending] = useState(immediate);
2158
+ const intervalRef = useRef(interval);
2159
+ intervalRef.current = interval;
2160
+ const callbackRef = useRef(callback);
2161
+ callbackRef.current = callback;
2162
+ const immediateCallbackRef = useRef(immediateCallback);
2163
+ immediateCallbackRef.current = immediateCallback;
2164
+ const timerRef = useRef(null);
2165
+ const stop = useCallback(() => {
2166
+ if (timerRef.current !== null) {
2167
+ clearTimeout(timerRef.current);
2168
+ timerRef.current = null;
2169
+ }
2170
+ setIsPending(false);
2171
+ }, []);
2172
+ const start = useCallback(() => {
2173
+ var _callbackRef$current;
2174
+ if (immediateCallbackRef.current) (_callbackRef$current = callbackRef.current) === null || _callbackRef$current === void 0 || _callbackRef$current.call(callbackRef);
2175
+ if (timerRef.current !== null) {
2176
+ clearTimeout(timerRef.current);
2177
+ timerRef.current = null;
2178
+ }
2179
+ setIsPending(true);
2180
+ const delay = toValue(intervalRef.current);
2181
+ timerRef.current = setTimeout(() => {
2182
+ var _callbackRef$current2;
2183
+ timerRef.current = null;
2184
+ setIsPending(false);
2185
+ (_callbackRef$current2 = callbackRef.current) === null || _callbackRef$current2 === void 0 || _callbackRef$current2.call(callbackRef);
2186
+ }, delay);
2187
+ }, []);
2188
+ useEffect(() => {
2189
+ if (immediate) start();
2190
+ return stop;
2191
+ }, []);
2192
+ const ready = !isPending;
2193
+ if (exposeControls) return {
2194
+ ready,
2195
+ isPending,
2196
+ start,
2197
+ stop
2198
+ };
2199
+ return ready;
2200
+ }
2201
+ //#endregion
2202
+ //#region useTimeoutFn/index.tsx
2203
+ /**
2204
+ * React port of VueUse's `useTimeoutFn` — wrapper for `setTimeout` with
2205
+ * controls.
2206
+ *
2207
+ * Map from @vueuse/shared `useTimeoutFn`
2208
+ * Mapping: upstream accepts `RefOrValue<number>` for the interval — this
2209
+ * port accepts a plain `number`. `isPending` becomes a boolean state
2210
+ * (upstream: a readonly shallow ref) that starts `false` and is set inside
2211
+ * the mount effect — like upstream's `shallowRef(false)` + `isClient` gate,
2212
+ * the server render does not report pending. `immediateCallback` runs the
2213
+ * callback synchronously on `start` (before the timer is armed). The timer
2214
+ * is scheduled in a mount effect (upstream starts synchronously during
2215
+ * setup) and a pending timer is cleared on unmount via effect cleanup. The
2216
+ * latest callback and interval are kept in refs so restarts always use the
2217
+ * newest ones.
2218
+ *
2219
+ * @example
2220
+ * const { isPending, start, stop } = useTimeoutFn(() => { ... }, 3000)
2221
+ */
2222
+ function useTimeoutFn(cb, interval, options = {}) {
2223
+ const { immediate = true, immediateCallback = false } = options;
2224
+ const [isPending, setIsPending] = useState(false);
2225
+ const cbRef = useRef(cb);
2226
+ const intervalRef = useRef(interval);
2227
+ const immediateCallbackRef = useRef(immediateCallback);
2228
+ const timerRef = useRef(null);
2229
+ cbRef.current = cb;
2230
+ intervalRef.current = interval;
2231
+ immediateCallbackRef.current = immediateCallback;
2232
+ function clear() {
2233
+ if (timerRef.current) {
2234
+ clearTimeout(timerRef.current);
2235
+ timerRef.current = null;
2236
+ }
2237
+ }
2238
+ const stop = useCallback(() => {
2239
+ setIsPending(false);
2240
+ clear();
2241
+ }, []);
2242
+ const start = useCallback((...args) => {
2243
+ if (immediateCallbackRef.current) cbRef.current();
2244
+ clear();
2245
+ setIsPending(true);
2246
+ timerRef.current = setTimeout(() => {
2247
+ timerRef.current = null;
2248
+ setIsPending(false);
2249
+ cbRef.current(...args);
2250
+ }, intervalRef.current);
2251
+ }, []);
2252
+ useEffect(() => {
2253
+ if (immediate) start();
2254
+ return () => {
2255
+ clear();
2256
+ };
2257
+ }, [immediate, start]);
2258
+ return {
2259
+ isPending,
2260
+ start,
2261
+ stop
2262
+ };
2263
+ }
2264
+ //#endregion
2265
+ //#region useToggle/index.tsx
2266
+ /**
2267
+ * React port of VueUse's `useToggle` — a toggler between a truthy and a falsy
2268
+ * value, both configurable.
2269
+ *
2270
+ * Map from @vueuse/shared `useToggle`
2271
+ * Mapping: `ref(initialValue)` → `useControllableState(initialValue)`,
2272
+ * `toggle()` → stable `useCallback`; accepts the full `State<T>` input.
2273
+ * `truthyValue` / `falsyValue` are plain values (upstream: `MaybeRefOrGetter` —
2274
+ * reactive refs/getters are not supported, see `RefOrValue`). Upstream's
2275
+ * `toggle` returns the new value synchronously; React state updates are async,
2276
+ * so here `toggle` is `() => void` and the new value is read from `value` on
2277
+ * the next render. Like upstream, a bare `toggle()` flips between
2278
+ * `truthyValue` and `falsyValue`, `toggle(value)` (including an explicit
2279
+ * `undefined`) forces the value, and a function argument is applied as a
2280
+ * functional update (React adaptation).
2281
+ *
2282
+ * @example
2283
+ * const [value, toggle] = useToggle()
2284
+ * toggle() // false → true
2285
+ * toggle(false) // force to false
2286
+ *
2287
+ * const [status, toggleStatus] = useToggle('on', { truthyValue: 'on', falsyValue: 'off' })
2288
+ * toggleStatus() // 'on' → 'off'
2289
+ */
2290
+ function useToggle(initialValue = false, options = {}) {
2291
+ const { truthyValue = true, falsyValue = false } = options;
2292
+ const [state, setState] = useControllableState(initialValue, { passive: true });
2293
+ const truthyRef = useRef(truthyValue);
2294
+ truthyRef.current = truthyValue;
2295
+ const falsyRef = useRef(falsyValue);
2296
+ falsyRef.current = falsyValue;
2297
+ return [state, useCallback((...args) => {
2298
+ const hasValue = args.length > 0;
2299
+ const value = args[0];
2300
+ setState((current) => {
2301
+ if (hasValue) {
2302
+ if (typeof value === "function") return value(current);
2303
+ return value;
2304
+ }
2305
+ return Object.is(current, truthyRef.current) ? falsyRef.current : truthyRef.current;
2306
+ });
2307
+ }, [setState])];
2308
+ }
2309
+ //#endregion
2310
+ //#region useToNumber/index.tsx
2311
+ /**
2312
+ * React port of VueUse's `useToNumber`.
2313
+ *
2314
+ * Map from @vueuse/shared `useToNumber`
2315
+ * Mapping: `ComputedRef<number>` → plain number recomputed from the current
2316
+ * value on every render (accepts `number | string`); no hook state needed.
2317
+ *
2318
+ * @__NO_SIDE_EFFECTS__
2319
+ * @example
2320
+ * useToNumber('123') // 123
2321
+ * useToNumber('0xFA', { method: 'parseInt', radix: 16 }) // 250
2322
+ */
2323
+ function useToNumber(value, options = {}) {
2324
+ const { method = "parseFloat", radix, nanToZero } = options;
2325
+ let resolved = value;
2326
+ if (typeof method === "function") resolved = method(resolved);
2327
+ else if (typeof resolved === "string") resolved = Number[method](resolved, radix);
2328
+ if (nanToZero && Number.isNaN(resolved)) resolved = 0;
2329
+ return resolved;
2330
+ }
2331
+ //#endregion
2332
+ //#region useToString/index.tsx
2333
+ /**
2334
+ * React port of VueUse's `useToString`.
2335
+ *
2336
+ * Map from @vueuse/shared `useToString`
2337
+ * Mapping: VueUse wraps the template-literal coercion in `computed(() => ...)`
2338
+ * and accepts a `MaybeRefOrGetter`; React has no reactive value tracking, so
2339
+ * this is a plain function returning the stringified value directly.
2340
+ *
2341
+ * The `value` param is a PLAIN read-only value — pass `ref.current` or the
2342
+ * state value. Getter inputs are intentionally not supported (getters were
2343
+ * removed repo-wide); unlike upstream, a getter passed here is coerced as-is
2344
+ * (its source text), not invoked.
2345
+ *
2346
+ * @example
2347
+ * useToString(123.345) // '123.345'
2348
+ * useToString('hi') // 'hi'
2349
+ * useToString({ foo: 'hi' }) // '[object Object]'
2350
+ */
2351
+ function useToString(value) {
2352
+ return `${value}`;
2353
+ }
2354
+ //#endregion
2355
+ //#region useUnmount/index.tsx
2356
+ /**
2357
+ * React port of react-use's `useUnmount`.
2358
+ *
2359
+ * Map from react-use `useUnmount`
2360
+ * Mapping: react-use's `useUnmount` keeps the callback in a `useRef`,
2361
+ * reassigning it on every render so the newest callback is invoked, and runs
2362
+ * it via an empty-dependency `useEffect` cleanup (react-use's `useEffectOnce`
2363
+ * is just `useEffect(effect, [])`). This port follows the same semantics.
2364
+ *
2365
+ * @example
2366
+ * useUnmount(() => cleanup())
2367
+ */
2368
+ function useUnmount(fn) {
2369
+ const fnRef = useRef(fn);
2370
+ fnRef.current = fn;
2371
+ useEffect(() => () => fnRef.current(), []);
2372
+ }
2373
+ //#endregion
2374
+ //#region useUpdate/index.tsx
2375
+ const updateReducer = (num) => (num + 1) % 1e6;
2376
+ /**
2377
+ * React port of react-use's `useUpdate`.
2378
+ *
2379
+ * Map from react-use `useUpdate`
2380
+ * Mapping: `useReducer` with a wrapping counter — the returned function
2381
+ * dispatches an update that forces a re-render and is stable across renders.
2382
+ *
2383
+ * @example
2384
+ * const update = useUpdate()
2385
+ * update() // forces a re-render
2386
+ */
2387
+ function useUpdate() {
2388
+ const [, update] = useReducer(updateReducer, 0);
2389
+ return update;
2390
+ }
2391
+ //#endregion
2392
+ //#region useWatch/index.tsx
2393
+ function useWatch(source, callback, options = {}) {
2394
+ const firstRender = useRef(true);
2395
+ const oldValueRef = useRef(void 0);
2396
+ useEffect(() => {
2397
+ const oldValue = oldValueRef.current;
2398
+ const first = firstRender.current;
2399
+ firstRender.current = false;
2400
+ if (!first || options.immediate) callback(source, oldValue);
2401
+ oldValueRef.current = source;
2402
+ }, Array.isArray(source) ? source : [source]);
2403
+ }
2404
+ //#endregion
2405
+ //#region useWatchArray/index.tsx
2406
+ /**
2407
+ * React port of VueUse's `watchArray` — watch for an array with additions and removals.
2408
+ *
2409
+ * Mapping: built on the house `useWatch` — the list is a plain array value tracked across
2410
+ * renders, `useWatch` handles the change detection, and the previous list is diffed against
2411
+ * the next one with item-identity matching (like upstream) so the callback receives
2412
+ * `(newList, oldList, added, removed)`. The list is wrapped as a single-element watch
2413
+ * source (`[list]`) so `useWatch` tracks it by reference identity instead of spreading a
2414
+ * variable-length list into its dependency list (React requires a constant deps size).
2415
+ *
2416
+ * Divergences from the upstream Vue API:
2417
+ * - `source` is a plain array value — Vue's `WatchSource` forms (ref / getter / reactive)
2418
+ * have no React equivalent, compute the array during render and pass it directly.
2419
+ * - The list is tracked by reference identity: replacing it with a new array fires the
2420
+ * callback even when the items are identical (like a Vue ref reassignment), while
2421
+ * re-renders that keep the same array reference do not fire.
2422
+ * - In-place mutations (`push` / `splice`) do not re-render — produce a new array
2423
+ * (`setList([...list, item])`) to trigger the watch.
2424
+ * - The upstream `onCleanup` callback parameter is not ported — `useWatch` has no
2425
+ * watch-cleanup equivalent, use `useEffect` cleanup in the component instead.
2426
+ * - The return value is `void` — upstream returns a stop `WatchHandle`; watching
2427
+ * ends when the component unmounts.
2428
+ *
2429
+ * @example
2430
+ * ```ts
2431
+ * useWatchArray(list, (newList, oldList, added, removed) => {
2432
+ * console.log('added:', added, 'removed:', removed)
2433
+ * })
2434
+ * ```
2435
+ */
2436
+ function useWatchArray(source, cb, options) {
2437
+ useWatch([source], (watched, watchedOld) => {
2438
+ var _watchedOld$;
2439
+ const newList = watched[0];
2440
+ const prevList = [...(_watchedOld$ = watchedOld === null || watchedOld === void 0 ? void 0 : watchedOld[0]) !== null && _watchedOld$ !== void 0 ? _watchedOld$ : []];
2441
+ const oldListRemains = Array.from({ length: prevList.length });
2442
+ const added = [];
2443
+ for (const obj of newList) {
2444
+ let found = false;
2445
+ for (let i = 0; i < prevList.length; i++) if (!oldListRemains[i] && obj === prevList[i]) {
2446
+ oldListRemains[i] = true;
2447
+ found = true;
2448
+ break;
2449
+ }
2450
+ if (!found) added.push(obj);
2451
+ }
2452
+ cb(newList, prevList, added, prevList.filter((_, i) => !oldListRemains[i]));
2453
+ }, options);
2454
+ }
2455
+ //#endregion
2456
+ //#region useWatchAtMost/index.tsx
2457
+ /**
2458
+ * React port of VueUse's `watchAtMost` — `watch` with the number of times
2459
+ * triggered.
2460
+ *
2461
+ * Map from @vueuse/shared `watchAtMost`
2462
+ * Mapping: built on the house `useWatch`. The callback is wrapped with a fire
2463
+ * counter: each invocation increments the `count` state (exposed in the
2464
+ * return so components re-render), and once the limit — `options.count` — is
2465
+ * reached the wrapper marks the watcher as stopped so further source changes
2466
+ * are ignored. A manual `stop()` has the same effect before the limit.
2467
+ *
2468
+ * Divergences from upstream:
2469
+ * - upstream stops the underlying watcher via `stop()` scheduled on
2470
+ * `nextTick`; this port keeps the effect registered but the wrapped callback
2471
+ * becomes a no-op — observable behavior is identical (the callback fires at
2472
+ * most `count` times).
2473
+ * - `pause` / `resume` (upstream: inherited from `watchWithFilter`) are ported
2474
+ * as a skip flag on the wrapped callback — React has no watcher to detach,
2475
+ * but the observable behavior matches the Pausable controls.
2476
+ * - upstream's `count` return is a shallow ref; here it is React state so
2477
+ * reads re-render.
2478
+ * - upstream's `WatchWithFilterOptions` members beyond `immediate` (`deep`,
2479
+ * `flush`, `onTrack`, `onTrigger`) are not accepted — they are not
2480
+ * expressible in React (no reactive graph, no configurable commit, no
2481
+ * reactivity bookkeeping), and the option type rejects them.
2482
+ *
2483
+ * @example
2484
+ * ```tsx
2485
+ * const { count, stop } = useWatchAtMost(num, (value, oldValue) => {
2486
+ * console.log(value, oldValue)
2487
+ * }, { count: 3 })
2488
+ * ```
2489
+ */
2490
+ function useWatchAtMost(source, callback, options) {
2491
+ const { count: maxCount, ...watchOptions } = options;
2492
+ const [count, setCount] = useState(0);
2493
+ const firedRef = useRef(0);
2494
+ const stoppedRef = useRef(false);
2495
+ const pausedRef = useRef(false);
2496
+ const maxCountRef = useRef(maxCount);
2497
+ maxCountRef.current = maxCount;
2498
+ const stop = useCallback(() => {
2499
+ stoppedRef.current = true;
2500
+ }, []);
2501
+ const pause = useCallback(() => {
2502
+ pausedRef.current = true;
2503
+ }, []);
2504
+ const resume = useCallback(() => {
2505
+ pausedRef.current = false;
2506
+ }, []);
2507
+ function wrapped(value, oldValue) {
2508
+ if (stoppedRef.current || pausedRef.current) return;
2509
+ firedRef.current += 1;
2510
+ setCount(firedRef.current);
2511
+ callback(value, oldValue);
2512
+ if (firedRef.current >= maxCountRef.current) stoppedRef.current = true;
2513
+ }
2514
+ useWatch(source, wrapped, watchOptions);
2515
+ return {
2516
+ count,
2517
+ stop,
2518
+ pause,
2519
+ resume
2520
+ };
2521
+ }
2522
+ //#endregion
2523
+ //#region useWatchDebounced/index.tsx
2524
+ function useWatchDebounced(source, callback, options = {}) {
2525
+ const { debounce = 0, maxWait, rejectOnCancel } = options;
2526
+ useWatch(source, useDebounceFn((value, oldValue) => callback(value, oldValue), debounce, {
2527
+ maxWait,
2528
+ rejectOnCancel
2529
+ }), { immediate: options.immediate });
2530
+ }
2531
+ //#endregion
2532
+ //#region useWatchDeep/index.tsx
2533
+ /**
2534
+ * Structural equality, mirroring the semantics of test `toEqual`: primitives
2535
+ * are compared with `Object.is`, and `Date`, `RegExp`, `Array`, `Map`, `Set`
2536
+ * and objects (plain or class instances) are compared by contents. Functions
2537
+ * compare by reference, and `Map` keys are matched by reference because key
2538
+ * lookups cannot deep-match, while `Map` values and `Set` items are compared
2539
+ * deeply.
2540
+ *
2541
+ * Shared single source of truth — used by {@link useWatchDeep} and imported
2542
+ * from `@reause/shared` by core hooks that need deep change detection
2543
+ * (e.g. `useCloned`).
2544
+ */
2545
+ function deepEqual(a, b) {
2546
+ if (Object.is(a, b)) return true;
2547
+ if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
2548
+ const aRecord = a;
2549
+ const bRecord = b;
2550
+ if (aRecord.constructor !== bRecord.constructor) return false;
2551
+ if (a instanceof Date) return a.getTime() === b.getTime();
2552
+ if (a instanceof RegExp) return a.source === b.source && a.flags === b.flags;
2553
+ if (Array.isArray(a)) {
2554
+ const bArray = b;
2555
+ return a.length === bArray.length && a.every((item, index) => deepEqual(item, bArray[index]));
2556
+ }
2557
+ if (a instanceof Map) {
2558
+ const bMap = b;
2559
+ if (a.size !== bMap.size) return false;
2560
+ for (const [key, value] of a) if (!bMap.has(key) || !deepEqual(value, bMap.get(key))) return false;
2561
+ return true;
2562
+ }
2563
+ if (a instanceof Set) {
2564
+ const bSet = b;
2565
+ if (a.size !== bSet.size) return false;
2566
+ const unmatched = [...bSet];
2567
+ for (const item of a) {
2568
+ const index = unmatched.findIndex((candidate) => deepEqual(item, candidate));
2569
+ if (index === -1) return false;
2570
+ unmatched.splice(index, 1);
2571
+ }
2572
+ return true;
2573
+ }
2574
+ const aKeys = Object.keys(aRecord);
2575
+ if (aKeys.length !== Object.keys(bRecord).length) return false;
2576
+ return aKeys.every((key) => Object.hasOwn(bRecord, key) && deepEqual(aRecord[key], bRecord[key]));
2577
+ }
2578
+ /**
2579
+ * Deep clone pairing with {@link deepEqual}'s type coverage — `Date`, `RegExp`,
2580
+ * `Array`, `Map`, `Set` and objects (plain or class instances) are copied
2581
+ * structurally, primitives and functions pass through. Used to snapshot a live
2582
+ * value into an isolated baseline for change detection (e.g. `useCloned`'s
2583
+ * source / cloned baselines, which must stay unaffected by in-place mutations).
2584
+ */
2585
+ function deepClone(value) {
2586
+ if (value === null || typeof value !== "object") return value;
2587
+ if (value instanceof Date) return new Date(value.getTime());
2588
+ if (value instanceof RegExp) return new RegExp(value.source, value.flags);
2589
+ if (Array.isArray(value)) return value.map((item) => deepClone(item));
2590
+ if (value instanceof Map) {
2591
+ const result = /* @__PURE__ */ new Map();
2592
+ for (const [key, item] of value) result.set(key, deepClone(item));
2593
+ return result;
2594
+ }
2595
+ if (value instanceof Set) {
2596
+ const result = /* @__PURE__ */ new Set();
2597
+ for (const item of value) result.add(deepClone(item));
2598
+ return result;
2599
+ }
2600
+ const result = Object.create(Object.getPrototypeOf(value));
2601
+ for (const key of Object.keys(value)) result[key] = deepClone(value[key]);
2602
+ return result;
2603
+ }
2604
+ function useWatchDeep(source, callback, options = {}) {
2605
+ const immediateCall = useRef(options.immediate === true);
2606
+ useWatch(source, (value, oldValue) => {
2607
+ if (immediateCall.current) {
2608
+ immediateCall.current = false;
2609
+ callback(value, oldValue);
2610
+ return;
2611
+ }
2612
+ if (!deepEqual(value, oldValue)) callback(value, oldValue);
2613
+ }, options);
2614
+ }
2615
+ //#endregion
2616
+ //#region useWatchIgnorable/index.tsx
2617
+ function useWatchIgnorable(source, callback, options = {}) {
2618
+ const { immediate, once } = options;
2619
+ const lastSeenRef = useRef(source);
2620
+ const snapshotRef = useRef(source);
2621
+ const ignoreRef = useRef(false);
2622
+ const stoppedRef = useRef(false);
2623
+ useWatch(source, (value, oldValue) => {
2624
+ lastSeenRef.current = value;
2625
+ const ignore = ignoreRef.current;
2626
+ ignoreRef.current = false;
2627
+ if (ignore || stoppedRef.current) return;
2628
+ callback(value, oldValue);
2629
+ if (once) stoppedRef.current = true;
2630
+ }, { immediate });
2631
+ useEffect(() => {
2632
+ if (ignoreRef.current && Object.is(source, snapshotRef.current)) ignoreRef.current = false;
2633
+ });
2634
+ return {
2635
+ ignoreUpdates: useCallback((updater) => {
2636
+ snapshotRef.current = lastSeenRef.current;
2637
+ updater();
2638
+ ignoreRef.current = true;
2639
+ }, []),
2640
+ ignorePrevAsyncUpdates: useCallback(() => {
2641
+ snapshotRef.current = lastSeenRef.current;
2642
+ ignoreRef.current = true;
2643
+ }, []),
2644
+ stop: useCallback(() => {
2645
+ stoppedRef.current = true;
2646
+ }, [])
2647
+ };
2648
+ }
2649
+ //#endregion
2650
+ //#region useWatchImmediate/index.tsx
2651
+ function useWatchImmediate(source, callback) {
2652
+ useWatch(source, callback, { immediate: true });
2653
+ }
2654
+ //#endregion
2655
+ //#region useWatchOnce/index.tsx
2656
+ /**
2657
+ * Shorthand for watching value with `{ once: true }` — the callback fires at
2658
+ * most once (the first matching change) and the watcher stops afterwards —
2659
+ * React port of VueUse's `watchOnce`.
2660
+ *
2661
+ * Map from @vueuse/shared `watchOnce`.
2662
+ *
2663
+ * Mapping: upstream is a shorthand for
2664
+ * `watch(source, cb, { ...options, once: true })`. This port builds the same
2665
+ * shorthand on the house `useWatch` (like `useWatchAtMost` does with
2666
+ * `count: 1`): the callback is wrapped with a `stopped` ref — the first
2667
+ * invocation forwards `(value, oldValue)` and marks the watcher stopped, so
2668
+ * every further source change is ignored. An `immediate: true` call counts
2669
+ * toward the once, matching upstream.
2670
+ *
2671
+ * Divergences from upstream:
2672
+ * - Returns `{ stop }` instead of the full Vue `WatchHandle` — `stop` disables
2673
+ * further fires early (matching upstream's `stop()`); disposal otherwise
2674
+ * follows the component lifecycle.
2675
+ * - The source is a plain value (or array of values) tracked across renders —
2676
+ * Vue's `WatchSource` forms (ref / getter / reactive) have no React
2677
+ * equivalent, and the `deep` / `flush` watch options don't apply.
2678
+ *
2679
+ * @example
2680
+ * ```ts
2681
+ * useWatchOnce(count, (value, oldValue) => console.log(value, oldValue))
2682
+ * ```
2683
+ */
2684
+ function useWatchOnce(source, callback, options = {}) {
2685
+ const stoppedRef = useRef(false);
2686
+ const stop = useCallback(() => {
2687
+ stoppedRef.current = true;
2688
+ }, []);
2689
+ function wrapped(value, oldValue) {
2690
+ if (stoppedRef.current) return;
2691
+ stoppedRef.current = true;
2692
+ callback(value, oldValue);
2693
+ }
2694
+ useWatch(source, wrapped, options);
2695
+ return { stop };
2696
+ }
2697
+ //#endregion
2698
+ //#region useWatchPausable/index.tsx
2699
+ function useWatchPausable(source, callback, options = {}) {
2700
+ const { initialState = "active", immediate } = options;
2701
+ const [isActive, setIsActive] = useState(initialState === "active");
2702
+ const activeRef = useRef(initialState === "active");
2703
+ const stoppedRef = useRef(false);
2704
+ const pause = useCallback(() => {
2705
+ activeRef.current = false;
2706
+ setIsActive(false);
2707
+ }, []);
2708
+ const resume = useCallback(() => {
2709
+ activeRef.current = true;
2710
+ setIsActive(true);
2711
+ }, []);
2712
+ const stop = useCallback(() => {
2713
+ stoppedRef.current = true;
2714
+ }, []);
2715
+ useWatch(source, (current, oldValue) => {
2716
+ if (!activeRef.current || stoppedRef.current) return;
2717
+ callback(current, oldValue);
2718
+ }, { immediate });
2719
+ return {
2720
+ pause,
2721
+ resume,
2722
+ isActive,
2723
+ stop
2724
+ };
2725
+ }
2726
+ //#endregion
2727
+ //#region useWatchThrottled/index.tsx
2728
+ function useWatchThrottled(source, callback, options = {}) {
2729
+ const { throttle = 0, trailing = true, leading = true } = options;
2730
+ useWatch(source, useThrottleFn((value, oldValue) => callback(value, oldValue), throttle, trailing, leading), { immediate: options.immediate });
2731
+ }
2732
+ //#endregion
2733
+ //#region useWatchTriggerable/index.tsx
2734
+ function useWatchTriggerable(source, callback, options = {}) {
2735
+ const { immediate } = options;
2736
+ const [, forceCommit] = useState(0);
2737
+ const cleanupFnRef = useRef(void 0);
2738
+ const onCleanup = useCallback((cleanupFn) => {
2739
+ cleanupFnRef.current = cleanupFn;
2740
+ }, []);
2741
+ const triggerableCallback = useCallback((value, oldValue) => {
2742
+ const cleanupFn = cleanupFnRef.current;
2743
+ if (cleanupFn) {
2744
+ cleanupFnRef.current = void 0;
2745
+ cleanupFn();
2746
+ }
2747
+ return callback(value, oldValue, onCleanup);
2748
+ }, [callback, onCleanup]);
2749
+ const lastSeenRef = useRef(source);
2750
+ const snapshotRef = useRef(source);
2751
+ const ignoreRef = useRef(false);
2752
+ const stoppedRef = useRef(false);
2753
+ useWatch(source, (value, oldValue) => {
2754
+ lastSeenRef.current = value;
2755
+ const ignore = ignoreRef.current;
2756
+ ignoreRef.current = false;
2757
+ if (ignore || stoppedRef.current) return;
2758
+ triggerableCallback(value, oldValue);
2759
+ }, { immediate });
2760
+ useEffect(() => {
2761
+ if (ignoreRef.current && isSameSource(source, snapshotRef.current)) ignoreRef.current = false;
2762
+ });
2763
+ const ignoreUpdates = useCallback((updater) => {
2764
+ snapshotRef.current = lastSeenRef.current;
2765
+ updater();
2766
+ ignoreRef.current = true;
2767
+ forceCommit((x) => x + 1);
2768
+ }, [forceCommit]);
2769
+ const ignorePrevAsyncUpdates = useCallback(() => {
2770
+ snapshotRef.current = lastSeenRef.current;
2771
+ ignoreRef.current = true;
2772
+ forceCommit((x) => x + 1);
2773
+ }, [forceCommit]);
2774
+ const stop = useCallback(() => {
2775
+ stoppedRef.current = true;
2776
+ }, []);
2777
+ return {
2778
+ trigger: useCallback(() => {
2779
+ let result;
2780
+ ignoreUpdates(() => {
2781
+ result = triggerableCallback(source, getOldValue(source));
2782
+ });
2783
+ return result;
2784
+ }, [
2785
+ source,
2786
+ triggerableCallback,
2787
+ ignoreUpdates
2788
+ ]),
2789
+ ignoreUpdates,
2790
+ ignorePrevAsyncUpdates,
2791
+ stop
2792
+ };
2793
+ }
2794
+ function getOldValue(source) {
2795
+ return Array.isArray(source) ? source.map(() => void 0) : void 0;
2796
+ }
2797
+ function isSameSource(a, b) {
2798
+ if (Object.is(a, b)) return true;
2799
+ return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((item, index) => Object.is(item, b[index]));
2800
+ }
2801
+ //#endregion
2802
+ //#region useWatchWithFilter/index.tsx
2803
+ function bypassFilter(invoke) {
2804
+ invoke();
2805
+ }
2806
+ /**
2807
+ * Create an EventFilter that debounce the events — in-house port of upstream
2808
+ * `@vueuse/shared` `debounceFilter` (trailing edge + `maxWait`).
2809
+ *
2810
+ * Mapping: same collapsing semantics as upstream (a newer call supersedes the
2811
+ * pending one; the `maxWait` timer survives re-scheduling and forces the call
2812
+ * with the latest `invoke`). Divergences: the promise-settlement plumbing
2813
+ * (`lastRejector` / `rejectOnCancel`) is dropped — the house `EventFilter`
2814
+ * contract returns `void` and the watch path consumes no promise, so
2815
+ * `rejectOnCancel` has no observable effect — and `isPending` is a plain
2816
+ * getter instead of a reactive ref. `ms` accepts a plain number or a React
2817
+ * ref (upstream: `RefOrValue<number>`) and is re-read on every call. Pending
2818
+ * timers are cleared by `cancel()` — the `useWatchWithFilter` hook calls it
2819
+ * on stop / unmount.
2820
+ *
2821
+ * @example
2822
+ * ```ts
2823
+ * useWatchWithFilter(input, callback, { eventFilter: debounceFilter(300, { maxWait: 1000 }) })
2824
+ * ```
2825
+ */
2826
+ function debounceFilter(ms = 200, options = {}) {
2827
+ let timer;
2828
+ let maxTimer;
2829
+ let pending = false;
2830
+ let lastInvoker;
2831
+ const clearTimers = () => {
2832
+ if (timer !== void 0) {
2833
+ clearTimeout(timer);
2834
+ timer = void 0;
2835
+ }
2836
+ if (maxTimer !== void 0) {
2837
+ clearTimeout(maxTimer);
2838
+ maxTimer = void 0;
2839
+ }
2840
+ };
2841
+ const handler = (invoke) => {
2842
+ const duration = toValue(ms);
2843
+ const maxDuration = toValue(options.maxWait);
2844
+ if (timer !== void 0) {
2845
+ clearTimeout(timer);
2846
+ timer = void 0;
2847
+ }
2848
+ if (duration === void 0 || duration <= 0 || maxDuration !== void 0 && maxDuration <= 0) {
2849
+ clearTimers();
2850
+ pending = false;
2851
+ invoke();
2852
+ return;
2853
+ }
2854
+ pending = true;
2855
+ lastInvoker = invoke;
2856
+ if (maxDuration !== void 0 && maxTimer === void 0) maxTimer = setTimeout(() => {
2857
+ maxTimer = void 0;
2858
+ if (timer !== void 0) {
2859
+ clearTimeout(timer);
2860
+ timer = void 0;
2861
+ }
2862
+ pending = false;
2863
+ lastInvoker === null || lastInvoker === void 0 || lastInvoker();
2864
+ }, maxDuration);
2865
+ timer = setTimeout(() => {
2866
+ timer = void 0;
2867
+ if (maxTimer !== void 0) {
2868
+ clearTimeout(maxTimer);
2869
+ maxTimer = void 0;
2870
+ }
2871
+ pending = false;
2872
+ invoke();
2873
+ }, duration);
2874
+ };
2875
+ const filter = handler;
2876
+ filter.cancel = () => {
2877
+ clearTimers();
2878
+ pending = false;
2879
+ lastInvoker = void 0;
2880
+ };
2881
+ filter.flush = () => {
2882
+ if (!pending) return;
2883
+ clearTimers();
2884
+ pending = false;
2885
+ const invoker = lastInvoker;
2886
+ lastInvoker = void 0;
2887
+ invoker === null || invoker === void 0 || invoker();
2888
+ };
2889
+ Object.defineProperty(filter, "isPending", {
2890
+ enumerable: true,
2891
+ get: () => pending
2892
+ });
2893
+ return filter;
2894
+ }
2895
+ /**
2896
+ * Create an EventFilter that throttle the events — in-house port of upstream
2897
+ * `@vueuse/shared` `throttleFilter` (leading/trailing edges with a trailing
2898
+ * invoke on window end).
2899
+ *
2900
+ * Mapping: same collapsing semantics as upstream — a call inside the throttle
2901
+ * window re-schedules the trailing timer with the remaining time, collapsing
2902
+ * bursts into one trailing call carrying the latest `invoke`. Divergences:
2903
+ * the promise-settlement plumbing (`rejectOnCancel`, upstream's fourth
2904
+ * parameter) is dropped — the house `EventFilter` contract returns `void` —
2905
+ * and the object options form is not ported (positional
2906
+ * `throttleFilter(ms, trailing, leading)` like the house `useThrottleFn`).
2907
+ * `ms` accepts a plain number or a React ref (upstream:
2908
+ * `RefOrValue<number>`) and is re-read on every call.
2909
+ *
2910
+ * @example
2911
+ * ```ts
2912
+ * useWatchWithFilter(scrollY, callback, { eventFilter: throttleFilter(100, true, false) })
2913
+ * ```
2914
+ */
2915
+ function throttleFilter(ms = 200, trailing = true, leading = true) {
2916
+ let lastExec = 0;
2917
+ let timer;
2918
+ let isLeading = true;
2919
+ const clear = () => {
2920
+ if (timer !== void 0) {
2921
+ clearTimeout(timer);
2922
+ timer = void 0;
2923
+ }
2924
+ };
2925
+ return (invoke) => {
2926
+ const duration = toValue(ms);
2927
+ const elapsed = Date.now() - lastExec;
2928
+ clear();
2929
+ if (duration === void 0 || duration <= 0) {
2930
+ lastExec = Date.now();
2931
+ invoke();
2932
+ return;
2933
+ }
2934
+ if (elapsed > duration) {
2935
+ lastExec = Date.now();
2936
+ if (leading || !isLeading) invoke();
2937
+ } else if (trailing) timer = setTimeout(() => {
2938
+ lastExec = Date.now();
2939
+ isLeading = true;
2940
+ invoke();
2941
+ clear();
2942
+ }, Math.max(0, duration - elapsed));
2943
+ if (!leading && timer === void 0) timer = setTimeout(() => {
2944
+ isLeading = true;
2945
+ }, duration);
2946
+ isLeading = false;
2947
+ };
2948
+ }
2949
+ /**
2950
+ * `watch` with additional EventFilter control — React port of VueUse's
2951
+ * `watchWithFilter`.
2952
+ * Map from @vueuse/shared watchWithFilter.
2953
+ *
2954
+ * Mapping: upstream builds `watch(source, createFilterWrapper(eventFilter, cb),
2955
+ * watchOptions)` — the event filter wraps the watch trigger, so every source
2956
+ * change hands an `invoke` closure to the filter, which decides whether and
2957
+ * when the callback actually runs. This port builds the same wrapper on the
2958
+ * house `useWatch` (Vue's reactive dependency tracking becomes the effect
2959
+ * dependency list): every source change invokes the captured `eventFilter`
2960
+ * with an `invoke` closure carrying the latest `(value, oldValue)` pair. The
2961
+ * hook holds no state of its own — the source is the caller's own value — and
2962
+ * returns a `stop` function (upstream's `WatchHandle`, reduced to the stop
2963
+ * capability): after `stop()`, further source changes and any pending
2964
+ * filtered invocation no longer fire the callback, and cancelable filters
2965
+ * (`debounceFilter`) are cancelled outright.
2966
+ *
2967
+ * Divergences from upstream:
2968
+ * - React batching: source changes made in the same tick collapse into a
2969
+ * single effect run, so the filter sees ONE trigger where Vue's watcher
2970
+ * would fire per mutation. For a trailing filter the collapsed call is
2971
+ * identical (the latest `(value, oldValue)` pair); a leading-edge filter
2972
+ * fires at most once per tick instead of once per mutation.
2973
+ * - `deep` is not ported: React values are not deeply reactive. The source is
2974
+ * tracked by reference across renders (the effect dependency list), so
2975
+ * mutating an object in place is invisible and `deep: true` would have
2976
+ * nothing to recurse into — watch a derived primitive (or key) instead.
2977
+ * The same applies to the `flush` watch option: React effects always run
2978
+ * after the commit, there is no pre/post/sync choice.
2979
+ * - The filter instance is captured once on mount (upstream evaluates watch
2980
+ * options once during setup) — an inline `debounceFilter(ms)` is safe; use
2981
+ * a getter-based delay for dynamic values.
2982
+ * - `stop()` also suppresses a pending filtered invocation (upstream: an
2983
+ * already-scheduled filtered invoke still fires after stop), and pending
2984
+ * timers are cancelled when the component unmounts (upstream leaves
2985
+ * disposal to the effect scope).
2986
+ * - The promise-settlement plumbing of upstream filters (`lastRejector` /
2987
+ * `rejectOnCancel`) is dropped — the house `EventFilter` contract returns
2988
+ * `void`, so `rejectOnCancel` has no observable effect.
2989
+ *
2990
+ * @example
2991
+ * ```ts
2992
+ * const stop = useWatchWithFilter(count, (value, oldValue) => console.log(value, oldValue))
2993
+ * useWatchWithFilter(count, callback, { eventFilter: debounceFilter(300) })
2994
+ * stop()
2995
+ * ```
2996
+ */
2997
+ function useWatchWithFilter(source, callback, options = {}) {
2998
+ const { eventFilter = bypassFilter, immediate = false } = options;
2999
+ const stoppedRef = useRef(false);
3000
+ const callbackRef = useRef(callback);
3001
+ callbackRef.current = callback;
3002
+ const filterRef = useRef(eventFilter);
3003
+ function wrapped(value, oldValue) {
3004
+ if (stoppedRef.current) return;
3005
+ filterRef.current(() => {
3006
+ if (stoppedRef.current) return;
3007
+ callbackRef.current(value, oldValue);
3008
+ });
3009
+ }
3010
+ useWatch(source, wrapped, { immediate });
3011
+ const stop = useCallback(() => {
3012
+ stoppedRef.current = true;
3013
+ const cancelable = filterRef.current;
3014
+ if (typeof cancelable.cancel === "function") cancelable.cancel();
3015
+ }, []);
3016
+ useEffect(() => stop, [stop]);
3017
+ return stop;
3018
+ }
3019
+ //#endregion
3020
+ //#region useWhenever/index.tsx
3021
+ /**
3022
+ * React port of VueUse's `whenever`.
3023
+ *
3024
+ * Map from @vueuse/shared `whenever`
3025
+ * Mapping: upstream `whenever` is Vue's `watch` plus a truthy guard — the
3026
+ * callback runs every time the source CHANGES to a truthy value (a re-render
3027
+ * with the same truthy value never fires). In React this becomes a `useEffect`
3028
+ * watching `[value]`: the initial mount is skipped unless `immediate` (which
3029
+ * fires with `oldValue` `undefined`), later runs fire when the value is truthy
3030
+ * and actually changed, and the previous value is tracked in a ref updated on
3031
+ * every run — mirroring `watch`'s `oldValue`, which advances through falsy
3032
+ * values too. The callback is kept in a ref so re-renders always invoke the
3033
+ * newest one.
3034
+ *
3035
+ * The `once` option stops the watch after the first truthy fire — expressible
3036
+ * in React as a one-shot flag consulted by the effect, mirroring upstream's
3037
+ * `if (options?.once) nextTick(() => stop())`.
3038
+ *
3039
+ * The return value is a `stop` function — upstream's `WatchHandle`, reduced to
3040
+ * the stop capability (house `useWatch` has no stop-handle infrastructure).
3041
+ * `stop()` is also called when the component unmounts.
3042
+ *
3043
+ * The upstream 3-arg callback `(value, oldValue, onInvalidate)` becomes a
3044
+ * 2-arg `(value, oldValue)` in this port — `onInvalidate` (Vue's effect
3045
+ * invalidation registration) has no React equivalent, so it is dropped.
3046
+ *
3047
+ * @see https://vueuse.org/shared/whenever/
3048
+ *
3049
+ * @example
3050
+ * useWhenever(ready, () => console.log(state))
3051
+ * useWhenever(ready, () => console.log(state), { immediate: true })
3052
+ * useWhenever(ready, () => console.log(state), { once: true })
3053
+ */
3054
+ function useWhenever(value, cb, options) {
3055
+ const cbRef = useRef(cb);
3056
+ cbRef.current = cb;
3057
+ const oldValueRef = useRef(void 0);
3058
+ const isFirstRenderRef = useRef(true);
3059
+ const stoppedRef = useRef(false);
3060
+ useEffect(() => {
3061
+ if (stoppedRef.current) return;
3062
+ const isFirstRender = isFirstRenderRef.current;
3063
+ isFirstRenderRef.current = false;
3064
+ const isMountFire = isFirstRender && (options === null || options === void 0 ? void 0 : options.immediate) === true;
3065
+ const isChange = !Object.is(oldValueRef.current, value);
3066
+ if (value && (isMountFire || !isFirstRender && isChange)) {
3067
+ if (options === null || options === void 0 ? void 0 : options.once) stoppedRef.current = true;
3068
+ cbRef.current(value, oldValueRef.current);
3069
+ }
3070
+ oldValueRef.current = value;
3071
+ }, [value]);
3072
+ const stop = useCallback(() => {
3073
+ stoppedRef.current = true;
3074
+ }, []);
3075
+ useEffect(() => stop, [stop]);
3076
+ return stop;
3077
+ }
3078
+ //#endregion
3079
+ //#region index.ts
3080
+ /**
3081
+ * @reause/shared — React port of @vueuse/shared
3082
+ * Shared utilities shared across all reause packages.
3083
+ *
3084
+ * Mapping note: @vueuse/shared exposes pure utilities + composables that
3085
+ * don't depend on the renderer. In the React world those become either
3086
+ * plain functions (no hook) or hooks without rendering logic.
3087
+ */
3088
+ const isClient = typeof window !== "undefined";
3089
+ function noop() {}
3090
+ //#endregion
3091
+ export { assert, clamp, createEventHook, createGlobalState, createInjectionState, createSharedHook, createSingletonPromise, debounceFilter, deepClone, deepEqual, formatDate, hasOwn, hyphenate, increaseWithUnit, isClient, isDef, isDefined, isIOS, isObject, isRefLike, makeDestructurable, noop, normalizeDate, now, objectOmit, objectPick, promiseTimeout, pxValue, rand, syncState, syncStates, throttleFilter, timestamp, toArray, toValue, until, useArrayDifference, useArrayEvery, useArrayFilter, useArrayFind, useArrayFindIndex, useArrayFindLast, useArrayIncludes, useArrayJoin, useArrayMap, useArrayReduce, useArraySome, useArrayUnique, useControllableState, useCounter, useDateFormat, useDebounceFn, useInterval, useIntervalFn, useLastChanged, useListener, useMount, useStateAutoReset, useStateDebounced, useStateDefault, useStateManualReset, useStateThrottled, useStateWithControl, useThrottleFn, useTimeout, useTimeoutFn, useToNumber, useToString, useToggle, useUnmount, useUpdate, useWatch, useWatchArray, useWatchAtMost, useWatchDebounced, useWatchDeep, useWatchIgnorable, useWatchImmediate, useWatchOnce, useWatchPausable, useWatchThrottled, useWatchTriggerable, useWatchWithFilter, useWhenever, writeState };