@vizejs/composable 0.302.0 → 0.303.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.d.mts +562 -5
- package/dist/index.mjs +504 -1
- package/dist/temporal.d.mts +18 -2
- package/dist/temporal.mjs +18 -2
- package/package.json +16 -16
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ubugeeei
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.mts
CHANGED
|
@@ -50,18 +50,47 @@ interface UseAsyncResourceOptions<Data> {
|
|
|
50
50
|
}
|
|
51
51
|
/** Reactive state and controls for an asynchronous loader. */
|
|
52
52
|
interface AsyncResource<Data, Arguments extends readonly unknown[], Failure> {
|
|
53
|
+
/** Data of the newest successful execution, retained according to `keepData`. */
|
|
53
54
|
readonly data: Readonly<ShallowRef<Data | undefined>>;
|
|
55
|
+
/** Failure of the newest settled execution, cleared when a new one starts. */
|
|
54
56
|
readonly error: Readonly<ShallowRef<Failure | undefined>>;
|
|
57
|
+
/** Current lifecycle status, driven only by the newest execution. */
|
|
55
58
|
readonly status: Readonly<Ref<AsyncResourceStatus>>;
|
|
59
|
+
/** Whether an execution is currently pending. */
|
|
56
60
|
readonly pending: ComputedRef<boolean>;
|
|
61
|
+
/**
|
|
62
|
+
* Run the loader. The returned promise never rejects: loader failures,
|
|
63
|
+
* cancellation, and supersession are reported as the discriminated result,
|
|
64
|
+
* and stale executions leave the reactive state untouched.
|
|
65
|
+
*/
|
|
57
66
|
readonly execute: (...arguments_: Arguments) => Promise<AsyncResourceExecution<Data, Failure>>;
|
|
67
|
+
/**
|
|
68
|
+
* Abort the active execution and mark the resource cancelled.
|
|
69
|
+
*
|
|
70
|
+
* @param reason Abort reason forwarded to the loader's signal.
|
|
71
|
+
* @default reason DOMException("AbortError")
|
|
72
|
+
* @returns Whether an active execution was cancelled.
|
|
73
|
+
*/
|
|
58
74
|
readonly cancel: (reason?: unknown) => boolean;
|
|
75
|
+
/** Cancel any active execution and restore the initial idle state. */
|
|
59
76
|
readonly reset: () => void;
|
|
60
77
|
}
|
|
61
78
|
/**
|
|
62
79
|
* Create a scoped, abortable asynchronous resource with latest-result-wins
|
|
63
80
|
* state. Every execution returns a discriminated result, so cancellation,
|
|
64
81
|
* supersession, loader failure, and successful `undefined` data stay distinct.
|
|
82
|
+
*
|
|
83
|
+
* When created inside an active reactive scope (and `scope` is enabled), the
|
|
84
|
+
* active execution is aborted when that scope stops; outside a scope,
|
|
85
|
+
* cancellation ownership stays with the caller. The execute promise never
|
|
86
|
+
* rejects — synchronous and asynchronous loader failures both settle into
|
|
87
|
+
* the `"error"` result. Safe during server rendering: no browser globals are
|
|
88
|
+
* read and abort reasons use the runtime-native `DOMException`.
|
|
89
|
+
*
|
|
90
|
+
* @param loader Asynchronous loader receiving the abort context first.
|
|
91
|
+
* @param options Data retention, supersession, and scope behavior.
|
|
92
|
+
* @default options {}
|
|
93
|
+
* @returns Reactive state and controls for the loader.
|
|
65
94
|
*/
|
|
66
95
|
declare function useAsyncResource<Data, Arguments extends readonly unknown[], Failure = unknown>(loader: (context: AsyncResourceContext, ...arguments_: Arguments) => Promise<Data>, options?: UseAsyncResourceOptions<Data>): AsyncResource<Data, Arguments, Failure>;
|
|
67
96
|
//#endregion
|
|
@@ -87,7 +116,8 @@ interface UseEventListenerOptions {
|
|
|
87
116
|
*/
|
|
88
117
|
readonly passive?: boolean;
|
|
89
118
|
/**
|
|
90
|
-
* Stop listening when this signal is aborted.
|
|
119
|
+
* Stop listening when this signal is aborted. An already-aborted signal
|
|
120
|
+
* prevents listening from ever starting.
|
|
91
121
|
*
|
|
92
122
|
* @default undefined
|
|
93
123
|
*/
|
|
@@ -112,7 +142,9 @@ interface EventListenerControls {
|
|
|
112
142
|
/**
|
|
113
143
|
* Begin listening.
|
|
114
144
|
*
|
|
115
|
-
* @returns Whether a new reactive
|
|
145
|
+
* @returns Whether a new reactive watcher was started. `false` while the
|
|
146
|
+
* watcher is already active (including a null-target watcher with no
|
|
147
|
+
* listener attached) and after the abort signal has fired.
|
|
116
148
|
*/
|
|
117
149
|
readonly start: () => boolean;
|
|
118
150
|
/** Stop listening. Repeated calls are safe. */
|
|
@@ -120,13 +152,23 @@ interface EventListenerControls {
|
|
|
120
152
|
}
|
|
121
153
|
/**
|
|
122
154
|
* Attach an event listener to a reactive target and clean it up with the
|
|
123
|
-
* current reactive scope. Missing targets are valid during server rendering
|
|
155
|
+
* current reactive scope. Missing targets are valid during server rendering:
|
|
156
|
+
* a `null`/`undefined` target keeps the listener detached until a concrete
|
|
157
|
+
* target appears, so no browser globals are required.
|
|
158
|
+
*
|
|
159
|
+
* The listener is re-attached whenever the reactive target changes and is
|
|
160
|
+
* removed when the owning reactive scope stops, when
|
|
161
|
+
* {@link EventListenerControls.stop} is called, or when the abort signal
|
|
162
|
+
* fires. Outside an active scope, teardown ownership stays with the caller,
|
|
163
|
+
* who must call `stop` explicitly. Errors thrown by a custom target's
|
|
164
|
+
* add/remove methods propagate to the active watcher run.
|
|
124
165
|
*
|
|
125
166
|
* @param target Reactive event target.
|
|
126
167
|
* @param event Event name.
|
|
127
168
|
* @param listener Typed event listener.
|
|
128
169
|
* @param options Listener lifecycle and scheduling options.
|
|
129
170
|
* @default options {}
|
|
171
|
+
* @returns Controls to observe and change the listening state.
|
|
130
172
|
*/
|
|
131
173
|
declare function useEventListener<Key extends keyof WindowEventMap>(target: MaybeRefOrGetter<Window | null | undefined>, event: Key, listener: (event: WindowEventMap[Key]) => void, options?: UseEventListenerOptions): EventListenerControls;
|
|
132
174
|
declare function useEventListener<Key extends keyof DocumentEventMap>(target: MaybeRefOrGetter<Document | null | undefined>, event: Key, listener: (event: DocumentEventMap[Key]) => void, options?: UseEventListenerOptions): EventListenerControls;
|
|
@@ -151,7 +193,12 @@ interface UseLocaleOptions {
|
|
|
151
193
|
*/
|
|
152
194
|
readonly fallback?: Intl.Locale | string;
|
|
153
195
|
}
|
|
154
|
-
/**
|
|
196
|
+
/**
|
|
197
|
+
* Reactive locale metadata and cached formatter factories.
|
|
198
|
+
*
|
|
199
|
+
* Formatter factories propagate `TypeError` and `RangeError` from the
|
|
200
|
+
* platform `Intl` constructors when the supplied options are invalid.
|
|
201
|
+
*/
|
|
155
202
|
interface LocaleControls {
|
|
156
203
|
/** Canonical Unicode locale identifier. */
|
|
157
204
|
readonly locale: ComputedRef<string>;
|
|
@@ -174,6 +221,19 @@ interface LocaleControls {
|
|
|
174
221
|
* Equivalent formatter options reuse instances. The bounded cache follows the
|
|
175
222
|
* active locale automatically and prevents repeated constructor overhead in
|
|
176
223
|
* reactive render paths.
|
|
224
|
+
*
|
|
225
|
+
* Detection is lazy and guarded: the default detector reads
|
|
226
|
+
* `navigator.language` behind a `typeof` check at call time, so importing and
|
|
227
|
+
* calling this during server rendering is safe, and runtimes without a
|
|
228
|
+
* `navigator` resolve to the fallback locale. The composable owns no timers
|
|
229
|
+
* or listeners, so no scope cleanup is required.
|
|
230
|
+
*
|
|
231
|
+
* @param source Reactive locale source. Empty values defer to the detector.
|
|
232
|
+
* @param options Detection and fallback behavior.
|
|
233
|
+
* @default options {}
|
|
234
|
+
* @throws `RangeError` on first read of the reactive values when the winning
|
|
235
|
+
* candidate is not a structurally valid locale identifier.
|
|
236
|
+
* @returns Reactive locale metadata and cached formatter factories.
|
|
177
237
|
*/
|
|
178
238
|
declare function useLocale(source?: MaybeRefOrGetter<Intl.Locale | string | null | undefined>, options?: UseLocaleOptions): LocaleControls;
|
|
179
239
|
//#endregion
|
|
@@ -201,9 +261,18 @@ interface UseMediaQueryOptions {
|
|
|
201
261
|
/**
|
|
202
262
|
* Evaluate a reactive media query without requiring browser globals.
|
|
203
263
|
*
|
|
264
|
+
* During server rendering (or whenever no capability host resolves) the ref
|
|
265
|
+
* holds the configured server value and no subscription is created. The
|
|
266
|
+
* change subscription follows the reactive query and host: each
|
|
267
|
+
* re-evaluation removes the previous listener, and the final listener is
|
|
268
|
+
* removed when the owning reactive scope stops. Call inside an active scope
|
|
269
|
+
* so the subscription is released. A host whose matcher throws propagates
|
|
270
|
+
* the error to the active effect run; the browser default never throws.
|
|
271
|
+
*
|
|
204
272
|
* @param query Reactive media-query source.
|
|
205
273
|
* @param options Runtime capability and server-rendered fallback.
|
|
206
274
|
* @default options {}
|
|
275
|
+
* @returns Readonly ref that is `true` while the query matches.
|
|
207
276
|
*/
|
|
208
277
|
declare function useMediaQuery(query: MaybeRefOrGetter<string>, options?: UseMediaQueryOptions): Readonly<Ref<boolean>>;
|
|
209
278
|
/** User motion preference exposed by {@link useReducedMotion}. */
|
|
@@ -211,8 +280,13 @@ type MotionPreference = "reduce" | "no-preference";
|
|
|
211
280
|
/**
|
|
212
281
|
* Return the reactive user motion preference.
|
|
213
282
|
*
|
|
283
|
+
* Shares {@link useMediaQuery} semantics: during server rendering the
|
|
284
|
+
* preference is `"no-preference"` unless `ssrValue` is `true`, and the
|
|
285
|
+
* underlying subscription is removed when the owning reactive scope stops.
|
|
286
|
+
*
|
|
214
287
|
* @param options Runtime capability and server-rendered fallback.
|
|
215
288
|
* @default options {}
|
|
289
|
+
* @returns Computed preference for `(prefers-reduced-motion: reduce)`.
|
|
216
290
|
*/
|
|
217
291
|
declare function useReducedMotion(options?: UseMediaQueryOptions): ComputedRef<MotionPreference>;
|
|
218
292
|
//#endregion
|
|
@@ -220,9 +294,492 @@ declare function useReducedMotion(options?: UseMediaQueryOptions): ComputedRef<M
|
|
|
220
294
|
/**
|
|
221
295
|
* Register cleanup in the active reactive scope when one exists.
|
|
222
296
|
*
|
|
297
|
+
* This is the shared lifecycle primitive of the package: composables hand
|
|
298
|
+
* their teardown here so owned resources are released when the surrounding
|
|
299
|
+
* scope stops.
|
|
300
|
+
*
|
|
301
|
+
* Never throws and is safe during server rendering; no browser globals are
|
|
302
|
+
* read. When no scope is active the cleanup is not registered and disposal
|
|
303
|
+
* ownership stays with the caller.
|
|
304
|
+
*
|
|
223
305
|
* @param cleanup Cleanup invoked exactly once when the scope is disposed.
|
|
224
306
|
* @returns Whether the cleanup was registered.
|
|
225
307
|
*/
|
|
226
308
|
declare function tryOnScopeDispose(cleanup: () => void): boolean;
|
|
227
309
|
//#endregion
|
|
228
|
-
|
|
310
|
+
//#region src/timeout-scheduler.d.ts
|
|
311
|
+
/**
|
|
312
|
+
* Single-shot timer host used by the debounced and throttled state
|
|
313
|
+
* utilities.
|
|
314
|
+
*
|
|
315
|
+
* Implement this interface to integrate a deterministic test clock, a native
|
|
316
|
+
* runtime timer, or an application-owned scheduler. Handles are opaque: the
|
|
317
|
+
* utilities only hand them back to {@link TimeoutScheduler.clearTimeout}.
|
|
318
|
+
* This module declares types only and contributes no runtime code.
|
|
319
|
+
*/
|
|
320
|
+
interface TimeoutScheduler {
|
|
321
|
+
/** Starts a single-shot callback and returns its opaque cancellation handle. */
|
|
322
|
+
readonly setTimeout: (callback: () => void, delayMs: number) => unknown;
|
|
323
|
+
/** Cancels a handle previously returned by {@link TimeoutScheduler.setTimeout}. */
|
|
324
|
+
readonly clearTimeout: (handle: unknown) => void;
|
|
325
|
+
}
|
|
326
|
+
//#endregion
|
|
327
|
+
//#region src/use-counter.d.ts
|
|
328
|
+
/** Options for {@link useCounter}. */
|
|
329
|
+
interface UseCounterOptions {
|
|
330
|
+
/**
|
|
331
|
+
* Inclusive lower bound applied to every value the counter takes.
|
|
332
|
+
*
|
|
333
|
+
* @default Number.NEGATIVE_INFINITY
|
|
334
|
+
*/
|
|
335
|
+
readonly min?: number;
|
|
336
|
+
/**
|
|
337
|
+
* Inclusive upper bound applied to every value the counter takes.
|
|
338
|
+
*
|
|
339
|
+
* @default Number.POSITIVE_INFINITY
|
|
340
|
+
*/
|
|
341
|
+
readonly max?: number;
|
|
342
|
+
}
|
|
343
|
+
/** Reactive controls returned by {@link useCounter}. */
|
|
344
|
+
interface CounterControls {
|
|
345
|
+
/** Current count. Changes only through the controls, never by assignment. */
|
|
346
|
+
readonly count: Readonly<ShallowRef<number>>;
|
|
347
|
+
/** Whether the count currently sits on the configured lower bound. */
|
|
348
|
+
readonly atMin: ComputedRef<boolean>;
|
|
349
|
+
/** Whether the count currently sits on the configured upper bound. */
|
|
350
|
+
readonly atMax: ComputedRef<boolean>;
|
|
351
|
+
/**
|
|
352
|
+
* Add `delta` (default `1`) to the count and clamp into the bounds.
|
|
353
|
+
*
|
|
354
|
+
* @param delta Amount added; may be negative or infinite.
|
|
355
|
+
* @returns The count after clamping.
|
|
356
|
+
*/
|
|
357
|
+
readonly increment: (delta?: number) => number;
|
|
358
|
+
/**
|
|
359
|
+
* Subtract `delta` (default `1`) from the count and clamp into the bounds.
|
|
360
|
+
*
|
|
361
|
+
* @param delta Amount subtracted; may be negative or infinite.
|
|
362
|
+
* @returns The count after clamping.
|
|
363
|
+
*/
|
|
364
|
+
readonly decrement: (delta?: number) => number;
|
|
365
|
+
/**
|
|
366
|
+
* Assign a value directly, clamped into the bounds.
|
|
367
|
+
*
|
|
368
|
+
* @returns The count after clamping.
|
|
369
|
+
*/
|
|
370
|
+
readonly set: (value: number) => number;
|
|
371
|
+
/**
|
|
372
|
+
* Restore the reset baseline, or establish a new one.
|
|
373
|
+
*
|
|
374
|
+
* Without an argument the count returns to the creation-time initial value
|
|
375
|
+
* (after its original clamping). With an argument, the clamped value
|
|
376
|
+
* becomes both the new count and the baseline used by later `reset()`
|
|
377
|
+
* calls.
|
|
378
|
+
*
|
|
379
|
+
* @param value Replacement baseline.
|
|
380
|
+
* @returns The count after clamping.
|
|
381
|
+
*/
|
|
382
|
+
readonly reset: (value?: number) => number;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Create a clamped counter whose every transition stays inside `[min, max]`.
|
|
386
|
+
*
|
|
387
|
+
* All operations clamp instead of failing, including the initial value, so
|
|
388
|
+
* the count is inside the bounds at every observable moment. Only `NaN` is
|
|
389
|
+
* rejected — silently corrupting the count is never an option. Purely
|
|
390
|
+
* synchronous state: safe during server rendering (no browser globals, no
|
|
391
|
+
* timers) and nothing to dispose, so it works inside and outside reactive
|
|
392
|
+
* scopes alike. Bounds are fixed at creation and not reactive.
|
|
393
|
+
*
|
|
394
|
+
* @example
|
|
395
|
+
* ```ts
|
|
396
|
+
* const { count, increment, atMax } = useCounter(9, { min: 0, max: 10 });
|
|
397
|
+
* increment(); // 10
|
|
398
|
+
* increment(); // 10 (clamped)
|
|
399
|
+
* atMax.value; // true
|
|
400
|
+
* ```
|
|
401
|
+
*
|
|
402
|
+
* @param initial Count before any operation, clamped into the bounds.
|
|
403
|
+
* @default initial 0
|
|
404
|
+
* @param options Inclusive bounds for every value the counter takes.
|
|
405
|
+
* @default options {}
|
|
406
|
+
* @throws `RangeError` tagged `VIZE_COMPOSE_COUNTER_INVALID_RANGE` when a
|
|
407
|
+
* bound is `NaN` or `min` exceeds `max`.
|
|
408
|
+
* @throws `RangeError` tagged `VIZE_COMPOSE_COUNTER_INVALID_VALUE` when an
|
|
409
|
+
* initial value, operand, or arithmetic result is `NaN` (for example
|
|
410
|
+
* incrementing `-Infinity` by `Infinity`); the count is left unchanged.
|
|
411
|
+
* @returns Reactive count, bound flags, and mutation controls.
|
|
412
|
+
*/
|
|
413
|
+
declare function useCounter(initial?: number, options?: UseCounterOptions): CounterControls;
|
|
414
|
+
//#endregion
|
|
415
|
+
//#region src/use-debounced.d.ts
|
|
416
|
+
/** Options for {@link useDebounced}. */
|
|
417
|
+
interface UseDebouncedOptions {
|
|
418
|
+
/**
|
|
419
|
+
* Applies the timing policy when no browser `window` is available.
|
|
420
|
+
*
|
|
421
|
+
* Keep this disabled during server rendering, where the debounced view
|
|
422
|
+
* mirrors the source synchronously instead of starting timers. Enable it
|
|
423
|
+
* for native, desktop, worker, and test runtimes whose scheduler is
|
|
424
|
+
* lifecycle-bound.
|
|
425
|
+
*
|
|
426
|
+
* @default false
|
|
427
|
+
*/
|
|
428
|
+
readonly runOnServer?: boolean;
|
|
429
|
+
/**
|
|
430
|
+
* Owns the single-shot timer.
|
|
431
|
+
*
|
|
432
|
+
* @default globalThis timer functions
|
|
433
|
+
*/
|
|
434
|
+
readonly scheduler?: TimeoutScheduler;
|
|
435
|
+
}
|
|
436
|
+
/** Reactive debounced view and controls returned by {@link useDebounced}. */
|
|
437
|
+
interface DebouncedControls<Value> {
|
|
438
|
+
/** Readonly view of the source that settles `waitMs` after the last change. */
|
|
439
|
+
readonly debounced: Readonly<ShallowRef<Value>>;
|
|
440
|
+
/** Whether a trailing update is currently scheduled. */
|
|
441
|
+
readonly pending: Readonly<ShallowRef<boolean>>;
|
|
442
|
+
/**
|
|
443
|
+
* Discard the scheduled trailing update and keep the last settled value.
|
|
444
|
+
* Later source changes debounce again as usual.
|
|
445
|
+
*
|
|
446
|
+
* @returns Whether a scheduled update was discarded.
|
|
447
|
+
*/
|
|
448
|
+
readonly cancel: () => boolean;
|
|
449
|
+
/**
|
|
450
|
+
* Apply the current source value immediately instead of waiting out the
|
|
451
|
+
* delay.
|
|
452
|
+
*
|
|
453
|
+
* @returns Whether a scheduled update was applied.
|
|
454
|
+
*/
|
|
455
|
+
readonly flush: () => boolean;
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Create a readonly debounced view of a reactive source.
|
|
459
|
+
*
|
|
460
|
+
* The view starts at the current source value. Each source change (observed
|
|
461
|
+
* with `flush: "sync"`, so every synchronous write counts) restarts a
|
|
462
|
+
* single-shot timer of `waitMs` milliseconds; when it fires, the view takes
|
|
463
|
+
* the source value current at that moment. `waitMs` is reactive and is read
|
|
464
|
+
* when a timer is scheduled; changing it does not restart an already-pending
|
|
465
|
+
* timer. A wait of `0` still defers to the next scheduler tick.
|
|
466
|
+
*
|
|
467
|
+
* Server rendering is explicit: without a browser `window` (and with
|
|
468
|
+
* {@link UseDebouncedOptions.runOnServer} disabled) no timer ever starts and
|
|
469
|
+
* the view mirrors the source synchronously, so server-rendered output shows
|
|
470
|
+
* current values and nothing leaks. `pending` stays `false` and the controls
|
|
471
|
+
* report `false` in that mode.
|
|
472
|
+
*
|
|
473
|
+
* Cleanup rule: the watcher and any pending timer are released when the
|
|
474
|
+
* owning reactive scope stops; call inside an active scope. Outside one, the
|
|
475
|
+
* watcher lives as long as the source and `cancel()` only clears the pending
|
|
476
|
+
* timer.
|
|
477
|
+
*
|
|
478
|
+
* @example
|
|
479
|
+
* ```ts
|
|
480
|
+
* const query = shallowRef("");
|
|
481
|
+
* const { debounced, flush } = useDebounced(query, 300);
|
|
482
|
+
* query.value = "vize"; // debounced.value still "" for 300ms
|
|
483
|
+
* flush(); // debounced.value === "vize" immediately
|
|
484
|
+
* ```
|
|
485
|
+
*
|
|
486
|
+
* @param source Reactive source to debounce.
|
|
487
|
+
* @param waitMs Reactive delay in milliseconds; must be finite and at least
|
|
488
|
+
* zero. Fractions are truncated.
|
|
489
|
+
* @param options Runtime scheduling overrides.
|
|
490
|
+
* @default options {}
|
|
491
|
+
* @throws `RangeError` tagged `VIZE_COMPOSE_DEBOUNCE_INVALID_WAIT` when the
|
|
492
|
+
* resolved wait is not finite or is negative, both synchronously at creation
|
|
493
|
+
* (even in mirror mode) and again for every scheduled delay.
|
|
494
|
+
* @returns Readonly debounced view, pending flag, and cancel/flush controls.
|
|
495
|
+
*/
|
|
496
|
+
declare function useDebounced<Value>(source: MaybeRefOrGetter<Value>, waitMs: MaybeRefOrGetter<number>, options?: UseDebouncedOptions): DebouncedControls<Value>;
|
|
497
|
+
//#endregion
|
|
498
|
+
//#region src/use-history.d.ts
|
|
499
|
+
/** Options for {@link useHistory}. */
|
|
500
|
+
interface UseHistoryOptions<Value> {
|
|
501
|
+
/**
|
|
502
|
+
* Maximum number of undo entries retained; recording a change beyond it
|
|
503
|
+
* drops the oldest entry. The redo stack is bounded by construction, since
|
|
504
|
+
* redo entries only ever come from undone changes. Must be an integer
|
|
505
|
+
* greater than zero and is fixed at creation.
|
|
506
|
+
*
|
|
507
|
+
* @default 100
|
|
508
|
+
*/
|
|
509
|
+
readonly capacity?: number;
|
|
510
|
+
/**
|
|
511
|
+
* Clone applied to every value captured into history and to every value
|
|
512
|
+
* restored out of it, isolating snapshots from later in-place mutation.
|
|
513
|
+
*
|
|
514
|
+
* @default identity — values are stored and restored by reference
|
|
515
|
+
*/
|
|
516
|
+
readonly clone?: (value: Value) => Value;
|
|
517
|
+
}
|
|
518
|
+
/** Reactive undo/redo controls returned by {@link useHistory}. */
|
|
519
|
+
interface HistoryControls {
|
|
520
|
+
/** Whether {@link HistoryControls.undo} currently has an entry to restore. */
|
|
521
|
+
readonly canUndo: ComputedRef<boolean>;
|
|
522
|
+
/** Whether {@link HistoryControls.redo} currently has an entry to restore. */
|
|
523
|
+
readonly canRedo: ComputedRef<boolean>;
|
|
524
|
+
/** Number of retained undo entries. */
|
|
525
|
+
readonly undoCount: ComputedRef<number>;
|
|
526
|
+
/** Number of retained redo entries. */
|
|
527
|
+
readonly redoCount: ComputedRef<number>;
|
|
528
|
+
/**
|
|
529
|
+
* Restore the newest undo entry and move the current value onto the redo
|
|
530
|
+
* stack. The restoring write is not recorded.
|
|
531
|
+
*
|
|
532
|
+
* @returns Whether an entry was restored.
|
|
533
|
+
*/
|
|
534
|
+
readonly undo: () => boolean;
|
|
535
|
+
/**
|
|
536
|
+
* Restore the newest redo entry and move the current value back onto the
|
|
537
|
+
* undo stack. The restoring write is not recorded.
|
|
538
|
+
*
|
|
539
|
+
* @returns Whether an entry was restored.
|
|
540
|
+
*/
|
|
541
|
+
readonly redo: () => boolean;
|
|
542
|
+
/**
|
|
543
|
+
* Group every source write inside `run` into at most one undo entry.
|
|
544
|
+
*
|
|
545
|
+
* The entry restores the value from just before the batch. It is committed
|
|
546
|
+
* only when the final value differs (`Object.is`) from the starting value,
|
|
547
|
+
* and it is committed even when `run` throws, so a partially applied batch
|
|
548
|
+
* stays undoable as one step. Nested calls collapse into the outermost
|
|
549
|
+
* batch. The callback's return value is passed through.
|
|
550
|
+
*/
|
|
551
|
+
readonly batch: <Result>(run: () => Result) => Result;
|
|
552
|
+
/** Drop every undo and redo entry while keeping the current value. */
|
|
553
|
+
readonly clear: () => void;
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Record bounded undo/redo history over the writes of a ref.
|
|
557
|
+
*
|
|
558
|
+
* Recording is shallow and identity-based, matching Vue's own change
|
|
559
|
+
* detection: assignments to `source.value` are recorded (observed with
|
|
560
|
+
* `flush: "sync"`, so every synchronous write counts), writes that are
|
|
561
|
+
* `Object.is`-equal to the current value are not changes, and in-place
|
|
562
|
+
* mutations of object values are invisible — pair mutable values with
|
|
563
|
+
* {@link UseHistoryOptions.clone} and reassign. Undoing and redoing restore
|
|
564
|
+
* values through `clone` as well, so snapshots never share identity with the
|
|
565
|
+
* live value unless the default identity clone is kept. When a
|
|
566
|
+
* user-provided `clone` throws, the failed operation leaves history
|
|
567
|
+
* unchanged and the error propagates.
|
|
568
|
+
*
|
|
569
|
+
* Safe during server rendering: no browser globals are read and no timers
|
|
570
|
+
* start. Cleanup rule: when the owning reactive scope stops, recording stops
|
|
571
|
+
* and every retained snapshot is released, so `undo`/`redo` return `false`
|
|
572
|
+
* afterwards; call inside an active scope, or the watcher lives as long as
|
|
573
|
+
* the source.
|
|
574
|
+
*
|
|
575
|
+
* @example
|
|
576
|
+
* ```ts
|
|
577
|
+
* const text = shallowRef("");
|
|
578
|
+
* const { undo, redo, batch } = useHistory(text);
|
|
579
|
+
* text.value = "a";
|
|
580
|
+
* batch(() => {
|
|
581
|
+
* text.value = "ab";
|
|
582
|
+
* text.value = "abc";
|
|
583
|
+
* });
|
|
584
|
+
* undo(); // text.value === "a" (the batch is one step)
|
|
585
|
+
* redo(); // text.value === "abc"
|
|
586
|
+
* ```
|
|
587
|
+
*
|
|
588
|
+
* @param source Ref whose writes are recorded.
|
|
589
|
+
* @param options Retention bound and snapshot cloning.
|
|
590
|
+
* @default options {}
|
|
591
|
+
* @throws `RangeError` tagged `VIZE_COMPOSE_HISTORY_INVALID_CAPACITY` when
|
|
592
|
+
* the capacity is not an integer greater than zero.
|
|
593
|
+
* @throws `Error` tagged `VIZE_COMPOSE_HISTORY_IN_BATCH` when `undo`,
|
|
594
|
+
* `redo`, or `clear` is called inside {@link HistoryControls.batch}, where
|
|
595
|
+
* stack movement would corrupt the pending group.
|
|
596
|
+
* @returns Reactive undo/redo state and controls.
|
|
597
|
+
*/
|
|
598
|
+
declare function useHistory<Value>(source: Ref<Value>, options?: UseHistoryOptions<Value>): HistoryControls;
|
|
599
|
+
//#endregion
|
|
600
|
+
//#region src/use-previous.d.ts
|
|
601
|
+
/**
|
|
602
|
+
* Track the value a reactive source held before its latest change.
|
|
603
|
+
*
|
|
604
|
+
* The semantics are precise and arity-based:
|
|
605
|
+
*
|
|
606
|
+
* - Without an `initial` argument the ref holds `undefined` until the source
|
|
607
|
+
* changes for the first time.
|
|
608
|
+
* - With an `initial` argument (including an explicit `undefined` when
|
|
609
|
+
* `Value` allows it) the ref holds that value until the first change, and
|
|
610
|
+
* the return type never widens with `undefined`.
|
|
611
|
+
* - Every synchronous write is observed (`flush: "sync"`), so a sequence of
|
|
612
|
+
* writes in one tick shifts the previous value step by step instead of
|
|
613
|
+
* collapsing into one batch.
|
|
614
|
+
* - Writes whose value is `Object.is`-equal to the current value do not
|
|
615
|
+
* count as changes, matching Vue's own change detection.
|
|
616
|
+
* - Tracking is shallow: reassignments are observed, in-place mutations of
|
|
617
|
+
* object values are not.
|
|
618
|
+
*
|
|
619
|
+
* Safe during server rendering: no browser globals are read and no timers
|
|
620
|
+
* start. The underlying watcher is bound to the current reactive scope and
|
|
621
|
+
* stops with it; call inside an active scope, or the watcher lives as long
|
|
622
|
+
* as the source. A plain non-reactive source never changes, so the ref stays
|
|
623
|
+
* at its initial value.
|
|
624
|
+
*
|
|
625
|
+
* @example
|
|
626
|
+
* ```ts
|
|
627
|
+
* const route = shallowRef("/home");
|
|
628
|
+
* const previousRoute = usePrevious(route, "/");
|
|
629
|
+
* route.value = "/settings";
|
|
630
|
+
* previousRoute.value; // "/home"
|
|
631
|
+
* ```
|
|
632
|
+
*
|
|
633
|
+
* @param source Reactive source to observe.
|
|
634
|
+
* @param initial Value reported before the first change.
|
|
635
|
+
* @returns Readonly shallow ref holding the previous source value.
|
|
636
|
+
*/
|
|
637
|
+
declare function usePrevious<Value>(source: MaybeRefOrGetter<Value>): Readonly<ShallowRef<Value | undefined>>;
|
|
638
|
+
declare function usePrevious<Value>(source: MaybeRefOrGetter<Value>, initial: Value): Readonly<ShallowRef<Value>>;
|
|
639
|
+
//#endregion
|
|
640
|
+
//#region src/use-throttled.d.ts
|
|
641
|
+
/** Options for {@link useThrottled}. */
|
|
642
|
+
interface UseThrottledOptions {
|
|
643
|
+
/**
|
|
644
|
+
* Apply the first change of a cooldown window immediately.
|
|
645
|
+
*
|
|
646
|
+
* @default true
|
|
647
|
+
*/
|
|
648
|
+
readonly leading?: boolean;
|
|
649
|
+
/**
|
|
650
|
+
* Apply the newest change collected during a cooldown window when the
|
|
651
|
+
* window ends. When disabled, changes inside a window are dropped.
|
|
652
|
+
*
|
|
653
|
+
* @default true
|
|
654
|
+
*/
|
|
655
|
+
readonly trailing?: boolean;
|
|
656
|
+
/**
|
|
657
|
+
* Applies the timing policy when no browser `window` is available.
|
|
658
|
+
*
|
|
659
|
+
* Keep this disabled during server rendering, where the throttled view
|
|
660
|
+
* mirrors the source synchronously instead of starting timers. Enable it
|
|
661
|
+
* for native, desktop, worker, and test runtimes whose scheduler is
|
|
662
|
+
* lifecycle-bound.
|
|
663
|
+
*
|
|
664
|
+
* @default false
|
|
665
|
+
*/
|
|
666
|
+
readonly runOnServer?: boolean;
|
|
667
|
+
/**
|
|
668
|
+
* Owns the single-shot cooldown timer.
|
|
669
|
+
*
|
|
670
|
+
* @default globalThis timer functions
|
|
671
|
+
*/
|
|
672
|
+
readonly scheduler?: TimeoutScheduler;
|
|
673
|
+
}
|
|
674
|
+
/** Reactive throttled view and controls returned by {@link useThrottled}. */
|
|
675
|
+
interface ThrottledControls<Value> {
|
|
676
|
+
/** Readonly view of the source updated at most once per cooldown window. */
|
|
677
|
+
readonly throttled: Readonly<ShallowRef<Value>>;
|
|
678
|
+
/** Whether a trailing update is waiting for the current window to end. */
|
|
679
|
+
readonly pending: Readonly<ShallowRef<boolean>>;
|
|
680
|
+
/**
|
|
681
|
+
* Discard the waiting trailing update and close the cooldown window, so
|
|
682
|
+
* the next change starts fresh on a leading edge.
|
|
683
|
+
*
|
|
684
|
+
* @returns Whether a waiting trailing update was discarded.
|
|
685
|
+
*/
|
|
686
|
+
readonly cancel: () => boolean;
|
|
687
|
+
/**
|
|
688
|
+
* Apply the waiting trailing update immediately and close the cooldown
|
|
689
|
+
* window. Without a waiting update the window is left untouched.
|
|
690
|
+
*
|
|
691
|
+
* @returns Whether a waiting trailing update was applied.
|
|
692
|
+
*/
|
|
693
|
+
readonly flush: () => boolean;
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Create a readonly throttled view of a reactive source.
|
|
697
|
+
*
|
|
698
|
+
* Changes are observed with `flush: "sync"`, so every synchronous write
|
|
699
|
+
* counts. Outside a cooldown window, a change applies immediately when
|
|
700
|
+
* {@link UseThrottledOptions.leading} is enabled (otherwise it waits as a
|
|
701
|
+
* trailing update) and opens a window of `waitMs` milliseconds. Changes
|
|
702
|
+
* inside a window are collected as the trailing candidate; when the window
|
|
703
|
+
* ends with a candidate waiting, the source value current at that moment is
|
|
704
|
+
* applied and the next window opens back to back, keeping applications
|
|
705
|
+
* spaced by `waitMs`. A window that ends without a candidate closes silently.
|
|
706
|
+
* `waitMs` is reactive and is read each time a window opens; changing it
|
|
707
|
+
* never disturbs an already-open window. A wait of `0` still defers trailing
|
|
708
|
+
* updates to the next scheduler tick.
|
|
709
|
+
*
|
|
710
|
+
* Server rendering is explicit: without a browser `window` (and with
|
|
711
|
+
* {@link UseThrottledOptions.runOnServer} disabled) no timer ever starts and
|
|
712
|
+
* the view mirrors the source synchronously, so server-rendered output shows
|
|
713
|
+
* current values and nothing leaks. `pending` stays `false` and the controls
|
|
714
|
+
* report `false` in that mode.
|
|
715
|
+
*
|
|
716
|
+
* Cleanup rule: the watcher and any open window timer are released when the
|
|
717
|
+
* owning reactive scope stops; call inside an active scope. Outside one, the
|
|
718
|
+
* watcher lives as long as the source and `cancel()` only clears the window.
|
|
719
|
+
*
|
|
720
|
+
* @example
|
|
721
|
+
* ```ts
|
|
722
|
+
* const scrollY = shallowRef(0);
|
|
723
|
+
* const { throttled } = useThrottled(scrollY, 100);
|
|
724
|
+
* scrollY.value = 40; // applied immediately (leading edge)
|
|
725
|
+
* scrollY.value = 80; // applied when the 100ms window ends
|
|
726
|
+
* ```
|
|
727
|
+
*
|
|
728
|
+
* @param source Reactive source to throttle.
|
|
729
|
+
* @param waitMs Reactive cooldown in milliseconds; must be finite and at
|
|
730
|
+
* least zero. Fractions are truncated.
|
|
731
|
+
* @param options Edge policy and runtime scheduling overrides.
|
|
732
|
+
* @default options {}
|
|
733
|
+
* @throws `RangeError` tagged `VIZE_COMPOSE_THROTTLE_INVALID_WAIT` when the
|
|
734
|
+
* resolved wait is not finite or is negative, both synchronously at creation
|
|
735
|
+
* (even in mirror mode) and again each time a window opens.
|
|
736
|
+
* @throws `TypeError` tagged `VIZE_COMPOSE_THROTTLE_INVALID_EDGES` when both
|
|
737
|
+
* `leading` and `trailing` are disabled, because updates could then never
|
|
738
|
+
* propagate.
|
|
739
|
+
* @returns Readonly throttled view, pending flag, and cancel/flush controls.
|
|
740
|
+
*/
|
|
741
|
+
declare function useThrottled<Value>(source: MaybeRefOrGetter<Value>, waitMs: MaybeRefOrGetter<number>, options?: UseThrottledOptions): ThrottledControls<Value>;
|
|
742
|
+
//#endregion
|
|
743
|
+
//#region src/use-toggle.d.ts
|
|
744
|
+
/** Reactive controls returned by {@link useToggle}. */
|
|
745
|
+
interface ToggleControls {
|
|
746
|
+
/**
|
|
747
|
+
* Owned boolean state.
|
|
748
|
+
*
|
|
749
|
+
* Deliberately writable: unlike the derived views elsewhere in this
|
|
750
|
+
* package, the toggle owns its state, so assigning the ref directly (for
|
|
751
|
+
* example through `v-model`) is equivalent to calling
|
|
752
|
+
* {@link ToggleControls.toggle} with a forced value.
|
|
753
|
+
*/
|
|
754
|
+
readonly state: Ref<boolean>;
|
|
755
|
+
/**
|
|
756
|
+
* Invert the state, or force it to `force` when the argument is given.
|
|
757
|
+
* Passing an explicit `undefined` behaves like passing no argument.
|
|
758
|
+
*
|
|
759
|
+
* @param force Value assigned instead of inverting.
|
|
760
|
+
* @returns The state after the change.
|
|
761
|
+
*/
|
|
762
|
+
readonly toggle: (force?: boolean) => boolean;
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Create owned boolean state with an inverting control.
|
|
766
|
+
*
|
|
767
|
+
* Purely synchronous state: safe during server rendering (no browser
|
|
768
|
+
* globals, no timers) and nothing to dispose, so it works inside and
|
|
769
|
+
* outside reactive scopes alike.
|
|
770
|
+
*
|
|
771
|
+
* @example
|
|
772
|
+
* ```ts
|
|
773
|
+
* const { state: open, toggle } = useToggle();
|
|
774
|
+
* toggle(); // true
|
|
775
|
+
* toggle(false); // false
|
|
776
|
+
* open.value; // false
|
|
777
|
+
* ```
|
|
778
|
+
*
|
|
779
|
+
* @param initial State before the first toggle.
|
|
780
|
+
* @default initial false
|
|
781
|
+
* @returns The writable state and its toggle control.
|
|
782
|
+
*/
|
|
783
|
+
declare function useToggle(initial?: boolean): ToggleControls;
|
|
784
|
+
//#endregion
|
|
785
|
+
export { AsyncResource, AsyncResourceContext, AsyncResourceExecution, AsyncResourceStatus, CounterControls, DebouncedControls, EventListenerControls, HistoryControls, LocaleControls, MediaQueryHost, MotionPreference, TextDirection, ThrottledControls, TimeoutScheduler, ToggleControls, UseAsyncResourceOptions, UseCounterOptions, UseDebouncedOptions, UseEventListenerOptions, UseHistoryOptions, UseLocaleOptions, UseMediaQueryOptions, UseThrottledOptions, tryOnScopeDispose, useAsyncResource, useCounter, useDebounced, useEventListener, useHistory, useLocale, useMediaQuery, usePrevious, useReducedMotion, useThrottled, useToggle };
|
package/dist/index.mjs
CHANGED
|
@@ -3,6 +3,14 @@ import { computed, getCurrentScope, onScopeDispose, readonly, ref, shallowRef, t
|
|
|
3
3
|
/**
|
|
4
4
|
* Register cleanup in the active reactive scope when one exists.
|
|
5
5
|
*
|
|
6
|
+
* This is the shared lifecycle primitive of the package: composables hand
|
|
7
|
+
* their teardown here so owned resources are released when the surrounding
|
|
8
|
+
* scope stops.
|
|
9
|
+
*
|
|
10
|
+
* Never throws and is safe during server rendering; no browser globals are
|
|
11
|
+
* read. When no scope is active the cleanup is not registered and disposal
|
|
12
|
+
* ownership stays with the caller.
|
|
13
|
+
*
|
|
6
14
|
* @param cleanup Cleanup invoked exactly once when the scope is disposed.
|
|
7
15
|
* @returns Whether the cleanup was registered.
|
|
8
16
|
*/
|
|
@@ -17,6 +25,18 @@ function tryOnScopeDispose(cleanup) {
|
|
|
17
25
|
* Create a scoped, abortable asynchronous resource with latest-result-wins
|
|
18
26
|
* state. Every execution returns a discriminated result, so cancellation,
|
|
19
27
|
* supersession, loader failure, and successful `undefined` data stay distinct.
|
|
28
|
+
*
|
|
29
|
+
* When created inside an active reactive scope (and `scope` is enabled), the
|
|
30
|
+
* active execution is aborted when that scope stops; outside a scope,
|
|
31
|
+
* cancellation ownership stays with the caller. The execute promise never
|
|
32
|
+
* rejects — synchronous and asynchronous loader failures both settle into
|
|
33
|
+
* the `"error"` result. Safe during server rendering: no browser globals are
|
|
34
|
+
* read and abort reasons use the runtime-native `DOMException`.
|
|
35
|
+
*
|
|
36
|
+
* @param loader Asynchronous loader receiving the abort context first.
|
|
37
|
+
* @param options Data retention, supersession, and scope behavior.
|
|
38
|
+
* @default options {}
|
|
39
|
+
* @returns Reactive state and controls for the loader.
|
|
20
40
|
*/
|
|
21
41
|
function useAsyncResource(loader, options = {}) {
|
|
22
42
|
const data = shallowRef(options.initialData);
|
|
@@ -152,6 +172,19 @@ const FORMATTER_CACHE_LIMIT = 32;
|
|
|
152
172
|
* Equivalent formatter options reuse instances. The bounded cache follows the
|
|
153
173
|
* active locale automatically and prevents repeated constructor overhead in
|
|
154
174
|
* reactive render paths.
|
|
175
|
+
*
|
|
176
|
+
* Detection is lazy and guarded: the default detector reads
|
|
177
|
+
* `navigator.language` behind a `typeof` check at call time, so importing and
|
|
178
|
+
* calling this during server rendering is safe, and runtimes without a
|
|
179
|
+
* `navigator` resolve to the fallback locale. The composable owns no timers
|
|
180
|
+
* or listeners, so no scope cleanup is required.
|
|
181
|
+
*
|
|
182
|
+
* @param source Reactive locale source. Empty values defer to the detector.
|
|
183
|
+
* @param options Detection and fallback behavior.
|
|
184
|
+
* @default options {}
|
|
185
|
+
* @throws `RangeError` on first read of the reactive values when the winning
|
|
186
|
+
* candidate is not a structurally valid locale identifier.
|
|
187
|
+
* @returns Reactive locale metadata and cached formatter factories.
|
|
155
188
|
*/
|
|
156
189
|
function useLocale(source, options = {}) {
|
|
157
190
|
const locale = computed(() => {
|
|
@@ -205,9 +238,18 @@ function serializeOptions(options) {
|
|
|
205
238
|
/**
|
|
206
239
|
* Evaluate a reactive media query without requiring browser globals.
|
|
207
240
|
*
|
|
241
|
+
* During server rendering (or whenever no capability host resolves) the ref
|
|
242
|
+
* holds the configured server value and no subscription is created. The
|
|
243
|
+
* change subscription follows the reactive query and host: each
|
|
244
|
+
* re-evaluation removes the previous listener, and the final listener is
|
|
245
|
+
* removed when the owning reactive scope stops. Call inside an active scope
|
|
246
|
+
* so the subscription is released. A host whose matcher throws propagates
|
|
247
|
+
* the error to the active effect run; the browser default never throws.
|
|
248
|
+
*
|
|
208
249
|
* @param query Reactive media-query source.
|
|
209
250
|
* @param options Runtime capability and server-rendered fallback.
|
|
210
251
|
* @default options {}
|
|
252
|
+
* @returns Readonly ref that is `true` while the query matches.
|
|
211
253
|
*/
|
|
212
254
|
function useMediaQuery(query, options = {}) {
|
|
213
255
|
const matches = ref(options.ssrValue ?? false);
|
|
@@ -230,8 +272,13 @@ function useMediaQuery(query, options = {}) {
|
|
|
230
272
|
/**
|
|
231
273
|
* Return the reactive user motion preference.
|
|
232
274
|
*
|
|
275
|
+
* Shares {@link useMediaQuery} semantics: during server rendering the
|
|
276
|
+
* preference is `"no-preference"` unless `ssrValue` is `true`, and the
|
|
277
|
+
* underlying subscription is removed when the owning reactive scope stops.
|
|
278
|
+
*
|
|
233
279
|
* @param options Runtime capability and server-rendered fallback.
|
|
234
280
|
* @default options {}
|
|
281
|
+
* @returns Computed preference for `(prefers-reduced-motion: reduce)`.
|
|
235
282
|
*/
|
|
236
283
|
function useReducedMotion(options = {}) {
|
|
237
284
|
const reduced = useMediaQuery("(prefers-reduced-motion: reduce)", options);
|
|
@@ -241,4 +288,460 @@ function browserMediaQueryHost() {
|
|
|
241
288
|
return typeof window !== "undefined" && typeof window.matchMedia === "function" ? window : void 0;
|
|
242
289
|
}
|
|
243
290
|
//#endregion
|
|
244
|
-
|
|
291
|
+
//#region src/use-counter.ts
|
|
292
|
+
/**
|
|
293
|
+
* Create a clamped counter whose every transition stays inside `[min, max]`.
|
|
294
|
+
*
|
|
295
|
+
* All operations clamp instead of failing, including the initial value, so
|
|
296
|
+
* the count is inside the bounds at every observable moment. Only `NaN` is
|
|
297
|
+
* rejected — silently corrupting the count is never an option. Purely
|
|
298
|
+
* synchronous state: safe during server rendering (no browser globals, no
|
|
299
|
+
* timers) and nothing to dispose, so it works inside and outside reactive
|
|
300
|
+
* scopes alike. Bounds are fixed at creation and not reactive.
|
|
301
|
+
*
|
|
302
|
+
* @example
|
|
303
|
+
* ```ts
|
|
304
|
+
* const { count, increment, atMax } = useCounter(9, { min: 0, max: 10 });
|
|
305
|
+
* increment(); // 10
|
|
306
|
+
* increment(); // 10 (clamped)
|
|
307
|
+
* atMax.value; // true
|
|
308
|
+
* ```
|
|
309
|
+
*
|
|
310
|
+
* @param initial Count before any operation, clamped into the bounds.
|
|
311
|
+
* @default initial 0
|
|
312
|
+
* @param options Inclusive bounds for every value the counter takes.
|
|
313
|
+
* @default options {}
|
|
314
|
+
* @throws `RangeError` tagged `VIZE_COMPOSE_COUNTER_INVALID_RANGE` when a
|
|
315
|
+
* bound is `NaN` or `min` exceeds `max`.
|
|
316
|
+
* @throws `RangeError` tagged `VIZE_COMPOSE_COUNTER_INVALID_VALUE` when an
|
|
317
|
+
* initial value, operand, or arithmetic result is `NaN` (for example
|
|
318
|
+
* incrementing `-Infinity` by `Infinity`); the count is left unchanged.
|
|
319
|
+
* @returns Reactive count, bound flags, and mutation controls.
|
|
320
|
+
*/
|
|
321
|
+
function useCounter(initial = 0, options = {}) {
|
|
322
|
+
const min = requireBound(options.min ?? Number.NEGATIVE_INFINITY, "min");
|
|
323
|
+
const max = requireBound(options.max ?? Number.POSITIVE_INFINITY, "max");
|
|
324
|
+
if (min > max) throw new RangeError(`[VIZE_COMPOSE_COUNTER_INVALID_RANGE] min must not exceed max; received min ${String(min)} and max ${String(max)}`);
|
|
325
|
+
const clamp = (value) => Math.min(max, Math.max(min, value));
|
|
326
|
+
const count = shallowRef(clamp(requireValue(initial)));
|
|
327
|
+
let baseline = count.value;
|
|
328
|
+
const setClamped = (next) => {
|
|
329
|
+
count.value = clamp(requireValue(next));
|
|
330
|
+
return count.value;
|
|
331
|
+
};
|
|
332
|
+
const reset = (value) => {
|
|
333
|
+
const applied = setClamped(value ?? baseline);
|
|
334
|
+
if (value !== void 0) baseline = applied;
|
|
335
|
+
return applied;
|
|
336
|
+
};
|
|
337
|
+
return {
|
|
338
|
+
count,
|
|
339
|
+
atMin: computed(() => count.value === min),
|
|
340
|
+
atMax: computed(() => count.value === max),
|
|
341
|
+
increment: (delta = 1) => setClamped(count.value + requireValue(delta)),
|
|
342
|
+
decrement: (delta = 1) => setClamped(count.value - requireValue(delta)),
|
|
343
|
+
set: setClamped,
|
|
344
|
+
reset
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
function requireBound(value, label) {
|
|
348
|
+
if (Number.isNaN(value)) throw new RangeError(`[VIZE_COMPOSE_COUNTER_INVALID_RANGE] ${label} must not be NaN`);
|
|
349
|
+
return value;
|
|
350
|
+
}
|
|
351
|
+
function requireValue(value) {
|
|
352
|
+
if (Number.isNaN(value)) throw new RangeError("[VIZE_COMPOSE_COUNTER_INVALID_VALUE] the value is NaN; counter state was left unchanged");
|
|
353
|
+
return value;
|
|
354
|
+
}
|
|
355
|
+
//#endregion
|
|
356
|
+
//#region src/use-debounced.ts
|
|
357
|
+
const defaultScheduler$1 = {
|
|
358
|
+
setTimeout: (callback, delayMs) => globalThis.setTimeout(callback, delayMs),
|
|
359
|
+
clearTimeout: (handle) => {
|
|
360
|
+
globalThis.clearTimeout(handle);
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
/**
|
|
364
|
+
* Create a readonly debounced view of a reactive source.
|
|
365
|
+
*
|
|
366
|
+
* The view starts at the current source value. Each source change (observed
|
|
367
|
+
* with `flush: "sync"`, so every synchronous write counts) restarts a
|
|
368
|
+
* single-shot timer of `waitMs` milliseconds; when it fires, the view takes
|
|
369
|
+
* the source value current at that moment. `waitMs` is reactive and is read
|
|
370
|
+
* when a timer is scheduled; changing it does not restart an already-pending
|
|
371
|
+
* timer. A wait of `0` still defers to the next scheduler tick.
|
|
372
|
+
*
|
|
373
|
+
* Server rendering is explicit: without a browser `window` (and with
|
|
374
|
+
* {@link UseDebouncedOptions.runOnServer} disabled) no timer ever starts and
|
|
375
|
+
* the view mirrors the source synchronously, so server-rendered output shows
|
|
376
|
+
* current values and nothing leaks. `pending` stays `false` and the controls
|
|
377
|
+
* report `false` in that mode.
|
|
378
|
+
*
|
|
379
|
+
* Cleanup rule: the watcher and any pending timer are released when the
|
|
380
|
+
* owning reactive scope stops; call inside an active scope. Outside one, the
|
|
381
|
+
* watcher lives as long as the source and `cancel()` only clears the pending
|
|
382
|
+
* timer.
|
|
383
|
+
*
|
|
384
|
+
* @example
|
|
385
|
+
* ```ts
|
|
386
|
+
* const query = shallowRef("");
|
|
387
|
+
* const { debounced, flush } = useDebounced(query, 300);
|
|
388
|
+
* query.value = "vize"; // debounced.value still "" for 300ms
|
|
389
|
+
* flush(); // debounced.value === "vize" immediately
|
|
390
|
+
* ```
|
|
391
|
+
*
|
|
392
|
+
* @param source Reactive source to debounce.
|
|
393
|
+
* @param waitMs Reactive delay in milliseconds; must be finite and at least
|
|
394
|
+
* zero. Fractions are truncated.
|
|
395
|
+
* @param options Runtime scheduling overrides.
|
|
396
|
+
* @default options {}
|
|
397
|
+
* @throws `RangeError` tagged `VIZE_COMPOSE_DEBOUNCE_INVALID_WAIT` when the
|
|
398
|
+
* resolved wait is not finite or is negative, both synchronously at creation
|
|
399
|
+
* (even in mirror mode) and again for every scheduled delay.
|
|
400
|
+
* @returns Readonly debounced view, pending flag, and cancel/flush controls.
|
|
401
|
+
*/
|
|
402
|
+
function useDebounced(source, waitMs, options = {}) {
|
|
403
|
+
const scheduler = options.scheduler ?? defaultScheduler$1;
|
|
404
|
+
const debounced = shallowRef(toValue(source));
|
|
405
|
+
const pending = shallowRef(false);
|
|
406
|
+
let handle;
|
|
407
|
+
resolveWaitMs$1(toValue(waitMs));
|
|
408
|
+
const apply = () => {
|
|
409
|
+
handle = void 0;
|
|
410
|
+
pending.value = false;
|
|
411
|
+
debounced.value = toValue(source);
|
|
412
|
+
};
|
|
413
|
+
const cancel = () => {
|
|
414
|
+
if (!pending.value) return false;
|
|
415
|
+
scheduler.clearTimeout(handle);
|
|
416
|
+
handle = void 0;
|
|
417
|
+
pending.value = false;
|
|
418
|
+
return true;
|
|
419
|
+
};
|
|
420
|
+
const flush = () => {
|
|
421
|
+
if (!pending.value) return false;
|
|
422
|
+
scheduler.clearTimeout(handle);
|
|
423
|
+
apply();
|
|
424
|
+
return true;
|
|
425
|
+
};
|
|
426
|
+
watch(() => toValue(source), (next) => {
|
|
427
|
+
if (typeof window === "undefined" && !(options.runOnServer ?? false)) {
|
|
428
|
+
debounced.value = next;
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
const delayMs = resolveWaitMs$1(toValue(waitMs));
|
|
432
|
+
if (pending.value) scheduler.clearTimeout(handle);
|
|
433
|
+
pending.value = true;
|
|
434
|
+
handle = scheduler.setTimeout(apply, delayMs);
|
|
435
|
+
}, { flush: "sync" });
|
|
436
|
+
tryOnScopeDispose(() => {
|
|
437
|
+
cancel();
|
|
438
|
+
});
|
|
439
|
+
return {
|
|
440
|
+
debounced,
|
|
441
|
+
pending,
|
|
442
|
+
cancel,
|
|
443
|
+
flush
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
function resolveWaitMs$1(value) {
|
|
447
|
+
if (!Number.isFinite(value) || value < 0) throw new RangeError(`[VIZE_COMPOSE_DEBOUNCE_INVALID_WAIT] waitMs must be finite and at least zero; received ${String(value)}`);
|
|
448
|
+
return Math.trunc(value);
|
|
449
|
+
}
|
|
450
|
+
//#endregion
|
|
451
|
+
//#region src/use-history.ts
|
|
452
|
+
/**
|
|
453
|
+
* Record bounded undo/redo history over the writes of a ref.
|
|
454
|
+
*
|
|
455
|
+
* Recording is shallow and identity-based, matching Vue's own change
|
|
456
|
+
* detection: assignments to `source.value` are recorded (observed with
|
|
457
|
+
* `flush: "sync"`, so every synchronous write counts), writes that are
|
|
458
|
+
* `Object.is`-equal to the current value are not changes, and in-place
|
|
459
|
+
* mutations of object values are invisible — pair mutable values with
|
|
460
|
+
* {@link UseHistoryOptions.clone} and reassign. Undoing and redoing restore
|
|
461
|
+
* values through `clone` as well, so snapshots never share identity with the
|
|
462
|
+
* live value unless the default identity clone is kept. When a
|
|
463
|
+
* user-provided `clone` throws, the failed operation leaves history
|
|
464
|
+
* unchanged and the error propagates.
|
|
465
|
+
*
|
|
466
|
+
* Safe during server rendering: no browser globals are read and no timers
|
|
467
|
+
* start. Cleanup rule: when the owning reactive scope stops, recording stops
|
|
468
|
+
* and every retained snapshot is released, so `undo`/`redo` return `false`
|
|
469
|
+
* afterwards; call inside an active scope, or the watcher lives as long as
|
|
470
|
+
* the source.
|
|
471
|
+
*
|
|
472
|
+
* @example
|
|
473
|
+
* ```ts
|
|
474
|
+
* const text = shallowRef("");
|
|
475
|
+
* const { undo, redo, batch } = useHistory(text);
|
|
476
|
+
* text.value = "a";
|
|
477
|
+
* batch(() => {
|
|
478
|
+
* text.value = "ab";
|
|
479
|
+
* text.value = "abc";
|
|
480
|
+
* });
|
|
481
|
+
* undo(); // text.value === "a" (the batch is one step)
|
|
482
|
+
* redo(); // text.value === "abc"
|
|
483
|
+
* ```
|
|
484
|
+
*
|
|
485
|
+
* @param source Ref whose writes are recorded.
|
|
486
|
+
* @param options Retention bound and snapshot cloning.
|
|
487
|
+
* @default options {}
|
|
488
|
+
* @throws `RangeError` tagged `VIZE_COMPOSE_HISTORY_INVALID_CAPACITY` when
|
|
489
|
+
* the capacity is not an integer greater than zero.
|
|
490
|
+
* @throws `Error` tagged `VIZE_COMPOSE_HISTORY_IN_BATCH` when `undo`,
|
|
491
|
+
* `redo`, or `clear` is called inside {@link HistoryControls.batch}, where
|
|
492
|
+
* stack movement would corrupt the pending group.
|
|
493
|
+
* @returns Reactive undo/redo state and controls.
|
|
494
|
+
*/
|
|
495
|
+
function useHistory(source, options = {}) {
|
|
496
|
+
const capacity = options.capacity ?? 100;
|
|
497
|
+
if (!Number.isInteger(capacity) || capacity < 1) throw new RangeError(`[VIZE_COMPOSE_HISTORY_INVALID_CAPACITY] capacity must be an integer greater than zero; received ${String(capacity)}`);
|
|
498
|
+
const clone = options.clone ?? ((value) => value);
|
|
499
|
+
const undoStack = shallowRef([]);
|
|
500
|
+
const redoStack = shallowRef([]);
|
|
501
|
+
let restoring = false;
|
|
502
|
+
let batchDepth = 0;
|
|
503
|
+
let activeBatch;
|
|
504
|
+
const pushUndo = (entry) => {
|
|
505
|
+
const next = [...undoStack.value, entry];
|
|
506
|
+
undoStack.value = next.length > capacity ? next.slice(next.length - capacity) : next;
|
|
507
|
+
redoStack.value = [];
|
|
508
|
+
};
|
|
509
|
+
const writeSilently = (value) => {
|
|
510
|
+
restoring = true;
|
|
511
|
+
try {
|
|
512
|
+
source.value = value;
|
|
513
|
+
} finally {
|
|
514
|
+
restoring = false;
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
const requireOutsideBatch = (operation) => {
|
|
518
|
+
if (batchDepth > 0) throw new Error(`[VIZE_COMPOSE_HISTORY_IN_BATCH] ${operation}() is not available inside batch()`);
|
|
519
|
+
};
|
|
520
|
+
const undo = () => {
|
|
521
|
+
requireOutsideBatch("undo");
|
|
522
|
+
const entry = undoStack.value.at(-1);
|
|
523
|
+
if (entry === void 0) return false;
|
|
524
|
+
const restored = clone(entry.value);
|
|
525
|
+
const recorded = { value: clone(source.value) };
|
|
526
|
+
undoStack.value = undoStack.value.slice(0, -1);
|
|
527
|
+
redoStack.value = [...redoStack.value, recorded];
|
|
528
|
+
writeSilently(restored);
|
|
529
|
+
return true;
|
|
530
|
+
};
|
|
531
|
+
const redo = () => {
|
|
532
|
+
requireOutsideBatch("redo");
|
|
533
|
+
const entry = redoStack.value.at(-1);
|
|
534
|
+
if (entry === void 0) return false;
|
|
535
|
+
const restored = clone(entry.value);
|
|
536
|
+
const recorded = { value: clone(source.value) };
|
|
537
|
+
redoStack.value = redoStack.value.slice(0, -1);
|
|
538
|
+
undoStack.value = [...undoStack.value, recorded];
|
|
539
|
+
writeSilently(restored);
|
|
540
|
+
return true;
|
|
541
|
+
};
|
|
542
|
+
const batch = (run) => {
|
|
543
|
+
if (batchDepth === 0) activeBatch = {
|
|
544
|
+
raw: source.value,
|
|
545
|
+
entry: { value: clone(source.value) }
|
|
546
|
+
};
|
|
547
|
+
batchDepth += 1;
|
|
548
|
+
try {
|
|
549
|
+
return run();
|
|
550
|
+
} finally {
|
|
551
|
+
batchDepth -= 1;
|
|
552
|
+
if (batchDepth === 0 && activeBatch !== void 0) {
|
|
553
|
+
const finished = activeBatch;
|
|
554
|
+
activeBatch = void 0;
|
|
555
|
+
if (!Object.is(finished.raw, source.value)) pushUndo(finished.entry);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
const clear = () => {
|
|
560
|
+
requireOutsideBatch("clear");
|
|
561
|
+
undoStack.value = [];
|
|
562
|
+
redoStack.value = [];
|
|
563
|
+
};
|
|
564
|
+
const handle = watch(source, (_next, replaced) => {
|
|
565
|
+
if (restoring || batchDepth > 0) return;
|
|
566
|
+
pushUndo({ value: clone(replaced) });
|
|
567
|
+
}, { flush: "sync" });
|
|
568
|
+
tryOnScopeDispose(() => {
|
|
569
|
+
handle.stop();
|
|
570
|
+
undoStack.value = [];
|
|
571
|
+
redoStack.value = [];
|
|
572
|
+
});
|
|
573
|
+
return {
|
|
574
|
+
canUndo: computed(() => undoStack.value.length > 0),
|
|
575
|
+
canRedo: computed(() => redoStack.value.length > 0),
|
|
576
|
+
undoCount: computed(() => undoStack.value.length),
|
|
577
|
+
redoCount: computed(() => redoStack.value.length),
|
|
578
|
+
undo,
|
|
579
|
+
redo,
|
|
580
|
+
batch,
|
|
581
|
+
clear
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
//#endregion
|
|
585
|
+
//#region src/use-previous.ts
|
|
586
|
+
function usePrevious(source, ...initial) {
|
|
587
|
+
const previous = shallowRef(initial.length === 1 ? initial[0] : void 0);
|
|
588
|
+
watch(() => toValue(source), (_next, replaced) => {
|
|
589
|
+
previous.value = replaced;
|
|
590
|
+
}, { flush: "sync" });
|
|
591
|
+
return previous;
|
|
592
|
+
}
|
|
593
|
+
//#endregion
|
|
594
|
+
//#region src/use-throttled.ts
|
|
595
|
+
const defaultScheduler = {
|
|
596
|
+
setTimeout: (callback, delayMs) => globalThis.setTimeout(callback, delayMs),
|
|
597
|
+
clearTimeout: (handle) => {
|
|
598
|
+
globalThis.clearTimeout(handle);
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
/**
|
|
602
|
+
* Create a readonly throttled view of a reactive source.
|
|
603
|
+
*
|
|
604
|
+
* Changes are observed with `flush: "sync"`, so every synchronous write
|
|
605
|
+
* counts. Outside a cooldown window, a change applies immediately when
|
|
606
|
+
* {@link UseThrottledOptions.leading} is enabled (otherwise it waits as a
|
|
607
|
+
* trailing update) and opens a window of `waitMs` milliseconds. Changes
|
|
608
|
+
* inside a window are collected as the trailing candidate; when the window
|
|
609
|
+
* ends with a candidate waiting, the source value current at that moment is
|
|
610
|
+
* applied and the next window opens back to back, keeping applications
|
|
611
|
+
* spaced by `waitMs`. A window that ends without a candidate closes silently.
|
|
612
|
+
* `waitMs` is reactive and is read each time a window opens; changing it
|
|
613
|
+
* never disturbs an already-open window. A wait of `0` still defers trailing
|
|
614
|
+
* updates to the next scheduler tick.
|
|
615
|
+
*
|
|
616
|
+
* Server rendering is explicit: without a browser `window` (and with
|
|
617
|
+
* {@link UseThrottledOptions.runOnServer} disabled) no timer ever starts and
|
|
618
|
+
* the view mirrors the source synchronously, so server-rendered output shows
|
|
619
|
+
* current values and nothing leaks. `pending` stays `false` and the controls
|
|
620
|
+
* report `false` in that mode.
|
|
621
|
+
*
|
|
622
|
+
* Cleanup rule: the watcher and any open window timer are released when the
|
|
623
|
+
* owning reactive scope stops; call inside an active scope. Outside one, the
|
|
624
|
+
* watcher lives as long as the source and `cancel()` only clears the window.
|
|
625
|
+
*
|
|
626
|
+
* @example
|
|
627
|
+
* ```ts
|
|
628
|
+
* const scrollY = shallowRef(0);
|
|
629
|
+
* const { throttled } = useThrottled(scrollY, 100);
|
|
630
|
+
* scrollY.value = 40; // applied immediately (leading edge)
|
|
631
|
+
* scrollY.value = 80; // applied when the 100ms window ends
|
|
632
|
+
* ```
|
|
633
|
+
*
|
|
634
|
+
* @param source Reactive source to throttle.
|
|
635
|
+
* @param waitMs Reactive cooldown in milliseconds; must be finite and at
|
|
636
|
+
* least zero. Fractions are truncated.
|
|
637
|
+
* @param options Edge policy and runtime scheduling overrides.
|
|
638
|
+
* @default options {}
|
|
639
|
+
* @throws `RangeError` tagged `VIZE_COMPOSE_THROTTLE_INVALID_WAIT` when the
|
|
640
|
+
* resolved wait is not finite or is negative, both synchronously at creation
|
|
641
|
+
* (even in mirror mode) and again each time a window opens.
|
|
642
|
+
* @throws `TypeError` tagged `VIZE_COMPOSE_THROTTLE_INVALID_EDGES` when both
|
|
643
|
+
* `leading` and `trailing` are disabled, because updates could then never
|
|
644
|
+
* propagate.
|
|
645
|
+
* @returns Readonly throttled view, pending flag, and cancel/flush controls.
|
|
646
|
+
*/
|
|
647
|
+
function useThrottled(source, waitMs, options = {}) {
|
|
648
|
+
const leading = options.leading ?? true;
|
|
649
|
+
const trailing = options.trailing ?? true;
|
|
650
|
+
if (!leading && !trailing) throw new TypeError("[VIZE_COMPOSE_THROTTLE_INVALID_EDGES] at least one of leading or trailing must be enabled");
|
|
651
|
+
const scheduler = options.scheduler ?? defaultScheduler;
|
|
652
|
+
const throttled = shallowRef(toValue(source));
|
|
653
|
+
const pending = shallowRef(false);
|
|
654
|
+
let windowHandle;
|
|
655
|
+
let windowOpen = false;
|
|
656
|
+
resolveWaitMs(toValue(waitMs));
|
|
657
|
+
const openWindow = () => {
|
|
658
|
+
windowOpen = true;
|
|
659
|
+
windowHandle = scheduler.setTimeout(() => {
|
|
660
|
+
windowHandle = void 0;
|
|
661
|
+
if (!pending.value) {
|
|
662
|
+
windowOpen = false;
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
pending.value = false;
|
|
666
|
+
throttled.value = toValue(source);
|
|
667
|
+
openWindow();
|
|
668
|
+
}, resolveWaitMs(toValue(waitMs)));
|
|
669
|
+
};
|
|
670
|
+
const closeWindow = () => {
|
|
671
|
+
if (windowOpen) scheduler.clearTimeout(windowHandle);
|
|
672
|
+
windowHandle = void 0;
|
|
673
|
+
windowOpen = false;
|
|
674
|
+
pending.value = false;
|
|
675
|
+
};
|
|
676
|
+
const cancel = () => {
|
|
677
|
+
const hadTrailing = pending.value;
|
|
678
|
+
closeWindow();
|
|
679
|
+
return hadTrailing;
|
|
680
|
+
};
|
|
681
|
+
const flush = () => {
|
|
682
|
+
if (!pending.value) return false;
|
|
683
|
+
closeWindow();
|
|
684
|
+
throttled.value = toValue(source);
|
|
685
|
+
return true;
|
|
686
|
+
};
|
|
687
|
+
watch(() => toValue(source), (next) => {
|
|
688
|
+
if (typeof window === "undefined" && !(options.runOnServer ?? false)) {
|
|
689
|
+
throttled.value = next;
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
if (windowOpen) {
|
|
693
|
+
if (trailing) pending.value = true;
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
if (leading) throttled.value = next;
|
|
697
|
+
else pending.value = true;
|
|
698
|
+
openWindow();
|
|
699
|
+
}, { flush: "sync" });
|
|
700
|
+
tryOnScopeDispose(() => {
|
|
701
|
+
cancel();
|
|
702
|
+
});
|
|
703
|
+
return {
|
|
704
|
+
throttled,
|
|
705
|
+
pending,
|
|
706
|
+
cancel,
|
|
707
|
+
flush
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
function resolveWaitMs(value) {
|
|
711
|
+
if (!Number.isFinite(value) || value < 0) throw new RangeError(`[VIZE_COMPOSE_THROTTLE_INVALID_WAIT] waitMs must be finite and at least zero; received ${String(value)}`);
|
|
712
|
+
return Math.trunc(value);
|
|
713
|
+
}
|
|
714
|
+
//#endregion
|
|
715
|
+
//#region src/use-toggle.ts
|
|
716
|
+
/**
|
|
717
|
+
* Create owned boolean state with an inverting control.
|
|
718
|
+
*
|
|
719
|
+
* Purely synchronous state: safe during server rendering (no browser
|
|
720
|
+
* globals, no timers) and nothing to dispose, so it works inside and
|
|
721
|
+
* outside reactive scopes alike.
|
|
722
|
+
*
|
|
723
|
+
* @example
|
|
724
|
+
* ```ts
|
|
725
|
+
* const { state: open, toggle } = useToggle();
|
|
726
|
+
* toggle(); // true
|
|
727
|
+
* toggle(false); // false
|
|
728
|
+
* open.value; // false
|
|
729
|
+
* ```
|
|
730
|
+
*
|
|
731
|
+
* @param initial State before the first toggle.
|
|
732
|
+
* @default initial false
|
|
733
|
+
* @returns The writable state and its toggle control.
|
|
734
|
+
*/
|
|
735
|
+
function useToggle(initial = false) {
|
|
736
|
+
const state = shallowRef(initial);
|
|
737
|
+
const toggle = (force) => {
|
|
738
|
+
state.value = force ?? !state.value;
|
|
739
|
+
return state.value;
|
|
740
|
+
};
|
|
741
|
+
return {
|
|
742
|
+
state,
|
|
743
|
+
toggle
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
//#endregion
|
|
747
|
+
export { tryOnScopeDispose, useAsyncResource, useCounter, useDebounced, useEventListener, useHistory, useLocale, useMediaQuery, usePrevious, useReducedMotion, useThrottled, useToggle };
|
package/dist/temporal.d.mts
CHANGED
|
@@ -75,15 +75,31 @@ interface UseTemporalZonedDateTimeOptions extends UseTemporalNowOptions {
|
|
|
75
75
|
* effect scope.
|
|
76
76
|
*
|
|
77
77
|
* The timer is replaced when reactive options change and is always cancelled
|
|
78
|
-
* when the owning scope stops.
|
|
78
|
+
* when the owning scope stops. Call inside an active effect scope; without
|
|
79
|
+
* one, there is no automatic timer cleanup. During
|
|
80
|
+
* server rendering, no timer starts unless
|
|
79
81
|
* {@link UseTemporalNowOptions.runOnServer} is explicitly enabled.
|
|
82
|
+
*
|
|
83
|
+
* @param options Clock scheduling, pause, and runtime overrides.
|
|
84
|
+
* @default options {}
|
|
85
|
+
* @throws `RangeError` tagged `VIZE_COMPOSE_TEMPORAL_INVALID_INTERVAL` when
|
|
86
|
+
* the resolved interval is not finite or not greater than zero, both
|
|
87
|
+
* synchronously at creation and on invalid reactive updates.
|
|
88
|
+
* @returns Reactive clock controls.
|
|
80
89
|
*/
|
|
81
90
|
declare function useTemporalNow(options?: UseTemporalNowOptions): TemporalClock;
|
|
82
91
|
/**
|
|
83
92
|
* Creates a reactive zoned date-time derived from a scoped Temporal clock.
|
|
84
93
|
*
|
|
85
94
|
* Changes to {@link UseTemporalZonedDateTimeOptions.timeZone} are reflected
|
|
86
|
-
* without replacing the underlying timer.
|
|
95
|
+
* without replacing the underlying timer. Scheduling, server rendering, and
|
|
96
|
+
* scope-cleanup rules are shared with {@link useTemporalNow}.
|
|
97
|
+
*
|
|
98
|
+
* @param options Clock scheduling plus the reactive time zone.
|
|
99
|
+
* @default options {}
|
|
100
|
+
* @throws `RangeError` for invalid intervals (see {@link useTemporalNow}),
|
|
101
|
+
* and on read when the reactive time zone cannot be resolved by Temporal.
|
|
102
|
+
* @returns Computed zoned date-time in the reactive time zone.
|
|
87
103
|
*/
|
|
88
104
|
declare function useTemporalZonedDateTime(options?: UseTemporalZonedDateTimeOptions): ComputedRef<Temporal.ZonedDateTime>;
|
|
89
105
|
//#endregion
|
package/dist/temporal.mjs
CHANGED
|
@@ -20,8 +20,17 @@ function shouldSchedule(options) {
|
|
|
20
20
|
* effect scope.
|
|
21
21
|
*
|
|
22
22
|
* The timer is replaced when reactive options change and is always cancelled
|
|
23
|
-
* when the owning scope stops.
|
|
23
|
+
* when the owning scope stops. Call inside an active effect scope; without
|
|
24
|
+
* one, there is no automatic timer cleanup. During
|
|
25
|
+
* server rendering, no timer starts unless
|
|
24
26
|
* {@link UseTemporalNowOptions.runOnServer} is explicitly enabled.
|
|
27
|
+
*
|
|
28
|
+
* @param options Clock scheduling, pause, and runtime overrides.
|
|
29
|
+
* @default options {}
|
|
30
|
+
* @throws `RangeError` tagged `VIZE_COMPOSE_TEMPORAL_INVALID_INTERVAL` when
|
|
31
|
+
* the resolved interval is not finite or not greater than zero, both
|
|
32
|
+
* synchronously at creation and on invalid reactive updates.
|
|
33
|
+
* @returns Reactive clock controls.
|
|
25
34
|
*/
|
|
26
35
|
function useTemporalNow(options = {}) {
|
|
27
36
|
const readNow = options.now ?? Temporal.Now.instant;
|
|
@@ -48,7 +57,14 @@ function useTemporalNow(options = {}) {
|
|
|
48
57
|
* Creates a reactive zoned date-time derived from a scoped Temporal clock.
|
|
49
58
|
*
|
|
50
59
|
* Changes to {@link UseTemporalZonedDateTimeOptions.timeZone} are reflected
|
|
51
|
-
* without replacing the underlying timer.
|
|
60
|
+
* without replacing the underlying timer. Scheduling, server rendering, and
|
|
61
|
+
* scope-cleanup rules are shared with {@link useTemporalNow}.
|
|
62
|
+
*
|
|
63
|
+
* @param options Clock scheduling plus the reactive time zone.
|
|
64
|
+
* @default options {}
|
|
65
|
+
* @throws `RangeError` for invalid intervals (see {@link useTemporalNow}),
|
|
66
|
+
* and on read when the reactive time zone cannot be resolved by Temporal.
|
|
67
|
+
* @returns Computed zoned date-time in the reactive time zone.
|
|
52
68
|
*/
|
|
53
69
|
function useTemporalZonedDateTime(options = {}) {
|
|
54
70
|
const clock = useTemporalNow(options);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vizejs/composable",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.303.0",
|
|
4
4
|
"description": "Lifecycle-safe composable foundations for Vize applications",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"composables",
|
|
@@ -41,29 +41,29 @@
|
|
|
41
41
|
"publishConfig": {
|
|
42
42
|
"access": "public"
|
|
43
43
|
},
|
|
44
|
-
"scripts": {
|
|
45
|
-
"build": "vp pack",
|
|
46
|
-
"dev": "vp pack --watch",
|
|
47
|
-
"pretest": "vp pack && pnpm check:size",
|
|
48
|
-
"test": "vp exec node --test 'src/**/*.test.ts'",
|
|
49
|
-
"check": "vp check src scripts vite.config.ts",
|
|
50
|
-
"check:fix": "vp check --fix src scripts vite.config.ts",
|
|
51
|
-
"check:size": "node scripts/check-size.mjs",
|
|
52
|
-
"fmt": "vp fmt --write src scripts vite.config.ts"
|
|
53
|
-
},
|
|
54
44
|
"dependencies": {
|
|
55
45
|
"temporal-polyfill-lite": "0.4.2"
|
|
56
46
|
},
|
|
57
47
|
"devDependencies": {
|
|
58
|
-
"@types/node": "
|
|
59
|
-
"typescript": "
|
|
60
|
-
"vite-plus": "
|
|
61
|
-
"vue": "
|
|
48
|
+
"@types/node": "25.9.2",
|
|
49
|
+
"typescript": "6.0.3",
|
|
50
|
+
"vite-plus": "0.1.21",
|
|
51
|
+
"vue": "3.5.35"
|
|
62
52
|
},
|
|
63
53
|
"peerDependencies": {
|
|
64
54
|
"vue": "^3.5.0"
|
|
65
55
|
},
|
|
66
56
|
"engines": {
|
|
67
57
|
"node": ">=24"
|
|
58
|
+
},
|
|
59
|
+
"scripts": {
|
|
60
|
+
"build": "vp pack",
|
|
61
|
+
"dev": "vp pack --watch",
|
|
62
|
+
"pretest": "vp pack && pnpm check:size",
|
|
63
|
+
"test": "vp exec node --test 'src/**/*.test.ts'",
|
|
64
|
+
"check": "vp check src scripts vite.config.ts",
|
|
65
|
+
"check:fix": "vp check --fix src scripts vite.config.ts",
|
|
66
|
+
"check:size": "node scripts/check-size.mjs",
|
|
67
|
+
"fmt": "vp fmt --write src scripts vite.config.ts"
|
|
68
68
|
}
|
|
69
|
-
}
|
|
69
|
+
}
|