@vuetify/v0 0.0.21 → 0.0.22

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.
@@ -1,3113 +0,0 @@
1
- import { a as isBoolean, c as isNull, d as isObject, g as mergeDeep, h as isUndefined, l as isNullOrUndefined, o as isFunction, p as isString, r as genId, t as clamp, u as isNumber } from "./utilities-CjDz-Xvn.mjs";
2
- import { C as createTokens, E as createSingle, J as createTrinity, P as toArray, Y as createContext, Z as useContext, d as useResizeObserver, g as useHydration, k as createGroup, q as createPlugin, z as useRegistry } from "./useStep-CfgBbrJB.mjs";
3
- import { i as SUPPORTS_MUTATION_OBSERVER, n as SUPPORTS_INTERSECTION_OBSERVER, t as IN_BROWSER } from "./globals-C3JrEDXZ.mjs";
4
- import { computed, effectScope, isRef, onScopeDispose, reactive, readonly, ref, shallowReadonly, shallowRef, toRef, toValue, unref, watch, watchEffect } from "vue";
5
-
6
- //#region src/composables/toReactive/index.ts
7
- /**
8
- * @module toReactive
9
- *
10
- * @remarks
11
- * Utility function to convert values and refs into reactive proxies with ref unwrapping.
12
- *
13
- * Key features:
14
- * - Automatic ref unwrapping
15
- * - Deep reactive proxying
16
- * - Map and Set support with ref unwrapping
17
- * - Nested object/array reactivity
18
- * - Type preservation
19
- *
20
- * Perfect for creating reactive versions of plain objects while automatically unwrapping refs.
21
- */
22
- /**
23
- * Converts a `MaybeRef` to a `UnwrapNestedRefs`.
24
- *
25
- * @param objectRef The object to convert.
26
- * @template Z The type of the object.
27
- * @returns The converted object.
28
- *
29
- * @see https://0.vuetifyjs.com/composables/transformers/to-reactive
30
- *
31
- * @example
32
- * ```ts
33
- * import { ref } from 'vue'
34
- * import { toReactive } from '@vuetify/v0'
35
- *
36
- * const state = ref({ name: 'John', age: 30 })
37
- * const rstate = toReactive(state)
38
- *
39
- * console.log(rstate.name) // John
40
- * ```
41
- */
42
- function toReactive(objectRef) {
43
- if (!isRef(objectRef)) return reactive(objectRef);
44
- const target = objectRef.value;
45
- if (target instanceof Map) {
46
- const mapProxy = new Proxy(/* @__PURE__ */ new Map(), { get(_, p) {
47
- const map = objectRef.value;
48
- if (p === "get") return (key) => unref(map.get(key));
49
- if (p === "set") return (key, value) => {
50
- const existingValue = map.get(key);
51
- if (isRef(existingValue)) existingValue.value = unref(value);
52
- else map.set(key, value);
53
- return mapProxy;
54
- };
55
- if (p === "has") return (key) => map.has(key);
56
- if (p === "delete") return (key) => map.delete(key);
57
- if (p === "clear") return () => map.clear();
58
- if (p === "size") return map.size;
59
- if (p === "keys") return () => map.keys();
60
- if (p === "values") return function* () {
61
- for (const value of map.values()) yield unref(value);
62
- };
63
- if (p === "entries") return function* () {
64
- for (const [key, value] of map.entries()) yield [key, unref(value)];
65
- };
66
- if (p === "forEach") return (callback, thisArg) => {
67
- for (const [key, value] of map.entries()) callback.call(thisArg, unref(value), key, mapProxy);
68
- };
69
- if (p === Symbol.iterator) return function* () {
70
- for (const [key, value] of map.entries()) yield [key, unref(value)];
71
- };
72
- return Reflect.get(map, p);
73
- } });
74
- return reactive(mapProxy);
75
- }
76
- if (target instanceof Set) {
77
- const setProxy = new Proxy(/* @__PURE__ */ new Set(), { get(_, p) {
78
- const set = objectRef.value;
79
- if (p === "add") return (value) => {
80
- set.add(value);
81
- return setProxy;
82
- };
83
- if (p === "has") return (value) => set.has(value);
84
- if (p === "delete") return (value) => set.delete(value);
85
- if (p === "clear") return () => set.clear();
86
- if (p === "size") return set.size;
87
- if (p === "keys" || p === "values") return function* () {
88
- for (const value of set.values()) yield unref(value);
89
- };
90
- if (p === "entries") return function* () {
91
- for (const value of set.values()) {
92
- const unreffedValue = unref(value);
93
- yield [unreffedValue, unreffedValue];
94
- }
95
- };
96
- if (p === "forEach") return (callback, thisArg) => {
97
- for (const value of set) {
98
- const unreffedValue = unref(value);
99
- callback.call(thisArg, unreffedValue, unreffedValue, setProxy);
100
- }
101
- };
102
- if (p === Symbol.iterator) return function* () {
103
- for (const value of set.values()) yield unref(value);
104
- };
105
- return Reflect.get(set, p);
106
- } });
107
- return reactive(setProxy);
108
- }
109
- return reactive(new Proxy({}, {
110
- get(_, p, receiver) {
111
- return unref(Reflect.get(objectRef.value, p, receiver));
112
- },
113
- set(_, p, value) {
114
- const currentTarget = objectRef.value;
115
- currentTarget[p] = value;
116
- return true;
117
- },
118
- deleteProperty(_, p) {
119
- return Reflect.deleteProperty(objectRef.value, p);
120
- },
121
- has(_, p) {
122
- return Reflect.has(objectRef.value, p);
123
- },
124
- ownKeys() {
125
- return Object.keys(objectRef.value);
126
- },
127
- getOwnPropertyDescriptor(_, p) {
128
- const desc = Reflect.getOwnPropertyDescriptor(objectRef.value, p);
129
- if (!desc) return;
130
- const newDesc = {
131
- ...desc,
132
- configurable: true
133
- };
134
- if ("value" in newDesc) newDesc.value = unref(newDesc.value);
135
- return newDesc;
136
- }
137
- }));
138
- }
139
-
140
- //#endregion
141
- //#region src/composables/useEventListener/index.ts
142
- /**
143
- * @module useEventListener
144
- *
145
- * @remarks
146
- * Event listener composable with automatic cleanup on scope disposal.
147
- *
148
- * Key features:
149
- * - Supports Window, Document, and HTMLElement targets
150
- * - Reactive targets, events, and listeners
151
- * - Event options support (capture, passive, once)
152
- * - Automatic removeEventListener on unmount
153
- * - Multiple overloads for type safety
154
- *
155
- * Perfect for safely managing event listeners in Vue components.
156
- */
157
- /**
158
- * Attaches an event listener to a target.
159
- *
160
- * @param target The target to attach the event listener to.
161
- * @param event The event to listen for.
162
- * @param listener The event listener.
163
- * @param options The event listener options.
164
- * @returns A function to remove the event listener.
165
- *
166
- * @see https://0.vuetifyjs.com/composables/system/use-event-listener
167
- */
168
- function useEventListener(target, event, listener, options) {
169
- const cleanups = [];
170
- function cleanup() {
171
- for (const fn of cleanups) fn();
172
- cleanups.length = 0;
173
- }
174
- function register(el, event$1, listener$1, options$1) {
175
- el.addEventListener(event$1, listener$1, options$1);
176
- return () => el.removeEventListener(event$1, listener$1, options$1);
177
- }
178
- const stopWatcher = watch(() => [
179
- toValue(target),
180
- toValue(event),
181
- unref(listener),
182
- toValue(options)
183
- ], ([el, events, listeners, opts]) => {
184
- cleanup();
185
- if (!el) return;
186
- const eventList = toArray(events);
187
- const listenerList = toArray(listeners);
188
- for (const event$1 of eventList) for (const listenerFn of listenerList) cleanups.push(register(el, event$1, listenerFn, opts));
189
- }, {
190
- immediate: true,
191
- flush: "post"
192
- });
193
- function stop() {
194
- stopWatcher();
195
- cleanup();
196
- }
197
- onScopeDispose(stop, true);
198
- return stop;
199
- }
200
- /**
201
- * Attaches an event listener to the window.
202
- *
203
- * @param event The event to listen for.
204
- * @param listener The event listener.
205
- * @param options The event listener options.
206
- * @template E The event type.
207
- * @returns A function to remove the event listener.
208
- *
209
- * @see https://0.vuetifyjs.com/composables/system/use-event-listener
210
- */
211
- function useWindowEventListener(event, listener, options) {
212
- return IN_BROWSER ? useEventListener(window, event, listener, options) : () => {};
213
- }
214
- /**
215
- * Attaches an event listener to the document.
216
- *
217
- * @param event The event to listen for.
218
- * @param listener The event listener.
219
- * @param options The event listener options.
220
- * @template E The event type.
221
- * @returns A function to remove the event listener.
222
- *
223
- * @see https://0.vuetifyjs.com/composables/system/use-event-listener
224
- */
225
- function useDocumentEventListener(event, listener, options) {
226
- return IN_BROWSER ? useEventListener(document, event, listener, options) : () => {};
227
- }
228
-
229
- //#endregion
230
- //#region src/composables/useBreakpoints/index.ts
231
- /**
232
- * @module useBreakpoints
233
- *
234
- * @see https://0.vuetifyjs.com/composables/plugins/use-breakpoints
235
- *
236
- * @remarks
237
- * Responsive breakpoint detection composable with window resize handling.
238
- *
239
- * Key features:
240
- * - Window matchMedia integration
241
- * - Six built-in breakpoints (xs, sm, md, lg, xl, xxl)
242
- * - Automatic resize listener with cleanup
243
- * - SSR-safe (checks IN_BROWSER)
244
- * - Hydration-aware
245
- * - Custom breakpoint configuration
246
- *
247
- * Perfect for responsive layouts and conditional rendering based on screen size.
248
- */
249
- /**
250
- * Creates default breakpoint configuration.
251
- *
252
- * @returns The default breakpoint configuration object.
253
- */
254
- function createDefaultBreakpoints() {
255
- return {
256
- mobileBreakpoint: "md",
257
- breakpoints: {
258
- xs: 0,
259
- sm: 600,
260
- md: 960,
261
- lg: 1280,
262
- xl: 1920,
263
- xxl: 2560
264
- }
265
- };
266
- }
267
- /**
268
- * Creates a new breakpoints instance.
269
- *
270
- * @param options The options for the breakpoints instance.
271
- * @template E The type of the breakpoints context.
272
- * @returns A new breakpoints instance.
273
- *
274
- * @see https://0.vuetifyjs.com/composables/plugins/use-breakpoints
275
- *
276
- * @example
277
- * ```ts
278
- * import { createBreakpoints } from '@vuetify/v0'
279
- *
280
- * export const [useBreakpoints, provideBreakpoints] = createBreakpoints({
281
- * namespace: 'v0:breakpoints',
282
- * mobileBreakpoint: 'sm',
283
- * breakpoints: {
284
- * xs: 0,
285
- * sm: 680,
286
- * md: 1024,
287
- * lg: 1280,
288
- * xl: 1920,
289
- * xxl: 2560,
290
- * },
291
- * })
292
- * ```
293
- */
294
- function createBreakpoints(_options = {}) {
295
- const { mobileBreakpoint, breakpoints } = /* @__PURE__ */ mergeDeep(createDefaultBreakpoints(), _options);
296
- const sorted = Object.entries(breakpoints).toSorted((a, b) => a[1] - b[1]);
297
- const names = sorted.map(([n]) => n);
298
- const mb = /* @__PURE__ */ isNumber(mobileBreakpoint) ? mobileBreakpoint : breakpoints[mobileBreakpoint] ?? breakpoints.md;
299
- const name = shallowRef("xs");
300
- const width = shallowRef(0);
301
- const height = shallowRef(0);
302
- const isMobile = shallowRef(true);
303
- const xs = shallowRef(true);
304
- const sm = shallowRef(false);
305
- const md = shallowRef(false);
306
- const lg = shallowRef(false);
307
- const xl = shallowRef(false);
308
- const xxl = shallowRef(false);
309
- const smAndUp = shallowRef(false);
310
- const mdAndUp = shallowRef(false);
311
- const lgAndUp = shallowRef(false);
312
- const xlAndUp = shallowRef(false);
313
- const xxlAndUp = shallowRef(false);
314
- const smAndDown = shallowRef(true);
315
- const mdAndDown = shallowRef(true);
316
- const lgAndDown = shallowRef(true);
317
- const xlAndDown = shallowRef(true);
318
- const xxlAndDown = shallowRef(true);
319
- function update() {
320
- if (!IN_BROWSER) return;
321
- width.value = window.innerWidth;
322
- height.value = window.innerHeight;
323
- let current = "xs";
324
- for (let i = sorted.length - 1; i >= 0; i--) if (width.value >= sorted[i][1]) {
325
- current = sorted[i][0];
326
- break;
327
- }
328
- name.value = current;
329
- const index = names.indexOf(current);
330
- isMobile.value = width.value < mb;
331
- xs.value = index === 0;
332
- sm.value = index === 1;
333
- md.value = index === 2;
334
- lg.value = index === 3;
335
- xl.value = index === 4;
336
- xxl.value = index === 5;
337
- smAndUp.value = index >= 1;
338
- mdAndUp.value = index >= 2;
339
- lgAndUp.value = index >= 3;
340
- xlAndUp.value = index >= 4;
341
- xxlAndUp.value = index >= 5;
342
- smAndDown.value = index <= 1;
343
- mdAndDown.value = index <= 2;
344
- lgAndDown.value = index <= 3;
345
- xlAndDown.value = index <= 4;
346
- xxlAndDown.value = index <= 5;
347
- }
348
- return {
349
- breakpoints,
350
- name: readonly(name),
351
- width: readonly(width),
352
- height: readonly(height),
353
- isMobile: readonly(isMobile),
354
- xs: readonly(xs),
355
- sm: readonly(sm),
356
- md: readonly(md),
357
- lg: readonly(lg),
358
- xl: readonly(xl),
359
- xxl: readonly(xxl),
360
- smAndUp: readonly(smAndUp),
361
- mdAndUp: readonly(mdAndUp),
362
- lgAndUp: readonly(lgAndUp),
363
- xlAndUp: readonly(xlAndUp),
364
- xxlAndUp: readonly(xxlAndUp),
365
- smAndDown: readonly(smAndDown),
366
- mdAndDown: readonly(mdAndDown),
367
- lgAndDown: readonly(lgAndDown),
368
- xlAndDown: readonly(xlAndDown),
369
- xxlAndDown: readonly(xxlAndDown),
370
- update
371
- };
372
- }
373
- /**
374
- * Creates a new breakpoints context.
375
- *
376
- * @param options The options for the breakpoints context.
377
- * @template E The type of the breakpoints context.
378
- * @returns A new breakpoints context.
379
- *
380
- * @see https://0.vuetifyjs.com/composables/plugins/use-breakpoints
381
- *
382
- * @example
383
- * ```ts
384
- * import { createBreakpointsContext } from '@vuetify/v0'
385
- *
386
- * export const [useBreakpoints, provideBreakpoints, context] = createBreakpointsContext({
387
- * namespace: 'v0:breakpoints',
388
- * mobileBreakpoint: 'sm',
389
- * })
390
- * ```
391
- */
392
- function createBreakpointsContext(_options = {}) {
393
- const { namespace = "v0:breakpoints", ...options } = _options;
394
- const [useBreakpointsContext, _provideBreakpointsContext] = createContext(namespace);
395
- const context = createBreakpoints(options);
396
- function provideBreakpointsContext(_context = context, app) {
397
- return _provideBreakpointsContext(_context, app);
398
- }
399
- return createTrinity(useBreakpointsContext, provideBreakpointsContext, context);
400
- }
401
- /**
402
- * Creates a new breakpoints plugin.
403
- *
404
- * @param options The options for the breakpoints plugin.
405
- * @template E The type of the breakpoints context.
406
- * @returns A new breakpoints plugin.
407
- *
408
- * @see https://0.vuetifyjs.com/composables/plugins/use-breakpoints
409
- *
410
- * @example
411
- * ```ts
412
- * import { createApp } from 'vue'
413
- * import { createBreakpointsPlugin } from '@vuetify/v0'
414
- * import App from './App.vue'
415
- *
416
- * const app = createApp(App)
417
- *
418
- * app.use(
419
- * createBreakpointsPlugin({
420
- * namespace: 'v0:breakpoints',
421
- * mobileBreakpoint: 'sm',
422
- * breakpoints: {
423
- * xs: 0,
424
- * sm: 680,
425
- * md: 1024,
426
- * lg: 1280,
427
- * xl: 1920,
428
- * xxl: 2560,
429
- * },
430
- * })
431
- * )
432
- *
433
- * app.mount('#app')
434
- * ```
435
- */
436
- function createBreakpointsPlugin(_options = {}) {
437
- const { namespace = "v0:breakpoints", ...options } = _options;
438
- const [, provideBreakpointsContext, context] = createBreakpointsContext({
439
- ...options,
440
- namespace
441
- });
442
- return createPlugin({
443
- namespace,
444
- provide: (app) => {
445
- provideBreakpointsContext(context, app);
446
- },
447
- setup: (app) => {
448
- app.mixin({ mounted() {
449
- if (!/* @__PURE__ */ isNull(this.$parent)) return;
450
- const hydration = useHydration();
451
- function listener() {
452
- context.update();
453
- }
454
- const unwatch = watch(hydration.isHydrated, (hydrated) => {
455
- if (hydrated) listener();
456
- }, { immediate: true });
457
- const cleanup = useWindowEventListener("resize", listener, { passive: true });
458
- onScopeDispose(() => {
459
- cleanup();
460
- unwatch();
461
- }, true);
462
- } });
463
- }
464
- });
465
- }
466
- /**
467
- * Returns the current breakpoints instance.
468
- *
469
- * @param namespace The namespace for the breakpoints context. Defaults to `v0:breakpoints`.
470
- * @returns The current breakpoints instance.
471
- *
472
- * @see https://0.vuetifyjs.com/composables/plugins/use-breakpoints
473
- *
474
- * @example
475
- * ```vue
476
- * <script setup lang="ts">
477
- * import { useBreakpoints } from '@vuetify/v0'
478
- *
479
- * const { isMobile, mdAndUp } = useBreakpoints()
480
- * <\/script>
481
- *
482
- * <template>
483
- * <div class="pa-4">
484
- * <p v-if="isMobile.value">Mobile layout active</p>
485
- * <p v-else-if="mdAndUp.value">Medium and up layout active</p>
486
- * </div>
487
- * </template>
488
- * ```
489
- */
490
- function useBreakpoints(namespace = "v0:breakpoints") {
491
- return useContext(namespace);
492
- }
493
-
494
- //#endregion
495
- //#region src/composables/useClickOutside/index.ts
496
- /**
497
- * @module useClickOutside
498
- *
499
- * @remarks
500
- * Detects clicks outside of specified element(s) with automatic cleanup.
501
- *
502
- * Key features:
503
- * - Two-phase detection (pointerdown → pointerup) prevents drag-out false positives
504
- * - Touch scroll threshold ignores swipes/scrolls on mobile
505
- * - Capture phase listeners work with stopPropagation
506
- * - Pause/resume/stop functionality
507
- * - Optional iframe focus detection
508
- * - SSR-safe (no-op when not in browser)
509
- *
510
- * Common use cases: closing popovers, dropdowns, modals, and menus.
511
- *
512
- * Accessibility: This composable handles pointer interactions only. For accessible
513
- * components (dialogs, popovers, menus), pair with `useKeydown` for Escape key
514
- * dismissal per WCAG/APG requirements.
515
- */
516
- /**
517
- * Detects clicks outside of the specified element(s).
518
- *
519
- * Uses two-phase detection (pointerdown → pointerup) to prevent false positives
520
- * when users drag from inside to outside an element.
521
- *
522
- * @param target Element ref(s) to detect clicks outside of. Accepts a single ref/getter or array of refs/getters.
523
- * @param handler Callback invoked when a click outside is detected.
524
- * @param options Configuration options.
525
- * @returns An object with methods to control the listener.
526
- *
527
- * @see https://0.vuetifyjs.com/composables/system/use-click-outside
528
- *
529
- * @example Native element ref
530
- * ```ts
531
- * const menuRef = useTemplateRef<HTMLElement>('menu')
532
- *
533
- * useClickOutside(menuRef, () => { isOpen.value = false })
534
- * ```
535
- *
536
- * @example Component ref (e.g., Atom)
537
- * ```ts
538
- * const atomRef = useTemplateRef<AtomExpose>('atom')
539
- *
540
- * // Pass the exposed element TemplateRef via getter
541
- * useClickOutside(
542
- * () => atomRef.value?.element,
543
- * () => { isOpen.value = false }
544
- * )
545
- * ```
546
- *
547
- * @example Multiple targets
548
- * ```ts
549
- * const popoverRef = useTemplateRef<AtomExpose>('popover')
550
- * const anchorRef = useTemplateRef<HTMLElement>('anchor')
551
- *
552
- * useClickOutside(
553
- * [() => popoverRef.value?.element, anchorRef],
554
- * () => { isOpen.value = false }
555
- * )
556
- * ```
557
- *
558
- * @example Ignoring elements (CSS selectors or refs)
559
- * ```ts
560
- * useClickOutside(
561
- * () => navRef.value?.element,
562
- * () => { isOpen.value = false },
563
- * { ignore: ['[data-app-bar]'] }
564
- * )
565
- * ```
566
- */
567
- function useClickOutside(target, handler, options = {}) {
568
- const { capture = true, touchScrollThreshold = 30, detectIframe = false, ignore = [] } = options;
569
- const isPaused = shallowRef(false);
570
- const isActive = toRef(() => !isPaused.value);
571
- let initialTarget = null;
572
- let startPosition = {
573
- x: 0,
574
- y: 0
575
- };
576
- let cleanupPointerDown;
577
- let cleanupPointerUp;
578
- let cleanupBlur;
579
- /**
580
- * Resolve target(s) to an array of HTMLElements.
581
- */
582
- function getTargets() {
583
- return toArray(target).map((source) => toValue(source)).filter((el) => !/* @__PURE__ */ isNullOrUndefined(el));
584
- }
585
- /**
586
- * Resolve ignore targets to a tuple of [selectors, elements].
587
- * Called once per event to avoid repeated toValue() calls in hot path.
588
- */
589
- function resolveIgnoreTargets() {
590
- const ignoreTargets = toValue(ignore);
591
- if (ignoreTargets.length === 0) return [[], []];
592
- const selectors = [];
593
- const elements = [];
594
- for (const ignoreTarget of ignoreTargets) if (/* @__PURE__ */ isString(ignoreTarget)) selectors.push(ignoreTarget);
595
- else {
596
- const ignoreEl = toValue(ignoreTarget);
597
- if (ignoreEl) elements.push(ignoreEl);
598
- }
599
- return [selectors, elements];
600
- }
601
- /**
602
- * Check if an element matches resolved ignore targets.
603
- */
604
- function isIgnored(el, selectors, elements) {
605
- if (!el) return false;
606
- for (const selector of selectors) try {
607
- if (el.matches(selector) || !/* @__PURE__ */ isNull(el.closest(selector))) return true;
608
- } catch {}
609
- for (const ignoreEl of elements) if (ignoreEl === el || ignoreEl.contains(el)) return true;
610
- return false;
611
- }
612
- /**
613
- * Check if any element in the event path should be ignored.
614
- */
615
- function shouldIgnore(path) {
616
- const [selectors, elements] = resolveIgnoreTargets();
617
- if (selectors.length === 0 && elements.length === 0) return false;
618
- return path.some((node) => node instanceof Element && isIgnored(node, selectors, elements));
619
- }
620
- /**
621
- * Check if the event target is outside all target elements.
622
- */
623
- function isOutside(eventTarget) {
624
- if (!eventTarget) return false;
625
- if (!(eventTarget instanceof Node)) return false;
626
- const targets = getTargets();
627
- if (targets.length === 0) return false;
628
- return targets.every((el) => {
629
- return el !== eventTarget && !el.contains(eventTarget);
630
- });
631
- }
632
- /**
633
- * Validate that the target is still in the DOM.
634
- */
635
- function isValidTarget(eventTarget) {
636
- if (!(eventTarget instanceof Element)) return false;
637
- if (!eventTarget.isConnected) return false;
638
- return true;
639
- }
640
- /**
641
- * Handle pointerdown - store initial target and position.
642
- */
643
- function onPointerDown(event) {
644
- if (isPaused.value) return;
645
- if (event.defaultPrevented) return;
646
- initialTarget = event.composedPath()[0] ?? event.target;
647
- startPosition = {
648
- x: event.clientX,
649
- y: event.clientY
650
- };
651
- }
652
- /**
653
- * Handle pointerup - check if it's an outside click.
654
- */
655
- function onPointerUp(event) {
656
- if (isPaused.value) return;
657
- if (event.defaultPrevented) return;
658
- if (!initialTarget) return;
659
- const pointerdownTarget = initialTarget;
660
- initialTarget = null;
661
- if (!isValidTarget(pointerdownTarget)) return;
662
- const path = event.composedPath();
663
- const pointerupTarget = path[0] ?? event.target;
664
- if (event.pointerType === "touch") {
665
- const dx = Math.abs(event.clientX - startPosition.x);
666
- const dy = Math.abs(event.clientY - startPosition.y);
667
- if (dx >= touchScrollThreshold || dy >= touchScrollThreshold) return;
668
- }
669
- if (isOutside(pointerdownTarget) && isOutside(pointerupTarget) && !shouldIgnore(path)) handler(event);
670
- }
671
- /**
672
- * Handle window blur - detect focus moving to iframe.
673
- */
674
- function onBlur(event) {
675
- if (isPaused.value) return;
676
- if (event.defaultPrevented) return;
677
- if (document.activeElement instanceof HTMLIFrameElement) {
678
- const iframeIsOutside = getTargets().every((el) => !el.contains(document.activeElement));
679
- const [selectors, elements] = resolveIgnoreTargets();
680
- if (iframeIsOutside && !isIgnored(document.activeElement, selectors, elements)) handler(event);
681
- }
682
- }
683
- function setup() {
684
- cleanupPointerDown = useDocumentEventListener("pointerdown", onPointerDown, capture);
685
- cleanupPointerUp = useDocumentEventListener("pointerup", onPointerUp, capture);
686
- if (!detectIframe) return;
687
- cleanupBlur = useWindowEventListener("blur", onBlur, capture);
688
- }
689
- function cleanup() {
690
- cleanupPointerDown?.();
691
- cleanupPointerUp?.();
692
- cleanupBlur?.();
693
- cleanupPointerDown = void 0;
694
- cleanupPointerUp = void 0;
695
- cleanupBlur = void 0;
696
- }
697
- function pause() {
698
- if (isPaused.value) return;
699
- isPaused.value = true;
700
- initialTarget = null;
701
- cleanup();
702
- }
703
- function resume() {
704
- if (!isPaused.value) return;
705
- isPaused.value = false;
706
- setup();
707
- }
708
- function stop() {
709
- isPaused.value = true;
710
- initialTarget = null;
711
- cleanup();
712
- }
713
- setup();
714
- onScopeDispose(stop, true);
715
- return {
716
- isActive: shallowReadonly(isActive),
717
- isPaused: shallowReadonly(isPaused),
718
- pause,
719
- resume,
720
- stop
721
- };
722
- }
723
-
724
- //#endregion
725
- //#region src/composables/useFeatures/index.ts
726
- /**
727
- * @module useFeatures
728
- *
729
- * @see https://0.vuetifyjs.com/composables/plugins/use-features
730
- *
731
- * @remarks
732
- * Feature flag system with boolean and token-based features.
733
- *
734
- * Key features:
735
- * - Boolean features (true/false activation)
736
- * - Token features with $variation support
737
- * - Auto-selection of enabled features
738
- * - Multi-select support for feature combinations
739
- * - Perfect for A/B testing, progressive rollout, feature toggles
740
- *
741
- * Inheritance chain: useRegistry → createSelection → createGroup → createFeatures
742
- * Integrates with useTokens for token-based features.
743
- */
744
- /**
745
- * Creates a new features instance.
746
- *
747
- * @param options The options for the features instance.
748
- * @template Z The type of the feature ticket.
749
- * @template E The type of the feature context.
750
- * @returns A new features instance.
751
- *
752
- * @see https://0.vuetifyjs.com/composables/plugins/use-features
753
- *
754
- * @example
755
- * ```ts
756
- * import { createFeatures } from '@vuetify/v0'
757
- *
758
- * const [useFeatures, provideFeaturesContext, context] = createFeatures({
759
- * namespace: 'v0:features',
760
- * features: {
761
- * 'dark-mode': true,
762
- * 'theme-color': { $variation: 'blue' },
763
- * },
764
- * })
765
- * ```
766
- */
767
- function createFeatures(_options = {}) {
768
- const { features, ...options } = _options;
769
- const tokens = createTokens(features, { flat: true });
770
- const registry = createGroup(options);
771
- for (const [id, { value }] of tokens.entries()) register({
772
- id,
773
- value
774
- });
775
- function variation(id, fallback = null) {
776
- const ticket = registry.get(id);
777
- if (!ticket) return fallback;
778
- return /* @__PURE__ */ isObject(ticket.value) ? ticket.value.$variation ?? fallback : ticket.value ?? fallback;
779
- }
780
- function register(registration = {}) {
781
- const item = {
782
- value: false,
783
- ...registration
784
- };
785
- const ticket = registry.register(item);
786
- if (/* @__PURE__ */ isBoolean(ticket.value) && ticket.value === true || /* @__PURE__ */ isObject(ticket.value) && /* @__PURE__ */ isBoolean(ticket.value.$value) && ticket.value.$value === true) registry.select(ticket.id);
787
- return ticket;
788
- }
789
- return {
790
- ...registry,
791
- variation,
792
- register,
793
- get size() {
794
- return registry.size;
795
- }
796
- };
797
- }
798
- /**
799
- * Creates a new features context.
800
- *
801
- * @param options The options for the features context.
802
- * @template Z The type of the feature ticket.
803
- * @template E The type of the feature context.
804
- * @returns A new features context.
805
- *
806
- * @see https://0.vuetifyjs.com/composables/plugins/use-features
807
- *
808
- * @example
809
- * ```ts
810
- * import { createFeaturesContext } from '@vuetify/v0'
811
- *
812
- * export const [useFeatures, provideFeatures, context] = createFeaturesContext({
813
- * namespace: 'app:features',
814
- * features: {
815
- * 'dark-mode': true,
816
- * 'theme-color': { $variation: 'blue' },
817
- * },
818
- * })
819
- * ```
820
- */
821
- function createFeaturesContext(_options = {}) {
822
- const { namespace = "v0:features", ...options } = _options;
823
- const [useFeaturesContext, _provideFeaturesContext] = createContext(namespace);
824
- const context = createFeatures(options);
825
- function provideFeaturesContext(_context = context, app) {
826
- return _provideFeaturesContext(_context, app);
827
- }
828
- return createTrinity(useFeaturesContext, provideFeaturesContext, context);
829
- }
830
- /**
831
- * Creates a new features plugin.
832
- *
833
- * @param options The options for the features plugin.
834
- * @template Z The type of the feature ticket.
835
- * @template E The type of the feature context.
836
- * @returns A new features plugin.
837
- *
838
- * @see https://0.vuetifyjs.com/composables/plugins/use-features
839
- *
840
- * @example
841
- * ```ts
842
- * import { createApp } from 'vue'
843
- * import { createFeaturesPlugin } from '@vuetify/v0'
844
- * import App from './App.vue'
845
- *
846
- * const app = createApp(App)
847
- *
848
- * app.use(
849
- * createFeaturesPlugin({
850
- * features: {
851
- * 'dark-mode': true,
852
- * 'theme-color': { $variation: 'blue' },
853
- * },
854
- * })
855
- * )
856
- *
857
- * app.mount('#app')
858
- * ```
859
- */
860
- function createFeaturesPlugin(_options = {}) {
861
- const { namespace = "v0:features", ...options } = _options;
862
- const [, provideFeaturesContext, context] = createFeaturesContext({
863
- ...options,
864
- namespace
865
- });
866
- return createPlugin({
867
- namespace,
868
- provide: (app) => {
869
- provideFeaturesContext(context, app);
870
- }
871
- });
872
- }
873
- /**
874
- * Returns the current features instance.
875
- *
876
- * @param namespace The namespace for the features context. Defaults to `v0:features`.
877
- * @template Z The type of the feature ticket.
878
- * @returns The current features instance.
879
- *
880
- * @see https://0.vuetifyjs.com/composables/plugins/use-features
881
- *
882
- * @example
883
- * ```vue
884
- * <script setup lang="ts">
885
- * import { useFeatures } from '@vuetify/v0'
886
- *
887
- * const features = useFeatures()
888
- * <\/script>
889
- *
890
- * <template>
891
- * <div>
892
- * <p>Features: {{ features.get('dark-mode') }}</p>
893
- * <p>Theme Color: {{ features.variation('theme-color') }}</p>
894
- * </div>
895
- * </template>
896
- * ```
897
- */
898
- function useFeatures(namespace = "v0:features") {
899
- return useContext(namespace);
900
- }
901
-
902
- //#endregion
903
- //#region src/composables/useFilter/index.ts
904
- /**
905
- * @module useFilter
906
- *
907
- * @remarks
908
- * Reactive array filtering composable with multiple filter modes.
909
- *
910
- * Key features:
911
- * - Four filter modes: some, every, union, intersection
912
- * - Case-insensitive filtering
913
- * - Custom filter functions
914
- * - Reactive updates
915
- * - Context-based DI support
916
- * - Perfect for search, multi-criteria filtering
917
- *
918
- * Filters arrays based on query strings with configurable matching strategies.
919
- */
920
- function defaultFilter(query, item, keys, mode = "some") {
921
- const queries = toArray(query).map((q) => String(q).toLowerCase());
922
- function match(value, q) {
923
- return String(value).toLowerCase().includes(q);
924
- }
925
- const stringValues = (/* @__PURE__ */ isObject(item) ? keys?.length ? keys.map((k) => item[k]) : Object.values(item) : [item]).map((v) => String(v).toLowerCase());
926
- if (mode === "some") return stringValues.some((val) => match(val, queries[0]));
927
- if (mode === "every") return stringValues.every((val) => match(val, queries[0]));
928
- if (mode === "union") return queries.some((q) => stringValues.some((val) => match(val, q)));
929
- if (mode === "intersection") return queries.every((q) => stringValues.some((val) => match(val, q)));
930
- return false;
931
- }
932
- /**
933
- * Creates a filter context with pre-configured options.
934
- *
935
- * @param options The filter options
936
- * @template Z The type of the items
937
- * @template E The type of the filter context
938
- * @returns A filter context
939
- *
940
- * @see https://0.vuetifyjs.com/composables/utilities/use-filter
941
- *
942
- * @example
943
- * ```ts
944
- * import { createFilter } from '@vuetify/v0'
945
- *
946
- * const filter = createFilter({
947
- * mode: 'intersection',
948
- * keys: ['name', 'email'],
949
- * })
950
- *
951
- * const { items } = filter.apply(query, users)
952
- * ```
953
- */
954
- function createFilter(options = {}) {
955
- const { customFilter, keys, mode = "some" } = options;
956
- const filterFunction = customFilter ?? ((q, i) => defaultFilter(q, i, keys, mode));
957
- const query = toRef("");
958
- function apply(_query, items) {
959
- const itemsRef = isRef(items) ? items : toRef(() => items);
960
- const queryRef = toRef(_query);
961
- return { items: computed(() => {
962
- const q = toValue(queryRef);
963
- query.value = q;
964
- const queries = toArray(q).filter((q$1) => String(q$1).trim());
965
- if (queries.length === 0) return itemsRef.value;
966
- const queryParam = queries.length === 1 ? queries[0] : queries;
967
- return itemsRef.value.filter((item) => filterFunction(queryParam, item));
968
- }) };
969
- }
970
- return {
971
- mode,
972
- keys,
973
- customFilter,
974
- query,
975
- apply
976
- };
977
- }
978
- /**
979
- * Creates a filter context with dependency injection support.
980
- *
981
- * @param options The filter context options
982
- * @template Z The type of the items
983
- * @template E The type of the filter context
984
- * @returns A trinity tuple: [useContext, provideContext, defaultContext]
985
- *
986
- * @see https://0.vuetifyjs.com/composables/utilities/use-filter
987
- *
988
- * @example
989
- * ```ts
990
- * import { createFilterContext } from '@vuetify/v0'
991
- *
992
- * export const [useSearchFilter, provideSearchFilter, searchFilter] = createFilterContext({
993
- * namespace: 'app:search',
994
- * mode: 'union',
995
- * keys: ['title', 'description'],
996
- * })
997
- *
998
- * // In parent component
999
- * provideSearchFilter()
1000
- *
1001
- * // In child component
1002
- * const filter = useSearchFilter()
1003
- * const { items } = filter.apply(query, products)
1004
- * ```
1005
- */
1006
- function createFilterContext(_options = {}) {
1007
- const { namespace = "v0:filter", ...options } = _options;
1008
- const [useFilterContext$1, _provideFilterContext] = createContext(namespace);
1009
- const context = createFilter(options);
1010
- function provideFilterContext(_context = context, app) {
1011
- return _provideFilterContext(_context, app);
1012
- }
1013
- return createTrinity(useFilterContext$1, provideFilterContext, context);
1014
- }
1015
- /**
1016
- * A reusable function for filtering an array of items.
1017
- *
1018
- * @param query The query to filter by.
1019
- * @param items The items to filter.
1020
- * @param options The filter options.
1021
- * @template Z The type of the items.
1022
- * @returns The filtered items.
1023
- *
1024
- * @see https://0.vuetifyjs.com/composables/utilities/use-filter
1025
- *
1026
- * @example
1027
- * ```ts
1028
- * import { ref } from 'vue'
1029
- * import { useFilter } from '@vuetify/v0'
1030
- *
1031
- * const items = ref([
1032
- * { name: 'John Doe', age: 30 },
1033
- * { name: 'Jane Doe', age: 25 },
1034
- * { name: 'Peter Jones', age: 40 },
1035
- * ])
1036
- *
1037
- * const query = ref('doe')
1038
- * const { items: filtered } = useFilter(query, items, { keys: ['name'] })
1039
- *
1040
- * console.log(filtered.value) // [ { name: 'John Doe', age: 30 }, { name: 'Jane Doe', age: 25 } ]
1041
- * ```
1042
- */
1043
- function useFilter(query, items, options = {}) {
1044
- return createFilter(options).apply(query, items);
1045
- }
1046
- /**
1047
- * Returns the current filter context from dependency injection.
1048
- *
1049
- * @param namespace The namespace for the filter context. Defaults to `'v0:filter'`.
1050
- * @template Z The type of the items.
1051
- * @template E The type of the filter context.
1052
- * @returns The current filter context.
1053
- *
1054
- * @see https://0.vuetifyjs.com/composables/utilities/use-filter
1055
- *
1056
- * @example
1057
- * ```vue
1058
- * <script setup lang="ts">
1059
- * import { useFilterContext } from '@vuetify/v0'
1060
- *
1061
- * const filter = useFilterContext()
1062
- * const { items } = filter.apply(query, products)
1063
- * <\/script>
1064
- * ```
1065
- */
1066
- function useFilterContext(namespace = "v0:filter") {
1067
- return useContext(namespace);
1068
- }
1069
-
1070
- //#endregion
1071
- //#region src/composables/useForm/index.ts
1072
- /**
1073
- * @module useForm
1074
- *
1075
- * @remarks
1076
- * Form validation composable with async rule support and multiple validation modes.
1077
- *
1078
- * Key features:
1079
- * - Sync and async validation rules
1080
- * - Multiple validation modes (submit, change, combined)
1081
- * - Tri-state isValid (null/true/false)
1082
- * - isPristine tracking
1083
- * - Silent validation mode
1084
- * - Form-level validation and reset
1085
- *
1086
- * Each field is registered with validation rules and tracks its own state independently.
1087
- */
1088
- /**
1089
- * Creates a new form instance.
1090
- *
1091
- * @param options The options for the form instance.
1092
- * @template Z The type of the form ticket.
1093
- * @template E The type of the form context.
1094
- * @returns A new form instance.
1095
- *
1096
- * @see https://0.vuetifyjs.com/composables/forms/use-form
1097
- *
1098
- * @example
1099
- * ```ts
1100
- * import { createForm } from '@vuetify/v0'
1101
- *
1102
- * const form = createForm()
1103
- *
1104
- * const username = form.register({
1105
- * id: 'username',
1106
- * value: '',
1107
- * rules: [(v) => v.length > 0 || 'Username is required'],
1108
- * })
1109
- *
1110
- * await form.submit()
1111
- *
1112
- * console.log(username.errors.value) // ['Username is required']
1113
- *
1114
- * form.reset()
1115
- * ```
1116
- */
1117
- function createForm(options) {
1118
- const registry = useRegistry(options);
1119
- const validateOn = options?.validateOn || "submit";
1120
- function parse(value) {
1121
- return value.toLowerCase().split(/\s+/);
1122
- }
1123
- function validatesOn(event) {
1124
- return parse(validateOn).includes(event);
1125
- }
1126
- const isValidating = computed(() => {
1127
- for (const ticket of registry.values()) if (ticket.isValidating.value) return true;
1128
- return false;
1129
- });
1130
- const isValid = computed(() => {
1131
- let hasFields = false;
1132
- for (const ticket of registry.values()) {
1133
- hasFields = true;
1134
- if (ticket.isValid.value === false) return false;
1135
- if (/* @__PURE__ */ isNull(ticket.isValid.value)) return null;
1136
- }
1137
- return hasFields ? true : null;
1138
- });
1139
- function reset() {
1140
- for (const ticket of registry.values()) ticket.reset();
1141
- }
1142
- async function submit() {
1143
- return validate(registry.keys());
1144
- }
1145
- async function validate(id) {
1146
- const validating = toArray(id);
1147
- if (validatesOn("submit")) return (await Promise.all(validating.map(async (id$1) => await registry.get(id$1)?.validate() ?? true))).every(Boolean);
1148
- return validating.map((id$1) => registry.get(id$1)).filter(Boolean).every((ticket) => ticket.isValid.value === true);
1149
- }
1150
- function register(registration) {
1151
- const model = shallowRef(/* @__PURE__ */ isNullOrUndefined(registration.value) ? "" : toValue(registration.value));
1152
- const rules = registration.rules || [];
1153
- const errors = shallowRef([]);
1154
- const isValidating$1 = shallowRef(false);
1155
- const initialValue = model.value;
1156
- const triggers = registration.validateOn || validateOn;
1157
- const isPristine = shallowRef(true);
1158
- const isValid$1 = shallowRef(null);
1159
- function _validatesOn(event) {
1160
- return parse(triggers).includes(event);
1161
- }
1162
- function _reset() {
1163
- model.value = initialValue;
1164
- errors.value = [];
1165
- isPristine.value = true;
1166
- isValid$1.value = null;
1167
- }
1168
- async function validate$1(silent = false) {
1169
- if (rules.length === 0) return isValid$1.value = true;
1170
- isValidating$1.value = true;
1171
- try {
1172
- const errorMessages = (await Promise.all(rules.map((rule) => rule(model.value)))).filter((result) => /* @__PURE__ */ isString(result));
1173
- if (!silent) {
1174
- errors.value = errorMessages;
1175
- isValid$1.value = errorMessages.length === 0;
1176
- isPristine.value = toValue(model) === initialValue;
1177
- }
1178
- return errorMessages.length === 0;
1179
- } finally {
1180
- isValidating$1.value = false;
1181
- }
1182
- }
1183
- const item = {
1184
- ...registration,
1185
- rules,
1186
- errors,
1187
- disabled: registration.disabled || false,
1188
- validateOn: triggers,
1189
- isValidating: isValidating$1,
1190
- isPristine,
1191
- isValid: isValid$1,
1192
- reset: _reset,
1193
- validate: validate$1
1194
- };
1195
- const ticket = registry.register(item);
1196
- Object.defineProperty(ticket, "value", {
1197
- get() {
1198
- return model.value;
1199
- },
1200
- set(val) {
1201
- model.value = val;
1202
- isPristine.value = val === initialValue;
1203
- isValid$1.value = null;
1204
- if (_validatesOn("change")) validate$1();
1205
- },
1206
- enumerable: true,
1207
- configurable: true
1208
- });
1209
- return ticket;
1210
- }
1211
- return {
1212
- ...registry,
1213
- register,
1214
- reset,
1215
- submit,
1216
- validateOn,
1217
- isValid,
1218
- isValidating,
1219
- get size() {
1220
- return registry.size;
1221
- }
1222
- };
1223
- }
1224
- /**
1225
- * Creates a new form context.
1226
- *
1227
- * @param options The options for the form context.
1228
- * @template Z The type of the form ticket.
1229
- * @template E The type of the form context.
1230
- * @returns A new form context.
1231
- *
1232
- * @see https://0.vuetifyjs.com/composables/forms/use-form
1233
- *
1234
- * @example
1235
- * ```ts
1236
- * import { createFormContext } from '@vuetify/v0'
1237
- *
1238
- * // With default namespace 'v0:form'
1239
- * export const [useMyForm, provideMyForm, myForm] = createFormContext({ validateOn: 'change' })
1240
- *
1241
- * // Or with custom namespace
1242
- * export const [useMyForm, provideMyForm, myForm] = createFormContext({
1243
- * namespace: 'my-form',
1244
- * validateOn: 'change',
1245
- * })
1246
- *
1247
- * // In a parent component:
1248
- * provideMyForm()
1249
- *
1250
- * // In a child component:
1251
- * const form = useMyForm()
1252
- * form.register({ id: 'field', value: ref(''), rules: [...] })
1253
- * ```
1254
- */
1255
- function createFormContext(_options = {}) {
1256
- const { namespace = "v0:form", ...options } = _options;
1257
- const [useFormContext, _provideFormContext] = createContext(namespace);
1258
- const context = createForm(options);
1259
- function provideFormContext(_context = context, app) {
1260
- return _provideFormContext(_context, app);
1261
- }
1262
- return createTrinity(useFormContext, provideFormContext, context);
1263
- }
1264
- /**
1265
- * Returns the current form instance.
1266
- *
1267
- * @param namespace The namespace for the form context. Defaults to `'v0:form'`.
1268
- * @returns The current form instance.
1269
- *
1270
- * @see https://0.vuetifyjs.com/composables/forms/use-form
1271
- *
1272
- * @example
1273
- * ```vue
1274
- * <script setup lang="ts">
1275
- * import { useForm } from '@vuetify/v0'
1276
- *
1277
- * const form = useForm()
1278
- * <\/script>
1279
- *
1280
- * <template>
1281
- * <div>
1282
- * <p>Form is {{ form.isValid.value ? 'valid' : 'invalid' }}</p>
1283
- * </div>
1284
- * </template>
1285
- * ```
1286
- */
1287
- function useForm(namespace = "v0:form") {
1288
- return useContext(namespace);
1289
- }
1290
-
1291
- //#endregion
1292
- //#region src/composables/useIntersectionObserver/index.ts
1293
- /**
1294
- * @module useIntersectionObserver
1295
- *
1296
- * @remarks
1297
- * IntersectionObserver composable with lifecycle management.
1298
- *
1299
- * Key features:
1300
- * - IntersectionObserver API wrapper
1301
- * - Pause/resume/stop functionality
1302
- * - Automatic cleanup on unmount
1303
- * - SSR-safe (checks SUPPORTS_INTERSECTION_OBSERVER)
1304
- * - Hydration-aware
1305
- * - Immediate callback option
1306
- *
1307
- * Perfect for lazy loading, infinite scroll, and visibility detection.
1308
- */
1309
- /**
1310
- * A composable that uses the Intersection Observer API to detect when an element
1311
- * is visible in the viewport.
1312
- *
1313
- * @param target The element to observe.
1314
- * @param callback The callback to execute when the element's intersection changes.
1315
- * @param options The options for the Intersection Observer.
1316
- * @returns An object with methods to control the observer.
1317
- *
1318
- * @see https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver
1319
- * @see https://0.vuetifyjs.com/composables/system/use-intersection-observer
1320
- *
1321
- * @example
1322
- * ```ts
1323
- * import { ref } from 'vue'
1324
- * import { useIntersectionObserver } from '@vuetify/v0'
1325
- *
1326
- * const target = ref<HTMLElement>()
1327
- * const isVisible = ref(false)
1328
- *
1329
- * const { isIntersecting, pause, resume } = useIntersectionObserver(
1330
- * target,
1331
- * (entries) => {
1332
- * const entry = entries[0]
1333
- * if (entry) {
1334
- * isVisible.value = entry.isIntersecting
1335
- * console.log('Element is visible:', entry.isIntersecting)
1336
- * }
1337
- * },
1338
- * { threshold: 0.5 }
1339
- * )
1340
- *
1341
- * // Pause observation
1342
- * pause()
1343
- *
1344
- * // Resume observation
1345
- * resume()
1346
- * ```
1347
- */
1348
- function useIntersectionObserver(target, callback, options = {}) {
1349
- const { isHydrated } = useHydration();
1350
- const targetRef = isRef(target) ? target : shallowRef(target);
1351
- const observer = shallowRef();
1352
- const isPaused = shallowRef(false);
1353
- const isIntersecting = shallowRef(false);
1354
- const isActive = toRef(() => !!observer.value);
1355
- function setup() {
1356
- if (/* @__PURE__ */ isNull(observer.value)) return;
1357
- if (!isHydrated.value || !SUPPORTS_INTERSECTION_OBSERVER || !targetRef.value || isPaused.value) return;
1358
- observer.value = new IntersectionObserver((entries) => {
1359
- const transformedEntries = entries.map((entry) => ({
1360
- boundingClientRect: entry.boundingClientRect,
1361
- intersectionRatio: entry.intersectionRatio,
1362
- intersectionRect: entry.intersectionRect,
1363
- isIntersecting: entry.isIntersecting,
1364
- rootBounds: entry.rootBounds,
1365
- target: entry.target,
1366
- time: entry.time
1367
- }));
1368
- const latestEntry = transformedEntries.at(-1);
1369
- if (latestEntry) isIntersecting.value = latestEntry.isIntersecting;
1370
- callback(transformedEntries);
1371
- if (options.once && latestEntry?.isIntersecting) stop();
1372
- }, {
1373
- root: options.root || null,
1374
- rootMargin: options.rootMargin || "0px",
1375
- threshold: options.threshold || 0
1376
- });
1377
- observer.value.observe(targetRef.value);
1378
- if (options.immediate) callback([{
1379
- boundingClientRect: targetRef.value.getBoundingClientRect(),
1380
- intersectionRatio: 0,
1381
- intersectionRect: new DOMRect(0, 0, 0, 0),
1382
- isIntersecting: false,
1383
- rootBounds: null,
1384
- target: targetRef.value,
1385
- time: performance.now()
1386
- }]);
1387
- }
1388
- watchEffect(() => {
1389
- const hydrated = isHydrated.value;
1390
- const target$1 = targetRef.value;
1391
- cleanup();
1392
- if (hydrated && target$1) setup();
1393
- });
1394
- function cleanup() {
1395
- if (observer.value) {
1396
- observer.value.disconnect();
1397
- observer.value = void 0;
1398
- }
1399
- }
1400
- function pause() {
1401
- isPaused.value = true;
1402
- isIntersecting.value = false;
1403
- observer.value?.disconnect();
1404
- }
1405
- function resume() {
1406
- isPaused.value = false;
1407
- setup();
1408
- }
1409
- function stop() {
1410
- cleanup();
1411
- observer.value = null;
1412
- }
1413
- onScopeDispose(stop, true);
1414
- return {
1415
- isActive: shallowReadonly(isActive),
1416
- isIntersecting: shallowReadonly(isIntersecting),
1417
- isPaused: shallowReadonly(isPaused),
1418
- pause,
1419
- resume,
1420
- stop
1421
- };
1422
- }
1423
- /**
1424
- * A convenience composable that uses the Intersection Observer API to detect
1425
- * when an element is visible in the viewport.
1426
- *
1427
- * @param target The element to observe.
1428
- * @param options The options for the Intersection Observer.
1429
- * @returns An object with the intersection state.
1430
- *
1431
- * @see https://0.vuetifyjs.com/composables/system/use-intersection-observer
1432
- *
1433
- * @example
1434
- * ```ts
1435
- * import { ref } from 'vue'
1436
- * import { useElementIntersection } from '@vuetify/v0'
1437
- *
1438
- * const myElement = ref<HTMLElement>()
1439
- * const { isIntersecting, intersectionRatio } = useElementIntersection(myElement, {
1440
- * threshold: 0.5
1441
- * })
1442
- *
1443
- * // Use in template to conditionally render or animate
1444
- * watchEffect(() => {
1445
- * if (isIntersecting.value) {
1446
- * console.log('Element is visible!', intersectionRatio.value)
1447
- * }
1448
- * })
1449
- * ```
1450
- */
1451
- function useElementIntersection(target, options = {}) {
1452
- const isIntersecting = shallowRef(false);
1453
- const intersectionRatio = shallowRef(0);
1454
- const { pause: _pause, resume, stop, isActive, isPaused } = useIntersectionObserver(target, (entries) => {
1455
- const entry = entries.at(-1);
1456
- if (entry) {
1457
- isIntersecting.value = entry.isIntersecting;
1458
- intersectionRatio.value = entry.intersectionRatio;
1459
- }
1460
- }, {
1461
- immediate: true,
1462
- ...options
1463
- });
1464
- function pause() {
1465
- isIntersecting.value = false;
1466
- intersectionRatio.value = 0;
1467
- _pause();
1468
- }
1469
- return {
1470
- isIntersecting: shallowReadonly(isIntersecting),
1471
- intersectionRatio: shallowReadonly(intersectionRatio),
1472
- isActive,
1473
- isPaused,
1474
- pause,
1475
- resume,
1476
- stop
1477
- };
1478
- }
1479
-
1480
- //#endregion
1481
- //#region src/composables/useKeydown/index.ts
1482
- /**
1483
- * @module useKeydown
1484
- *
1485
- * @remarks
1486
- * Keydown event listener composable with key filtering.
1487
- *
1488
- * Key features:
1489
- * - Key-specific event handling
1490
- * - preventDefault and stopPropagation options
1491
- * - Automatic cleanup on scope disposal
1492
- * - Built on useEventListener for consistent event handling
1493
- *
1494
- * Simplified wrapper around useEventListener for keyboard interactions.
1495
- */
1496
- /**
1497
- * A composable that adds a keydown event listener to the document.
1498
- *
1499
- * @param handlers The key handlers to add.
1500
- * @returns An object with methods to start and stop listening.
1501
- *
1502
- * @see https://0.vuetifyjs.com/composables/system/use-keydown
1503
- *
1504
- * @example
1505
- * ```ts
1506
- * import { useKeydown } from '@vuetify/v0'
1507
- *
1508
- * // Single handler
1509
- * useKeydown({ key: 'Escape', handler: () => console.log('Escape pressed') })
1510
- *
1511
- * // Multiple handlers
1512
- * const { isActive, start, stop } = useKeydown([
1513
- * { key: 'Enter', handler: () => console.log('Enter pressed') },
1514
- * { key: 'Escape', handler: () => console.log('Escape pressed'), preventDefault: true },
1515
- * ])
1516
- *
1517
- * // Listener is automatically active when called in component setup
1518
- * // Manually control if needed:
1519
- * stop()
1520
- * start()
1521
- * ```
1522
- */
1523
- function useKeydown(handlers) {
1524
- let cleanup = null;
1525
- const isActive = toRef(() => !!cleanup);
1526
- function onKeydown(event) {
1527
- const handler = toArray(toValue(handlers)).find((h$1) => h$1.key === event.key);
1528
- if (handler) {
1529
- if (handler.preventDefault) event.preventDefault();
1530
- if (handler.stopPropagation) event.stopPropagation();
1531
- handler.handler(event);
1532
- }
1533
- }
1534
- function start() {
1535
- if (cleanup) return;
1536
- cleanup = useDocumentEventListener("keydown", onKeydown);
1537
- }
1538
- function stop() {
1539
- if (!cleanup) return;
1540
- cleanup();
1541
- cleanup = null;
1542
- }
1543
- cleanup = useDocumentEventListener("keydown", onKeydown);
1544
- onScopeDispose(stop);
1545
- return {
1546
- isActive,
1547
- start,
1548
- stop
1549
- };
1550
- }
1551
-
1552
- //#endregion
1553
- //#region src/composables/useMutationObserver/index.ts
1554
- /**
1555
- * @module useMutationObserver
1556
- *
1557
- * @remarks
1558
- * MutationObserver composable with lifecycle management.
1559
- *
1560
- * Key features:
1561
- * - MutationObserver API wrapper
1562
- * - Pause/resume/stop functionality
1563
- * - Automatic cleanup on unmount
1564
- * - SSR-safe (checks SUPPORTS_MUTATION_OBSERVER)
1565
- * - Hydration-aware
1566
- * - Configurable observation options (childList, attributes, characterData, etc.)
1567
- *
1568
- * Perfect for detecting DOM changes and responding to mutations.
1569
- */
1570
- /**
1571
- * A composable that uses the Mutation Observer API to detect changes in the DOM.
1572
- *
1573
- * @param target The element to observe.
1574
- * @param callback The callback to execute when a mutation is observed.
1575
- * @param options The options for the Mutation Observer.
1576
- * @returns An object with methods to control the observer.
1577
- *
1578
- * @see https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
1579
- * @see https://0.vuetifyjs.com/composables/system/use-mutation-observer
1580
- *
1581
- * @example
1582
- * ```ts
1583
- * import { ref } from 'vue'
1584
- * import { useMutationObserver } from '@vuetify/v0'
1585
- *
1586
- * const container = ref<HTMLElement>()
1587
- *
1588
- * const { pause, resume, isPaused } = useMutationObserver(
1589
- * container,
1590
- * (mutations) => {
1591
- * mutations.forEach((mutation) => {
1592
- * if (mutation.type === 'childList') {
1593
- * console.log('Children changed:', mutation.addedNodes, mutation.removedNodes)
1594
- * } else if (mutation.type === 'attributes') {
1595
- * console.log('Attribute changed:', mutation.attributeName)
1596
- * }
1597
- * })
1598
- * },
1599
- * {
1600
- * childList: true,
1601
- * attributes: true,
1602
- * subtree: true
1603
- * }
1604
- * )
1605
- *
1606
- * // Pause observation
1607
- * pause()
1608
- *
1609
- * // Resume observation
1610
- * resume()
1611
- * ```
1612
- */
1613
- function useMutationObserver(target, callback, options = {}) {
1614
- const { isHydrated } = useHydration();
1615
- const observer = shallowRef();
1616
- const isPaused = shallowRef(false);
1617
- const isActive = toRef(() => !!observer.value);
1618
- const observerOptions = {
1619
- childList: options.childList ?? true,
1620
- attributes: options.attributes ?? false,
1621
- characterData: options.characterData ?? false,
1622
- subtree: options.subtree ?? false,
1623
- attributeOldValue: options.attributeOldValue ?? false,
1624
- characterDataOldValue: options.characterDataOldValue ?? false,
1625
- attributeFilter: options.attributeFilter
1626
- };
1627
- function setup() {
1628
- if (/* @__PURE__ */ isNull(observer.value)) return;
1629
- if (!isHydrated.value || !SUPPORTS_MUTATION_OBSERVER || !target.value || isPaused.value) return;
1630
- observer.value = new MutationObserver((mutations) => {
1631
- callback(mutations.map((mutation) => ({
1632
- type: mutation.type,
1633
- target: mutation.target,
1634
- addedNodes: mutation.addedNodes,
1635
- removedNodes: mutation.removedNodes,
1636
- previousSibling: mutation.previousSibling,
1637
- nextSibling: mutation.nextSibling,
1638
- attributeName: mutation.attributeName,
1639
- attributeNamespace: mutation.attributeNamespace,
1640
- oldValue: mutation.oldValue
1641
- })));
1642
- if (options.once) stop();
1643
- });
1644
- observer.value.observe(target.value, observerOptions);
1645
- if (options.immediate) {
1646
- const emptyNodeList = {
1647
- length: 0,
1648
- item: () => null,
1649
- forEach: () => {},
1650
- *[Symbol.iterator]() {}
1651
- };
1652
- callback([{
1653
- type: "childList",
1654
- target: target.value,
1655
- addedNodes: emptyNodeList,
1656
- removedNodes: emptyNodeList,
1657
- previousSibling: null,
1658
- nextSibling: null,
1659
- attributeName: null,
1660
- attributeNamespace: null,
1661
- oldValue: null
1662
- }]);
1663
- if (options.once) stop();
1664
- }
1665
- }
1666
- watchEffect(() => {
1667
- const hydrated = isHydrated.value;
1668
- const el = target.value;
1669
- cleanup();
1670
- if (hydrated && el) setup();
1671
- });
1672
- function cleanup() {
1673
- if (observer.value) {
1674
- observer.value.disconnect();
1675
- observer.value = void 0;
1676
- }
1677
- }
1678
- function pause() {
1679
- isPaused.value = true;
1680
- observer.value?.disconnect();
1681
- }
1682
- function resume() {
1683
- isPaused.value = false;
1684
- setup();
1685
- }
1686
- function stop() {
1687
- cleanup();
1688
- observer.value = null;
1689
- }
1690
- onScopeDispose(stop, true);
1691
- return {
1692
- isActive: shallowReadonly(isActive),
1693
- isPaused: shallowReadonly(isPaused),
1694
- pause,
1695
- resume,
1696
- stop
1697
- };
1698
- }
1699
-
1700
- //#endregion
1701
- //#region src/composables/usePermissions/adapters/adapter.ts
1702
- var PermissionAdapter = class {};
1703
-
1704
- //#endregion
1705
- //#region src/composables/usePermissions/adapters/v0.ts
1706
- var Vuetify0PermissionAdapter = class extends PermissionAdapter {
1707
- constructor() {
1708
- super();
1709
- }
1710
- can(role, action, subject, context, permissions) {
1711
- const access = `${role}.${action}.${subject}`;
1712
- const ticket = permissions.get(access);
1713
- if (!ticket || !ticket.value) return false;
1714
- return /* @__PURE__ */ isFunction(ticket.value) ? ticket.value(context) : ticket.value;
1715
- }
1716
- };
1717
-
1718
- //#endregion
1719
- //#region src/composables/usePermissions/index.ts
1720
- /**
1721
- * @module usePermissions
1722
- *
1723
- * @see https://0.vuetifyjs.com/composables/plugins/use-permissions
1724
- *
1725
- * @remarks
1726
- * Permission management composable with support for RBAC and ABAC patterns.
1727
- *
1728
- * Key features:
1729
- * - Role-Based Access Control (RBAC) support
1730
- * - Attribute-Based Access Control (ABAC) with context
1731
- * - Functional permission conditions
1732
- * - Token-based permission storage
1733
- * - Adapter pattern for custom permission systems
1734
- *
1735
- * Built on useTokens for flexible permission configuration.
1736
- */
1737
- /**
1738
- * Creates a new permissions instance.
1739
- *
1740
- * @param options The options for the permissions instance.
1741
- * @template Z The type of the permission ticket.
1742
- * @template E The type of the permission context.
1743
- * @returns A new permissions instance.
1744
- *
1745
- * @see https://0.vuetifyjs.com/composables/plugins/use-permissions
1746
- *
1747
- * @example
1748
- * ```ts
1749
- * import { createPermissions } from '@vuetify/v0'
1750
- *
1751
- * const [usePermissions, providePermissions] = createPermissions({
1752
- * namespace: 'v0:permissions',
1753
- * permissions: {
1754
- * admin: [['read', 'users']],
1755
- * editor: [['edit', 'posts']],
1756
- * },
1757
- * })
1758
- * ```
1759
- */
1760
- function createPermissions(_options = {}) {
1761
- const { adapter = new Vuetify0PermissionAdapter(), permissions = {}, ...options } = _options;
1762
- const record = {};
1763
- for (const role in permissions) {
1764
- if (!record[role]) record[role] = {};
1765
- for (const [actions, subjects, condition = true] of permissions[role]) for (const action of toArray(actions)) for (const subject of toArray(subjects)) {
1766
- if (!record[role][action]) record[role][action] = {};
1767
- record[role][action][subject] = condition;
1768
- }
1769
- }
1770
- const tokens = createTokens(record, options);
1771
- function can(id, action, subject, context = {}) {
1772
- return adapter.can(id, action, subject, context, tokens);
1773
- }
1774
- return {
1775
- ...tokens,
1776
- can
1777
- };
1778
- }
1779
- /**
1780
- * Creates a new permissions context.
1781
- *
1782
- * @param options The options for the permissions context.
1783
- * @template Z The type of the permission ticket.
1784
- * @template E The type of the permission context.
1785
- * @returns A new permissions context.
1786
- *
1787
- * @see https://0.vuetifyjs.com/composables/plugins/use-permissions
1788
- *
1789
- * @example
1790
- * ```ts
1791
- * import { createPermissionsContext } from '@vuetify/v0'
1792
- *
1793
- * export const [usePermissions, providePermissions, context] = createPermissionsContext({
1794
- * namespace: 'app:permissions',
1795
- * permissions: {
1796
- * admin: [['read', 'users'], ['edit', 'users']],
1797
- * editor: [['edit', 'posts']],
1798
- * },
1799
- * })
1800
- * ```
1801
- */
1802
- function createPermissionsContext(_options = {}) {
1803
- const { namespace = "v0:permissions", ...options } = _options;
1804
- const [usePermissionsContext, _providePermissionsContext] = createContext(namespace);
1805
- const context = createPermissions(options);
1806
- function providePermissionsContext(_context = context, app) {
1807
- return _providePermissionsContext(_context, app);
1808
- }
1809
- return createTrinity(usePermissionsContext, providePermissionsContext, context);
1810
- }
1811
- /**
1812
- * Creates a new permissions plugin.
1813
- *
1814
- * @param options The options for the permissions plugin.
1815
- * @template Z The type of the permission ticket.
1816
- * @template E The type of the permission context.
1817
- * @returns A new permissions plugin.
1818
- *
1819
- * @see https://0.vuetifyjs.com/composables/plugins/use-permissions
1820
- *
1821
- * @example
1822
- * ```ts
1823
- * import { createApp } from 'vue'
1824
- * import { createPermissionsPlugin } from '@vuetify/v0'
1825
- * import App from './App.vue'
1826
- *
1827
- * const app = createApp(App)
1828
- *
1829
- * app.use(
1830
- * createPermissionsPlugin({
1831
- * permissions: {
1832
- * admin: [['read', 'users']],
1833
- * editor: [['edit', 'posts']],
1834
- * },
1835
- * })
1836
- * )
1837
- *
1838
- * app.mount('#app')
1839
- * ```
1840
- */
1841
- function createPermissionsPlugin(_options = {}) {
1842
- const { namespace = "v0:permissions", ...options } = _options;
1843
- const [, providePermissionContext, context] = createPermissionsContext({
1844
- ...options,
1845
- namespace
1846
- });
1847
- return createPlugin({
1848
- namespace,
1849
- provide: (app) => {
1850
- providePermissionContext(context, app);
1851
- }
1852
- });
1853
- }
1854
- /**
1855
- * Returns the current permissions instance.
1856
- *
1857
- * @template Z The type of the permission ticket.
1858
- * @returns The current permissions instance.
1859
- *
1860
- * @see https://0.vuetifyjs.com/composables/plugins/use-permissions
1861
- *
1862
- * @example
1863
- * ```vue
1864
- * <script setup lang="ts">
1865
- * import { usePermissions } from '@vuetify/v0'
1866
- *
1867
- * const { can } = usePermissions()
1868
- * <\/script>
1869
- *
1870
- * <template>
1871
- * <div>
1872
- * <p v-if="can('admin', 'read', 'users')">Admin access</p>
1873
- * </div>
1874
- * </template>
1875
- * ```
1876
- */
1877
- function usePermissions(namespace = "v0:permissions") {
1878
- return useContext(namespace);
1879
- }
1880
-
1881
- //#endregion
1882
- //#region src/composables/useQueue/index.ts
1883
- /**
1884
- * @module useQueue
1885
- *
1886
- * @remarks
1887
- * A queue composable for managing time-based collections with:
1888
- * - Automatic timeout-based removal
1889
- * - Pause/resume functionality
1890
- * - FIFO (First In, First Out) ordering
1891
- * - Manual dismissal support
1892
- * - Queue progression management
1893
- *
1894
- * Built on top of useRegistry, the queue automatically manages timeouts for tickets,
1895
- * ensuring only the first ticket in the queue is active at any time. When an ticket
1896
- * expires or is removed, the next ticket in the queue automatically becomes active.
1897
- */
1898
- /**
1899
- * Creates a new queue instance
1900
- *
1901
- * @param options The options for the queue instance
1902
- * @template Z The type of queue ticket that extends QueueTicket. Use this to add custom properties to tickets.
1903
- * @template E The type of queue context that extends QueueContext<Z>. Use this when extending the queue with additional methods.
1904
- * @returns A new queue instance
1905
- *
1906
- * @see https://0.vuetifyjs.com/composables/registration/use-queue
1907
- *
1908
- * @example
1909
- * ```ts
1910
- * import { useQueue } from '@vuetify/v0'
1911
- *
1912
- * const queue = useQueue()
1913
- *
1914
- * // Register an ticket with default timeout (3000ms)
1915
- * const ticket1 = queue.register({ value: 'Ticket 1' })
1916
- *
1917
- * // Register an ticket with custom timeout
1918
- * const ticket2 = queue.register({ value: 'Ticket 2', timeout: 5000 })
1919
- *
1920
- * // Register a persistent ticket that must be manually dismissed
1921
- * const ticket3 = queue.register({ value: 'Ticket 3', timeout: -1 })
1922
- *
1923
- * // Dismiss an ticket using the convenience method
1924
- * ticket3.dismiss()
1925
- *
1926
- * console.log(queue.size) // 2
1927
- * ```
1928
- */
1929
- function createQueue(_options = {}) {
1930
- const { timeout: _timeout = 3e3, ...options } = _options;
1931
- const registry = useRegistry({
1932
- ...options,
1933
- events: true
1934
- });
1935
- const timeouts = /* @__PURE__ */ new Map();
1936
- function startTimeout(ticket) {
1937
- if (/* @__PURE__ */ isUndefined(ticket.timeout) || ticket.timeout < 0 || ticket.isPaused) return;
1938
- const timeout = setTimeout(() => {
1939
- timeouts.delete(ticket.id);
1940
- registry.unregister(ticket.id);
1941
- resume();
1942
- }, ticket.timeout);
1943
- timeouts.set(ticket.id, timeout);
1944
- }
1945
- function clearTimeout(id) {
1946
- const timeout = timeouts.get(id);
1947
- if (timeout) {
1948
- globalThis.clearTimeout(timeout);
1949
- timeouts.delete(id);
1950
- }
1951
- }
1952
- function register(registration = {}) {
1953
- const id = registration.id ?? /* @__PURE__ */ genId();
1954
- const timeout = Object.prototype.hasOwnProperty.call(registration, "timeout") ? registration.timeout : _timeout;
1955
- const ticket = {
1956
- ...registration,
1957
- id,
1958
- timeout,
1959
- isPaused: registry.size > 0,
1960
- dismiss: () => unregister(id)
1961
- };
1962
- const registered = registry.register(ticket);
1963
- startTimeout(registered);
1964
- return registered;
1965
- }
1966
- function unregister(id) {
1967
- const ticket = /* @__PURE__ */ isUndefined(id) ? registry.seek("first") : registry.get(id);
1968
- if (!ticket) return void 0;
1969
- const wasFirst = ticket.index === 0;
1970
- clearTimeout(ticket.id);
1971
- registry.unregister(ticket.id);
1972
- if (wasFirst) resume();
1973
- return ticket;
1974
- }
1975
- function offboard(ids) {
1976
- let hadFirst = false;
1977
- for (const id of ids) {
1978
- const ticket = registry.get(id);
1979
- if (!ticket) continue;
1980
- if (ticket.index === 0) hadFirst = true;
1981
- clearTimeout(ticket.id);
1982
- }
1983
- registry.offboard(ids);
1984
- if (hadFirst) resume();
1985
- }
1986
- function pause() {
1987
- const ticket = registry.seek("first");
1988
- if (!ticket || ticket.isPaused) return void 0;
1989
- clearTimeout(ticket.id);
1990
- return registry.upsert(ticket.id, { isPaused: true });
1991
- }
1992
- function resume() {
1993
- const ticket = registry.seek("first");
1994
- if (!ticket || ticket.index !== 0 || !ticket.isPaused) return void 0;
1995
- const updated = registry.upsert(ticket.id, { isPaused: false });
1996
- startTimeout(updated);
1997
- return updated;
1998
- }
1999
- function clear() {
2000
- for (const id of timeouts.keys()) clearTimeout(id);
2001
- registry.clear();
2002
- }
2003
- function dispose() {
2004
- clear();
2005
- registry.dispose();
2006
- }
2007
- onScopeDispose(dispose, true);
2008
- return {
2009
- ...registry,
2010
- register,
2011
- unregister,
2012
- offboard,
2013
- pause,
2014
- resume,
2015
- clear,
2016
- dispose,
2017
- get size() {
2018
- return registry.size;
2019
- }
2020
- };
2021
- }
2022
- /**
2023
- * Creates a new queue context.
2024
- *
2025
- * @param options The options for the queue context.
2026
- * @template Z The type of the queue ticket.
2027
- * @template E The type of the queue context.
2028
- * @returns A new queue context.
2029
- *
2030
- * @see https://0.vuetifyjs.com/composables/registration/use-queue
2031
- *
2032
- * @example
2033
- * ```ts
2034
- * import { createQueueContext } from '@vuetify/v0'
2035
- *
2036
- * export const [useQueue, provideQueue, context] = createQueueContext({
2037
- * timeout: 5000,
2038
- * })
2039
- * ```
2040
- */
2041
- function createQueueContext(_options = {}) {
2042
- const { namespace = "v0:queue", ...options } = _options;
2043
- const [useQueueContext, _provideQueueContext] = createContext(namespace);
2044
- const context = createQueue(options);
2045
- function provideQueueContext(_context = context, app) {
2046
- return _provideQueueContext(_context, app);
2047
- }
2048
- return createTrinity(useQueueContext, provideQueueContext, context);
2049
- }
2050
- /**
2051
- * Returns the current queue instance.
2052
- *
2053
- * @param namespace The namespace for the queue context. Defaults to `'v0:queue'`.
2054
- * @returns The current queue instance.
2055
- *
2056
- * @see https://0.vuetifyjs.com/composables/registration/use-queue
2057
- *
2058
- * @example
2059
- * ```vue
2060
- * <script setup lang="ts">
2061
- * import { useQueue } from '@vuetify/v0'
2062
- *
2063
- * const queue = useQueue()
2064
- * <\/script>
2065
- * ```
2066
- */
2067
- function useQueue(namespace = "v0:queue") {
2068
- return useContext(namespace);
2069
- }
2070
-
2071
- //#endregion
2072
- //#region src/composables/useStorage/adapters/memory.ts
2073
- /**
2074
- * In-memory storage adapter that implements the StorageAdapter interface.
2075
- * This adapter provides temporary storage that persists only for the current
2076
- * session and is useful for testing or when persistent storage is not available.
2077
- */
2078
- var MemoryAdapter = class {
2079
- store = /* @__PURE__ */ new Map();
2080
- get length() {
2081
- return this.store.size;
2082
- }
2083
- getItem(key) {
2084
- return this.store.get(key) ?? null;
2085
- }
2086
- setItem(key, value) {
2087
- this.store.set(key, value);
2088
- }
2089
- removeItem(key) {
2090
- this.store.delete(key);
2091
- }
2092
- key(index) {
2093
- return String(Array.from(this.store.keys())[index] ?? "");
2094
- }
2095
- };
2096
-
2097
- //#endregion
2098
- //#region src/composables/useStorage/index.ts
2099
- /**
2100
- * @module useStorage
2101
- *
2102
- * @remarks
2103
- * Reactive storage composable with adapter pattern for localStorage, sessionStorage, or memory.
2104
- *
2105
- * Key features:
2106
- * - Reactive refs that sync with storage
2107
- * - localStorage, sessionStorage, and memory adapters
2108
- * - Custom serialization support
2109
- * - SSR fallback to memory adapter
2110
- * - Automatic cleanup on remove/clear
2111
- *
2112
- * Uses adapter pattern to abstract storage implementation details.
2113
- */
2114
- /**
2115
- * Creates a new storage instance.
2116
- *
2117
- * @param options The options for the storage instance.
2118
- * @template E The type of the storage context.
2119
- * @returns A new storage instance.
2120
- *
2121
- * @see https://0.vuetifyjs.com/composables/plugins/use-storage
2122
- *
2123
- * @example
2124
- * ```ts
2125
- * import { createStorage } from '@vuetify/v0'
2126
- *
2127
- * const storage = createStorage()
2128
- *
2129
- * storage.set('username', 'MyUsername')
2130
- *
2131
- * const username = storage.get('username')
2132
- *
2133
- * console.log(username.value) // MyUsername
2134
- *
2135
- * storage.clear()
2136
- * ```
2137
- */
2138
- function createStorage(options = {}) {
2139
- const { adapter = IN_BROWSER ? window.localStorage : new MemoryAdapter(), prefix = "v0:", serializer = {
2140
- read: JSON.parse,
2141
- write: JSON.stringify
2142
- } } = options;
2143
- const cache = /* @__PURE__ */ new Map();
2144
- const watchers = /* @__PURE__ */ new Map();
2145
- function has(key) {
2146
- const prefixedKey = `${prefix}${key}`;
2147
- return cache.has(prefixedKey);
2148
- }
2149
- function get(key, defaultValue) {
2150
- const prefixedKey = `${prefix}${key}`;
2151
- if (cache.has(prefixedKey)) return cache.get(prefixedKey);
2152
- const storedValue = adapter?.getItem(prefixedKey);
2153
- let initialValue = defaultValue;
2154
- if (storedValue) try {
2155
- initialValue = serializer.read(storedValue);
2156
- } catch (error) {
2157
- console.error(`[v0:storage] Failed to parse stored value for key "${prefixedKey}":`, error);
2158
- }
2159
- const valueRef = ref(initialValue);
2160
- const stop = watch(valueRef, (newValue) => {
2161
- if (/* @__PURE__ */ isNullOrUndefined(newValue)) adapter?.removeItem(prefixedKey);
2162
- else adapter?.setItem(prefixedKey, serializer.write(newValue));
2163
- }, { deep: true });
2164
- watchers.set(prefixedKey, stop);
2165
- cache.set(prefixedKey, valueRef);
2166
- return valueRef;
2167
- }
2168
- function set(key, value) {
2169
- const valueRef = get(key);
2170
- valueRef.value = value;
2171
- }
2172
- function remove(key) {
2173
- const prefixedKey = `${prefix}${key}`;
2174
- const stop = watchers.get(prefixedKey);
2175
- if (!stop) return;
2176
- stop();
2177
- watchers.delete(prefixedKey);
2178
- adapter?.removeItem(prefixedKey);
2179
- cache.delete(prefixedKey);
2180
- }
2181
- function clear() {
2182
- if (watchers.size > 0) {
2183
- for (const stop of watchers.values()) stop();
2184
- watchers.clear();
2185
- }
2186
- if (cache.size > 0) {
2187
- for (const key of cache.keys()) adapter?.removeItem(key);
2188
- cache.clear();
2189
- }
2190
- }
2191
- return {
2192
- has,
2193
- get,
2194
- set,
2195
- remove,
2196
- clear
2197
- };
2198
- }
2199
- function createStorageContext(_options = {}) {
2200
- const { namespace = "v0:storage", ...options } = _options;
2201
- const [useStorageContext, _provideStorageContext] = createContext(namespace);
2202
- const context = createStorage(options);
2203
- function provideStorageContext(_context = context, app) {
2204
- return _provideStorageContext(_context, app);
2205
- }
2206
- return createTrinity(useStorageContext, provideStorageContext, context);
2207
- }
2208
- /**
2209
- * Creates a new storage plugin.
2210
- *
2211
- * @param options The options for the storage plugin.
2212
- * @returns A new storage plugin.
2213
- *
2214
- * @see https://0.vuetifyjs.com/composables/plugins/use-storage
2215
- *
2216
- * @example
2217
- * ```ts
2218
- * import { createApp } from 'vue'
2219
- * import { createStoragePlugin } from '@vuetify/v0'
2220
- * import App from './App.vue'
2221
- *
2222
- * const app = createApp(App)
2223
- *
2224
- * app.use(createStoragePlugin())
2225
- *
2226
- * app.mount('#app')
2227
- * ```
2228
- */
2229
- function createStoragePlugin(_options = {}) {
2230
- const { namespace = "v0:storage", ...options } = _options;
2231
- const [, provideStorageContext, context] = createStorageContext({
2232
- ...options,
2233
- namespace
2234
- });
2235
- return createPlugin({
2236
- namespace,
2237
- provide: (app) => {
2238
- provideStorageContext(context, app);
2239
- }
2240
- });
2241
- }
2242
- /**
2243
- * Returns the current storage instance.
2244
- *
2245
- * @param namespace The namespace for the storage context. Defaults to `'v0:storage'`.
2246
- * @returns The current storage instance.
2247
- *
2248
- * @see https://0.vuetifyjs.com/composables/plugins/use-storage
2249
- *
2250
- * @example
2251
- * ```vue
2252
- * <script setup lang="ts">
2253
- * import { useStorage } from '@vuetify/v0'
2254
- *
2255
- * const storage = useStorage()
2256
- * const username = storage.get('username', 'Guest')
2257
- * <\/script>
2258
- *
2259
- * <template>
2260
- * <div>
2261
- * <p>Username: {{ username }}</p>
2262
- * </div>
2263
- * </template>
2264
- * ```
2265
- */
2266
- function useStorage(namespace = "v0:storage") {
2267
- return useContext(namespace);
2268
- }
2269
-
2270
- //#endregion
2271
- //#region src/composables/useTheme/adapters/adapter.ts
2272
- var ThemeAdapter = class {
2273
- stylesheetId = "v0-theme-stylesheet";
2274
- prefix;
2275
- constructor(prefix) {
2276
- this.prefix = prefix;
2277
- }
2278
- generate(colors, isDark) {
2279
- let css = "";
2280
- for (const theme in colors) {
2281
- const themeColors = colors[theme];
2282
- if (!themeColors) continue;
2283
- const vars = Object.entries(themeColors).map(([key, val]) => ` --${this.prefix}-${key}: ${val};`).join("\n");
2284
- css += `[data-theme="${theme}"] {\n${vars}\n}\n`;
2285
- }
2286
- if (!/* @__PURE__ */ isUndefined(isDark)) css += `:root {\n color-scheme: ${isDark ? "dark" : "light"};\n}\n`;
2287
- return css;
2288
- }
2289
- };
2290
-
2291
- //#endregion
2292
- //#region src/composables/useTheme/adapters/v0.ts
2293
- /**
2294
- * Theme adapter implementation for Vuetify v0 design system.
2295
- * This adapter generates CSS custom properties and injects them into the DOM
2296
- * as a stylesheet, allowing themes to be applied globally.
2297
- */
2298
- var Vuetify0ThemeAdapter = class extends ThemeAdapter {
2299
- cspNonce;
2300
- constructor(options = {}) {
2301
- super(options.prefix ?? "v0");
2302
- this.cspNonce = options.cspNonce;
2303
- this.stylesheetId = options.stylesheetId ?? this.stylesheetId;
2304
- }
2305
- setup(app, context, target) {
2306
- if (IN_BROWSER) {
2307
- onScopeDispose(watch([context.colors, context.isDark], ([colors, isDark]) => {
2308
- this.update(colors, isDark);
2309
- }, { immediate: true }), true);
2310
- if (/* @__PURE__ */ isNull(target)) return;
2311
- const targetEl = target instanceof HTMLElement ? target : /* @__PURE__ */ isString(target) ? document.querySelector(target) : app._container || document.querySelector("#app") || document.body;
2312
- if (!targetEl) return;
2313
- onScopeDispose(watch(context.selectedId, (id) => {
2314
- if (!id) return;
2315
- targetEl.dataset.theme = String(id);
2316
- }, { immediate: true }), true);
2317
- } else {
2318
- const head = app._context?.provides?.usehead ?? app._context?.provides?.head;
2319
- if (head?.push) {
2320
- const id = context.selectedId.value;
2321
- head.push({
2322
- htmlAttrs: { "data-theme": id ? String(id) : "" },
2323
- style: [{
2324
- innerHTML: this.generate(context.colors.value, context.isDark.value),
2325
- id: this.stylesheetId
2326
- }]
2327
- });
2328
- }
2329
- }
2330
- }
2331
- update(colors, isDark) {
2332
- if (!IN_BROWSER) return;
2333
- this.upsert(this.generate(colors, isDark));
2334
- }
2335
- upsert(styles) {
2336
- if (!IN_BROWSER) return;
2337
- const selector = this.stylesheetId.startsWith("#") ? this.stylesheetId : `#${this.stylesheetId}`;
2338
- const id = this.stylesheetId.startsWith("#") ? this.stylesheetId.slice(1) : this.stylesheetId;
2339
- let styleEl = document.querySelector(selector);
2340
- if (!styleEl) {
2341
- styleEl = document.createElement("style");
2342
- styleEl.id = id;
2343
- if (this.cspNonce) styleEl.setAttribute("nonce", this.cspNonce);
2344
- document.head.append(styleEl);
2345
- }
2346
- styleEl.textContent = styles;
2347
- }
2348
- };
2349
-
2350
- //#endregion
2351
- //#region src/composables/useTheme/index.ts
2352
- /**
2353
- * @module useTheme
2354
- *
2355
- * @see https://0.vuetifyjs.com/composables/plugins/use-theme
2356
- *
2357
- * @remarks
2358
- * Theme management composable with token resolution and CSS variable injection.
2359
- *
2360
- * Key features:
2361
- * - Single-selection theme switching (extends createSingle)
2362
- * - Token alias resolution via useTokens
2363
- * - Lazy theme loading (compute colors only when selected)
2364
- * - CSS variable generation via adapter pattern
2365
- * - SSR support with head integration
2366
- * - Theme cycling
2367
- *
2368
- * Integrates with createSingle for selection and useTokens for color resolution.
2369
- */
2370
- /**
2371
- * Creates a new theme instance.
2372
- *
2373
- * @param options The options for the theme instance.
2374
- * @template Z The type of the theme ticket.
2375
- * @template E The type of the theme context.
2376
- * @returns A new theme instance.
2377
- *
2378
- * @see https://0.vuetifyjs.com/composables/plugins/use-theme
2379
- *
2380
- * @example
2381
- * ```ts
2382
- * import { createTheme } from '@vuetify/v0'
2383
- *
2384
- * export const [useTheme, provideTheme] = createTheme({
2385
- * namespace: 'v0:theme',
2386
- * default: 'light',
2387
- * themes: {
2388
- * light: {
2389
- * dark: false,
2390
- * colors: {
2391
- * primary: '#3b82f6',
2392
- * },
2393
- * },
2394
- * dark: {
2395
- * dark: true,
2396
- * colors: {
2397
- * primary: '#675496',
2398
- * },
2399
- * },
2400
- * },
2401
- * })
2402
- * ```
2403
- */
2404
- function createTheme(_options = {}) {
2405
- const { themes = {}, palette = {}, ...options } = _options;
2406
- const tokens = createTokens({
2407
- palette,
2408
- ...themes
2409
- }, { flat: true });
2410
- const registry = createSingle(options);
2411
- for (const id in themes) {
2412
- const { colors: value, ...theme } = themes[id];
2413
- register({
2414
- id,
2415
- value,
2416
- ...theme
2417
- });
2418
- if (id === options.default && !registry.selectedId.value) registry.select(id);
2419
- }
2420
- const names = computed(() => registry.keys());
2421
- const colors = computed(() => {
2422
- const resolved = {};
2423
- for (const theme of registry.values()) {
2424
- if (theme.lazy && theme.id !== registry.selectedId.value) continue;
2425
- resolved[theme.id] = resolve(theme.value);
2426
- }
2427
- return resolved;
2428
- });
2429
- const isDark = toRef(() => registry.selectedItem.value?.dark ?? false);
2430
- function cycle(themes$1 = names.value) {
2431
- const current = themes$1.indexOf(registry.selectedId.value ?? "");
2432
- const next = current === -1 ? 0 : (current + 1) % themes$1.length;
2433
- registry.select(themes$1[next]);
2434
- }
2435
- function resolve(colors$1) {
2436
- const resolved = {};
2437
- for (const [key, value] of Object.entries(colors$1)) resolved[key] = tokens.isAlias(value) ? tokens.resolve(value) : value;
2438
- return resolved;
2439
- }
2440
- function register(registration = {}) {
2441
- const item = {
2442
- lazy: false,
2443
- dark: false,
2444
- ...registration
2445
- };
2446
- return registry.register(item);
2447
- }
2448
- return {
2449
- ...registry,
2450
- colors,
2451
- isDark,
2452
- register,
2453
- cycle,
2454
- get size() {
2455
- return registry.size;
2456
- }
2457
- };
2458
- }
2459
- /**
2460
- * Creates a new theme context trinity.
2461
- *
2462
- * @param options The options for the theme context.
2463
- * @template Z The type of the theme ticket.
2464
- * @template E The type of the theme context.
2465
- * @returns A new theme context trinity.
2466
- *
2467
- * @see https://0.vuetifyjs.com/composables/plugins/use-theme
2468
- *
2469
- * @example
2470
- * ```ts
2471
- * import { createThemeContext } from '@vuetify/v0'
2472
- *
2473
- * export const [useThemeContext, provideThemeContext, context] = createThemeContext({
2474
- * namespace: 'v0:theme',
2475
- * default: 'light',
2476
- * themes: {
2477
- * light: {
2478
- * dark: false,
2479
- * colors: {
2480
- * primary: '#3b82f6',
2481
- * },
2482
- * },
2483
- * dark: {
2484
- * dark: true,
2485
- * colors: {
2486
- * primary: '#675496',
2487
- * },
2488
- * },
2489
- * },
2490
- * })
2491
- * ```
2492
- */
2493
- function createThemeContext(_options = {}) {
2494
- const { namespace = "v0:theme", ...options } = _options;
2495
- const [useThemeContext, _provideThemeContext] = createContext(namespace);
2496
- const context = createTheme(options);
2497
- function provideThemeContext(_context = context, app) {
2498
- return _provideThemeContext(_context, app);
2499
- }
2500
- return createTrinity(useThemeContext, provideThemeContext, context);
2501
- }
2502
- /**
2503
- * Creates a new theme plugin.
2504
- *
2505
- * @param options The options for the theme plugin.
2506
- * @template Z The type of the theme ticket.
2507
- * @template E The type of the theme context.
2508
- * @returns A new theme plugin.
2509
- *
2510
- * @see https://0.vuetifyjs.com/composables/plugins/use-theme
2511
- *
2512
- * @example
2513
- * ```ts
2514
- * import { createApp } from 'vue'
2515
- * import { createThemePlugin } from '@vuetify/v0'
2516
- * import App from './App.vue'
2517
- *
2518
- * const app = createApp(App)
2519
- *
2520
- * app.use(
2521
- * createThemePlugin({
2522
- * default: 'light',
2523
- * themes: {
2524
- * light: {
2525
- * dark: false,
2526
- * colors: {
2527
- * primary: '#3b82f6',
2528
- * },
2529
- * },
2530
- * dark: {
2531
- * dark: true,
2532
- * colors: {
2533
- * primary: '#675496',
2534
- * },
2535
- * },
2536
- * },
2537
- * })
2538
- * )
2539
- *
2540
- * app.mount('#app')
2541
- * ```
2542
- */
2543
- function createThemePlugin(_options = {}) {
2544
- const { adapter = new Vuetify0ThemeAdapter(), namespace = "v0:theme", palette = {}, themes = {}, target, ...options } = _options;
2545
- const [, provideThemeContext, context] = createThemeContext({
2546
- ...options,
2547
- namespace,
2548
- themes,
2549
- palette
2550
- });
2551
- return createPlugin({
2552
- namespace,
2553
- provide: (app) => {
2554
- provideThemeContext(context, app);
2555
- },
2556
- setup: (app) => {
2557
- adapter.setup(app, context, target);
2558
- }
2559
- });
2560
- }
2561
- /**
2562
- * Returns the current theme instance.
2563
- *
2564
- * @param namespace The namespace for the theme context. Defaults to `v0:theme`.
2565
- * @returns The current theme instance.
2566
- *
2567
- * @see https://0.vuetifyjs.com/composables/plugins/use-theme
2568
- *
2569
- * @example
2570
- * ```vue
2571
- * <script setup lang="ts">
2572
- * import { useTheme } from '@vuetify/v0'
2573
- *
2574
- * const theme = useTheme()
2575
- * <\/script>
2576
- *
2577
- * <template>
2578
- * <div>
2579
- * <p>Current theme: {{ theme.selected.value }}</p>
2580
- * </div>
2581
- * </template>
2582
- * ```
2583
- */
2584
- function useTheme(namespace = "v0:theme") {
2585
- return useContext(namespace);
2586
- }
2587
-
2588
- //#endregion
2589
- //#region src/composables/useTimeline/index.ts
2590
- /**
2591
- * @module useTimeline
2592
- *
2593
- * @remarks
2594
- * Bounded undo/redo system with overflow management.
2595
- *
2596
- * Key features:
2597
- * - Fixed-size history (default: 10 items)
2598
- * - Undo/redo stack management
2599
- * - Overflow queue (preserves oldest items)
2600
- * - Automatic reindexing after operations
2601
- * - Perfect for command pattern, history tracking
2602
- *
2603
- * Extends useRegistry with temporal navigation capabilities.
2604
- */
2605
- /**
2606
- * Creates a new timeline instance.
2607
- *
2608
- * @param _options The options for the timeline instance.
2609
- * @template Z The type of the timeline ticket.
2610
- * @template E The type of the timeline context.
2611
- * @returns A new timeline instance.
2612
- *
2613
- * @see https://0.vuetifyjs.com/composables/registration/use-timeline
2614
- *
2615
- * @example
2616
- * ```ts
2617
- * import { useTimeline } from '@vuetify/v0'
2618
- *
2619
- * const timeline = useTimeline({ size: 3 })
2620
- *
2621
- * timeline.onboard([{ id: 'one' }, { id: 'two' }, { id: 'three' }])
2622
- *
2623
- * console.log(timeline.values()) // [{ id: 'one' }, { id: 'two' }, { id: 'three' }]
2624
- *
2625
- * timeline.undo()
2626
- * console.log(timeline.values()) // [{ id: 'one' }, { id: 'two' }]
2627
- *
2628
- * timeline.redo()
2629
- * console.log(timeline.values()) // [{ id: 'one' }, { id: 'two' }, { id: 'three' }]
2630
- * ```
2631
- */
2632
- function createTimeline(_options = {}) {
2633
- const { size = 10, ...options } = _options;
2634
- const registry = useRegistry(options);
2635
- const stack = [];
2636
- const overflow = [];
2637
- function register(item) {
2638
- stack.length = 0;
2639
- if (registry.size < size) return registry.register({ ...item });
2640
- const removing = registry.seek("first");
2641
- if (overflow.length === size) overflow.shift();
2642
- overflow.push(removing);
2643
- registry.unregister(removing.id);
2644
- const ticket = registry.register({ ...item });
2645
- registry.reindex();
2646
- return ticket;
2647
- }
2648
- function undo() {
2649
- const item = registry.seek("last");
2650
- if (!item) return void 0;
2651
- stack.push(item);
2652
- registry.unregister(item.id);
2653
- const restored = overflow.pop();
2654
- if (restored) {
2655
- const remaining = registry.values();
2656
- registry.clear();
2657
- registry.onboard([restored, ...remaining]);
2658
- registry.reindex();
2659
- }
2660
- return item;
2661
- }
2662
- function redo() {
2663
- if (stack.length === 0) return void 0;
2664
- const item = stack.pop();
2665
- const ticket = registry.register(item);
2666
- registry.reindex();
2667
- return ticket;
2668
- }
2669
- return {
2670
- ...registry,
2671
- register,
2672
- undo,
2673
- redo,
2674
- get size() {
2675
- return registry.size;
2676
- }
2677
- };
2678
- }
2679
- /**
2680
- * Creates a new timeline context.
2681
- *
2682
- * @param options The options for the timeline context.
2683
- * @template Z The type of the timeline ticket.
2684
- * @template E The type of the timeline context.
2685
- * @returns A new timeline context.
2686
- *
2687
- * @see https://0.vuetifyjs.com/composables/registration/use-timeline
2688
- *
2689
- * @example
2690
- * ```ts
2691
- * import { createTimelineContext } from '@vuetify/v0'
2692
- *
2693
- * // With default namespace 'v0:timeline'
2694
- * export const [useTimeline, provideTimeline, context] = createTimelineContext({ size: 5 })
2695
- *
2696
- * context.register({ id: 'example' })
2697
- *
2698
- * // In a parent component
2699
- * provideTimeline()
2700
- *
2701
- * // In a child component
2702
- * const timeline = useTimeline()
2703
- *
2704
- * console.log(timeline.values()) // [{ id: 'example' }]
2705
- * ```
2706
- */
2707
- function createTimelineContext(_options = {}) {
2708
- const { namespace = "v0:timeline", ...options } = _options;
2709
- const [useTimelineContext, _provideTimelineContext] = createContext(namespace);
2710
- const context = createTimeline(options);
2711
- function provideTimelineContext(_context = context, app) {
2712
- return _provideTimelineContext(_context, app);
2713
- }
2714
- return createTrinity(useTimelineContext, provideTimelineContext, context);
2715
- }
2716
- /**
2717
- * Returns the current timeline instance.
2718
- *
2719
- * @param namespace The namespace for the timeline context. Defaults to `'v0:timeline'`.
2720
- * @returns The current timeline instance.
2721
- *
2722
- * @see https://0.vuetifyjs.com/composables/registration/use-timeline
2723
- *
2724
- * @example
2725
- * ```vue
2726
- * <script setup lang="ts">
2727
- * import { useTimeline } from '@vuetify/v0'
2728
- *
2729
- * const timeline = useTimeline()
2730
- * <\/script>
2731
- * ```
2732
- */
2733
- function useTimeline(namespace = "v0:timeline") {
2734
- return useContext(namespace);
2735
- }
2736
-
2737
- //#endregion
2738
- //#region src/composables/useToggleScope/index.ts
2739
- /**
2740
- * @module useToggleScope
2741
- *
2742
- * @remarks
2743
- * Conditionally manages an effect scope based on a reactive boolean condition.
2744
- * When the source becomes true, creates and runs an effect scope. When false, stops the scope.
2745
- * All reactive effects created within the scoped function are automatically cleaned up on deactivation.
2746
- *
2747
- * Key features:
2748
- * - Uses Vue's effectScope for efficient reactive effect lifecycle management
2749
- * - Automatic cleanup when condition becomes false
2750
- * - Supports optional reset callback for scope restart capability
2751
- * - Handles rapid toggling and parent scope disposal safely
2752
- * - SSR-safe (effectScope is part of Vue core)
2753
- *
2754
- * Perfect for conditional side effects, feature flags, and performance optimization
2755
- * by only running reactive effects when needed.
2756
- */
2757
- /**
2758
- * Conditionally manages an effect scope based on a reactive boolean source.
2759
- *
2760
- * @param source A reactive boolean value or getter that controls the scope lifecycle
2761
- * @param fn The function to run within the effect scope. Can optionally receive controls for manual scope management.
2762
- * @returns Controls object with isActive state and start/stop/reset methods
2763
- *
2764
- * @see https://vuejs.org/api/reactivity-advanced.html#effectscope
2765
- * @see https://0.vuetifyjs.com/composables/system/use-toggle-scope
2766
- *
2767
- * @example
2768
- * ```ts
2769
- * import { ref } from 'vue'
2770
- * import { useToggleScope } from '@vuetify/v0'
2771
- *
2772
- * const isEnabled = ref(false)
2773
- *
2774
- * const { isActive } = useToggleScope(isEnabled, () => {
2775
- * // This code runs when isEnabled becomes true
2776
- * const unwatch = watch(someRef, () => {
2777
- * console.log('Watching...')
2778
- * })
2779
- *
2780
- * // Cleanup happens automatically when isEnabled becomes false
2781
- * })
2782
- *
2783
- * // Toggle the scope on/off
2784
- * isEnabled.value = true // Starts the scope
2785
- * console.log(isActive.value) // true
2786
- * isEnabled.value = false // Stops and cleans up
2787
- * console.log(isActive.value) // false
2788
- * ```
2789
- *
2790
- * @example
2791
- * With manual controls:
2792
- * ```ts
2793
- * const isEnabled = ref(true)
2794
- *
2795
- * useToggleScope(isEnabled, (controls) => {
2796
- * // Access controls inside the scope
2797
- * console.log('Active:', controls.isActive.value)
2798
- *
2799
- * // Manually reset the scope if needed
2800
- * someEvent.on('reset', () => controls.reset())
2801
- * })
2802
- * ```
2803
- */
2804
- function useToggleScope(source, fn) {
2805
- const scope = shallowRef();
2806
- const isActive = toRef(() => !!scope.value);
2807
- function start() {
2808
- if (scope.value) return;
2809
- scope.value = effectScope();
2810
- scope.value.run(() => fn.length > 0 ? fn(controls) : fn());
2811
- }
2812
- function stop() {
2813
- scope.value?.stop();
2814
- scope.value = void 0;
2815
- }
2816
- function reset() {
2817
- stop();
2818
- start();
2819
- }
2820
- const controls = {
2821
- isActive: shallowReadonly(isActive),
2822
- start,
2823
- stop,
2824
- reset
2825
- };
2826
- watch(source, (active) => {
2827
- if (active && !scope.value) start();
2828
- else if (!active) stop();
2829
- }, { immediate: true });
2830
- onScopeDispose(() => {
2831
- stop();
2832
- });
2833
- return controls;
2834
- }
2835
-
2836
- //#endregion
2837
- //#region src/composables/useVirtual/index.ts
2838
- /**
2839
- * @module useVirtual
2840
- *
2841
- * @remarks
2842
- * Virtual scrolling composable for efficiently rendering large lists.
2843
- *
2844
- * Key features:
2845
- * - Renders only visible items (viewport + overscan)
2846
- * - Dynamic or fixed item heights
2847
- * - SSR-safe (checks IN_BROWSER)
2848
- * - Bidirectional scrolling (forward/reverse for chat apps)
2849
- * - Scroll anchoring (maintains position across data changes)
2850
- * - Edge detection for infinite scroll
2851
- * - iOS momentum and elastic scrolling
2852
- * - Configurable overscan (extra items rendered for smooth scrolling)
2853
- *
2854
- * Perfect for large data sets, chat apps, and infinite scroll implementations.
2855
- */
2856
- /**
2857
- * Virtual scrolling composable for efficiently rendering large lists
2858
- *
2859
- * @param items Reactive array of items to virtualize
2860
- * @param options Configuration options
2861
- * @returns Virtual scrolling context
2862
- *
2863
- * @see https://0.vuetifyjs.com/composables/utilities/use-virtual
2864
- *
2865
- * @example
2866
- * ```vue
2867
- * <script setup lang="ts">
2868
- * import { useVirtual } from '@vuetify/v0'
2869
- *
2870
- * const items = ref(Array.from({ length: 10000 }, (_, i) => ({ id: i, name: `Item ${i}` })))
2871
- * <\/script>
2872
- *
2873
- * <template>
2874
- * <div
2875
- * ref="element"
2876
- * style="height: 600px; overflow-y: auto;"
2877
- * @scroll="scroll"
2878
- * >
2879
- * <div :style="{ height: `${offset}px` }" />
2880
- *
2881
- * <div v-for="item in items" :key="item.index">
2882
- * {{ item.raw.name }}
2883
- * </div>
2884
- *
2885
- * <div :style="{ height: `${size}px` }" />
2886
- * </div>
2887
- * </template>
2888
- * ```
2889
- */
2890
- function useVirtual(items, _options = {}) {
2891
- const { itemHeight: _itemHeight, height, overscan = 5, direction = "forward", anchor = "auto", anchorSmooth = true, onStartReached, onEndReached, startThreshold = 0, endThreshold = 0, momentum: momentumOption, elastic: elasticOption } = _options;
2892
- const element = ref();
2893
- const itemHeight = shallowRef(Number.parseFloat(String(_itemHeight || 0)));
2894
- const heights = shallowRef([]);
2895
- const offsets = shallowRef([]);
2896
- const first = shallowRef(0);
2897
- const last = shallowRef(0);
2898
- const offset = shallowRef(0);
2899
- const size = shallowRef(0);
2900
- const viewportHeight = shallowRef(0);
2901
- const state = shallowRef("ok");
2902
- const isIOS = IN_BROWSER && /iPad|iPhone|iPod/.test(navigator.userAgent);
2903
- const momentum = momentumOption ?? isIOS;
2904
- const elastic = elasticOption ?? isIOS;
2905
- let raf = -1;
2906
- let rebuildRaf = -1;
2907
- let edgeRaf = -1;
2908
- const cachedViewport = Number.parseInt(String(height)) || 0;
2909
- let anchorIndex = -1;
2910
- let anchorOffset = 0;
2911
- const computedItems = computed(() => items.value.slice(first.value, last.value).map((item, i) => ({
2912
- raw: item,
2913
- index: i + first.value
2914
- })));
2915
- watch(element, (el) => {
2916
- if (!IN_BROWSER || !el?.style) return;
2917
- if (momentum) el.style.webkitOverflowScrolling = "touch";
2918
- if (!elastic) el.style.overscrollBehavior = "none";
2919
- });
2920
- useResizeObserver(element, (entries) => {
2921
- if (!entries[0]) return;
2922
- viewportHeight.value = entries[0].contentRect.height;
2923
- update();
2924
- });
2925
- watch(items, (newItems) => {
2926
- captureAnchor();
2927
- const length = newItems.length;
2928
- const newHeights = heights.value.length === length ? heights.value : Array.from({ length }, () => null);
2929
- if (heights.value !== newHeights) heights.value = newHeights;
2930
- rebuild();
2931
- }, { immediate: true });
2932
- watch(element, () => {
2933
- if (!element.value) return;
2934
- if (direction === "reverse" && items.value.length > 0) {
2935
- const lastIndex = items.value.length - 1;
2936
- const totalHeight = (offsets.value[lastIndex] || 0) + (heights.value[lastIndex] || itemHeight.value);
2937
- element.value.scrollTop = totalHeight;
2938
- }
2939
- update();
2940
- });
2941
- function captureAnchor() {
2942
- if (!element.value || anchor === "auto") return;
2943
- if (anchor === "start") {
2944
- anchorIndex = 0;
2945
- anchorOffset = 0;
2946
- } else if (anchor === "end") {
2947
- anchorIndex = items.value.length - 1;
2948
- anchorOffset = 0;
2949
- } else if (/* @__PURE__ */ isFunction(anchor)) {
2950
- const result = anchor(items.value);
2951
- if (/* @__PURE__ */ isNumber(result)) {
2952
- anchorIndex = result;
2953
- anchorOffset = element.value.scrollTop - (offsets.value[result] || 0);
2954
- }
2955
- } else {
2956
- anchorIndex = first.value;
2957
- anchorOffset = element.value.scrollTop - (offsets.value[first.value] || 0);
2958
- }
2959
- }
2960
- function restoreAnchor() {
2961
- if (!element.value || anchorIndex < 0) return;
2962
- const newScrollTop = (offsets.value[anchorIndex] || 0) + anchorOffset;
2963
- if (anchorSmooth && direction === "forward") element.value.scrollTo({
2964
- top: newScrollTop,
2965
- behavior: "smooth"
2966
- });
2967
- else element.value.scrollTop = newScrollTop;
2968
- anchorIndex = -1;
2969
- anchorOffset = 0;
2970
- }
2971
- function rebuild() {
2972
- const length = items.value.length;
2973
- const newOffsets = offsets.value.length === length ? offsets.value : Array.from({ length });
2974
- let offset$1 = 0;
2975
- for (let i = 0; i < length; i++) {
2976
- newOffsets[i] = offset$1;
2977
- offset$1 += heights.value[i] || itemHeight.value;
2978
- }
2979
- if (offsets.value !== newOffsets) offsets.value = newOffsets;
2980
- update();
2981
- restoreAnchor();
2982
- }
2983
- function findIndex(scrollTop) {
2984
- const arr = offsets.value;
2985
- if (arr.length === 0) return 0;
2986
- let low = 0;
2987
- let high = arr.length - 1;
2988
- while (low <= high) {
2989
- const mid = low + high >> 1;
2990
- if (arr[mid] <= scrollTop) low = mid + 1;
2991
- else high = mid - 1;
2992
- }
2993
- return Math.max(0, high);
2994
- }
2995
- function update() {
2996
- if (!element.value) return;
2997
- const viewport = viewportHeight.value || cachedViewport;
2998
- if (!viewport || !itemHeight.value) return;
2999
- const scrollTop = element.value.scrollTop || 0;
3000
- const length = items.value.length;
3001
- const visibleStart = findIndex(scrollTop);
3002
- const visibleEnd = findIndex(scrollTop + viewport) + 1;
3003
- const start = /* @__PURE__ */ clamp(visibleStart - overscan, 0, length);
3004
- const end = /* @__PURE__ */ clamp(visibleEnd + overscan, start, length);
3005
- first.value = start;
3006
- last.value = end;
3007
- offset.value = offsets.value[start] || 0;
3008
- const lastIndex = length - 1;
3009
- const totalHeight = (offsets.value[lastIndex] || 0) + (heights.value[lastIndex] || itemHeight.value);
3010
- size.value = totalHeight - (offsets.value[end] || totalHeight);
3011
- }
3012
- function checkEdges() {
3013
- if (!element.value) return;
3014
- const scrollTop = element.value.scrollTop;
3015
- const scrollHeight = element.value.scrollHeight;
3016
- const clientHeight = element.value.clientHeight;
3017
- const distanceFromStart = scrollTop;
3018
- const distanceFromEnd = scrollHeight - (scrollTop + clientHeight);
3019
- if (IN_BROWSER) {
3020
- cancelAnimationFrame(edgeRaf);
3021
- edgeRaf = requestAnimationFrame(() => {
3022
- if (onStartReached && distanceFromStart <= startThreshold) onStartReached(distanceFromStart);
3023
- if (onEndReached && distanceFromEnd <= endThreshold) onEndReached(distanceFromEnd);
3024
- });
3025
- } else {
3026
- if (onStartReached && distanceFromStart <= startThreshold) onStartReached(distanceFromStart);
3027
- if (onEndReached && distanceFromEnd <= endThreshold) onEndReached(distanceFromEnd);
3028
- }
3029
- }
3030
- function resize(index, height$1) {
3031
- if (heights.value[index] === height$1) return;
3032
- heights.value[index] = height$1;
3033
- if (!itemHeight.value) itemHeight.value = height$1;
3034
- if (IN_BROWSER) {
3035
- cancelAnimationFrame(rebuildRaf);
3036
- rebuildRaf = requestAnimationFrame(rebuild);
3037
- } else rebuild();
3038
- }
3039
- function scroll() {
3040
- if (IN_BROWSER) {
3041
- cancelAnimationFrame(raf);
3042
- raf = requestAnimationFrame(update);
3043
- checkEdges();
3044
- } else update();
3045
- }
3046
- function scrollTo(index, scrollOptions) {
3047
- if (!element.value) return;
3048
- const targetOffset = offsets.value[index] || 0;
3049
- const behavior = scrollOptions?.behavior ?? "auto";
3050
- const block = scrollOptions?.block ?? "start";
3051
- const extraOffset = scrollOptions?.offset ?? 0;
3052
- let scrollTop = targetOffset + extraOffset;
3053
- switch (block) {
3054
- case "center": {
3055
- const viewport = viewportHeight.value || cachedViewport;
3056
- const itemH = heights.value[index] || itemHeight.value;
3057
- scrollTop = targetOffset - viewport / 2 + itemH / 2 + extraOffset;
3058
- break;
3059
- }
3060
- case "end": {
3061
- const viewport = viewportHeight.value || cachedViewport;
3062
- const itemH = heights.value[index] || itemHeight.value;
3063
- scrollTop = targetOffset - viewport + itemH + extraOffset;
3064
- break;
3065
- }
3066
- case "nearest": {
3067
- const viewport = viewportHeight.value || cachedViewport;
3068
- const currentScroll = element.value.scrollTop;
3069
- const itemH = heights.value[index] || itemHeight.value;
3070
- if (targetOffset < currentScroll) scrollTop = targetOffset + extraOffset;
3071
- else if (targetOffset + itemH > currentScroll + viewport) scrollTop = targetOffset - viewport + itemH + extraOffset;
3072
- else return;
3073
- break;
3074
- }
3075
- }
3076
- if (behavior === "smooth") element.value.scrollTo({
3077
- top: scrollTop,
3078
- behavior: "smooth"
3079
- });
3080
- else element.value.scrollTop = scrollTop;
3081
- update();
3082
- }
3083
- function reset() {
3084
- state.value = "ok";
3085
- anchorIndex = -1;
3086
- anchorOffset = 0;
3087
- if (element.value && direction === "reverse" && items.value.length > 0) {
3088
- const lastIndex = items.value.length - 1;
3089
- const totalHeight = (offsets.value[lastIndex] || 0) + (heights.value[lastIndex] || itemHeight.value);
3090
- element.value.scrollTop = totalHeight;
3091
- }
3092
- }
3093
- onScopeDispose(() => {
3094
- cancelAnimationFrame(raf);
3095
- cancelAnimationFrame(rebuildRaf);
3096
- cancelAnimationFrame(edgeRaf);
3097
- }, true);
3098
- return {
3099
- element,
3100
- items: computedItems,
3101
- offset: readonly(offset),
3102
- size: readonly(size),
3103
- state,
3104
- scrollTo,
3105
- scroll,
3106
- scrollend: scroll,
3107
- resize,
3108
- reset
3109
- };
3110
- }
3111
-
3112
- //#endregion
3113
- export { useForm as A, createBreakpoints as B, PermissionAdapter as C, useIntersectionObserver as D, useElementIntersection as E, createFeatures as F, useEventListener as G, createBreakpointsPlugin as H, createFeaturesContext as I, useWindowEventListener as K, createFeaturesPlugin as L, createFilterContext as M, useFilter as N, createForm as O, useFilterContext as P, useFeatures as R, usePermissions as S, useKeydown as T, useBreakpoints as U, createBreakpointsContext as V, useDocumentEventListener as W, createQueueContext as _, useTimeline as a, createPermissionsContext as b, createThemePlugin as c, createStorage as d, createStorageContext as f, createQueue as g, MemoryAdapter as h, createTimelineContext as i, createFilter as j, createFormContext as k, useTheme as l, useStorage as m, useToggleScope as n, createTheme as o, createStoragePlugin as p, toReactive as q, createTimeline as r, createThemeContext as s, useVirtual as t, Vuetify0ThemeAdapter as u, useQueue as v, useMutationObserver as w, createPermissionsPlugin as x, createPermissions as y, useClickOutside as z };