@vuetify/v0 0.0.15 → 0.0.18

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.
@@ -0,0 +1,2866 @@
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-JxCe-nF2.mjs";
2
+ import { C as useResizeObserver, J as createTrinity, O as useHydration, P as toArray, Y as createContext, Z as useContext, k as createGroup, p as createSingle, q as createPlugin, u as createTokens, z as useRegistry } from "./useStep-DC_JvZpy.mjs";
3
+ import { i as SUPPORTS_MUTATION_OBSERVER, n as SUPPORTS_INTERSECTION_OBSERVER, t as IN_BROWSER } from "./globals-BGVqrlN7.mjs";
4
+ import { computed, effectScope, isRef, onScopeDispose, reactive, readonly, ref, shallowReadonly, shallowRef, toRef, toValue, unref, watch } 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 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 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 (this.$parent !== null) 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/useFeatures/index.ts
496
+ /**
497
+ * @module useFeatures
498
+ *
499
+ * @see https://0.vuetifyjs.com/composables/plugins/use-features
500
+ *
501
+ * @remarks
502
+ * Feature flag system with boolean and token-based features.
503
+ *
504
+ * Key features:
505
+ * - Boolean features (true/false activation)
506
+ * - Token features with $variation support
507
+ * - Auto-selection of enabled features
508
+ * - Multi-select support for feature combinations
509
+ * - Perfect for A/B testing, progressive rollout, feature toggles
510
+ *
511
+ * Inheritance chain: useRegistry → createSelection → createGroup → createFeatures
512
+ * Integrates with useTokens for token-based features.
513
+ */
514
+ /**
515
+ * Creates a new features instance.
516
+ *
517
+ * @param options The options for the features instance.
518
+ * @template Z The type of the feature ticket.
519
+ * @template E The type of the feature context.
520
+ * @returns A new features instance.
521
+ *
522
+ * @see https://0.vuetifyjs.com/composables/plugins/use-features
523
+ *
524
+ * @example
525
+ * ```ts
526
+ * import { createFeatures } from '@vuetify/v0'
527
+ *
528
+ * const [useFeatures, provideFeaturesContext, context] = createFeatures({
529
+ * namespace: 'v0:features',
530
+ * features: {
531
+ * 'dark-mode': true,
532
+ * 'theme-color': { $variation: 'blue' },
533
+ * },
534
+ * })
535
+ * ```
536
+ */
537
+ function createFeatures(_options = {}) {
538
+ const { features, ...options } = _options;
539
+ const tokens = createTokens(features, { flat: true });
540
+ const registry = createGroup(options);
541
+ for (const [id, { value }] of tokens.entries()) register({
542
+ id,
543
+ value
544
+ });
545
+ function variation(id, fallback = null) {
546
+ const ticket = registry.get(id);
547
+ if (!ticket) return fallback;
548
+ return /* @__PURE__ */ isObject(ticket.value) ? ticket.value.$variation ?? fallback : ticket.value ?? fallback;
549
+ }
550
+ function register(registration = {}) {
551
+ const item = {
552
+ value: false,
553
+ ...registration
554
+ };
555
+ const ticket = registry.register(item);
556
+ 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);
557
+ return ticket;
558
+ }
559
+ return {
560
+ ...registry,
561
+ variation,
562
+ register,
563
+ get size() {
564
+ return registry.size;
565
+ }
566
+ };
567
+ }
568
+ /**
569
+ * Creates a new features context.
570
+ *
571
+ * @param options The options for the features context.
572
+ * @template Z The type of the feature ticket.
573
+ * @template E The type of the feature context.
574
+ * @returns A new features context.
575
+ *
576
+ * @see https://0.vuetifyjs.com/composables/plugins/use-features
577
+ *
578
+ * @example
579
+ * ```ts
580
+ * import { createFeaturesContext } from '@vuetify/v0'
581
+ *
582
+ * export const [useFeatures, provideFeatures, context] = createFeaturesContext({
583
+ * namespace: 'app:features',
584
+ * features: {
585
+ * 'dark-mode': true,
586
+ * 'theme-color': { $variation: 'blue' },
587
+ * },
588
+ * })
589
+ * ```
590
+ */
591
+ function createFeaturesContext(_options = {}) {
592
+ const { namespace = "v0:features", ...options } = _options;
593
+ const [useFeaturesContext, _provideFeaturesContext] = createContext(namespace);
594
+ const context = createFeatures(options);
595
+ function provideFeaturesContext(_context = context, app) {
596
+ return _provideFeaturesContext(_context, app);
597
+ }
598
+ return createTrinity(useFeaturesContext, provideFeaturesContext, context);
599
+ }
600
+ /**
601
+ * Creates a new features plugin.
602
+ *
603
+ * @param options The options for the features plugin.
604
+ * @template Z The type of the feature ticket.
605
+ * @template E The type of the feature context.
606
+ * @returns A new features plugin.
607
+ *
608
+ * @see https://0.vuetifyjs.com/composables/plugins/use-features
609
+ *
610
+ * @example
611
+ * ```ts
612
+ * import { createApp } from 'vue'
613
+ * import { createFeaturesPlugin } from '@vuetify/v0'
614
+ * import App from './App.vue'
615
+ *
616
+ * const app = createApp(App)
617
+ *
618
+ * app.use(
619
+ * createFeaturesPlugin({
620
+ * features: {
621
+ * 'dark-mode': true,
622
+ * 'theme-color': { $variation: 'blue' },
623
+ * },
624
+ * })
625
+ * )
626
+ *
627
+ * app.mount('#app')
628
+ * ```
629
+ */
630
+ function createFeaturesPlugin(_options = {}) {
631
+ const { namespace = "v0:features", ...options } = _options;
632
+ const [, provideFeaturesContext, context] = createFeaturesContext({
633
+ ...options,
634
+ namespace
635
+ });
636
+ return createPlugin({
637
+ namespace,
638
+ provide: (app) => {
639
+ provideFeaturesContext(context, app);
640
+ }
641
+ });
642
+ }
643
+ /**
644
+ * Returns the current features instance.
645
+ *
646
+ * @param namespace The namespace for the features context. Defaults to `v0:features`.
647
+ * @template Z The type of the feature ticket.
648
+ * @returns The current features instance.
649
+ *
650
+ * @see https://0.vuetifyjs.com/composables/plugins/use-features
651
+ *
652
+ * @example
653
+ * ```vue
654
+ * <script setup lang="ts">
655
+ * import { useFeatures } from '@vuetify/v0'
656
+ *
657
+ * const features = useFeatures()
658
+ * <\/script>
659
+ *
660
+ * <template>
661
+ * <div>
662
+ * <p>Features: {{ features.get('dark-mode') }}</p>
663
+ * <p>Theme Color: {{ features.variation('theme-color') }}</p>
664
+ * </div>
665
+ * </template>
666
+ * ```
667
+ */
668
+ function useFeatures(namespace = "v0:features") {
669
+ return useContext(namespace);
670
+ }
671
+
672
+ //#endregion
673
+ //#region src/composables/useFilter/index.ts
674
+ /**
675
+ * @module useFilter
676
+ *
677
+ * @remarks
678
+ * Reactive array filtering composable with multiple filter modes.
679
+ *
680
+ * Key features:
681
+ * - Four filter modes: some, every, union, intersection
682
+ * - Case-insensitive filtering
683
+ * - Custom filter functions
684
+ * - Reactive updates
685
+ * - Context-based DI support
686
+ * - Perfect for search, multi-criteria filtering
687
+ *
688
+ * Filters arrays based on query strings with configurable matching strategies.
689
+ */
690
+ function defaultFilter(query, item, keys, mode = "some") {
691
+ const queries = Array.isArray(query) ? query.map((q) => String(q).toLowerCase()) : [String(query).toLowerCase()];
692
+ function match(value, q) {
693
+ return String(value).toLowerCase().includes(q);
694
+ }
695
+ const stringValues = (/* @__PURE__ */ isObject(item) ? keys?.length ? keys.map((k) => item[k]) : Object.values(item) : [item]).map((v) => String(v).toLowerCase());
696
+ if (mode === "some") return stringValues.some((val) => match(val, queries[0]));
697
+ if (mode === "every") return stringValues.every((val) => match(val, queries[0]));
698
+ if (mode === "union") return queries.some((q) => stringValues.some((val) => match(val, q)));
699
+ if (mode === "intersection") return queries.every((q) => stringValues.some((val) => match(val, q)));
700
+ return false;
701
+ }
702
+ /**
703
+ * Creates a filter context with pre-configured options.
704
+ *
705
+ * @param options The filter options
706
+ * @template Z The type of the items
707
+ * @template E The type of the filter context
708
+ * @returns A filter context
709
+ *
710
+ * @see https://0.vuetifyjs.com/composables/utilities/use-filter
711
+ *
712
+ * @example
713
+ * ```ts
714
+ * import { createFilter } from '@vuetify/v0'
715
+ *
716
+ * const filter = createFilter({
717
+ * mode: 'intersection',
718
+ * keys: ['name', 'email'],
719
+ * })
720
+ *
721
+ * const { items } = filter.apply(query, users)
722
+ * ```
723
+ */
724
+ function createFilter(options = {}) {
725
+ const { customFilter, keys, mode = "some" } = options;
726
+ const filterFunction = customFilter ?? ((q, i) => defaultFilter(q, i, keys, mode));
727
+ const query = toRef("");
728
+ function apply(_query, items) {
729
+ const itemsRef = isRef(items) ? items : toRef(() => items);
730
+ const queryRef = toRef(_query);
731
+ return { items: computed(() => {
732
+ const q = toValue(queryRef);
733
+ query.value = q;
734
+ const queries = (Array.isArray(q) ? q : [q]).filter((q$1) => String(q$1).trim());
735
+ if (queries.length === 0) return itemsRef.value;
736
+ const queryParam = queries.length === 1 ? queries[0] : queries;
737
+ return itemsRef.value.filter((item) => filterFunction(queryParam, item));
738
+ }) };
739
+ }
740
+ return {
741
+ mode,
742
+ keys,
743
+ customFilter,
744
+ query,
745
+ apply
746
+ };
747
+ }
748
+ /**
749
+ * Creates a filter context with dependency injection support.
750
+ *
751
+ * @param options The filter context options
752
+ * @template Z The type of the items
753
+ * @template E The type of the filter context
754
+ * @returns A trinity tuple: [useContext, provideContext, defaultContext]
755
+ *
756
+ * @see https://0.vuetifyjs.com/composables/utilities/use-filter
757
+ *
758
+ * @example
759
+ * ```ts
760
+ * import { createFilterContext } from '@vuetify/v0'
761
+ *
762
+ * export const [useSearchFilter, provideSearchFilter, searchFilter] = createFilterContext({
763
+ * namespace: 'app:search',
764
+ * mode: 'union',
765
+ * keys: ['title', 'description'],
766
+ * })
767
+ *
768
+ * // In parent component
769
+ * provideSearchFilter()
770
+ *
771
+ * // In child component
772
+ * const filter = useSearchFilter()
773
+ * const { items } = filter.apply(query, products)
774
+ * ```
775
+ */
776
+ function createFilterContext(_options = {}) {
777
+ const { namespace = "v0:filter", ...options } = _options;
778
+ const [useFilterContext$1, _provideFilterContext] = createContext(namespace);
779
+ const context = createFilter(options);
780
+ function provideFilterContext(_context = context, app) {
781
+ return _provideFilterContext(_context, app);
782
+ }
783
+ return createTrinity(useFilterContext$1, provideFilterContext, context);
784
+ }
785
+ /**
786
+ * A reusable function for filtering an array of items.
787
+ *
788
+ * @param query The query to filter by.
789
+ * @param items The items to filter.
790
+ * @param options The filter options.
791
+ * @template Z The type of the items.
792
+ * @returns The filtered items.
793
+ *
794
+ * @see https://0.vuetifyjs.com/composables/utilities/use-filter
795
+ *
796
+ * @example
797
+ * ```ts
798
+ * import { ref } from 'vue'
799
+ * import { useFilter } from '@vuetify/v0'
800
+ *
801
+ * const items = ref([
802
+ * { name: 'John Doe', age: 30 },
803
+ * { name: 'Jane Doe', age: 25 },
804
+ * { name: 'Peter Jones', age: 40 },
805
+ * ])
806
+ *
807
+ * const query = ref('doe')
808
+ * const { items: filtered } = useFilter(query, items, { keys: ['name'] })
809
+ *
810
+ * console.log(filtered.value) // [ { name: 'John Doe', age: 30 }, { name: 'Jane Doe', age: 25 } ]
811
+ * ```
812
+ */
813
+ function useFilter(query, items, options = {}) {
814
+ return createFilter(options).apply(query, items);
815
+ }
816
+ /**
817
+ * Returns the current filter context from dependency injection.
818
+ *
819
+ * @param namespace The namespace for the filter context. Defaults to `'v0:filter'`.
820
+ * @template Z The type of the items.
821
+ * @template E The type of the filter context.
822
+ * @returns The current filter context.
823
+ *
824
+ * @see https://0.vuetifyjs.com/composables/utilities/use-filter
825
+ *
826
+ * @example
827
+ * ```vue
828
+ * <script setup lang="ts">
829
+ * import { useFilterContext } from '@vuetify/v0'
830
+ *
831
+ * const filter = useFilterContext()
832
+ * const { items } = filter.apply(query, products)
833
+ * <\/script>
834
+ * ```
835
+ */
836
+ function useFilterContext(namespace = "v0:filter") {
837
+ return useContext(namespace);
838
+ }
839
+
840
+ //#endregion
841
+ //#region src/composables/useForm/index.ts
842
+ /**
843
+ * @module useForm
844
+ *
845
+ * @remarks
846
+ * Form validation composable with async rule support and multiple validation modes.
847
+ *
848
+ * Key features:
849
+ * - Sync and async validation rules
850
+ * - Multiple validation modes (submit, change, combined)
851
+ * - Tri-state isValid (null/true/false)
852
+ * - isPristine tracking
853
+ * - Silent validation mode
854
+ * - Form-level validation and reset
855
+ *
856
+ * Each field is registered with validation rules and tracks its own state independently.
857
+ */
858
+ /**
859
+ * Creates a new form instance.
860
+ *
861
+ * @param options The options for the form instance.
862
+ * @template Z The type of the form ticket.
863
+ * @template E The type of the form context.
864
+ * @returns A new form instance.
865
+ *
866
+ * @see https://0.vuetifyjs.com/composables/forms/use-form
867
+ *
868
+ * @example
869
+ * ```ts
870
+ * import { createForm } from '@vuetify/v0'
871
+ *
872
+ * const form = createForm()
873
+ *
874
+ * const username = form.register({
875
+ * id: 'username',
876
+ * value: '',
877
+ * rules: [(v) => v.length > 0 || 'Username is required'],
878
+ * })
879
+ *
880
+ * await form.submit()
881
+ *
882
+ * console.log(username.errors.value) // ['Username is required']
883
+ *
884
+ * form.reset()
885
+ * ```
886
+ */
887
+ function createForm(options) {
888
+ const registry = useRegistry(options);
889
+ const validateOn = options?.validateOn || "submit";
890
+ function parse(value) {
891
+ return value.toLowerCase().split(/\s+/);
892
+ }
893
+ function validatesOn(event) {
894
+ return parse(validateOn).includes(event);
895
+ }
896
+ const isValidating = computed(() => {
897
+ for (const ticket of registry.collection.values()) if (ticket.isValidating.value) return true;
898
+ return false;
899
+ });
900
+ const isValid = computed(() => {
901
+ let hasFields = false;
902
+ for (const ticket of registry.values()) {
903
+ hasFields = true;
904
+ if (ticket.isValid.value === false) return false;
905
+ if (ticket.isValid.value === null) return null;
906
+ }
907
+ return hasFields ? true : null;
908
+ });
909
+ function reset() {
910
+ for (const ticket of registry.values()) ticket.reset();
911
+ }
912
+ async function submit() {
913
+ return validate(registry.keys());
914
+ }
915
+ async function validate(id) {
916
+ const validating = toArray(id);
917
+ if (validatesOn("submit")) return (await Promise.all(validating.map(async (id$1) => await registry.get(id$1)?.validate() ?? true))).every(Boolean);
918
+ return validating.map((id$1) => registry.get(id$1)).filter(Boolean).every((ticket) => ticket.isValid.value === true);
919
+ }
920
+ function register(registration) {
921
+ const model = shallowRef(registration.value == null ? "" : toValue(registration.value));
922
+ const rules = registration.rules || [];
923
+ const errors = shallowRef([]);
924
+ const isValidating$1 = shallowRef(false);
925
+ const initialValue = model.value;
926
+ const triggers = registration.validateOn || validateOn;
927
+ const isPristine = shallowRef(true);
928
+ const isValid$1 = shallowRef(null);
929
+ function _validatesOn(event) {
930
+ return parse(triggers).includes(event);
931
+ }
932
+ function _reset() {
933
+ model.value = initialValue;
934
+ errors.value = [];
935
+ isPristine.value = true;
936
+ isValid$1.value = null;
937
+ }
938
+ async function validate$1(silent = false) {
939
+ if (rules.length === 0) return isValid$1.value = true;
940
+ isValidating$1.value = true;
941
+ try {
942
+ const errorMessages = (await Promise.all(rules.map((rule) => rule(model.value)))).filter((result) => /* @__PURE__ */ isString(result));
943
+ if (!silent) {
944
+ errors.value = errorMessages;
945
+ isValid$1.value = errorMessages.length === 0;
946
+ isPristine.value = toValue(model) === initialValue;
947
+ }
948
+ return errorMessages.length === 0;
949
+ } finally {
950
+ isValidating$1.value = false;
951
+ }
952
+ }
953
+ const item = {
954
+ ...registration,
955
+ rules,
956
+ errors,
957
+ disabled: registration.disabled || false,
958
+ validateOn: triggers,
959
+ isValidating: isValidating$1,
960
+ isPristine,
961
+ isValid: isValid$1,
962
+ reset: _reset,
963
+ validate: validate$1
964
+ };
965
+ const ticket = registry.register(item);
966
+ Object.defineProperty(ticket, "value", {
967
+ get() {
968
+ return model.value;
969
+ },
970
+ set(val) {
971
+ model.value = val;
972
+ isPristine.value = val === initialValue;
973
+ isValid$1.value = null;
974
+ if (_validatesOn("change")) validate$1();
975
+ },
976
+ enumerable: true,
977
+ configurable: true
978
+ });
979
+ return ticket;
980
+ }
981
+ return {
982
+ ...registry,
983
+ register,
984
+ reset,
985
+ submit,
986
+ validateOn,
987
+ isValid,
988
+ isValidating,
989
+ get size() {
990
+ return registry.size;
991
+ }
992
+ };
993
+ }
994
+ /**
995
+ * Creates a new form context.
996
+ *
997
+ * @param options The options for the form context.
998
+ * @template Z The type of the form ticket.
999
+ * @template E The type of the form context.
1000
+ * @returns A new form context.
1001
+ *
1002
+ * @see https://0.vuetifyjs.com/composables/forms/use-form
1003
+ *
1004
+ * @example
1005
+ * ```ts
1006
+ * import { createFormContext } from '@vuetify/v0'
1007
+ *
1008
+ * // With default namespace 'v0:form'
1009
+ * export const [useMyForm, provideMyForm, myForm] = createFormContext({ validateOn: 'change' })
1010
+ *
1011
+ * // Or with custom namespace
1012
+ * export const [useMyForm, provideMyForm, myForm] = createFormContext({
1013
+ * namespace: 'my-form',
1014
+ * validateOn: 'change',
1015
+ * })
1016
+ *
1017
+ * // In a parent component:
1018
+ * provideMyForm()
1019
+ *
1020
+ * // In a child component:
1021
+ * const form = useMyForm()
1022
+ * form.register({ id: 'field', value: ref(''), rules: [...] })
1023
+ * ```
1024
+ */
1025
+ function createFormContext(_options = {}) {
1026
+ const { namespace = "v0:form", ...options } = _options;
1027
+ const [useFormContext, _provideFormContext] = createContext(namespace);
1028
+ const context = createForm(options);
1029
+ function provideFormContext(_context = context, app) {
1030
+ return _provideFormContext(_context, app);
1031
+ }
1032
+ return createTrinity(useFormContext, provideFormContext, context);
1033
+ }
1034
+ /**
1035
+ * Returns the current form instance.
1036
+ *
1037
+ * @param namespace The namespace for the form context. Defaults to `'v0:form'`.
1038
+ * @returns The current form instance.
1039
+ *
1040
+ * @see https://0.vuetifyjs.com/composables/forms/use-form
1041
+ *
1042
+ * @example
1043
+ * ```vue
1044
+ * <script setup lang="ts">
1045
+ * import { useForm } from '@vuetify/v0'
1046
+ *
1047
+ * const form = useForm()
1048
+ * <\/script>
1049
+ *
1050
+ * <template>
1051
+ * <div>
1052
+ * <p>Form is {{ form.isValid.value ? 'valid' : 'invalid' }}</p>
1053
+ * </div>
1054
+ * </template>
1055
+ * ```
1056
+ */
1057
+ function useForm(namespace = "v0:form") {
1058
+ return useContext(namespace);
1059
+ }
1060
+
1061
+ //#endregion
1062
+ //#region src/composables/useIntersectionObserver/index.ts
1063
+ /**
1064
+ * @module useIntersectionObserver
1065
+ *
1066
+ * @remarks
1067
+ * IntersectionObserver composable with lifecycle management.
1068
+ *
1069
+ * Key features:
1070
+ * - IntersectionObserver API wrapper
1071
+ * - Pause/resume/stop functionality
1072
+ * - Automatic cleanup on unmount
1073
+ * - SSR-safe (checks SUPPORTS_INTERSECTION_OBSERVER)
1074
+ * - Hydration-aware
1075
+ * - Immediate callback option
1076
+ *
1077
+ * Perfect for lazy loading, infinite scroll, and visibility detection.
1078
+ */
1079
+ /**
1080
+ * A composable that uses the Intersection Observer API to detect when an element
1081
+ * is visible in the viewport.
1082
+ *
1083
+ * @param target The element to observe.
1084
+ * @param callback The callback to execute when the element's intersection changes.
1085
+ * @param options The options for the Intersection Observer.
1086
+ * @returns An object with methods to control the observer.
1087
+ *
1088
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver
1089
+ * @see https://0.vuetifyjs.com/composables/system/use-intersection-observer
1090
+ *
1091
+ * @example
1092
+ * ```ts
1093
+ * import { ref } from 'vue'
1094
+ * import { useIntersectionObserver } from '@vuetify/v0'
1095
+ *
1096
+ * const target = ref<HTMLElement>()
1097
+ * const isVisible = ref(false)
1098
+ *
1099
+ * const { isIntersecting, pause, resume } = useIntersectionObserver(
1100
+ * target,
1101
+ * (entries) => {
1102
+ * const entry = entries[0]
1103
+ * if (entry) {
1104
+ * isVisible.value = entry.isIntersecting
1105
+ * console.log('Element is visible:', entry.isIntersecting)
1106
+ * }
1107
+ * },
1108
+ * { threshold: 0.5 }
1109
+ * )
1110
+ *
1111
+ * // Pause observation
1112
+ * pause()
1113
+ *
1114
+ * // Resume observation
1115
+ * resume()
1116
+ * ```
1117
+ */
1118
+ function useIntersectionObserver(target, callback, options = {}) {
1119
+ const { isHydrated } = useHydration();
1120
+ const observer = shallowRef();
1121
+ const isPaused = shallowRef(false);
1122
+ const isIntersecting = shallowRef(false);
1123
+ const isActive = toRef(() => !!observer.value);
1124
+ function setup() {
1125
+ if (!isHydrated.value || !SUPPORTS_INTERSECTION_OBSERVER || !target.value || isPaused.value) return;
1126
+ observer.value = new IntersectionObserver((entries) => {
1127
+ const transformedEntries = entries.map((entry) => ({
1128
+ boundingClientRect: entry.boundingClientRect,
1129
+ intersectionRatio: entry.intersectionRatio,
1130
+ intersectionRect: entry.intersectionRect,
1131
+ isIntersecting: entry.isIntersecting,
1132
+ rootBounds: entry.rootBounds,
1133
+ target: entry.target,
1134
+ time: entry.time
1135
+ }));
1136
+ const latestEntry = transformedEntries.at(-1);
1137
+ if (latestEntry) isIntersecting.value = latestEntry.isIntersecting;
1138
+ callback(transformedEntries);
1139
+ }, {
1140
+ root: options.root || null,
1141
+ rootMargin: options.rootMargin || "0px",
1142
+ threshold: options.threshold || 0
1143
+ });
1144
+ observer.value.observe(target.value);
1145
+ if (options.immediate) callback([{
1146
+ boundingClientRect: target.value.getBoundingClientRect(),
1147
+ intersectionRatio: 0,
1148
+ intersectionRect: new DOMRect(0, 0, 0, 0),
1149
+ isIntersecting: false,
1150
+ rootBounds: null,
1151
+ target: target.value,
1152
+ time: performance.now()
1153
+ }]);
1154
+ }
1155
+ watch([isHydrated, target], () => {
1156
+ cleanup();
1157
+ setup();
1158
+ }, { immediate: true });
1159
+ function cleanup() {
1160
+ if (observer.value) {
1161
+ observer.value.disconnect();
1162
+ observer.value = void 0;
1163
+ }
1164
+ }
1165
+ function pause() {
1166
+ isPaused.value = true;
1167
+ isIntersecting.value = false;
1168
+ observer.value?.disconnect();
1169
+ }
1170
+ function resume() {
1171
+ isPaused.value = false;
1172
+ setup();
1173
+ }
1174
+ function stop() {
1175
+ cleanup();
1176
+ }
1177
+ onScopeDispose(stop, true);
1178
+ return {
1179
+ isActive: shallowReadonly(isActive),
1180
+ isIntersecting: shallowReadonly(isIntersecting),
1181
+ isPaused: shallowReadonly(isPaused),
1182
+ pause,
1183
+ resume,
1184
+ stop
1185
+ };
1186
+ }
1187
+ /**
1188
+ * A convenience composable that uses the Intersection Observer API to detect
1189
+ * when an element is visible in the viewport.
1190
+ *
1191
+ * @param target The element to observe.
1192
+ * @param options The options for the Intersection Observer.
1193
+ * @returns An object with the intersection state.
1194
+ *
1195
+ * @see https://0.vuetifyjs.com/composables/system/use-intersection-observer
1196
+ *
1197
+ * @example
1198
+ * ```ts
1199
+ * import { ref } from 'vue'
1200
+ * import { useElementIntersection } from '@vuetify/v0'
1201
+ *
1202
+ * const myElement = ref<HTMLElement>()
1203
+ * const { isIntersecting, intersectionRatio } = useElementIntersection(myElement, {
1204
+ * threshold: 0.5
1205
+ * })
1206
+ *
1207
+ * // Use in template to conditionally render or animate
1208
+ * watchEffect(() => {
1209
+ * if (isIntersecting.value) {
1210
+ * console.log('Element is visible!', intersectionRatio.value)
1211
+ * }
1212
+ * })
1213
+ * ```
1214
+ */
1215
+ function useElementIntersection(target, options = {}) {
1216
+ const isIntersecting = shallowRef(false);
1217
+ const intersectionRatio = shallowRef(0);
1218
+ const { pause: _pause, resume, stop, isActive, isPaused } = useIntersectionObserver(target, (entries) => {
1219
+ const entry = entries.at(-1);
1220
+ if (entry) {
1221
+ isIntersecting.value = entry.isIntersecting;
1222
+ intersectionRatio.value = entry.intersectionRatio;
1223
+ }
1224
+ }, {
1225
+ immediate: true,
1226
+ ...options
1227
+ });
1228
+ function pause() {
1229
+ isIntersecting.value = false;
1230
+ intersectionRatio.value = 0;
1231
+ _pause();
1232
+ }
1233
+ return {
1234
+ isIntersecting: shallowReadonly(isIntersecting),
1235
+ intersectionRatio: shallowReadonly(intersectionRatio),
1236
+ isActive,
1237
+ isPaused,
1238
+ pause,
1239
+ resume,
1240
+ stop
1241
+ };
1242
+ }
1243
+
1244
+ //#endregion
1245
+ //#region src/composables/useKeydown/index.ts
1246
+ /**
1247
+ * @module useKeydown
1248
+ *
1249
+ * @remarks
1250
+ * Keydown event listener composable with key filtering.
1251
+ *
1252
+ * Key features:
1253
+ * - Key-specific event handling
1254
+ * - preventDefault and stopPropagation options
1255
+ * - Automatic cleanup on scope disposal
1256
+ * - Built on useEventListener for consistent event handling
1257
+ *
1258
+ * Simplified wrapper around useEventListener for keyboard interactions.
1259
+ */
1260
+ /**
1261
+ * A composable that adds a keydown event listener to the document.
1262
+ *
1263
+ * @param handlers The key handlers to add.
1264
+ * @returns An object with methods to start and stop listening.
1265
+ *
1266
+ * @see https://0.vuetifyjs.com/composables/system/use-keydown
1267
+ *
1268
+ * @example
1269
+ * ```ts
1270
+ * import { useKeydown } from '@vuetify/v0'
1271
+ *
1272
+ * const { isActive, start, stop } = useKeydown([
1273
+ * { key: 'Enter', handler: () => console.log('Enter pressed') },
1274
+ * { key: 'Escape', handler: () => console.log('Escape pressed'), preventDefault: true },
1275
+ * ])
1276
+ *
1277
+ * // Listener is automatically active when called in component setup
1278
+ * // Manually control if needed:
1279
+ * stop()
1280
+ * start()
1281
+ * ```
1282
+ */
1283
+ function useKeydown(handlers) {
1284
+ let cleanup = null;
1285
+ const isActive = toRef(() => !!cleanup);
1286
+ function onKeydown(event) {
1287
+ const handlerList = toValue(handlers);
1288
+ const handler = (Array.isArray(handlerList) ? handlerList : [handlerList]).find((h$1) => h$1.key === event.key);
1289
+ if (handler) {
1290
+ if (handler.preventDefault) event.preventDefault();
1291
+ if (handler.stopPropagation) event.stopPropagation();
1292
+ handler.handler(event);
1293
+ }
1294
+ }
1295
+ function start() {
1296
+ if (cleanup) return;
1297
+ cleanup = useDocumentEventListener("keydown", onKeydown);
1298
+ }
1299
+ function stop() {
1300
+ if (!cleanup) return;
1301
+ cleanup();
1302
+ cleanup = null;
1303
+ }
1304
+ cleanup = useDocumentEventListener("keydown", onKeydown);
1305
+ onScopeDispose(stop);
1306
+ return {
1307
+ isActive,
1308
+ start,
1309
+ stop
1310
+ };
1311
+ }
1312
+
1313
+ //#endregion
1314
+ //#region src/composables/useMutationObserver/index.ts
1315
+ /**
1316
+ * @module useMutationObserver
1317
+ *
1318
+ * @remarks
1319
+ * MutationObserver composable with lifecycle management.
1320
+ *
1321
+ * Key features:
1322
+ * - MutationObserver API wrapper
1323
+ * - Pause/resume/stop functionality
1324
+ * - Automatic cleanup on unmount
1325
+ * - SSR-safe (checks SUPPORTS_MUTATION_OBSERVER)
1326
+ * - Hydration-aware
1327
+ * - Configurable observation options (childList, attributes, characterData, etc.)
1328
+ *
1329
+ * Perfect for detecting DOM changes and responding to mutations.
1330
+ */
1331
+ /**
1332
+ * A composable that uses the Mutation Observer API to detect changes in the DOM.
1333
+ *
1334
+ * @param target The element to observe.
1335
+ * @param callback The callback to execute when a mutation is observed.
1336
+ * @param options The options for the Mutation Observer.
1337
+ * @returns An object with methods to control the observer.
1338
+ *
1339
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
1340
+ * @see https://0.vuetifyjs.com/composables/system/use-mutation-observer
1341
+ *
1342
+ * @example
1343
+ * ```ts
1344
+ * import { ref } from 'vue'
1345
+ * import { useMutationObserver } from '@vuetify/v0'
1346
+ *
1347
+ * const container = ref<HTMLElement>()
1348
+ *
1349
+ * const { pause, resume, isPaused } = useMutationObserver(
1350
+ * container,
1351
+ * (mutations) => {
1352
+ * mutations.forEach((mutation) => {
1353
+ * if (mutation.type === 'childList') {
1354
+ * console.log('Children changed:', mutation.addedNodes, mutation.removedNodes)
1355
+ * } else if (mutation.type === 'attributes') {
1356
+ * console.log('Attribute changed:', mutation.attributeName)
1357
+ * }
1358
+ * })
1359
+ * },
1360
+ * {
1361
+ * childList: true,
1362
+ * attributes: true,
1363
+ * subtree: true
1364
+ * }
1365
+ * )
1366
+ *
1367
+ * // Pause observation
1368
+ * pause()
1369
+ *
1370
+ * // Resume observation
1371
+ * resume()
1372
+ * ```
1373
+ */
1374
+ function useMutationObserver(target, callback, options = {}) {
1375
+ const { isHydrated } = useHydration();
1376
+ const observer = shallowRef();
1377
+ const isPaused = shallowRef(false);
1378
+ const isActive = computed(() => !!observer.value);
1379
+ const observerOptions = {
1380
+ childList: options.childList ?? true,
1381
+ attributes: options.attributes ?? false,
1382
+ characterData: options.characterData ?? false,
1383
+ subtree: options.subtree ?? false,
1384
+ attributeOldValue: options.attributeOldValue ?? false,
1385
+ characterDataOldValue: options.characterDataOldValue ?? false,
1386
+ attributeFilter: options.attributeFilter
1387
+ };
1388
+ function setup() {
1389
+ if (!isHydrated.value || !SUPPORTS_MUTATION_OBSERVER || !target.value || isPaused.value) return;
1390
+ observer.value = new MutationObserver((mutations) => {
1391
+ callback(mutations.map((mutation) => ({
1392
+ type: mutation.type,
1393
+ target: mutation.target,
1394
+ addedNodes: mutation.addedNodes,
1395
+ removedNodes: mutation.removedNodes,
1396
+ previousSibling: mutation.previousSibling,
1397
+ nextSibling: mutation.nextSibling,
1398
+ attributeName: mutation.attributeName,
1399
+ attributeNamespace: mutation.attributeNamespace,
1400
+ oldValue: mutation.oldValue
1401
+ })));
1402
+ });
1403
+ observer.value.observe(target.value, observerOptions);
1404
+ if (options.immediate) {
1405
+ const emptyNodeList = {
1406
+ length: 0,
1407
+ item: () => null,
1408
+ forEach: () => {},
1409
+ *[Symbol.iterator]() {}
1410
+ };
1411
+ callback([{
1412
+ type: "childList",
1413
+ target: target.value,
1414
+ addedNodes: emptyNodeList,
1415
+ removedNodes: emptyNodeList,
1416
+ previousSibling: null,
1417
+ nextSibling: null,
1418
+ attributeName: null,
1419
+ attributeNamespace: null,
1420
+ oldValue: null
1421
+ }]);
1422
+ }
1423
+ }
1424
+ watch([isHydrated, target], () => {
1425
+ cleanup();
1426
+ setup();
1427
+ }, { immediate: true });
1428
+ function cleanup() {
1429
+ if (observer.value) {
1430
+ observer.value.disconnect();
1431
+ observer.value = void 0;
1432
+ }
1433
+ }
1434
+ function pause() {
1435
+ isPaused.value = true;
1436
+ observer.value?.disconnect();
1437
+ }
1438
+ function resume() {
1439
+ isPaused.value = false;
1440
+ setup();
1441
+ }
1442
+ function stop() {
1443
+ cleanup();
1444
+ }
1445
+ onScopeDispose(stop, true);
1446
+ return {
1447
+ isActive: shallowReadonly(isActive),
1448
+ isPaused: shallowReadonly(isPaused),
1449
+ pause,
1450
+ resume,
1451
+ stop
1452
+ };
1453
+ }
1454
+
1455
+ //#endregion
1456
+ //#region src/composables/usePermissions/adapters/adapter.ts
1457
+ var PermissionAdapter = class {};
1458
+
1459
+ //#endregion
1460
+ //#region src/composables/usePermissions/adapters/v0.ts
1461
+ var Vuetify0PermissionAdapter = class extends PermissionAdapter {
1462
+ constructor() {
1463
+ super();
1464
+ }
1465
+ can(role, action, subject, context, permissions) {
1466
+ const access = `${role}.${action}.${subject}`;
1467
+ const ticket = permissions.get(access);
1468
+ if (!ticket || !ticket.value) return false;
1469
+ return /* @__PURE__ */ isFunction(ticket.value) ? ticket.value(context) : ticket.value;
1470
+ }
1471
+ };
1472
+
1473
+ //#endregion
1474
+ //#region src/composables/usePermissions/index.ts
1475
+ /**
1476
+ * @module usePermissions
1477
+ *
1478
+ * @see https://0.vuetifyjs.com/composables/plugins/use-permissions
1479
+ *
1480
+ * @remarks
1481
+ * Permission management composable with support for RBAC and ABAC patterns.
1482
+ *
1483
+ * Key features:
1484
+ * - Role-Based Access Control (RBAC) support
1485
+ * - Attribute-Based Access Control (ABAC) with context
1486
+ * - Functional permission conditions
1487
+ * - Token-based permission storage
1488
+ * - Adapter pattern for custom permission systems
1489
+ *
1490
+ * Built on useTokens for flexible permission configuration.
1491
+ */
1492
+ /**
1493
+ * Creates a new permissions instance.
1494
+ *
1495
+ * @param options The options for the permissions instance.
1496
+ * @template Z The type of the permission ticket.
1497
+ * @template E The type of the permission context.
1498
+ * @returns A new permissions instance.
1499
+ *
1500
+ * @see https://0.vuetifyjs.com/composables/plugins/use-permissions
1501
+ *
1502
+ * @example
1503
+ * ```ts
1504
+ * import { createPermissions } from '@vuetify/v0'
1505
+ *
1506
+ * const [usePermissions, providePermissions] = createPermissions({
1507
+ * namespace: 'v0:permissions',
1508
+ * permissions: {
1509
+ * admin: [['read', 'users']],
1510
+ * editor: [['edit', 'posts']],
1511
+ * },
1512
+ * })
1513
+ * ```
1514
+ */
1515
+ function createPermissions(_options = {}) {
1516
+ const { adapter = new Vuetify0PermissionAdapter(), permissions = {}, ...options } = _options;
1517
+ const record = {};
1518
+ for (const role in permissions) {
1519
+ if (!record[role]) record[role] = {};
1520
+ for (const [actions, subjects, condition = true] of permissions[role]) for (const action of toArray(actions)) for (const subject of toArray(subjects)) {
1521
+ if (!record[role][action]) record[role][action] = {};
1522
+ record[role][action][subject] = condition;
1523
+ }
1524
+ }
1525
+ const tokens = createTokens(record, options);
1526
+ function can(id, action, subject, context = {}) {
1527
+ return adapter.can(id, action, subject, context, tokens);
1528
+ }
1529
+ return {
1530
+ ...tokens,
1531
+ can
1532
+ };
1533
+ }
1534
+ /**
1535
+ * Creates a new permissions context.
1536
+ *
1537
+ * @param options The options for the permissions context.
1538
+ * @template Z The type of the permission ticket.
1539
+ * @template E The type of the permission context.
1540
+ * @returns A new permissions context.
1541
+ *
1542
+ * @see https://0.vuetifyjs.com/composables/plugins/use-permissions
1543
+ *
1544
+ * @example
1545
+ * ```ts
1546
+ * import { createPermissionsContext } from '@vuetify/v0'
1547
+ *
1548
+ * export const [usePermissions, providePermissions, context] = createPermissionsContext({
1549
+ * namespace: 'app:permissions',
1550
+ * permissions: {
1551
+ * admin: [['read', 'users'], ['edit', 'users']],
1552
+ * editor: [['edit', 'posts']],
1553
+ * },
1554
+ * })
1555
+ * ```
1556
+ */
1557
+ function createPermissionsContext(_options = {}) {
1558
+ const { namespace = "v0:permissions", ...options } = _options;
1559
+ const [usePermissionsContext, _providePermissionsContext] = createContext(namespace);
1560
+ const context = createPermissions(options);
1561
+ function providePermissionsContext(_context = context, app) {
1562
+ return _providePermissionsContext(_context, app);
1563
+ }
1564
+ return createTrinity(usePermissionsContext, providePermissionsContext, context);
1565
+ }
1566
+ /**
1567
+ * Creates a new permissions plugin.
1568
+ *
1569
+ * @param options The options for the permissions plugin.
1570
+ * @template Z The type of the permission ticket.
1571
+ * @template E The type of the permission context.
1572
+ * @returns A new permissions plugin.
1573
+ *
1574
+ * @see https://0.vuetifyjs.com/composables/plugins/use-permissions
1575
+ *
1576
+ * @example
1577
+ * ```ts
1578
+ * import { createApp } from 'vue'
1579
+ * import { createPermissionsPlugin } from '@vuetify/v0'
1580
+ * import App from './App.vue'
1581
+ *
1582
+ * const app = createApp(App)
1583
+ *
1584
+ * app.use(
1585
+ * createPermissionsPlugin({
1586
+ * permissions: {
1587
+ * admin: [['read', 'users']],
1588
+ * editor: [['edit', 'posts']],
1589
+ * },
1590
+ * })
1591
+ * )
1592
+ *
1593
+ * app.mount('#app')
1594
+ * ```
1595
+ */
1596
+ function createPermissionsPlugin(_options = {}) {
1597
+ const { namespace = "v0:permissions", ...options } = _options;
1598
+ const [, providePermissionContext, context] = createPermissionsContext({
1599
+ ...options,
1600
+ namespace
1601
+ });
1602
+ return createPlugin({
1603
+ namespace,
1604
+ provide: (app) => {
1605
+ providePermissionContext(context, app);
1606
+ }
1607
+ });
1608
+ }
1609
+ /**
1610
+ * Returns the current permissions instance.
1611
+ *
1612
+ * @template Z The type of the permission ticket.
1613
+ * @returns The current permissions instance.
1614
+ *
1615
+ * @see https://0.vuetifyjs.com/composables/plugins/use-permissions
1616
+ *
1617
+ * @example
1618
+ * ```vue
1619
+ * <script setup lang="ts">
1620
+ * import { usePermissions } from '@vuetify/v0'
1621
+ *
1622
+ * const { can } = usePermissions()
1623
+ * <\/script>
1624
+ *
1625
+ * <template>
1626
+ * <div>
1627
+ * <p v-if="can('admin', 'read', 'users')">Admin access</p>
1628
+ * </div>
1629
+ * </template>
1630
+ * ```
1631
+ */
1632
+ function usePermissions(namespace = "v0:permissions") {
1633
+ return useContext(namespace);
1634
+ }
1635
+
1636
+ //#endregion
1637
+ //#region src/composables/useQueue/index.ts
1638
+ /**
1639
+ * @module useQueue
1640
+ *
1641
+ * @remarks
1642
+ * A queue composable for managing time-based collections with:
1643
+ * - Automatic timeout-based removal
1644
+ * - Pause/resume functionality
1645
+ * - FIFO (First In, First Out) ordering
1646
+ * - Manual dismissal support
1647
+ * - Queue progression management
1648
+ *
1649
+ * Built on top of useRegistry, the queue automatically manages timeouts for tickets,
1650
+ * ensuring only the first ticket in the queue is active at any time. When an ticket
1651
+ * expires or is removed, the next ticket in the queue automatically becomes active.
1652
+ */
1653
+ /**
1654
+ * Creates a new queue instance
1655
+ *
1656
+ * @param options The options for the queue instance
1657
+ * @template Z The type of queue ticket that extends QueueTicket. Use this to add custom properties to tickets.
1658
+ * @template E The type of queue context that extends QueueContext<Z>. Use this when extending the queue with additional methods.
1659
+ * @returns A new queue instance
1660
+ *
1661
+ * @see https://0.vuetifyjs.com/composables/registration/use-queue
1662
+ *
1663
+ * @example
1664
+ * ```ts
1665
+ * import { useQueue } from '@vuetify/v0'
1666
+ *
1667
+ * const queue = useQueue()
1668
+ *
1669
+ * // Register an ticket with default timeout (3000ms)
1670
+ * const ticket1 = queue.register({ value: 'Ticket 1' })
1671
+ *
1672
+ * // Register an ticket with custom timeout
1673
+ * const ticket2 = queue.register({ value: 'Ticket 2', timeout: 5000 })
1674
+ *
1675
+ * // Register a persistent ticket that must be manually dismissed
1676
+ * const ticket3 = queue.register({ value: 'Ticket 3', timeout: -1 })
1677
+ *
1678
+ * // Dismiss an ticket using the convenience method
1679
+ * ticket3.dismiss()
1680
+ *
1681
+ * console.log(queue.size) // 2
1682
+ * ```
1683
+ */
1684
+ function createQueue(_options = {}) {
1685
+ const { timeout: _timeout = 3e3, ...options } = _options;
1686
+ const registry = useRegistry({
1687
+ ...options,
1688
+ events: true
1689
+ });
1690
+ const timeouts = /* @__PURE__ */ new Map();
1691
+ function startTimeout(ticket) {
1692
+ if (/* @__PURE__ */ isUndefined(ticket.timeout) || ticket.timeout < 0 || ticket.isPaused) return;
1693
+ const timeout = setTimeout(() => {
1694
+ timeouts.delete(ticket.id);
1695
+ registry.unregister(ticket.id);
1696
+ resume();
1697
+ }, ticket.timeout);
1698
+ timeouts.set(ticket.id, timeout);
1699
+ }
1700
+ function clearTimeout(id) {
1701
+ const timeout = timeouts.get(id);
1702
+ if (timeout) {
1703
+ globalThis.clearTimeout(timeout);
1704
+ timeouts.delete(id);
1705
+ }
1706
+ }
1707
+ function register(registration = {}) {
1708
+ const id = registration.id ?? /* @__PURE__ */ genId();
1709
+ const timeout = Object.prototype.hasOwnProperty.call(registration, "timeout") ? registration.timeout : _timeout;
1710
+ const ticket = {
1711
+ ...registration,
1712
+ id,
1713
+ timeout,
1714
+ isPaused: registry.size > 0,
1715
+ dismiss: () => unregister(id)
1716
+ };
1717
+ const registered = registry.register(ticket);
1718
+ startTimeout(registered);
1719
+ return registered;
1720
+ }
1721
+ function unregister(id) {
1722
+ const ticket = /* @__PURE__ */ isUndefined(id) ? registry.seek("first") : registry.get(id);
1723
+ if (!ticket) return void 0;
1724
+ const wasFirst = ticket.index === 0;
1725
+ clearTimeout(ticket.id);
1726
+ registry.unregister(ticket.id);
1727
+ if (wasFirst) resume();
1728
+ return ticket;
1729
+ }
1730
+ function offboard(ids) {
1731
+ let hadFirst = false;
1732
+ for (const id of ids) {
1733
+ const ticket = registry.get(id);
1734
+ if (!ticket) continue;
1735
+ if (ticket.index === 0) hadFirst = true;
1736
+ clearTimeout(ticket.id);
1737
+ }
1738
+ registry.offboard(ids);
1739
+ if (hadFirst) resume();
1740
+ }
1741
+ function pause() {
1742
+ const ticket = registry.seek("first");
1743
+ if (!ticket || ticket.isPaused) return void 0;
1744
+ clearTimeout(ticket.id);
1745
+ return registry.upsert(ticket.id, { isPaused: true });
1746
+ }
1747
+ function resume() {
1748
+ const ticket = registry.seek("first");
1749
+ if (!ticket || ticket.index !== 0 || !ticket.isPaused) return void 0;
1750
+ const updated = registry.upsert(ticket.id, { isPaused: false });
1751
+ startTimeout(updated);
1752
+ return updated;
1753
+ }
1754
+ function clear() {
1755
+ for (const id of timeouts.keys()) clearTimeout(id);
1756
+ registry.clear();
1757
+ }
1758
+ function dispose() {
1759
+ clear();
1760
+ registry.dispose();
1761
+ }
1762
+ onScopeDispose(dispose, true);
1763
+ return {
1764
+ ...registry,
1765
+ register,
1766
+ unregister,
1767
+ offboard,
1768
+ pause,
1769
+ resume,
1770
+ clear,
1771
+ dispose,
1772
+ get size() {
1773
+ return registry.size;
1774
+ }
1775
+ };
1776
+ }
1777
+ /**
1778
+ * Creates a new queue context.
1779
+ *
1780
+ * @param options The options for the queue context.
1781
+ * @template Z The type of the queue ticket.
1782
+ * @template E The type of the queue context.
1783
+ * @returns A new queue context.
1784
+ *
1785
+ * @see https://0.vuetifyjs.com/composables/registration/use-queue
1786
+ *
1787
+ * @example
1788
+ * ```ts
1789
+ * import { createQueueContext } from '@vuetify/v0'
1790
+ *
1791
+ * export const [useQueue, provideQueue, context] = createQueueContext({
1792
+ * timeout: 5000,
1793
+ * })
1794
+ * ```
1795
+ */
1796
+ function createQueueContext(_options = {}) {
1797
+ const { namespace = "v0:queue", ...options } = _options;
1798
+ const [useQueueContext, _provideQueueContext] = createContext(namespace);
1799
+ const context = createQueue(options);
1800
+ function provideQueueContext(_context = context, app) {
1801
+ return _provideQueueContext(_context, app);
1802
+ }
1803
+ return createTrinity(useQueueContext, provideQueueContext, context);
1804
+ }
1805
+ /**
1806
+ * Returns the current queue instance.
1807
+ *
1808
+ * @param namespace The namespace for the queue context. Defaults to `'v0:queue'`.
1809
+ * @returns The current queue instance.
1810
+ *
1811
+ * @see https://0.vuetifyjs.com/composables/registration/use-queue
1812
+ *
1813
+ * @example
1814
+ * ```vue
1815
+ * <script setup lang="ts">
1816
+ * import { useQueue } from '@vuetify/v0'
1817
+ *
1818
+ * const queue = useQueue()
1819
+ * <\/script>
1820
+ * ```
1821
+ */
1822
+ function useQueue(namespace = "v0:queue") {
1823
+ return useContext(namespace);
1824
+ }
1825
+
1826
+ //#endregion
1827
+ //#region src/composables/useStorage/adapters/memory.ts
1828
+ /**
1829
+ * In-memory storage adapter that implements the StorageAdapter interface.
1830
+ * This adapter provides temporary storage that persists only for the current
1831
+ * session and is useful for testing or when persistent storage is not available.
1832
+ */
1833
+ var MemoryAdapter = class {
1834
+ store = /* @__PURE__ */ new Map();
1835
+ get length() {
1836
+ return this.store.size;
1837
+ }
1838
+ getItem(key) {
1839
+ return this.store.get(key) ?? null;
1840
+ }
1841
+ setItem(key, value) {
1842
+ this.store.set(key, value);
1843
+ }
1844
+ removeItem(key) {
1845
+ this.store.delete(key);
1846
+ }
1847
+ key(index) {
1848
+ return String(Array.from(this.store.keys())[index] ?? "");
1849
+ }
1850
+ };
1851
+
1852
+ //#endregion
1853
+ //#region src/composables/useStorage/index.ts
1854
+ /**
1855
+ * @module useStorage
1856
+ *
1857
+ * @remarks
1858
+ * Reactive storage composable with adapter pattern for localStorage, sessionStorage, or memory.
1859
+ *
1860
+ * Key features:
1861
+ * - Reactive refs that sync with storage
1862
+ * - localStorage, sessionStorage, and memory adapters
1863
+ * - Custom serialization support
1864
+ * - SSR fallback to memory adapter
1865
+ * - Automatic cleanup on remove/clear
1866
+ *
1867
+ * Uses adapter pattern to abstract storage implementation details.
1868
+ */
1869
+ /**
1870
+ * Creates a new storage instance.
1871
+ *
1872
+ * @param options The options for the storage instance.
1873
+ * @template E The type of the storage context.
1874
+ * @returns A new storage instance.
1875
+ *
1876
+ * @see https://0.vuetifyjs.com/composables/plugins/use-storage
1877
+ *
1878
+ * @example
1879
+ * ```ts
1880
+ * import { createStorage } from '@vuetify/v0'
1881
+ *
1882
+ * const storage = createStorage()
1883
+ *
1884
+ * storage.set('username', 'MyUsername')
1885
+ *
1886
+ * const username = storage.get('username')
1887
+ *
1888
+ * console.log(username.value) // MyUsername
1889
+ *
1890
+ * storage.clear()
1891
+ * ```
1892
+ */
1893
+ function createStorage(options = {}) {
1894
+ const { adapter = IN_BROWSER ? window.localStorage : new MemoryAdapter(), prefix = "v0:", serializer = {
1895
+ read: JSON.parse,
1896
+ write: JSON.stringify
1897
+ } } = options;
1898
+ const cache = /* @__PURE__ */ new Map();
1899
+ const watchers = /* @__PURE__ */ new Map();
1900
+ function has(key) {
1901
+ const prefixedKey = `${prefix}${key}`;
1902
+ return cache.has(prefixedKey);
1903
+ }
1904
+ function get(key, defaultValue) {
1905
+ const prefixedKey = `${prefix}${key}`;
1906
+ if (cache.has(prefixedKey)) return cache.get(prefixedKey);
1907
+ const storedValue = adapter?.getItem(prefixedKey);
1908
+ let initialValue = defaultValue;
1909
+ if (storedValue) try {
1910
+ initialValue = serializer.read(storedValue);
1911
+ } catch (error) {
1912
+ console.error(`[v0:storage] Failed to parse stored value for key "${prefixedKey}":`, error);
1913
+ }
1914
+ const valueRef = ref(initialValue);
1915
+ const stop = watch(valueRef, (newValue) => {
1916
+ if (/* @__PURE__ */ isNullOrUndefined(newValue)) adapter?.removeItem(prefixedKey);
1917
+ else adapter?.setItem(prefixedKey, serializer.write(newValue));
1918
+ }, { deep: true });
1919
+ watchers.set(prefixedKey, stop);
1920
+ cache.set(prefixedKey, valueRef);
1921
+ return valueRef;
1922
+ }
1923
+ function set(key, value) {
1924
+ const valueRef = get(key);
1925
+ valueRef.value = value;
1926
+ }
1927
+ function remove(key) {
1928
+ const prefixedKey = `${prefix}${key}`;
1929
+ const stop = watchers.get(prefixedKey);
1930
+ if (!stop) return;
1931
+ stop();
1932
+ watchers.delete(prefixedKey);
1933
+ adapter?.removeItem(prefixedKey);
1934
+ cache.delete(prefixedKey);
1935
+ }
1936
+ function clear() {
1937
+ if (watchers.size > 0) {
1938
+ for (const stop of watchers.values()) stop();
1939
+ watchers.clear();
1940
+ }
1941
+ if (cache.size > 0) {
1942
+ for (const key of cache.keys()) adapter?.removeItem(key);
1943
+ cache.clear();
1944
+ }
1945
+ }
1946
+ return {
1947
+ has,
1948
+ get,
1949
+ set,
1950
+ remove,
1951
+ clear
1952
+ };
1953
+ }
1954
+ function createStorageContext(_options = {}) {
1955
+ const { namespace = "v0:storage", ...options } = _options;
1956
+ const [useStorageContext, _provideStorageContext] = createContext(namespace);
1957
+ const context = createStorage(options);
1958
+ function provideStorageContext(_context = context, app) {
1959
+ return _provideStorageContext(_context, app);
1960
+ }
1961
+ return createTrinity(useStorageContext, provideStorageContext, context);
1962
+ }
1963
+ /**
1964
+ * Creates a new storage plugin.
1965
+ *
1966
+ * @param options The options for the storage plugin.
1967
+ * @returns A new storage plugin.
1968
+ *
1969
+ * @see https://0.vuetifyjs.com/composables/plugins/use-storage
1970
+ *
1971
+ * @example
1972
+ * ```ts
1973
+ * import { createApp } from 'vue'
1974
+ * import { createStoragePlugin } from '@vuetify/v0'
1975
+ * import App from './App.vue'
1976
+ *
1977
+ * const app = createApp(App)
1978
+ *
1979
+ * app.use(createStoragePlugin())
1980
+ *
1981
+ * app.mount('#app')
1982
+ * ```
1983
+ */
1984
+ function createStoragePlugin(_options = {}) {
1985
+ const { namespace = "v0:storage", ...options } = _options;
1986
+ const [, provideStorageContext, context] = createStorageContext({
1987
+ ...options,
1988
+ namespace
1989
+ });
1990
+ return createPlugin({
1991
+ namespace,
1992
+ provide: (app) => {
1993
+ provideStorageContext(context, app);
1994
+ }
1995
+ });
1996
+ }
1997
+ /**
1998
+ * Returns the current storage instance.
1999
+ *
2000
+ * @param namespace The namespace for the storage context. Defaults to `'v0:storage'`.
2001
+ * @returns The current storage instance.
2002
+ *
2003
+ * @see https://0.vuetifyjs.com/composables/plugins/use-storage
2004
+ *
2005
+ * @example
2006
+ * ```vue
2007
+ * <script setup lang="ts">
2008
+ * import { useStorage } from '@vuetify/v0'
2009
+ *
2010
+ * const storage = useStorage()
2011
+ * const username = storage.get('username', 'Guest')
2012
+ * <\/script>
2013
+ *
2014
+ * <template>
2015
+ * <div>
2016
+ * <p>Username: {{ username }}</p>
2017
+ * </div>
2018
+ * </template>
2019
+ * ```
2020
+ */
2021
+ function useStorage(namespace = "v0:storage") {
2022
+ return useContext(namespace);
2023
+ }
2024
+
2025
+ //#endregion
2026
+ //#region src/composables/useTheme/adapters/adapter.ts
2027
+ var ThemeAdapter = class {
2028
+ stylesheetId = "v0-theme-stylesheet";
2029
+ prefix;
2030
+ constructor(prefix) {
2031
+ this.prefix = prefix;
2032
+ }
2033
+ generate(colors, isDark) {
2034
+ let css = "";
2035
+ for (const theme in colors) {
2036
+ const themeColors = colors[theme];
2037
+ if (!themeColors) continue;
2038
+ const vars = Object.entries(themeColors).map(([key, val]) => ` --${this.prefix}-${key}: ${val};`).join("\n");
2039
+ css += `[data-theme="${theme}"] {\n${vars}\n}\n`;
2040
+ }
2041
+ if (!/* @__PURE__ */ isUndefined(isDark)) css += `:root {\n color-scheme: ${isDark ? "dark" : "light"};\n}\n`;
2042
+ return css;
2043
+ }
2044
+ };
2045
+
2046
+ //#endregion
2047
+ //#region src/composables/useTheme/adapters/v0.ts
2048
+ /**
2049
+ * Theme adapter implementation for Vuetify v0 design system.
2050
+ * This adapter generates CSS custom properties and injects them into the DOM
2051
+ * as a stylesheet, allowing themes to be applied globally.
2052
+ */
2053
+ var Vuetify0ThemeAdapter = class extends ThemeAdapter {
2054
+ cspNonce;
2055
+ constructor(options = {}) {
2056
+ super(options.prefix ?? "v0");
2057
+ this.cspNonce = options.cspNonce;
2058
+ this.stylesheetId = options.stylesheetId ?? this.stylesheetId;
2059
+ }
2060
+ setup(app, context, target) {
2061
+ if (IN_BROWSER) {
2062
+ onScopeDispose(watch([context.colors, context.isDark], ([colors, isDark]) => {
2063
+ this.update(colors, isDark);
2064
+ }, { immediate: true }), true);
2065
+ if (/* @__PURE__ */ isNull(target)) return;
2066
+ const targetEl = target instanceof HTMLElement ? target : /* @__PURE__ */ isString(target) ? document.querySelector(target) : app._container || document.querySelector("#app") || document.body;
2067
+ if (!targetEl) return;
2068
+ onScopeDispose(watch(context.selectedId, (id) => {
2069
+ if (!id) return;
2070
+ targetEl.dataset.theme = String(id);
2071
+ }, { immediate: true }), true);
2072
+ } else {
2073
+ const head = app._context?.provides?.usehead ?? app._context?.provides?.head;
2074
+ if (head?.push) {
2075
+ const id = context.selectedId.value;
2076
+ head.push({
2077
+ htmlAttrs: { "data-theme": id ? String(id) : "" },
2078
+ style: [{
2079
+ innerHTML: this.generate(context.colors.value, context.isDark.value),
2080
+ id: this.stylesheetId
2081
+ }]
2082
+ });
2083
+ }
2084
+ }
2085
+ }
2086
+ update(colors, isDark) {
2087
+ if (!IN_BROWSER) return;
2088
+ this.upsert(this.generate(colors, isDark));
2089
+ }
2090
+ upsert(styles) {
2091
+ if (!IN_BROWSER) return;
2092
+ let styleEl = document.querySelector(`#${this.stylesheetId}`);
2093
+ if (!styleEl) {
2094
+ styleEl = document.createElement("style");
2095
+ styleEl.id = this.stylesheetId.startsWith("#") ? this.stylesheetId.slice(1) : this.stylesheetId;
2096
+ if (this.cspNonce) styleEl.setAttribute("nonce", this.cspNonce);
2097
+ document.head.append(styleEl);
2098
+ }
2099
+ styleEl.textContent = styles;
2100
+ }
2101
+ };
2102
+
2103
+ //#endregion
2104
+ //#region src/composables/useTheme/index.ts
2105
+ /**
2106
+ * @module useTheme
2107
+ *
2108
+ * @see https://0.vuetifyjs.com/composables/plugins/use-theme
2109
+ *
2110
+ * @remarks
2111
+ * Theme management composable with token resolution and CSS variable injection.
2112
+ *
2113
+ * Key features:
2114
+ * - Single-selection theme switching (extends createSingle)
2115
+ * - Token alias resolution via useTokens
2116
+ * - Lazy theme loading (compute colors only when selected)
2117
+ * - CSS variable generation via adapter pattern
2118
+ * - SSR support with head integration
2119
+ * - Theme cycling
2120
+ *
2121
+ * Integrates with createSingle for selection and useTokens for color resolution.
2122
+ */
2123
+ /**
2124
+ * Creates a new theme instance.
2125
+ *
2126
+ * @param options The options for the theme instance.
2127
+ * @template Z The type of the theme ticket.
2128
+ * @template E The type of the theme context.
2129
+ * @returns A new theme instance.
2130
+ *
2131
+ * @see https://0.vuetifyjs.com/composables/plugins/use-theme
2132
+ *
2133
+ * @example
2134
+ * ```ts
2135
+ * import { createTheme } from '@vuetify/v0'
2136
+ *
2137
+ * export const [useTheme, provideTheme] = createTheme({
2138
+ * namespace: 'v0:theme',
2139
+ * default: 'light',
2140
+ * themes: {
2141
+ * light: {
2142
+ * dark: false,
2143
+ * colors: {
2144
+ * primary: '#3b82f6',
2145
+ * },
2146
+ * },
2147
+ * dark: {
2148
+ * dark: true,
2149
+ * colors: {
2150
+ * primary: '#675496',
2151
+ * },
2152
+ * },
2153
+ * },
2154
+ * })
2155
+ * ```
2156
+ */
2157
+ function createTheme(_options = {}) {
2158
+ const { themes = {}, palette = {}, ...options } = _options;
2159
+ const tokens = createTokens({
2160
+ palette,
2161
+ ...themes
2162
+ }, { flat: true });
2163
+ const registry = createSingle(options);
2164
+ for (const id in themes) {
2165
+ const { colors: value, ...theme } = themes[id];
2166
+ register({
2167
+ id,
2168
+ value,
2169
+ ...theme
2170
+ });
2171
+ if (id === options.default && !registry.selectedId.value) registry.select(id);
2172
+ }
2173
+ const names = computed(() => registry.keys());
2174
+ const colors = computed(() => {
2175
+ const resolved = {};
2176
+ for (const theme of registry.values()) {
2177
+ if (theme.lazy && theme.id !== registry.selectedId.value) continue;
2178
+ resolved[theme.id] = resolve(theme.value);
2179
+ }
2180
+ return resolved;
2181
+ });
2182
+ const isDark = toRef(() => registry.selectedItem.value?.dark ?? false);
2183
+ function cycle(themes$1 = names.value) {
2184
+ const current = themes$1.indexOf(registry.selectedId.value ?? "");
2185
+ const next = current === -1 ? 0 : (current + 1) % themes$1.length;
2186
+ registry.select(themes$1[next]);
2187
+ }
2188
+ function resolve(colors$1) {
2189
+ const resolved = {};
2190
+ for (const [key, value] of Object.entries(colors$1)) resolved[key] = tokens.isAlias(value) ? tokens.resolve(value) : value;
2191
+ return resolved;
2192
+ }
2193
+ function register(registration = {}) {
2194
+ const item = {
2195
+ lazy: false,
2196
+ dark: false,
2197
+ ...registration
2198
+ };
2199
+ return registry.register(item);
2200
+ }
2201
+ return {
2202
+ ...registry,
2203
+ colors,
2204
+ isDark,
2205
+ register,
2206
+ cycle,
2207
+ get size() {
2208
+ return registry.size;
2209
+ }
2210
+ };
2211
+ }
2212
+ /**
2213
+ * Creates a new theme context trinity.
2214
+ *
2215
+ * @param options The options for the theme context.
2216
+ * @template Z The type of the theme ticket.
2217
+ * @template E The type of the theme context.
2218
+ * @returns A new theme context trinity.
2219
+ *
2220
+ * @see https://0.vuetifyjs.com/composables/plugins/use-theme
2221
+ *
2222
+ * @example
2223
+ * ```ts
2224
+ * import { createThemeContext } from '@vuetify/v0'
2225
+ *
2226
+ * export const [useThemeContext, provideThemeContext, context] = createThemeContext({
2227
+ * namespace: 'v0:theme',
2228
+ * default: 'light',
2229
+ * themes: {
2230
+ * light: {
2231
+ * dark: false,
2232
+ * colors: {
2233
+ * primary: '#3b82f6',
2234
+ * },
2235
+ * },
2236
+ * dark: {
2237
+ * dark: true,
2238
+ * colors: {
2239
+ * primary: '#675496',
2240
+ * },
2241
+ * },
2242
+ * },
2243
+ * })
2244
+ * ```
2245
+ */
2246
+ function createThemeContext(_options = {}) {
2247
+ const { namespace = "v0:theme", ...options } = _options;
2248
+ const [useThemeContext, _provideThemeContext] = createContext(namespace);
2249
+ const context = createTheme(options);
2250
+ function provideThemeContext(_context = context, app) {
2251
+ return _provideThemeContext(_context, app);
2252
+ }
2253
+ return createTrinity(useThemeContext, provideThemeContext, context);
2254
+ }
2255
+ /**
2256
+ * Creates a new theme plugin.
2257
+ *
2258
+ * @param options The options for the theme plugin.
2259
+ * @template Z The type of the theme ticket.
2260
+ * @template E The type of the theme context.
2261
+ * @returns A new theme plugin.
2262
+ *
2263
+ * @see https://0.vuetifyjs.com/composables/plugins/use-theme
2264
+ *
2265
+ * @example
2266
+ * ```ts
2267
+ * import { createApp } from 'vue'
2268
+ * import { createThemePlugin } from '@vuetify/v0'
2269
+ * import App from './App.vue'
2270
+ *
2271
+ * const app = createApp(App)
2272
+ *
2273
+ * app.use(
2274
+ * createThemePlugin({
2275
+ * default: 'light',
2276
+ * themes: {
2277
+ * light: {
2278
+ * dark: false,
2279
+ * colors: {
2280
+ * primary: '#3b82f6',
2281
+ * },
2282
+ * },
2283
+ * dark: {
2284
+ * dark: true,
2285
+ * colors: {
2286
+ * primary: '#675496',
2287
+ * },
2288
+ * },
2289
+ * },
2290
+ * })
2291
+ * )
2292
+ *
2293
+ * app.mount('#app')
2294
+ * ```
2295
+ */
2296
+ function createThemePlugin(_options = {}) {
2297
+ const { adapter = new Vuetify0ThemeAdapter(), namespace = "v0:theme", palette = {}, themes = {}, target, ...options } = _options;
2298
+ const [, provideThemeContext, context] = createThemeContext({
2299
+ ...options,
2300
+ namespace,
2301
+ themes,
2302
+ palette
2303
+ });
2304
+ return createPlugin({
2305
+ namespace,
2306
+ provide: (app) => {
2307
+ provideThemeContext(context, app);
2308
+ },
2309
+ setup: (app) => {
2310
+ adapter.setup(app, context, target);
2311
+ }
2312
+ });
2313
+ }
2314
+ /**
2315
+ * Returns the current theme instance.
2316
+ *
2317
+ * @param namespace The namespace for the theme context. Defaults to `v0:theme`.
2318
+ * @returns The current theme instance.
2319
+ *
2320
+ * @see https://0.vuetifyjs.com/composables/plugins/use-theme
2321
+ *
2322
+ * @example
2323
+ * ```vue
2324
+ * <script setup lang="ts">
2325
+ * import { useTheme } from '@vuetify/v0'
2326
+ *
2327
+ * const theme = useTheme()
2328
+ * <\/script>
2329
+ *
2330
+ * <template>
2331
+ * <div>
2332
+ * <p>Current theme: {{ theme.selected.value }}</p>
2333
+ * </div>
2334
+ * </template>
2335
+ * ```
2336
+ */
2337
+ function useTheme(namespace = "v0:theme") {
2338
+ return useContext(namespace);
2339
+ }
2340
+
2341
+ //#endregion
2342
+ //#region src/composables/useTimeline/index.ts
2343
+ /**
2344
+ * @module useTimeline
2345
+ *
2346
+ * @remarks
2347
+ * Bounded undo/redo system with overflow management.
2348
+ *
2349
+ * Key features:
2350
+ * - Fixed-size history (default: 10 items)
2351
+ * - Undo/redo stack management
2352
+ * - Overflow queue (preserves oldest items)
2353
+ * - Automatic reindexing after operations
2354
+ * - Perfect for command pattern, history tracking
2355
+ *
2356
+ * Extends useRegistry with temporal navigation capabilities.
2357
+ */
2358
+ /**
2359
+ * Creates a new timeline instance.
2360
+ *
2361
+ * @param _options The options for the timeline instance.
2362
+ * @template Z The type of the timeline ticket.
2363
+ * @template E The type of the timeline context.
2364
+ * @returns A new timeline instance.
2365
+ *
2366
+ * @see https://0.vuetifyjs.com/composables/registration/use-timeline
2367
+ *
2368
+ * @example
2369
+ * ```ts
2370
+ * import { useTimeline } from '@vuetify/v0'
2371
+ *
2372
+ * const timeline = useTimeline({ size: 3 })
2373
+ *
2374
+ * timeline.onboard([{ id: 'one' }, { id: 'two' }, { id: 'three' }])
2375
+ *
2376
+ * console.log(timeline.values()) // [{ id: 'one' }, { id: 'two' }, { id: 'three' }]
2377
+ *
2378
+ * timeline.undo()
2379
+ * console.log(timeline.values()) // [{ id: 'one' }, { id: 'two' }]
2380
+ *
2381
+ * timeline.redo()
2382
+ * console.log(timeline.values()) // [{ id: 'one' }, { id: 'two' }, { id: 'three' }]
2383
+ * ```
2384
+ */
2385
+ function createTimeline(_options = {}) {
2386
+ const { size = 10, ...options } = _options;
2387
+ const registry = useRegistry(options);
2388
+ const stack = [];
2389
+ const overflow = [];
2390
+ function register(item) {
2391
+ stack.length = 0;
2392
+ if (registry.size < size) return registry.register({ ...item });
2393
+ const removing = registry.seek("first");
2394
+ if (overflow.length === size) overflow.shift();
2395
+ overflow.push(removing);
2396
+ registry.unregister(removing.id);
2397
+ const ticket = registry.register({ ...item });
2398
+ registry.reindex();
2399
+ return ticket;
2400
+ }
2401
+ function undo() {
2402
+ const item = registry.seek("last");
2403
+ if (!item) return void 0;
2404
+ stack.push(item);
2405
+ registry.unregister(item.id);
2406
+ const restored = overflow.pop();
2407
+ if (restored) {
2408
+ const remaining = registry.values();
2409
+ registry.clear();
2410
+ registry.onboard([restored, ...remaining]);
2411
+ registry.reindex();
2412
+ }
2413
+ return item;
2414
+ }
2415
+ function redo() {
2416
+ if (stack.length === 0) return void 0;
2417
+ const item = stack.pop();
2418
+ const ticket = registry.register(item);
2419
+ registry.reindex();
2420
+ return ticket;
2421
+ }
2422
+ return {
2423
+ ...registry,
2424
+ register,
2425
+ undo,
2426
+ redo,
2427
+ get size() {
2428
+ return registry.size;
2429
+ }
2430
+ };
2431
+ }
2432
+ /**
2433
+ * Creates a new timeline context.
2434
+ *
2435
+ * @param options The options for the timeline context.
2436
+ * @template Z The type of the timeline ticket.
2437
+ * @template E The type of the timeline context.
2438
+ * @returns A new timeline context.
2439
+ *
2440
+ * @see https://0.vuetifyjs.com/composables/registration/use-timeline
2441
+ *
2442
+ * @example
2443
+ * ```ts
2444
+ * import { createTimelineContext } from '@vuetify/v0'
2445
+ *
2446
+ * // With default namespace 'v0:timeline'
2447
+ * export const [useTimeline, provideTimeline, context] = createTimelineContext({ size: 5 })
2448
+ *
2449
+ * context.register({ id: 'example' })
2450
+ *
2451
+ * // In a parent component
2452
+ * provideTimeline()
2453
+ *
2454
+ * // In a child component
2455
+ * const timeline = useTimeline()
2456
+ *
2457
+ * console.log(timeline.values()) // [{ id: 'example' }]
2458
+ * ```
2459
+ */
2460
+ function createTimelineContext(_options = {}) {
2461
+ const { namespace = "v0:timeline", ...options } = _options;
2462
+ const [useTimelineContext, _provideTimelineContext] = createContext(namespace);
2463
+ const context = createTimeline(options);
2464
+ function provideTimelineContext(_context = context, app) {
2465
+ return _provideTimelineContext(_context, app);
2466
+ }
2467
+ return createTrinity(useTimelineContext, provideTimelineContext, context);
2468
+ }
2469
+ /**
2470
+ * Returns the current timeline instance.
2471
+ *
2472
+ * @param namespace The namespace for the timeline context. Defaults to `'v0:timeline'`.
2473
+ * @returns The current timeline instance.
2474
+ *
2475
+ * @see https://0.vuetifyjs.com/composables/registration/use-timeline
2476
+ *
2477
+ * @example
2478
+ * ```vue
2479
+ * <script setup lang="ts">
2480
+ * import { useTimeline } from '@vuetify/v0'
2481
+ *
2482
+ * const timeline = useTimeline()
2483
+ * <\/script>
2484
+ * ```
2485
+ */
2486
+ function useTimeline(namespace = "v0:timeline") {
2487
+ return useContext(namespace);
2488
+ }
2489
+
2490
+ //#endregion
2491
+ //#region src/composables/useToggleScope/index.ts
2492
+ /**
2493
+ * @module useToggleScope
2494
+ *
2495
+ * @remarks
2496
+ * Conditionally manages an effect scope based on a reactive boolean condition.
2497
+ * When the source becomes true, creates and runs an effect scope. When false, stops the scope.
2498
+ * All reactive effects created within the scoped function are automatically cleaned up on deactivation.
2499
+ *
2500
+ * Key features:
2501
+ * - Uses Vue's effectScope for efficient reactive effect lifecycle management
2502
+ * - Automatic cleanup when condition becomes false
2503
+ * - Supports optional reset callback for scope restart capability
2504
+ * - Handles rapid toggling and parent scope disposal safely
2505
+ * - SSR-safe (effectScope is part of Vue core)
2506
+ *
2507
+ * Perfect for conditional side effects, feature flags, and performance optimization
2508
+ * by only running reactive effects when needed.
2509
+ */
2510
+ /**
2511
+ * Conditionally manages an effect scope based on a reactive boolean source.
2512
+ *
2513
+ * @param source A reactive boolean value or getter that controls the scope lifecycle
2514
+ * @param fn The function to run within the effect scope. Can optionally receive controls for manual scope management.
2515
+ * @returns Controls object with isActive state and start/stop/reset methods
2516
+ *
2517
+ * @see https://vuejs.org/api/reactivity-advanced.html#effectscope
2518
+ * @see https://0.vuetifyjs.com/composables/system/use-toggle-scope
2519
+ *
2520
+ * @example
2521
+ * ```ts
2522
+ * import { ref } from 'vue'
2523
+ * import { useToggleScope } from '@vuetify/v0'
2524
+ *
2525
+ * const isEnabled = ref(false)
2526
+ *
2527
+ * const { isActive } = useToggleScope(isEnabled, () => {
2528
+ * // This code runs when isEnabled becomes true
2529
+ * const unwatch = watch(someRef, () => {
2530
+ * console.log('Watching...')
2531
+ * })
2532
+ *
2533
+ * // Cleanup happens automatically when isEnabled becomes false
2534
+ * })
2535
+ *
2536
+ * // Toggle the scope on/off
2537
+ * isEnabled.value = true // Starts the scope
2538
+ * console.log(isActive.value) // true
2539
+ * isEnabled.value = false // Stops and cleans up
2540
+ * console.log(isActive.value) // false
2541
+ * ```
2542
+ *
2543
+ * @example
2544
+ * With manual controls:
2545
+ * ```ts
2546
+ * const isEnabled = ref(true)
2547
+ *
2548
+ * useToggleScope(isEnabled, (controls) => {
2549
+ * // Access controls inside the scope
2550
+ * console.log('Active:', controls.isActive.value)
2551
+ *
2552
+ * // Manually reset the scope if needed
2553
+ * someEvent.on('reset', () => controls.reset())
2554
+ * })
2555
+ * ```
2556
+ */
2557
+ function useToggleScope(source, fn) {
2558
+ const scope = shallowRef();
2559
+ const isActive = toRef(() => !!scope.value);
2560
+ function start() {
2561
+ if (scope.value) return;
2562
+ scope.value = effectScope();
2563
+ scope.value.run(() => fn.length > 0 ? fn(controls) : fn());
2564
+ }
2565
+ function stop() {
2566
+ scope.value?.stop();
2567
+ scope.value = void 0;
2568
+ }
2569
+ function reset() {
2570
+ stop();
2571
+ start();
2572
+ }
2573
+ const controls = {
2574
+ isActive: shallowReadonly(isActive),
2575
+ start,
2576
+ stop,
2577
+ reset
2578
+ };
2579
+ watch(source, (active) => {
2580
+ if (active && !scope.value) start();
2581
+ else if (!active) stop();
2582
+ }, { immediate: true });
2583
+ onScopeDispose(() => {
2584
+ stop();
2585
+ });
2586
+ return controls;
2587
+ }
2588
+
2589
+ //#endregion
2590
+ //#region src/composables/useVirtual/index.ts
2591
+ /**
2592
+ * @module useVirtual
2593
+ *
2594
+ * @remarks
2595
+ * Virtual scrolling composable for efficiently rendering large lists.
2596
+ *
2597
+ * Key features:
2598
+ * - Renders only visible items (viewport + overscan)
2599
+ * - Dynamic or fixed item heights
2600
+ * - SSR-safe (checks IN_BROWSER)
2601
+ * - Bidirectional scrolling (forward/reverse for chat apps)
2602
+ * - Scroll anchoring (maintains position across data changes)
2603
+ * - Edge detection for infinite scroll
2604
+ * - iOS momentum and elastic scrolling
2605
+ * - Configurable overscan (extra items rendered for smooth scrolling)
2606
+ *
2607
+ * Perfect for large data sets, chat apps, and infinite scroll implementations.
2608
+ */
2609
+ /**
2610
+ * Virtual scrolling composable for efficiently rendering large lists
2611
+ *
2612
+ * @param items Reactive array of items to virtualize
2613
+ * @param options Configuration options
2614
+ * @returns Virtual scrolling context
2615
+ *
2616
+ * @see https://0.vuetifyjs.com/composables/utilities/use-virtual
2617
+ *
2618
+ * @example
2619
+ * ```vue
2620
+ * <script setup lang="ts">
2621
+ * import { useVirtual } from '@vuetify/v0'
2622
+ *
2623
+ * const items = ref(Array.from({ length: 10000 }, (_, i) => ({ id: i, name: `Item ${i}` })))
2624
+ * <\/script>
2625
+ *
2626
+ * <template>
2627
+ * <div
2628
+ * ref="element"
2629
+ * style="height: 600px; overflow-y: auto;"
2630
+ * @scroll="scroll"
2631
+ * >
2632
+ * <div :style="{ height: `${offset}px` }" />
2633
+ *
2634
+ * <div v-for="item in items" :key="item.index">
2635
+ * {{ item.raw.name }}
2636
+ * </div>
2637
+ *
2638
+ * <div :style="{ height: `${size}px` }" />
2639
+ * </div>
2640
+ * </template>
2641
+ * ```
2642
+ */
2643
+ function useVirtual(items, _options = {}) {
2644
+ const { itemHeight: _itemHeight, height, overscan = 5, direction = "forward", anchor = "auto", anchorSmooth = true, onStartReached, onEndReached, startThreshold = 0, endThreshold = 0, momentum: momentumOption, elastic: elasticOption } = _options;
2645
+ const element = ref();
2646
+ const itemHeight = shallowRef(Number.parseFloat(String(_itemHeight || 0)));
2647
+ const heights = shallowRef([]);
2648
+ const offsets = shallowRef([]);
2649
+ const first = shallowRef(0);
2650
+ const last = shallowRef(0);
2651
+ const offset = shallowRef(0);
2652
+ const size = shallowRef(0);
2653
+ const viewportHeight = shallowRef(0);
2654
+ const state = shallowRef("ok");
2655
+ const isIOS = IN_BROWSER && /iPad|iPhone|iPod/.test(navigator.userAgent);
2656
+ const momentum = momentumOption ?? isIOS;
2657
+ const elastic = elasticOption ?? isIOS;
2658
+ let raf = -1;
2659
+ let rebuildRaf = -1;
2660
+ let edgeRaf = -1;
2661
+ const cachedViewport = Number.parseInt(String(height)) || 0;
2662
+ let anchorIndex = -1;
2663
+ let anchorOffset = 0;
2664
+ const computedItems = computed(() => items.value.slice(first.value, last.value).map((item, i) => ({
2665
+ raw: item,
2666
+ index: i + first.value
2667
+ })));
2668
+ watch(element, (el) => {
2669
+ if (!IN_BROWSER || !el?.style) return;
2670
+ if (momentum) el.style.webkitOverflowScrolling = "touch";
2671
+ if (!elastic) el.style.overscrollBehavior = "none";
2672
+ });
2673
+ useResizeObserver(element, (entries) => {
2674
+ if (!entries[0]) return;
2675
+ viewportHeight.value = entries[0].contentRect.height;
2676
+ update();
2677
+ });
2678
+ watch(items, (newItems) => {
2679
+ captureAnchor();
2680
+ const length = newItems.length;
2681
+ const newHeights = heights.value.length === length ? heights.value : Array.from({ length }, () => null);
2682
+ if (heights.value !== newHeights) heights.value = newHeights;
2683
+ rebuild();
2684
+ }, { immediate: true });
2685
+ watch(element, () => {
2686
+ if (!element.value) return;
2687
+ if (direction === "reverse" && items.value.length > 0) {
2688
+ const lastIndex = items.value.length - 1;
2689
+ const totalHeight = (offsets.value[lastIndex] || 0) + (heights.value[lastIndex] || itemHeight.value);
2690
+ element.value.scrollTop = totalHeight;
2691
+ }
2692
+ update();
2693
+ });
2694
+ function captureAnchor() {
2695
+ if (!element.value || anchor === "auto") return;
2696
+ if (anchor === "start") {
2697
+ anchorIndex = 0;
2698
+ anchorOffset = 0;
2699
+ } else if (anchor === "end") {
2700
+ anchorIndex = items.value.length - 1;
2701
+ anchorOffset = 0;
2702
+ } else if (/* @__PURE__ */ isFunction(anchor)) {
2703
+ const result = anchor(items.value);
2704
+ if (/* @__PURE__ */ isNumber(result)) {
2705
+ anchorIndex = result;
2706
+ anchorOffset = element.value.scrollTop - (offsets.value[result] || 0);
2707
+ }
2708
+ } else {
2709
+ anchorIndex = first.value;
2710
+ anchorOffset = element.value.scrollTop - (offsets.value[first.value] || 0);
2711
+ }
2712
+ }
2713
+ function restoreAnchor() {
2714
+ if (!element.value || anchorIndex < 0) return;
2715
+ const newScrollTop = (offsets.value[anchorIndex] || 0) + anchorOffset;
2716
+ if (anchorSmooth && direction === "forward") element.value.scrollTo({
2717
+ top: newScrollTop,
2718
+ behavior: "smooth"
2719
+ });
2720
+ else element.value.scrollTop = newScrollTop;
2721
+ anchorIndex = -1;
2722
+ anchorOffset = 0;
2723
+ }
2724
+ function rebuild() {
2725
+ const length = items.value.length;
2726
+ const newOffsets = offsets.value.length === length ? offsets.value : Array.from({ length });
2727
+ let offset$1 = 0;
2728
+ for (let i = 0; i < length; i++) {
2729
+ newOffsets[i] = offset$1;
2730
+ offset$1 += heights.value[i] || itemHeight.value;
2731
+ }
2732
+ if (offsets.value !== newOffsets) offsets.value = newOffsets;
2733
+ update();
2734
+ restoreAnchor();
2735
+ }
2736
+ function findIndex(scrollTop) {
2737
+ const arr = offsets.value;
2738
+ if (arr.length === 0) return 0;
2739
+ let low = 0;
2740
+ let high = arr.length - 1;
2741
+ while (low <= high) {
2742
+ const mid = low + high >> 1;
2743
+ if (arr[mid] <= scrollTop) low = mid + 1;
2744
+ else high = mid - 1;
2745
+ }
2746
+ return Math.max(0, high);
2747
+ }
2748
+ function update() {
2749
+ if (!element.value) return;
2750
+ const viewport = viewportHeight.value || cachedViewport;
2751
+ if (!viewport || !itemHeight.value) return;
2752
+ const scrollTop = element.value.scrollTop || 0;
2753
+ const length = items.value.length;
2754
+ const visibleStart = findIndex(scrollTop);
2755
+ const visibleEnd = findIndex(scrollTop + viewport) + 1;
2756
+ const start = /* @__PURE__ */ clamp(visibleStart - overscan, 0, length);
2757
+ const end = /* @__PURE__ */ clamp(visibleEnd + overscan, start, length);
2758
+ first.value = start;
2759
+ last.value = end;
2760
+ offset.value = offsets.value[start] || 0;
2761
+ const lastIndex = length - 1;
2762
+ const totalHeight = (offsets.value[lastIndex] || 0) + (heights.value[lastIndex] || itemHeight.value);
2763
+ size.value = totalHeight - (offsets.value[end] || totalHeight);
2764
+ }
2765
+ function checkEdges() {
2766
+ if (!element.value) return;
2767
+ const scrollTop = element.value.scrollTop;
2768
+ const scrollHeight = element.value.scrollHeight;
2769
+ const clientHeight = element.value.clientHeight;
2770
+ const distanceFromStart = scrollTop;
2771
+ const distanceFromEnd = scrollHeight - (scrollTop + clientHeight);
2772
+ if (IN_BROWSER) {
2773
+ cancelAnimationFrame(edgeRaf);
2774
+ edgeRaf = requestAnimationFrame(() => {
2775
+ if (onStartReached && distanceFromStart <= startThreshold) onStartReached(distanceFromStart);
2776
+ if (onEndReached && distanceFromEnd <= endThreshold) onEndReached(distanceFromEnd);
2777
+ });
2778
+ } else {
2779
+ if (onStartReached && distanceFromStart <= startThreshold) onStartReached(distanceFromStart);
2780
+ if (onEndReached && distanceFromEnd <= endThreshold) onEndReached(distanceFromEnd);
2781
+ }
2782
+ }
2783
+ function resize(index, height$1) {
2784
+ if (heights.value[index] === height$1) return;
2785
+ heights.value[index] = height$1;
2786
+ if (!itemHeight.value) itemHeight.value = height$1;
2787
+ if (IN_BROWSER) {
2788
+ cancelAnimationFrame(rebuildRaf);
2789
+ rebuildRaf = requestAnimationFrame(rebuild);
2790
+ } else rebuild();
2791
+ }
2792
+ function scroll() {
2793
+ if (IN_BROWSER) {
2794
+ cancelAnimationFrame(raf);
2795
+ raf = requestAnimationFrame(update);
2796
+ checkEdges();
2797
+ } else update();
2798
+ }
2799
+ function scrollTo(index, scrollOptions) {
2800
+ if (!element.value) return;
2801
+ const targetOffset = offsets.value[index] || 0;
2802
+ const behavior = scrollOptions?.behavior ?? "auto";
2803
+ const block = scrollOptions?.block ?? "start";
2804
+ const extraOffset = scrollOptions?.offset ?? 0;
2805
+ let scrollTop = targetOffset + extraOffset;
2806
+ switch (block) {
2807
+ case "center": {
2808
+ const viewport = viewportHeight.value || cachedViewport;
2809
+ const itemH = heights.value[index] || itemHeight.value;
2810
+ scrollTop = targetOffset - viewport / 2 + itemH / 2 + extraOffset;
2811
+ break;
2812
+ }
2813
+ case "end": {
2814
+ const viewport = viewportHeight.value || cachedViewport;
2815
+ const itemH = heights.value[index] || itemHeight.value;
2816
+ scrollTop = targetOffset - viewport + itemH + extraOffset;
2817
+ break;
2818
+ }
2819
+ case "nearest": {
2820
+ const viewport = viewportHeight.value || cachedViewport;
2821
+ const currentScroll = element.value.scrollTop;
2822
+ const itemH = heights.value[index] || itemHeight.value;
2823
+ if (targetOffset < currentScroll) scrollTop = targetOffset + extraOffset;
2824
+ else if (targetOffset + itemH > currentScroll + viewport) scrollTop = targetOffset - viewport + itemH + extraOffset;
2825
+ else return;
2826
+ break;
2827
+ }
2828
+ }
2829
+ if (behavior === "smooth") element.value.scrollTo({
2830
+ top: scrollTop,
2831
+ behavior: "smooth"
2832
+ });
2833
+ else element.value.scrollTop = scrollTop;
2834
+ update();
2835
+ }
2836
+ function reset() {
2837
+ state.value = "ok";
2838
+ anchorIndex = -1;
2839
+ anchorOffset = 0;
2840
+ if (element.value && direction === "reverse" && items.value.length > 0) {
2841
+ const lastIndex = items.value.length - 1;
2842
+ const totalHeight = (offsets.value[lastIndex] || 0) + (heights.value[lastIndex] || itemHeight.value);
2843
+ element.value.scrollTop = totalHeight;
2844
+ }
2845
+ }
2846
+ onScopeDispose(() => {
2847
+ cancelAnimationFrame(raf);
2848
+ cancelAnimationFrame(rebuildRaf);
2849
+ cancelAnimationFrame(edgeRaf);
2850
+ });
2851
+ return {
2852
+ element,
2853
+ items: computedItems,
2854
+ offset: readonly(offset),
2855
+ size: readonly(size),
2856
+ state,
2857
+ scrollTo,
2858
+ scroll,
2859
+ scrollend: scroll,
2860
+ resize,
2861
+ reset
2862
+ };
2863
+ }
2864
+
2865
+ //#endregion
2866
+ export { useForm as A, createBreakpointsContext as B, PermissionAdapter as C, useIntersectionObserver as D, useElementIntersection as E, createFeatures as F, useWindowEventListener as G, useBreakpoints as H, createFeaturesContext as I, toReactive 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, useDocumentEventListener as U, createBreakpointsPlugin as V, useEventListener 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, createTimeline as r, createThemeContext as s, useVirtual as t, Vuetify0ThemeAdapter as u, useQueue as v, useMutationObserver as w, createPermissionsPlugin as x, createPermissions as y, createBreakpoints as z };