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