@vuetify/v0 0.0.20 → 0.0.22

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