@vuetify/v0 0.0.13 → 0.0.15
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 +2439 -820
- package/dist/components/index.d.mts +3 -3
- package/dist/components/index.mjs +5 -5
- package/dist/{components-z-JwOlZj.mjs → components-Cf1h368z.mjs} +570 -44
- package/dist/composables/index.d.mts +2 -3
- package/dist/composables/index.mjs +4 -4
- package/dist/{composables-BbLwk7s2.mjs → composables-CrW3IMUM.mjs} +1813 -792
- package/dist/constants/index.d.mts +1 -1
- package/dist/constants/index.mjs +1 -1
- package/dist/{globals-Dh0BVnN0.mjs → globals-CqryJ9G3.mjs} +1 -1
- package/dist/index-BPAr2atm.d.mts +70 -0
- package/dist/index-CQcSUYj_.d.mts +1120 -0
- package/dist/{index-CQjOW_w5.d.mts → index-CkmnMjE3.d.mts} +1754 -162
- package/dist/index.d.mts +5 -6
- package/dist/index.mjs +5 -5
- package/dist/utilities/index.d.mts +2 -2
- package/dist/utilities/index.mjs +2 -2
- package/dist/utilities-C6o-keMm.mjs +135 -0
- package/package.json +11 -13
- package/dist/index-CHMeCyP_.d.mts +0 -815
- package/dist/index-CSdGoUCI.d.mts +0 -17
- package/dist/index-D-EP6KrS.d.mts +0 -812
- package/dist/utilities-C26nB74O.mjs +0 -63
- /package/dist/{index-8AIxAM2d.d.mts → index-BmxOG13-.d.mts} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import { a as SUPPORTS_OBSERVER, i as SUPPORTS_MUTATION_OBSERVER, n as SUPPORTS_INTERSECTION_OBSERVER, s as __LOGGER_ENABLED__, t as IN_BROWSER } from "./globals-
|
|
3
|
-
import { computed,
|
|
1
|
+
import { a as isBoolean, c as isNull, d as isObject, g as range, h as mergeDeep, i as isArray, l as isNullOrUndefined, m as isUndefined, o as isFunction, p as isString, r as genId, s as isNaN, t as clamp, u as isNumber } from "./utilities-C6o-keMm.mjs";
|
|
2
|
+
import { a as SUPPORTS_OBSERVER, i as SUPPORTS_MUTATION_OBSERVER, n as SUPPORTS_INTERSECTION_OBSERVER, s as __LOGGER_ENABLED__, t as IN_BROWSER } from "./globals-CqryJ9G3.mjs";
|
|
3
|
+
import { computed, effectScope, getCurrentInstance, inject, isRef, onScopeDispose, provide, reactive, readonly, ref, shallowReactive, shallowReadonly, shallowRef, toRef, toValue, unref, watch } from "vue";
|
|
4
4
|
|
|
5
5
|
//#region src/composables/createContext/index.ts
|
|
6
6
|
/**
|
|
@@ -38,7 +38,7 @@ import { computed, getCurrentInstance, getCurrentScope, inject, isRef, onMounted
|
|
|
38
38
|
*/
|
|
39
39
|
function useContext(key, defaultValue) {
|
|
40
40
|
const context = inject(key, defaultValue);
|
|
41
|
-
if (
|
|
41
|
+
if (/* @__PURE__ */ isUndefined(context)) throw new Error(`Context "${String(key)}" not found. Ensure it's provided by an ancestor.`);
|
|
42
42
|
return context;
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
@@ -356,6 +356,95 @@ function toReactive(objectRef) {
|
|
|
356
356
|
}));
|
|
357
357
|
}
|
|
358
358
|
|
|
359
|
+
//#endregion
|
|
360
|
+
//#region src/composables/useEventListener/index.ts
|
|
361
|
+
/**
|
|
362
|
+
* @module useEventListener
|
|
363
|
+
*
|
|
364
|
+
* @remarks
|
|
365
|
+
* Event listener composable with automatic cleanup on scope disposal.
|
|
366
|
+
*
|
|
367
|
+
* Key features:
|
|
368
|
+
* - Supports Window, Document, and HTMLElement targets
|
|
369
|
+
* - Reactive targets, events, and listeners
|
|
370
|
+
* - Event options support (capture, passive, once)
|
|
371
|
+
* - Automatic removeEventListener on unmount
|
|
372
|
+
* - Multiple overloads for type safety
|
|
373
|
+
*
|
|
374
|
+
* Perfect for safely managing event listeners in Vue components.
|
|
375
|
+
*/
|
|
376
|
+
/**
|
|
377
|
+
* Attaches an event listener to a target.
|
|
378
|
+
*
|
|
379
|
+
* @param target The target to attach the event listener to.
|
|
380
|
+
* @param event The event to listen for.
|
|
381
|
+
* @param listener The event listener.
|
|
382
|
+
* @param options The event listener options.
|
|
383
|
+
* @returns A function to remove the event listener.
|
|
384
|
+
*
|
|
385
|
+
* @see https://0.vuetifyjs.com/composables/system/use-event-listener
|
|
386
|
+
*/
|
|
387
|
+
function useEventListener(target, event, listener, options) {
|
|
388
|
+
const cleanups = [];
|
|
389
|
+
function cleanup() {
|
|
390
|
+
for (const fn of cleanups) fn();
|
|
391
|
+
cleanups.length = 0;
|
|
392
|
+
}
|
|
393
|
+
function register(el, event$1, listener$1, options$1) {
|
|
394
|
+
el.addEventListener(event$1, listener$1, options$1);
|
|
395
|
+
return () => el.removeEventListener(event$1, listener$1, options$1);
|
|
396
|
+
}
|
|
397
|
+
const stopWatcher = watch(() => [
|
|
398
|
+
toValue(target),
|
|
399
|
+
toValue(event),
|
|
400
|
+
unref(listener),
|
|
401
|
+
toValue(options)
|
|
402
|
+
], ([el, events, listeners, opts]) => {
|
|
403
|
+
cleanup();
|
|
404
|
+
if (!el) return;
|
|
405
|
+
const eventList = toArray(events);
|
|
406
|
+
const listenerList = toArray(listeners);
|
|
407
|
+
for (const event$1 of eventList) for (const listenerFn of listenerList) cleanups.push(register(el, event$1, listenerFn, opts));
|
|
408
|
+
}, {
|
|
409
|
+
immediate: true,
|
|
410
|
+
flush: "post"
|
|
411
|
+
});
|
|
412
|
+
function stop() {
|
|
413
|
+
stopWatcher();
|
|
414
|
+
cleanup();
|
|
415
|
+
}
|
|
416
|
+
onScopeDispose(stop, true);
|
|
417
|
+
return stop;
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Attaches an event listener to the window.
|
|
421
|
+
*
|
|
422
|
+
* @param event The event to listen for.
|
|
423
|
+
* @param listener The event listener.
|
|
424
|
+
* @param options The event listener options.
|
|
425
|
+
* @template E The event type.
|
|
426
|
+
* @returns A function to remove the event listener.
|
|
427
|
+
*
|
|
428
|
+
* @see https://0.vuetifyjs.com/composables/system/use-event-listener
|
|
429
|
+
*/
|
|
430
|
+
function useWindowEventListener(event, listener, options) {
|
|
431
|
+
return useEventListener(window, event, listener, options);
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Attaches an event listener to the document.
|
|
435
|
+
*
|
|
436
|
+
* @param event The event to listen for.
|
|
437
|
+
* @param listener The event listener.
|
|
438
|
+
* @param options The event listener options.
|
|
439
|
+
* @template E The event type.
|
|
440
|
+
* @returns A function to remove the event listener.
|
|
441
|
+
*
|
|
442
|
+
* @see https://0.vuetifyjs.com/composables/system/use-event-listener
|
|
443
|
+
*/
|
|
444
|
+
function useDocumentEventListener(event, listener, options) {
|
|
445
|
+
return useEventListener(document, event, listener, options);
|
|
446
|
+
}
|
|
447
|
+
|
|
359
448
|
//#endregion
|
|
360
449
|
//#region src/composables/useHydration/index.ts
|
|
361
450
|
/**
|
|
@@ -402,6 +491,12 @@ function createHydration() {
|
|
|
402
491
|
hydrate
|
|
403
492
|
};
|
|
404
493
|
}
|
|
494
|
+
function createFallbackHydration() {
|
|
495
|
+
return {
|
|
496
|
+
isHydrated: shallowReadonly(shallowRef(true)),
|
|
497
|
+
hydrate: () => {}
|
|
498
|
+
};
|
|
499
|
+
}
|
|
405
500
|
/**
|
|
406
501
|
* Creates a new hydration context trinity.
|
|
407
502
|
*
|
|
@@ -494,7 +589,13 @@ function createHydrationPlugin(_options = {}) {
|
|
|
494
589
|
* ```
|
|
495
590
|
*/
|
|
496
591
|
function useHydration(namespace = "v0:hydration") {
|
|
497
|
-
|
|
592
|
+
const fallback = createFallbackHydration();
|
|
593
|
+
if (!getCurrentInstance()) return fallback;
|
|
594
|
+
try {
|
|
595
|
+
return useContext(namespace, fallback);
|
|
596
|
+
} catch {
|
|
597
|
+
return fallback;
|
|
598
|
+
}
|
|
498
599
|
}
|
|
499
600
|
|
|
500
601
|
//#endregion
|
|
@@ -725,9 +826,9 @@ function createBreakpointsPlugin(_options = {}) {
|
|
|
725
826
|
const unwatch = watch(hydration.isHydrated, (hydrated) => {
|
|
726
827
|
if (hydrated) listener();
|
|
727
828
|
}, { immediate: true });
|
|
728
|
-
|
|
829
|
+
const cleanup = useWindowEventListener("resize", listener, { passive: true });
|
|
729
830
|
onScopeDispose(() => {
|
|
730
|
-
|
|
831
|
+
cleanup();
|
|
731
832
|
unwatch();
|
|
732
833
|
}, true);
|
|
733
834
|
} });
|
|
@@ -762,95 +863,6 @@ function useBreakpoints(namespace = "v0:breakpoints") {
|
|
|
762
863
|
return useContext(namespace);
|
|
763
864
|
}
|
|
764
865
|
|
|
765
|
-
//#endregion
|
|
766
|
-
//#region src/composables/useEventListener/index.ts
|
|
767
|
-
/**
|
|
768
|
-
* @module useEventListener
|
|
769
|
-
*
|
|
770
|
-
* @remarks
|
|
771
|
-
* Event listener composable with automatic cleanup on scope disposal.
|
|
772
|
-
*
|
|
773
|
-
* Key features:
|
|
774
|
-
* - Supports Window, Document, and HTMLElement targets
|
|
775
|
-
* - Reactive targets, events, and listeners
|
|
776
|
-
* - Event options support (capture, passive, once)
|
|
777
|
-
* - Automatic removeEventListener on unmount
|
|
778
|
-
* - Multiple overloads for type safety
|
|
779
|
-
*
|
|
780
|
-
* Perfect for safely managing event listeners in Vue components.
|
|
781
|
-
*/
|
|
782
|
-
/**
|
|
783
|
-
* Attaches an event listener to a target.
|
|
784
|
-
*
|
|
785
|
-
* @param target The target to attach the event listener to.
|
|
786
|
-
* @param event The event to listen for.
|
|
787
|
-
* @param listener The event listener.
|
|
788
|
-
* @param options The event listener options.
|
|
789
|
-
* @returns A function to remove the event listener.
|
|
790
|
-
*
|
|
791
|
-
* @see https://0.vuetifyjs.com/composables/system/use-event-listener
|
|
792
|
-
*/
|
|
793
|
-
function useEventListener(target, event, listener, options) {
|
|
794
|
-
const cleanups = [];
|
|
795
|
-
function cleanup() {
|
|
796
|
-
for (const fn of cleanups) fn();
|
|
797
|
-
cleanups.length = 0;
|
|
798
|
-
}
|
|
799
|
-
function register(el, event$1, listener$1, options$1) {
|
|
800
|
-
el.addEventListener(event$1, listener$1, options$1);
|
|
801
|
-
return () => el.removeEventListener(event$1, listener$1, options$1);
|
|
802
|
-
}
|
|
803
|
-
const stopWatcher = watch(() => [
|
|
804
|
-
toValue(target),
|
|
805
|
-
toValue(event),
|
|
806
|
-
unref(listener),
|
|
807
|
-
toValue(options)
|
|
808
|
-
], ([el, events, listeners, opts]) => {
|
|
809
|
-
cleanup();
|
|
810
|
-
if (!el) return;
|
|
811
|
-
const eventList = toArray(events);
|
|
812
|
-
const listenerList = toArray(listeners);
|
|
813
|
-
for (const event$1 of eventList) for (const listenerFn of listenerList) cleanups.push(register(el, event$1, listenerFn, opts));
|
|
814
|
-
}, {
|
|
815
|
-
immediate: true,
|
|
816
|
-
flush: "post"
|
|
817
|
-
});
|
|
818
|
-
function stop() {
|
|
819
|
-
stopWatcher();
|
|
820
|
-
cleanup();
|
|
821
|
-
}
|
|
822
|
-
onScopeDispose(stop, true);
|
|
823
|
-
return stop;
|
|
824
|
-
}
|
|
825
|
-
/**
|
|
826
|
-
* Attaches an event listener to the window.
|
|
827
|
-
*
|
|
828
|
-
* @param event The event to listen for.
|
|
829
|
-
* @param listener The event listener.
|
|
830
|
-
* @param options The event listener options.
|
|
831
|
-
* @template E The event type.
|
|
832
|
-
* @returns A function to remove the event listener.
|
|
833
|
-
*
|
|
834
|
-
* @see https://0.vuetifyjs.com/composables/system/use-event-listener
|
|
835
|
-
*/
|
|
836
|
-
function useWindowEventListener(event, listener, options) {
|
|
837
|
-
return useEventListener(window, event, listener, options);
|
|
838
|
-
}
|
|
839
|
-
/**
|
|
840
|
-
* Attaches an event listener to the document.
|
|
841
|
-
*
|
|
842
|
-
* @param event The event to listen for.
|
|
843
|
-
* @param listener The event listener.
|
|
844
|
-
* @param options The event listener options.
|
|
845
|
-
* @template E The event type.
|
|
846
|
-
* @returns A function to remove the event listener.
|
|
847
|
-
*
|
|
848
|
-
* @see https://0.vuetifyjs.com/composables/system/use-event-listener
|
|
849
|
-
*/
|
|
850
|
-
function useDocumentEventListener(event, listener, options) {
|
|
851
|
-
return useEventListener(document, event, listener, options);
|
|
852
|
-
}
|
|
853
|
-
|
|
854
866
|
//#endregion
|
|
855
867
|
//#region src/composables/useLogger/adapters/consola.ts
|
|
856
868
|
var ConsolaLoggerAdapter = class {
|
|
@@ -1273,6 +1285,9 @@ function useRegistry(options) {
|
|
|
1273
1285
|
const cache = /* @__PURE__ */ new Map();
|
|
1274
1286
|
const listeners = /* @__PURE__ */ new Map();
|
|
1275
1287
|
const events = options?.events ?? false;
|
|
1288
|
+
let indexDependentCount = 0;
|
|
1289
|
+
let needsReindex = false;
|
|
1290
|
+
let minDirtyIndex = Infinity;
|
|
1276
1291
|
function emit(event, data = void 0) {
|
|
1277
1292
|
if (!events) return;
|
|
1278
1293
|
const cbs = listeners.get(event);
|
|
@@ -1307,13 +1322,15 @@ function useRegistry(options) {
|
|
|
1307
1322
|
let value = existing.value;
|
|
1308
1323
|
let valueIsIndex = existing.valueIsIndex;
|
|
1309
1324
|
if (hasValue) {
|
|
1310
|
-
if (patch.value
|
|
1325
|
+
if (/* @__PURE__ */ isUndefined(patch.value)) {
|
|
1311
1326
|
value = existing.index;
|
|
1312
1327
|
valueIsIndex = true;
|
|
1313
1328
|
} else {
|
|
1314
1329
|
value = patch.value;
|
|
1315
1330
|
valueIsIndex = false;
|
|
1316
1331
|
}
|
|
1332
|
+
if (valueIsIndex !== existing.valueIsIndex) if (valueIsIndex) indexDependentCount++;
|
|
1333
|
+
else indexDependentCount--;
|
|
1317
1334
|
if (!Object.is(value, existing.value)) {
|
|
1318
1335
|
unassign(existing.value, id);
|
|
1319
1336
|
assign(value, id);
|
|
@@ -1333,9 +1350,11 @@ function useRegistry(options) {
|
|
|
1333
1350
|
return updated;
|
|
1334
1351
|
}
|
|
1335
1352
|
function browse(value) {
|
|
1353
|
+
if (indexDependentCount > 0 && needsReindex) reindex();
|
|
1336
1354
|
return catalog.get(value);
|
|
1337
1355
|
}
|
|
1338
1356
|
function lookup(index) {
|
|
1357
|
+
if (needsReindex) reindex();
|
|
1339
1358
|
return directory.get(index);
|
|
1340
1359
|
}
|
|
1341
1360
|
function has(id) {
|
|
@@ -1385,25 +1404,39 @@ function useRegistry(options) {
|
|
|
1385
1404
|
if (catalog.size > 0) catalog.clear();
|
|
1386
1405
|
if (directory.size > 0) directory.clear();
|
|
1387
1406
|
invalidate();
|
|
1407
|
+
indexDependentCount = 0;
|
|
1408
|
+
needsReindex = false;
|
|
1409
|
+
minDirtyIndex = Infinity;
|
|
1388
1410
|
emit("clear:registry");
|
|
1389
1411
|
}
|
|
1390
1412
|
function invalidate() {
|
|
1391
1413
|
if (cache.size > 0) cache.clear();
|
|
1392
1414
|
}
|
|
1393
1415
|
function reindex() {
|
|
1394
|
-
|
|
1395
|
-
if (
|
|
1416
|
+
const startIndex = minDirtyIndex === Infinity ? 0 : minDirtyIndex;
|
|
1417
|
+
if (startIndex === 0) {
|
|
1418
|
+
if (catalog.size > 0) catalog.clear();
|
|
1419
|
+
if (directory.size > 0) directory.clear();
|
|
1420
|
+
}
|
|
1396
1421
|
invalidate();
|
|
1397
1422
|
let index = 0;
|
|
1398
|
-
for (const ticket of values()) {
|
|
1399
|
-
if (
|
|
1400
|
-
|
|
1401
|
-
|
|
1423
|
+
for (const ticket of collection.values()) {
|
|
1424
|
+
if (index < startIndex) {
|
|
1425
|
+
index++;
|
|
1426
|
+
continue;
|
|
1402
1427
|
}
|
|
1428
|
+
if (startIndex > 0) directory.delete(ticket.index);
|
|
1429
|
+
if (ticket.valueIsIndex) {
|
|
1430
|
+
if (startIndex > 0) unassign(ticket.value, ticket.id);
|
|
1431
|
+
ticket.value = index;
|
|
1432
|
+
assign(ticket.value, ticket.id);
|
|
1433
|
+
} else if (startIndex === 0) assign(ticket.value, ticket.id);
|
|
1434
|
+
ticket.index = index;
|
|
1403
1435
|
directory.set(index, ticket.id);
|
|
1404
|
-
assign(ticket.value, ticket.id);
|
|
1405
1436
|
index++;
|
|
1406
1437
|
}
|
|
1438
|
+
needsReindex = false;
|
|
1439
|
+
minDirtyIndex = Infinity;
|
|
1407
1440
|
emit("reindex:registry");
|
|
1408
1441
|
}
|
|
1409
1442
|
function register(registration = {}) {
|
|
@@ -1417,6 +1450,7 @@ function useRegistry(options) {
|
|
|
1417
1450
|
const index = registration.index ?? size;
|
|
1418
1451
|
const value = valueIsUndefined ? index : registration.value;
|
|
1419
1452
|
const valueIsIndex = valueIsUndefined;
|
|
1453
|
+
if (valueIsIndex) indexDependentCount++;
|
|
1420
1454
|
const ticket = {
|
|
1421
1455
|
...registration,
|
|
1422
1456
|
id,
|
|
@@ -1434,15 +1468,40 @@ function useRegistry(options) {
|
|
|
1434
1468
|
function unregister(id) {
|
|
1435
1469
|
const ticket = collection.get(id);
|
|
1436
1470
|
if (!ticket) return;
|
|
1471
|
+
if (ticket.valueIsIndex) indexDependentCount--;
|
|
1437
1472
|
collection.delete(ticket.id);
|
|
1438
1473
|
directory.delete(ticket.index);
|
|
1439
1474
|
unassign(ticket.value, ticket.id);
|
|
1440
1475
|
invalidate();
|
|
1441
1476
|
emit("unregister:ticket", ticket);
|
|
1442
|
-
|
|
1477
|
+
if (indexDependentCount > 0 && ticket.index < collection.size) {
|
|
1478
|
+
minDirtyIndex = Math.min(minDirtyIndex, ticket.index);
|
|
1479
|
+
reindex();
|
|
1480
|
+
} else {
|
|
1481
|
+
minDirtyIndex = Math.min(minDirtyIndex, ticket.index);
|
|
1482
|
+
needsReindex = true;
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
function offboard(ids) {
|
|
1486
|
+
const removed = [];
|
|
1487
|
+
for (const id of ids) {
|
|
1488
|
+
const ticket = collection.get(id);
|
|
1489
|
+
if (!ticket) continue;
|
|
1490
|
+
if (ticket.valueIsIndex) indexDependentCount--;
|
|
1491
|
+
minDirtyIndex = Math.min(minDirtyIndex, ticket.index);
|
|
1492
|
+
collection.delete(ticket.id);
|
|
1493
|
+
directory.delete(ticket.index);
|
|
1494
|
+
unassign(ticket.value, ticket.id);
|
|
1495
|
+
removed.push(ticket);
|
|
1496
|
+
}
|
|
1497
|
+
if (removed.length === 0) return;
|
|
1498
|
+
invalidate();
|
|
1499
|
+
if (events) for (const ticket of removed) emit("unregister:ticket", ticket);
|
|
1500
|
+
needsReindex = true;
|
|
1443
1501
|
}
|
|
1444
1502
|
function seek(direction = "first", from, predicate) {
|
|
1445
1503
|
if (collection.size === 0) return void 0;
|
|
1504
|
+
if (needsReindex) reindex();
|
|
1446
1505
|
const tickets = values();
|
|
1447
1506
|
const index = /* @__PURE__ */ isUndefined(from) ? void 0 : Math.max(0, Math.min(from, tickets.length - 1));
|
|
1448
1507
|
if (direction === "last") {
|
|
@@ -1479,8 +1538,9 @@ function useRegistry(options) {
|
|
|
1479
1538
|
reindex,
|
|
1480
1539
|
seek,
|
|
1481
1540
|
onboard(registrations) {
|
|
1482
|
-
return registrations.map((registration) =>
|
|
1541
|
+
return registrations.map((registration) => register(registration));
|
|
1483
1542
|
},
|
|
1543
|
+
offboard,
|
|
1484
1544
|
get size() {
|
|
1485
1545
|
return collection.size;
|
|
1486
1546
|
}
|
|
@@ -1501,7 +1561,11 @@ function useRegistry(options) {
|
|
|
1501
1561
|
* ```ts
|
|
1502
1562
|
* import { createRegistryContext } from '@vuetify/v0'
|
|
1503
1563
|
*
|
|
1504
|
-
*
|
|
1564
|
+
* // With default namespace 'v0:registry'
|
|
1565
|
+
* export const [useItems, provideItems, items] = createRegistryContext()
|
|
1566
|
+
*
|
|
1567
|
+
* // Or with custom namespace
|
|
1568
|
+
* export const [useItems, provideItems, items] = createRegistryContext({ namespace: 'my-items' })
|
|
1505
1569
|
*
|
|
1506
1570
|
* // In a parent component:
|
|
1507
1571
|
* provideItems()
|
|
@@ -1511,8 +1575,8 @@ function useRegistry(options) {
|
|
|
1511
1575
|
* items.register({ id: 'item-1', value: 'Value 1' })
|
|
1512
1576
|
* ```
|
|
1513
1577
|
*/
|
|
1514
|
-
function createRegistryContext(_options) {
|
|
1515
|
-
const { namespace,...options } = _options;
|
|
1578
|
+
function createRegistryContext(_options = {}) {
|
|
1579
|
+
const { namespace = "v0:registry",...options } = _options;
|
|
1516
1580
|
const [useRegistryContext, _provideRegistryContext] = createContext(namespace);
|
|
1517
1581
|
const context = useRegistry(options);
|
|
1518
1582
|
function provideRegistryContext(_context = context, app) {
|
|
@@ -1623,12 +1687,12 @@ function createSelection(_options = {}) {
|
|
|
1623
1687
|
const id = registration.id ?? /* @__PURE__ */ genId();
|
|
1624
1688
|
const item = {
|
|
1625
1689
|
disabled: false,
|
|
1626
|
-
...registration,
|
|
1627
|
-
id,
|
|
1628
|
-
isSelected: toRef(() => selected(id)),
|
|
1629
1690
|
select: () => select(id),
|
|
1630
1691
|
unselect: () => unselect(id),
|
|
1631
|
-
toggle: () => toggle(id)
|
|
1692
|
+
toggle: () => toggle(id),
|
|
1693
|
+
isSelected: toRef(() => selected(id)),
|
|
1694
|
+
...registration,
|
|
1695
|
+
id
|
|
1632
1696
|
};
|
|
1633
1697
|
const ticket = registry.register(item);
|
|
1634
1698
|
if (enroll && !toValue(item.disabled)) selectedIds.add(ticket.id);
|
|
@@ -1639,6 +1703,13 @@ function createSelection(_options = {}) {
|
|
|
1639
1703
|
selectedIds.delete(id);
|
|
1640
1704
|
registry.unregister(id);
|
|
1641
1705
|
}
|
|
1706
|
+
function offboard(ids) {
|
|
1707
|
+
for (const id of ids) selectedIds.delete(id);
|
|
1708
|
+
registry.offboard(ids);
|
|
1709
|
+
}
|
|
1710
|
+
function onboard(registrations) {
|
|
1711
|
+
return registrations.map((registration) => register(registration));
|
|
1712
|
+
}
|
|
1642
1713
|
function reset() {
|
|
1643
1714
|
registry.clear();
|
|
1644
1715
|
selectedIds.clear();
|
|
@@ -1652,6 +1723,8 @@ function createSelection(_options = {}) {
|
|
|
1652
1723
|
selectedValues,
|
|
1653
1724
|
register,
|
|
1654
1725
|
unregister,
|
|
1726
|
+
onboard,
|
|
1727
|
+
offboard,
|
|
1655
1728
|
reset,
|
|
1656
1729
|
mandate,
|
|
1657
1730
|
seek,
|
|
@@ -1667,7 +1740,6 @@ function createSelection(_options = {}) {
|
|
|
1667
1740
|
/**
|
|
1668
1741
|
* Creates a new selection context.
|
|
1669
1742
|
*
|
|
1670
|
-
* @param namespace The namespace for the selection context.
|
|
1671
1743
|
* @param options The options for the selection context.
|
|
1672
1744
|
* @template Z The type of the selection ticket.
|
|
1673
1745
|
* @template E The type of the selection context.
|
|
@@ -1679,7 +1751,11 @@ function createSelection(_options = {}) {
|
|
|
1679
1751
|
* ```ts
|
|
1680
1752
|
* import { createSelectionContext } from '@vuetify/v0'
|
|
1681
1753
|
*
|
|
1682
|
-
*
|
|
1754
|
+
* // With default namespace 'v0:selection'
|
|
1755
|
+
* export const [useCheckboxes, provideCheckboxes, checkboxes] = createSelectionContext()
|
|
1756
|
+
*
|
|
1757
|
+
* // Or with custom namespace
|
|
1758
|
+
* export const [useCheckboxes, provideCheckboxes, checkboxes] = createSelectionContext({ namespace: 'checkboxes' })
|
|
1683
1759
|
*
|
|
1684
1760
|
* // In a parent component:
|
|
1685
1761
|
* provideCheckboxes()
|
|
@@ -1689,8 +1765,8 @@ function createSelection(_options = {}) {
|
|
|
1689
1765
|
* checkboxes.select('checkbox-1')
|
|
1690
1766
|
* ```
|
|
1691
1767
|
*/
|
|
1692
|
-
function createSelectionContext(_options) {
|
|
1693
|
-
const { namespace,...options } = _options;
|
|
1768
|
+
function createSelectionContext(_options = {}) {
|
|
1769
|
+
const { namespace = "v0:selection",...options } = _options;
|
|
1694
1770
|
const [useSelectionContext, _provideSelectionContext] = createContext(namespace);
|
|
1695
1771
|
const context = createSelection(options);
|
|
1696
1772
|
function provideSelectionContext(_context = context, app) {
|
|
@@ -1726,58 +1802,138 @@ function useSelection(namespace = "v0:selection") {
|
|
|
1726
1802
|
}
|
|
1727
1803
|
|
|
1728
1804
|
//#endregion
|
|
1729
|
-
//#region src/composables/
|
|
1805
|
+
//#region src/composables/useProxyRegistry/index.ts
|
|
1730
1806
|
/**
|
|
1731
|
-
* @module
|
|
1807
|
+
* @module useProxyRegistry
|
|
1732
1808
|
*
|
|
1733
1809
|
* @remarks
|
|
1734
|
-
*
|
|
1810
|
+
* Proxy composable for reactive registry keys, values, entries, and size.
|
|
1735
1811
|
*
|
|
1736
1812
|
* Key features:
|
|
1737
|
-
* -
|
|
1738
|
-
* -
|
|
1739
|
-
* -
|
|
1813
|
+
* - Reactive proxy for registry data
|
|
1814
|
+
* - Deep or shallow reactivity options
|
|
1815
|
+
* - Event-based updates
|
|
1816
|
+
* - Automatic cleanup on scope disposal
|
|
1817
|
+
* - Transforms Map-based registry into reactive refs
|
|
1740
1818
|
*
|
|
1741
|
-
*
|
|
1742
|
-
* Extended by: useFeatures
|
|
1819
|
+
* Perfect for exposing registry data as reactive computed properties.
|
|
1743
1820
|
*/
|
|
1744
1821
|
/**
|
|
1745
|
-
* Creates a
|
|
1746
|
-
*
|
|
1747
|
-
* Extends `createSelection` to support selecting, unselecting, and toggling multiple items
|
|
1748
|
-
* at once by passing an array of IDs. Adds `selectedIndexes` computed property.
|
|
1749
|
-
*
|
|
1750
|
-
* @param options The options for the group instance.
|
|
1751
|
-
* @template Z The type of the group ticket.
|
|
1752
|
-
* @template E The type of the group context.
|
|
1753
|
-
* @returns A new group instance with batch selection support.
|
|
1754
|
-
*
|
|
1755
|
-
* @remarks
|
|
1756
|
-
* **Key Differences from `createSelection`:**
|
|
1757
|
-
* - `select()` accepts `ID | ID[]` for batch operations
|
|
1758
|
-
* - `unselect()` accepts `ID | ID[]` for batch operations
|
|
1759
|
-
* - `toggle()` accepts `ID | ID[]` for batch operations
|
|
1760
|
-
* - Adds `selectedIndexes` computed Set for getting selected item indexes
|
|
1761
|
-
* - Perfect for checkboxes, multi-select dropdowns, and bulk operations
|
|
1762
|
-
*
|
|
1763
|
-
* **Batch Operations:**
|
|
1764
|
-
* - Single ID: `group.select('item-1')`
|
|
1765
|
-
* - Array of IDs: `group.select(['item-1', 'item-2', 'item-3'])`
|
|
1766
|
-
* - Uses `toArray()` utility internally to normalize input
|
|
1767
|
-
* - Disabled items are automatically skipped in batch operations
|
|
1768
|
-
* - Non-existent IDs are silently ignored
|
|
1769
|
-
*
|
|
1770
|
-
* **Inheritance Chain:**
|
|
1771
|
-
* `useRegistry` → `createSelection` → `createGroup`
|
|
1822
|
+
* Creates a proxy registry that provides reactive objects for registry data.
|
|
1772
1823
|
*
|
|
1773
|
-
*
|
|
1774
|
-
*
|
|
1824
|
+
* @param registry The registry instance to proxy.
|
|
1825
|
+
* @param options The options for the proxy registry.
|
|
1826
|
+
* @template Z The type of the registry ticket.
|
|
1827
|
+
* @returns A proxy registry with reactive objects.
|
|
1775
1828
|
*
|
|
1776
|
-
* @see https://0.vuetifyjs.com/composables/
|
|
1829
|
+
* @see https://0.vuetifyjs.com/composables/registration/use-proxy-registry
|
|
1777
1830
|
*
|
|
1778
1831
|
* @example
|
|
1779
1832
|
* ```ts
|
|
1780
|
-
* import {
|
|
1833
|
+
* import { useRegistry, useProxyRegistry } from '@vuetify/v0'
|
|
1834
|
+
*
|
|
1835
|
+
* const registry = useRegistry({ events: true })
|
|
1836
|
+
* const proxy = useProxyRegistry(registry)
|
|
1837
|
+
*
|
|
1838
|
+
* registry.register({ value: 'Item 1' })
|
|
1839
|
+
* console.log(proxy.size) // 1
|
|
1840
|
+
* ```
|
|
1841
|
+
*/
|
|
1842
|
+
function useProxyRegistry(registry, options) {
|
|
1843
|
+
const state = (options?.deep ? reactive : shallowReactive)({
|
|
1844
|
+
keys: registry.keys(),
|
|
1845
|
+
values: registry.values(),
|
|
1846
|
+
entries: registry.entries(),
|
|
1847
|
+
size: registry.size
|
|
1848
|
+
});
|
|
1849
|
+
function update() {
|
|
1850
|
+
state.keys = registry.keys();
|
|
1851
|
+
state.values = registry.values();
|
|
1852
|
+
state.entries = registry.entries();
|
|
1853
|
+
state.size = registry.size;
|
|
1854
|
+
}
|
|
1855
|
+
registry.on("register:ticket", update);
|
|
1856
|
+
registry.on("unregister:ticket", update);
|
|
1857
|
+
registry.on("update:ticket", update);
|
|
1858
|
+
registry.on("clear:registry", update);
|
|
1859
|
+
onScopeDispose(() => {
|
|
1860
|
+
registry.off("register:ticket", update);
|
|
1861
|
+
registry.off("unregister:ticket", update);
|
|
1862
|
+
registry.off("update:ticket", update);
|
|
1863
|
+
registry.off("clear:registry", update);
|
|
1864
|
+
}, true);
|
|
1865
|
+
return state;
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
//#endregion
|
|
1869
|
+
//#region src/composables/useGroup/index.ts
|
|
1870
|
+
/**
|
|
1871
|
+
* @module useGroup
|
|
1872
|
+
*
|
|
1873
|
+
* @remarks
|
|
1874
|
+
* Multi-selection composable that extends useSelection with batch operations and tri-state support.
|
|
1875
|
+
*
|
|
1876
|
+
* Key features:
|
|
1877
|
+
* - Batch operations (select/unselect/toggle accept ID | ID[])
|
|
1878
|
+
* - Tri-state support via mixed/indeterminate state (mix/unmix)
|
|
1879
|
+
* - selectedIndexes computed Set for position-based tracking
|
|
1880
|
+
* - Perfect for checkbox trees, multi-select dropdowns, filter panels
|
|
1881
|
+
*
|
|
1882
|
+
* Tri-state behavior:
|
|
1883
|
+
* - Items can be selected, mixed (indeterminate), or unselected
|
|
1884
|
+
* - select() clears mixed state, mix() clears selected state (mutually exclusive)
|
|
1885
|
+
* - toggle() on a mixed item selects it (resolves positively)
|
|
1886
|
+
*
|
|
1887
|
+
* Inheritance chain: useRegistry → useSelection → useGroup
|
|
1888
|
+
* Extended by: useFeatures
|
|
1889
|
+
*/
|
|
1890
|
+
/**
|
|
1891
|
+
* Creates a new group instance with batch selection and tri-state support.
|
|
1892
|
+
*
|
|
1893
|
+
* Extends `createSelection` to support selecting, unselecting, and toggling multiple items
|
|
1894
|
+
* at once by passing an array of IDs. Adds tri-state (mixed/indeterminate) support for
|
|
1895
|
+
* checkbox trees and similar use cases.
|
|
1896
|
+
*
|
|
1897
|
+
* @param options The options for the group instance.
|
|
1898
|
+
* @template Z The type of the group ticket.
|
|
1899
|
+
* @template E The type of the group context.
|
|
1900
|
+
* @returns A new group instance with batch selection and tri-state support.
|
|
1901
|
+
*
|
|
1902
|
+
* @remarks
|
|
1903
|
+
* **Key Differences from `createSelection`:**
|
|
1904
|
+
* - `select()` accepts `ID | ID[]` for batch operations
|
|
1905
|
+
* - `unselect()` accepts `ID | ID[]` for batch operations
|
|
1906
|
+
* - `toggle()` accepts `ID | ID[]` for batch operations
|
|
1907
|
+
* - Adds `selectedIndexes` computed Set for getting selected item indexes
|
|
1908
|
+
* - Adds tri-state support via `mix()`, `unmix()`, `mixed()`, `mixedIds`, `mixedItems`
|
|
1909
|
+
* - Perfect for checkbox trees, multi-select dropdowns, and bulk operations
|
|
1910
|
+
*
|
|
1911
|
+
* **Tri-State Support:**
|
|
1912
|
+
* - Items can be in one of three states: selected, mixed (indeterminate), or unselected
|
|
1913
|
+
* - `mix(id)` sets item to mixed state (clears selected if set)
|
|
1914
|
+
* - `unmix(id)` clears mixed state
|
|
1915
|
+
* - `select(id)` clears mixed state before selecting
|
|
1916
|
+
* - `toggle(id)` on a mixed item selects it (resolves the indeterminate state positively)
|
|
1917
|
+
* - Mixed state works on disabled items (it's a computed state, not user action)
|
|
1918
|
+
*
|
|
1919
|
+
* **Batch Operations:**
|
|
1920
|
+
* - Single ID: `group.select('item-1')`
|
|
1921
|
+
* - Array of IDs: `group.select(['item-1', 'item-2', 'item-3'])`
|
|
1922
|
+
* - Uses `toArray()` utility internally to normalize input
|
|
1923
|
+
* - Disabled items are automatically skipped in select operations
|
|
1924
|
+
* - Non-existent IDs are silently ignored
|
|
1925
|
+
*
|
|
1926
|
+
* **Inheritance Chain:**
|
|
1927
|
+
* `useRegistry` → `createSelection` → `createGroup`
|
|
1928
|
+
*
|
|
1929
|
+
* **Used By:**
|
|
1930
|
+
* - `createFeatures` for feature flag management with multiple selections
|
|
1931
|
+
*
|
|
1932
|
+
* @see https://0.vuetifyjs.com/composables/selection/use-group
|
|
1933
|
+
*
|
|
1934
|
+
* @example
|
|
1935
|
+
* ```ts
|
|
1936
|
+
* import { createGroup } from '@vuetify/v0'
|
|
1781
1937
|
*
|
|
1782
1938
|
* const checkboxes = createGroup()
|
|
1783
1939
|
*
|
|
@@ -1793,45 +1949,148 @@ function useSelection(namespace = "v0:selection") {
|
|
|
1793
1949
|
* console.log(checkboxes.selectedIds) // Set { 'option-a', 'option-c' }
|
|
1794
1950
|
* console.log(Array.from(checkboxes.selectedIndexes.value)) // [0, 2]
|
|
1795
1951
|
*
|
|
1796
|
-
* //
|
|
1797
|
-
* checkboxes.
|
|
1798
|
-
* console.log(checkboxes.
|
|
1952
|
+
* // Set item to mixed/indeterminate state
|
|
1953
|
+
* checkboxes.mix('option-a')
|
|
1954
|
+
* console.log(checkboxes.mixedIds) // Set { 'option-a' }
|
|
1955
|
+
* console.log(checkboxes.selectedIds) // Set { 'option-c' } (option-a removed)
|
|
1956
|
+
*
|
|
1957
|
+
* // Toggle a mixed item selects it
|
|
1958
|
+
* checkboxes.toggle('option-a')
|
|
1959
|
+
* console.log(checkboxes.selectedIds) // Set { 'option-a', 'option-c' }
|
|
1960
|
+
* console.log(checkboxes.mixedIds) // Set {} (cleared)
|
|
1799
1961
|
* ```
|
|
1800
1962
|
*/
|
|
1801
1963
|
function createGroup(_options = {}) {
|
|
1802
1964
|
const { mandatory = false, multiple = true,...options } = _options;
|
|
1803
|
-
const
|
|
1965
|
+
const selection = createSelection({
|
|
1804
1966
|
...options,
|
|
1805
1967
|
mandatory,
|
|
1806
|
-
multiple
|
|
1968
|
+
multiple,
|
|
1969
|
+
events: true
|
|
1807
1970
|
});
|
|
1971
|
+
const proxy = useProxyRegistry(selection);
|
|
1972
|
+
const mixedIds = shallowReactive(/* @__PURE__ */ new Set());
|
|
1808
1973
|
const selectedIndexes = computed(() => {
|
|
1809
|
-
return new Set(Array.from(
|
|
1974
|
+
return new Set(Array.from(selection.selectedItems.value).map((item) => item?.index));
|
|
1810
1975
|
});
|
|
1976
|
+
const mixedItems = computed(() => {
|
|
1977
|
+
return new Set(Array.from(mixedIds).map((id) => selection.get(id)));
|
|
1978
|
+
});
|
|
1979
|
+
function mixed(id) {
|
|
1980
|
+
return mixedIds.has(id);
|
|
1981
|
+
}
|
|
1982
|
+
function mix(ids) {
|
|
1983
|
+
for (const id of toArray(ids)) {
|
|
1984
|
+
if (!selection.has(id)) continue;
|
|
1985
|
+
selection.selectedIds.delete(id);
|
|
1986
|
+
mixedIds.add(id);
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
function unmix(ids) {
|
|
1990
|
+
for (const id of toArray(ids)) mixedIds.delete(id);
|
|
1991
|
+
}
|
|
1811
1992
|
function select(ids) {
|
|
1812
|
-
for (const id of toArray(ids))
|
|
1993
|
+
for (const id of toArray(ids)) {
|
|
1994
|
+
mixedIds.delete(id);
|
|
1995
|
+
selection.select(id);
|
|
1996
|
+
}
|
|
1813
1997
|
}
|
|
1814
1998
|
function unselect(ids) {
|
|
1815
|
-
for (const id of toArray(ids))
|
|
1999
|
+
for (const id of toArray(ids)) selection.unselect(id);
|
|
1816
2000
|
}
|
|
1817
2001
|
function toggle(ids) {
|
|
1818
|
-
for (const id of toArray(ids))
|
|
2002
|
+
for (const id of toArray(ids)) if (mixed(id)) select(id);
|
|
2003
|
+
else selection.toggle(id);
|
|
2004
|
+
}
|
|
2005
|
+
function register(registration = {}) {
|
|
2006
|
+
const id = registration.id ?? /* @__PURE__ */ genId();
|
|
2007
|
+
const item = {
|
|
2008
|
+
...registration,
|
|
2009
|
+
id,
|
|
2010
|
+
isMixed: toRef(() => mixed(id)),
|
|
2011
|
+
select: () => select(id),
|
|
2012
|
+
unselect: () => unselect(id),
|
|
2013
|
+
toggle: () => toggle(id),
|
|
2014
|
+
mix: () => mix(id),
|
|
2015
|
+
unmix: () => unmix(id)
|
|
2016
|
+
};
|
|
2017
|
+
const ticket = selection.register(item);
|
|
2018
|
+
if (toValue(registration.indeterminate)) mix(id);
|
|
2019
|
+
return ticket;
|
|
2020
|
+
}
|
|
2021
|
+
function unregister(id) {
|
|
2022
|
+
mixedIds.delete(id);
|
|
2023
|
+
selection.unregister(id);
|
|
2024
|
+
}
|
|
2025
|
+
function offboard(ids) {
|
|
2026
|
+
for (const id of ids) mixedIds.delete(id);
|
|
2027
|
+
selection.offboard(ids);
|
|
2028
|
+
}
|
|
2029
|
+
function onboard(registrations) {
|
|
2030
|
+
return registrations.map((registration) => register(registration));
|
|
2031
|
+
}
|
|
2032
|
+
function reset() {
|
|
2033
|
+
mixedIds.clear();
|
|
2034
|
+
selection.reset();
|
|
2035
|
+
}
|
|
2036
|
+
const selectableItems = computed(() => {
|
|
2037
|
+
return proxy.values.filter((item) => !toValue(item.disabled));
|
|
2038
|
+
});
|
|
2039
|
+
const isAllSelected = computed(() => {
|
|
2040
|
+
const items = selectableItems.value;
|
|
2041
|
+
if (items.length === 0) return false;
|
|
2042
|
+
return items.every((item) => selection.selectedIds.has(item.id));
|
|
2043
|
+
});
|
|
2044
|
+
const isMixed = toRef(() => {
|
|
2045
|
+
return mixedIds.size > 0 || !isNoneSelected.value && !isAllSelected.value;
|
|
2046
|
+
});
|
|
2047
|
+
const isNoneSelected = toRef(() => selection.selectedIds.size === 0);
|
|
2048
|
+
function selectAll() {
|
|
2049
|
+
for (const item of selectableItems.value) {
|
|
2050
|
+
mixedIds.delete(item.id);
|
|
2051
|
+
selection.select(item.id);
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
function unselectAll() {
|
|
2055
|
+
const first = selection.selectedIds.values().next().value;
|
|
2056
|
+
selection.selectedIds.clear();
|
|
2057
|
+
if (!mandatory || !first) return;
|
|
2058
|
+
selection.select(first);
|
|
2059
|
+
}
|
|
2060
|
+
function toggleAll() {
|
|
2061
|
+
if (isAllSelected.value) unselectAll();
|
|
2062
|
+
else selectAll();
|
|
1819
2063
|
}
|
|
1820
2064
|
return {
|
|
1821
|
-
...
|
|
2065
|
+
...selection,
|
|
2066
|
+
mixed,
|
|
2067
|
+
mix,
|
|
2068
|
+
unmix,
|
|
1822
2069
|
select,
|
|
1823
2070
|
unselect,
|
|
1824
2071
|
toggle,
|
|
2072
|
+
register,
|
|
2073
|
+
unregister,
|
|
2074
|
+
offboard,
|
|
2075
|
+
onboard,
|
|
2076
|
+
reset,
|
|
2077
|
+
selectAll,
|
|
2078
|
+
unselectAll,
|
|
2079
|
+
toggleAll,
|
|
2080
|
+
mixedIds,
|
|
2081
|
+
mixedItems,
|
|
1825
2082
|
selectedIndexes,
|
|
2083
|
+
isNoneSelected,
|
|
2084
|
+
isAllSelected,
|
|
2085
|
+
isMixed,
|
|
1826
2086
|
get size() {
|
|
1827
|
-
return
|
|
2087
|
+
return selection.size;
|
|
1828
2088
|
}
|
|
1829
2089
|
};
|
|
1830
2090
|
}
|
|
1831
2091
|
/**
|
|
1832
2092
|
* Creates a new group context.
|
|
1833
2093
|
*
|
|
1834
|
-
* @param namespace The namespace for the group context.
|
|
1835
2094
|
* @param options The options for the group context.
|
|
1836
2095
|
* @template Z The type of the group ticket.
|
|
1837
2096
|
* @template E The type of the group context.
|
|
@@ -1843,7 +2102,11 @@ function createGroup(_options = {}) {
|
|
|
1843
2102
|
* ```ts
|
|
1844
2103
|
* import { createGroupContext } from '@vuetify/v0'
|
|
1845
2104
|
*
|
|
1846
|
-
*
|
|
2105
|
+
* // With default namespace 'v0:group'
|
|
2106
|
+
* export const [useMyGroup, provideMyGroup, myGroup] = createGroupContext()
|
|
2107
|
+
*
|
|
2108
|
+
* // Or with custom namespace
|
|
2109
|
+
* export const [useMyGroup, provideMyGroup, myGroup] = createGroupContext({ namespace: 'my-group' })
|
|
1847
2110
|
*
|
|
1848
2111
|
* // In a parent component:
|
|
1849
2112
|
* provideMyGroup()
|
|
@@ -1852,8 +2115,8 @@ function createGroup(_options = {}) {
|
|
|
1852
2115
|
* const group = useMyGroup()
|
|
1853
2116
|
* ```
|
|
1854
2117
|
*/
|
|
1855
|
-
function createGroupContext(_options) {
|
|
1856
|
-
const { namespace,...options } = _options;
|
|
2118
|
+
function createGroupContext(_options = {}) {
|
|
2119
|
+
const { namespace = "v0:group",...options } = _options;
|
|
1857
2120
|
const [useGroupContext, _provideGroupContext] = createContext(namespace);
|
|
1858
2121
|
const context = createGroup(options);
|
|
1859
2122
|
function provideGroupContext(_context = context, app) {
|
|
@@ -1884,7 +2147,7 @@ function createGroupContext(_options) {
|
|
|
1884
2147
|
* </template>
|
|
1885
2148
|
* ```
|
|
1886
2149
|
*/
|
|
1887
|
-
function useGroup(namespace) {
|
|
2150
|
+
function useGroup(namespace = "v0:group") {
|
|
1888
2151
|
return useContext(namespace);
|
|
1889
2152
|
}
|
|
1890
2153
|
|
|
@@ -1948,9 +2211,10 @@ function createTokens(tokens = {}, options = {}) {
|
|
|
1948
2211
|
function resolve(token, visited = /* @__PURE__ */ new Set()) {
|
|
1949
2212
|
const cacheKey = /* @__PURE__ */ isString(token) ? token : JSON.stringify(token);
|
|
1950
2213
|
const cached = cache.get(cacheKey);
|
|
1951
|
-
if (
|
|
2214
|
+
if (!/* @__PURE__ */ isUndefined(cached)) return cached;
|
|
1952
2215
|
const reference = isTokenAlias(token) ? token.$value : token;
|
|
1953
|
-
const
|
|
2216
|
+
const isAliasReference = /* @__PURE__ */ isString(reference) && isAlias(reference);
|
|
2217
|
+
const clean = isAliasReference ? reference.slice(1, -1) : String(reference);
|
|
1954
2218
|
if (visited.has(clean)) {
|
|
1955
2219
|
logger.warn(`Circular alias detected for "${clean}"`);
|
|
1956
2220
|
cache.set(cacheKey, void 0);
|
|
@@ -1965,15 +2229,15 @@ function createTokens(tokens = {}, options = {}) {
|
|
|
1965
2229
|
const prefix = parts.slice(0, i).join(".");
|
|
1966
2230
|
const suffix = parts.slice(i);
|
|
1967
2231
|
const candidate = registry.get(prefix);
|
|
1968
|
-
if (candidate?.value
|
|
2232
|
+
if (!/* @__PURE__ */ isUndefined(candidate?.value)) {
|
|
1969
2233
|
found = candidate;
|
|
1970
2234
|
segments = suffix;
|
|
1971
2235
|
break;
|
|
1972
2236
|
}
|
|
1973
2237
|
}
|
|
1974
2238
|
}
|
|
1975
|
-
if (found?.value
|
|
1976
|
-
logger.warn(`Alias not found for "${String(reference)}"`);
|
|
2239
|
+
if (/* @__PURE__ */ isUndefined(found?.value)) {
|
|
2240
|
+
if (isAliasReference) logger.warn(`Alias not found for "${String(reference)}"`);
|
|
1977
2241
|
cache.set(cacheKey, void 0);
|
|
1978
2242
|
return;
|
|
1979
2243
|
}
|
|
@@ -1989,7 +2253,7 @@ function createTokens(tokens = {}, options = {}) {
|
|
|
1989
2253
|
current = current[segment];
|
|
1990
2254
|
if (isTokenAlias(current)) current = current.$value;
|
|
1991
2255
|
}
|
|
1992
|
-
if (
|
|
2256
|
+
if (/* @__PURE__ */ isUndefined(current)) {
|
|
1993
2257
|
logger.warn(`Path not found inside "${clean}": ${segments.join(".")}`);
|
|
1994
2258
|
cache.set(cacheKey, void 0);
|
|
1995
2259
|
return;
|
|
@@ -2040,7 +2304,7 @@ function createTokens(tokens = {}, options = {}) {
|
|
|
2040
2304
|
* ```
|
|
2041
2305
|
*/
|
|
2042
2306
|
function createTokensContext(_options) {
|
|
2043
|
-
const { namespace, tokens = {},...options } = _options;
|
|
2307
|
+
const { namespace = "v0:tokens", tokens = {},...options } = _options;
|
|
2044
2308
|
const [useTokensContext, _provideTokensContext] = createContext(namespace);
|
|
2045
2309
|
const context = createTokens(tokens, options);
|
|
2046
2310
|
function provideTokensContext(_context = context, app) {
|
|
@@ -2360,7 +2624,7 @@ function defaultFilter(query, item, keys, mode = "some") {
|
|
|
2360
2624
|
* @template Z The type of the items.
|
|
2361
2625
|
* @returns The filtered items.
|
|
2362
2626
|
*
|
|
2363
|
-
* @see https://0.vuetifyjs.com/composables/
|
|
2627
|
+
* @see https://0.vuetifyjs.com/composables/utilities/use-filter
|
|
2364
2628
|
*
|
|
2365
2629
|
* @example
|
|
2366
2630
|
* ```ts
|
|
@@ -2460,7 +2724,7 @@ function createForm(options) {
|
|
|
2460
2724
|
if (ticket.isValid.value === false) return false;
|
|
2461
2725
|
if (ticket.isValid.value === null) return null;
|
|
2462
2726
|
}
|
|
2463
|
-
return hasFields
|
|
2727
|
+
return hasFields ? true : null;
|
|
2464
2728
|
});
|
|
2465
2729
|
function reset() {
|
|
2466
2730
|
for (const ticket of registry.values()) ticket.reset();
|
|
@@ -2550,7 +2814,6 @@ function createForm(options) {
|
|
|
2550
2814
|
/**
|
|
2551
2815
|
* Creates a new form context.
|
|
2552
2816
|
*
|
|
2553
|
-
* @param namespace The namespace for the form context.
|
|
2554
2817
|
* @param options The options for the form context.
|
|
2555
2818
|
* @template Z The type of the form ticket.
|
|
2556
2819
|
* @template E The type of the form context.
|
|
@@ -2562,7 +2825,12 @@ function createForm(options) {
|
|
|
2562
2825
|
* ```ts
|
|
2563
2826
|
* import { createFormContext } from '@vuetify/v0'
|
|
2564
2827
|
*
|
|
2565
|
-
*
|
|
2828
|
+
* // With default namespace 'v0:form'
|
|
2829
|
+
* export const [useMyForm, provideMyForm, myForm] = createFormContext({ validateOn: 'change' })
|
|
2830
|
+
*
|
|
2831
|
+
* // Or with custom namespace
|
|
2832
|
+
* export const [useMyForm, provideMyForm, myForm] = createFormContext({
|
|
2833
|
+
* namespace: 'my-form',
|
|
2566
2834
|
* validateOn: 'change',
|
|
2567
2835
|
* })
|
|
2568
2836
|
*
|
|
@@ -2574,8 +2842,8 @@ function createForm(options) {
|
|
|
2574
2842
|
* form.register({ id: 'field', value: ref(''), rules: [...] })
|
|
2575
2843
|
* ```
|
|
2576
2844
|
*/
|
|
2577
|
-
function createFormContext(_options) {
|
|
2578
|
-
const { namespace,...options } = _options;
|
|
2845
|
+
function createFormContext(_options = {}) {
|
|
2846
|
+
const { namespace = "v0:form",...options } = _options;
|
|
2579
2847
|
const [useFormContext, _provideFormContext] = createContext(namespace);
|
|
2580
2848
|
const context = createForm(options);
|
|
2581
2849
|
function provideFormContext(_context = context, app) {
|
|
@@ -2672,6 +2940,7 @@ function useIntersectionObserver(target, callback, options = {}) {
|
|
|
2672
2940
|
const observer = shallowRef();
|
|
2673
2941
|
const isPaused = shallowRef(false);
|
|
2674
2942
|
const isIntersecting = shallowRef(false);
|
|
2943
|
+
const isActive = toRef(() => !!observer.value);
|
|
2675
2944
|
function setup() {
|
|
2676
2945
|
if (!isHydrated.value || !SUPPORTS_INTERSECTION_OBSERVER || !target.value || isPaused.value) return;
|
|
2677
2946
|
observer.value = new IntersectionObserver((entries) => {
|
|
@@ -2725,10 +2994,11 @@ function useIntersectionObserver(target, callback, options = {}) {
|
|
|
2725
2994
|
function stop() {
|
|
2726
2995
|
cleanup();
|
|
2727
2996
|
}
|
|
2728
|
-
|
|
2997
|
+
onScopeDispose(stop, true);
|
|
2729
2998
|
return {
|
|
2730
|
-
|
|
2731
|
-
|
|
2999
|
+
isActive: shallowReadonly(isActive),
|
|
3000
|
+
isIntersecting: shallowReadonly(isIntersecting),
|
|
3001
|
+
isPaused: shallowReadonly(isPaused),
|
|
2732
3002
|
pause,
|
|
2733
3003
|
resume,
|
|
2734
3004
|
stop
|
|
@@ -2765,7 +3035,7 @@ function useIntersectionObserver(target, callback, options = {}) {
|
|
|
2765
3035
|
function useElementIntersection(target, options = {}) {
|
|
2766
3036
|
const isIntersecting = shallowRef(false);
|
|
2767
3037
|
const intersectionRatio = shallowRef(0);
|
|
2768
|
-
const { pause: _pause, resume, stop, isPaused } = useIntersectionObserver(target, (entries) => {
|
|
3038
|
+
const { pause: _pause, resume, stop, isActive, isPaused } = useIntersectionObserver(target, (entries) => {
|
|
2769
3039
|
const entry = entries.at(-1);
|
|
2770
3040
|
if (entry) {
|
|
2771
3041
|
isIntersecting.value = entry.isIntersecting;
|
|
@@ -2781,8 +3051,9 @@ function useElementIntersection(target, options = {}) {
|
|
|
2781
3051
|
_pause();
|
|
2782
3052
|
}
|
|
2783
3053
|
return {
|
|
2784
|
-
isIntersecting:
|
|
2785
|
-
intersectionRatio:
|
|
3054
|
+
isIntersecting: shallowReadonly(isIntersecting),
|
|
3055
|
+
intersectionRatio: shallowReadonly(intersectionRatio),
|
|
3056
|
+
isActive,
|
|
2786
3057
|
isPaused,
|
|
2787
3058
|
pause,
|
|
2788
3059
|
resume,
|
|
@@ -2802,7 +3073,7 @@ function useElementIntersection(target, options = {}) {
|
|
|
2802
3073
|
* - Key-specific event handling
|
|
2803
3074
|
* - preventDefault and stopPropagation options
|
|
2804
3075
|
* - Automatic cleanup on scope disposal
|
|
2805
|
-
* -
|
|
3076
|
+
* - Built on useEventListener for consistent event handling
|
|
2806
3077
|
*
|
|
2807
3078
|
* Simplified wrapper around useEventListener for keyboard interactions.
|
|
2808
3079
|
*/
|
|
@@ -2818,36 +3089,44 @@ function useElementIntersection(target, options = {}) {
|
|
|
2818
3089
|
* ```ts
|
|
2819
3090
|
* import { useKeydown } from '@vuetify/v0'
|
|
2820
3091
|
*
|
|
2821
|
-
* const {
|
|
3092
|
+
* const { isActive, start, stop } = useKeydown([
|
|
2822
3093
|
* { key: 'Enter', handler: () => console.log('Enter pressed') },
|
|
2823
3094
|
* { key: 'Escape', handler: () => console.log('Escape pressed'), preventDefault: true },
|
|
2824
3095
|
* ])
|
|
2825
3096
|
*
|
|
2826
|
-
*
|
|
2827
|
-
*
|
|
3097
|
+
* // Listener is automatically active when called in component setup
|
|
3098
|
+
* // Manually control if needed:
|
|
3099
|
+
* stop()
|
|
3100
|
+
* start()
|
|
2828
3101
|
* ```
|
|
2829
3102
|
*/
|
|
2830
3103
|
function useKeydown(handlers) {
|
|
2831
|
-
|
|
3104
|
+
let cleanup = null;
|
|
3105
|
+
const isActive = toRef(() => !!cleanup);
|
|
2832
3106
|
function onKeydown(event) {
|
|
2833
|
-
const
|
|
3107
|
+
const handlerList = toValue(handlers);
|
|
3108
|
+
const handler = (Array.isArray(handlerList) ? handlerList : [handlerList]).find((h$1) => h$1.key === event.key);
|
|
2834
3109
|
if (handler) {
|
|
2835
3110
|
if (handler.preventDefault) event.preventDefault();
|
|
2836
3111
|
if (handler.stopPropagation) event.stopPropagation();
|
|
2837
3112
|
handler.handler(event);
|
|
2838
3113
|
}
|
|
2839
3114
|
}
|
|
2840
|
-
function
|
|
2841
|
-
|
|
3115
|
+
function start() {
|
|
3116
|
+
if (cleanup) return;
|
|
3117
|
+
cleanup = useDocumentEventListener("keydown", onKeydown);
|
|
2842
3118
|
}
|
|
2843
|
-
function
|
|
2844
|
-
|
|
3119
|
+
function stop() {
|
|
3120
|
+
if (!cleanup) return;
|
|
3121
|
+
cleanup();
|
|
3122
|
+
cleanup = null;
|
|
2845
3123
|
}
|
|
2846
|
-
|
|
2847
|
-
onScopeDispose(
|
|
3124
|
+
cleanup = useDocumentEventListener("keydown", onKeydown);
|
|
3125
|
+
onScopeDispose(stop);
|
|
2848
3126
|
return {
|
|
2849
|
-
|
|
2850
|
-
|
|
3127
|
+
isActive,
|
|
3128
|
+
start,
|
|
3129
|
+
stop
|
|
2851
3130
|
};
|
|
2852
3131
|
}
|
|
2853
3132
|
|
|
@@ -2951,7 +3230,6 @@ function createSingle(_options = {}) {
|
|
|
2951
3230
|
/**
|
|
2952
3231
|
* Creates a new single selection context.
|
|
2953
3232
|
*
|
|
2954
|
-
* @param namespace The namespace for the single selection context.
|
|
2955
3233
|
* @param options The options for the single selection context.
|
|
2956
3234
|
* @template Z The type of the single selection ticket.
|
|
2957
3235
|
* @template E The type of the single selection context.
|
|
@@ -2963,18 +3241,19 @@ function createSingle(_options = {}) {
|
|
|
2963
3241
|
* ```ts
|
|
2964
3242
|
* import { createSingleContext } from '@vuetify/v0'
|
|
2965
3243
|
*
|
|
2966
|
-
*
|
|
3244
|
+
* // With default namespace 'v0:single'
|
|
3245
|
+
* export const [useSingle, provideSingle, context] = createSingleContext()
|
|
2967
3246
|
*
|
|
2968
3247
|
* // In a parent component:
|
|
2969
|
-
*
|
|
3248
|
+
* provideSingle()
|
|
2970
3249
|
*
|
|
2971
3250
|
* // In a child component:
|
|
2972
|
-
* const
|
|
2973
|
-
*
|
|
3251
|
+
* const single = useSingle()
|
|
3252
|
+
* single.select('tab-1')
|
|
2974
3253
|
* ```
|
|
2975
3254
|
*/
|
|
2976
|
-
function createSingleContext(_options) {
|
|
2977
|
-
const { namespace,...options } = _options;
|
|
3255
|
+
function createSingleContext(_options = {}) {
|
|
3256
|
+
const { namespace = "v0:single",...options } = _options;
|
|
2978
3257
|
const [useSingleContext, _provideSingleContext] = createContext(namespace);
|
|
2979
3258
|
const context = createSingle(options);
|
|
2980
3259
|
function provideSingleContext(_context = context, app) {
|
|
@@ -3016,7 +3295,7 @@ function useSingle(namespace = "v0:single") {
|
|
|
3016
3295
|
*
|
|
3017
3296
|
* This adapter provides translation and number formatting
|
|
3018
3297
|
* capabilities using the Intl API and supports both
|
|
3019
|
-
* numbered and named variables in translation strings.
|
|
3298
|
+
* numbered ({0}, {1}) and named ({name}) variables in translation strings.
|
|
3020
3299
|
*/
|
|
3021
3300
|
var Vuetify0LocaleAdapter = class {
|
|
3022
3301
|
t(message, ...params) {
|
|
@@ -3024,13 +3303,13 @@ var Vuetify0LocaleAdapter = class {
|
|
|
3024
3303
|
if (params.length > 0 && /* @__PURE__ */ isObject(params[0])) {
|
|
3025
3304
|
const variables = params[0];
|
|
3026
3305
|
resolvedMessage = resolvedMessage.replace(/{([a-zA-Z][a-zA-Z0-9_]*)}/g, (match, name) => {
|
|
3027
|
-
return variables[name]
|
|
3306
|
+
return /* @__PURE__ */ isUndefined(variables[name]) ? match : String(variables[name]);
|
|
3028
3307
|
});
|
|
3029
3308
|
params = params.slice(1);
|
|
3030
3309
|
}
|
|
3031
3310
|
resolvedMessage = resolvedMessage.replace(/\{(\d+)\}/g, (match, index) => {
|
|
3032
3311
|
const idx = Number.parseInt(index, 10);
|
|
3033
|
-
if (params[idx]
|
|
3312
|
+
if (!/* @__PURE__ */ isUndefined(params[idx])) return String(params[idx]);
|
|
3034
3313
|
return match;
|
|
3035
3314
|
});
|
|
3036
3315
|
return resolvedMessage;
|
|
@@ -3071,21 +3350,20 @@ var Vuetify0LocaleAdapter = class {
|
|
|
3071
3350
|
*/
|
|
3072
3351
|
function createLocale(_options = {}) {
|
|
3073
3352
|
const { adapter = new Vuetify0LocaleAdapter(), messages = {},...options } = _options;
|
|
3074
|
-
const tokens = createTokens(messages
|
|
3353
|
+
const tokens = createTokens(messages);
|
|
3075
3354
|
const registry = createSingle(options);
|
|
3076
3355
|
for (const id in messages) {
|
|
3077
|
-
registry.register({
|
|
3078
|
-
id,
|
|
3079
|
-
value: messages[id]
|
|
3080
|
-
});
|
|
3356
|
+
registry.register({ id });
|
|
3081
3357
|
if (id === options.default && !registry.selectedId.value) registry.select(id);
|
|
3082
3358
|
}
|
|
3083
|
-
function t(key,
|
|
3359
|
+
function t(key, params, fallback) {
|
|
3084
3360
|
const locale = registry.selectedId.value;
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
const
|
|
3088
|
-
|
|
3361
|
+
const args = toArray(params);
|
|
3362
|
+
if (!locale) return adapter.t(fallback ?? key, ...args);
|
|
3363
|
+
const path = `${locale}.${key}`;
|
|
3364
|
+
const message = tokens.get(path)?.value;
|
|
3365
|
+
const template = /* @__PURE__ */ isString(message) ? resolve(locale, message) : fallback ?? key;
|
|
3366
|
+
return adapter.t(template, ...args);
|
|
3089
3367
|
}
|
|
3090
3368
|
function n(value, ...params) {
|
|
3091
3369
|
return adapter.n(value, registry.selectedId.value, ...params);
|
|
@@ -3093,17 +3371,10 @@ function createLocale(_options = {}) {
|
|
|
3093
3371
|
function resolve(locale, str) {
|
|
3094
3372
|
return str.replace(/{([a-zA-Z0-9.-_]+)}/g, (match, key) => {
|
|
3095
3373
|
const [prefix, ...rest] = key.split(".");
|
|
3096
|
-
const
|
|
3097
|
-
const
|
|
3098
|
-
const
|
|
3099
|
-
const name = prefixTicket ? path : key;
|
|
3100
|
-
const resolved = (registry.get(target)?.value)?.[name];
|
|
3374
|
+
const target = registry.has(prefix) ? prefix : locale;
|
|
3375
|
+
const path = `${target}.${registry.has(prefix) ? rest.join(".") : key}`;
|
|
3376
|
+
const resolved = tokens.get(path)?.value;
|
|
3101
3377
|
if (/* @__PURE__ */ isString(resolved)) return resolve(target, resolved);
|
|
3102
|
-
const alias = `{${key}}`;
|
|
3103
|
-
if (tokens.isAlias(alias)) {
|
|
3104
|
-
const result = tokens.resolve(alias);
|
|
3105
|
-
return /* @__PURE__ */ isString(result) ? result : match;
|
|
3106
|
-
}
|
|
3107
3378
|
return match;
|
|
3108
3379
|
});
|
|
3109
3380
|
}
|
|
@@ -3116,6 +3387,13 @@ function createLocale(_options = {}) {
|
|
|
3116
3387
|
}
|
|
3117
3388
|
};
|
|
3118
3389
|
}
|
|
3390
|
+
function createLocaleFallback() {
|
|
3391
|
+
return {
|
|
3392
|
+
size: 0,
|
|
3393
|
+
t: (key, _params, fallback) => fallback ?? key,
|
|
3394
|
+
n: String
|
|
3395
|
+
};
|
|
3396
|
+
}
|
|
3119
3397
|
/**
|
|
3120
3398
|
* Creates a new locale context.
|
|
3121
3399
|
*
|
|
@@ -3190,7 +3468,13 @@ function createLocalePlugin(_options = {}) {
|
|
|
3190
3468
|
* @see https://0.vuetifyjs.com/composables/plugins/use-locale
|
|
3191
3469
|
*/
|
|
3192
3470
|
function useLocale(namespace = "v0:locale") {
|
|
3193
|
-
|
|
3471
|
+
const fallback = createLocaleFallback();
|
|
3472
|
+
if (!getCurrentInstance()) return fallback;
|
|
3473
|
+
try {
|
|
3474
|
+
return useContext(namespace, fallback);
|
|
3475
|
+
} catch {
|
|
3476
|
+
return fallback;
|
|
3477
|
+
}
|
|
3194
3478
|
}
|
|
3195
3479
|
|
|
3196
3480
|
//#endregion
|
|
@@ -3258,6 +3542,7 @@ function useMutationObserver(target, callback, options = {}) {
|
|
|
3258
3542
|
const { isHydrated } = useHydration();
|
|
3259
3543
|
const observer = shallowRef();
|
|
3260
3544
|
const isPaused = shallowRef(false);
|
|
3545
|
+
const isActive = computed(() => !!observer.value);
|
|
3261
3546
|
const observerOptions = {
|
|
3262
3547
|
childList: options.childList ?? true,
|
|
3263
3548
|
attributes: options.attributes ?? false,
|
|
@@ -3324,9 +3609,10 @@ function useMutationObserver(target, callback, options = {}) {
|
|
|
3324
3609
|
function stop() {
|
|
3325
3610
|
cleanup();
|
|
3326
3611
|
}
|
|
3327
|
-
|
|
3612
|
+
onScopeDispose(stop, true);
|
|
3328
3613
|
return {
|
|
3329
|
-
|
|
3614
|
+
isActive: shallowReadonly(isActive),
|
|
3615
|
+
isPaused: shallowReadonly(isPaused),
|
|
3330
3616
|
pause,
|
|
3331
3617
|
resume,
|
|
3332
3618
|
stop
|
|
@@ -3334,360 +3620,881 @@ function useMutationObserver(target, callback, options = {}) {
|
|
|
3334
3620
|
}
|
|
3335
3621
|
|
|
3336
3622
|
//#endregion
|
|
3337
|
-
//#region src/composables/
|
|
3338
|
-
var PermissionAdapter = class {};
|
|
3339
|
-
|
|
3340
|
-
//#endregion
|
|
3341
|
-
//#region src/composables/usePermissions/adapters/v0.ts
|
|
3342
|
-
var Vuetify0PermissionAdapter = class extends PermissionAdapter {
|
|
3343
|
-
constructor() {
|
|
3344
|
-
super();
|
|
3345
|
-
}
|
|
3346
|
-
can(role, action, subject, context, permissions) {
|
|
3347
|
-
const access = `${role}.${action}.${subject}`;
|
|
3348
|
-
const ticket = permissions.get(access);
|
|
3349
|
-
if (!ticket || !ticket.value) return false;
|
|
3350
|
-
return /* @__PURE__ */ isFunction(ticket.value) ? ticket.value(context) : ticket.value;
|
|
3351
|
-
}
|
|
3352
|
-
};
|
|
3353
|
-
|
|
3354
|
-
//#endregion
|
|
3355
|
-
//#region src/composables/usePermissions/index.ts
|
|
3623
|
+
//#region src/composables/useResizeObserver/index.ts
|
|
3356
3624
|
/**
|
|
3357
|
-
* @module
|
|
3358
|
-
*
|
|
3359
|
-
* @see https://0.vuetifyjs.com/composables/plugins/use-permissions
|
|
3625
|
+
* @module useResizeObserver
|
|
3360
3626
|
*
|
|
3361
3627
|
* @remarks
|
|
3362
|
-
*
|
|
3628
|
+
* ResizeObserver composable with lifecycle management.
|
|
3363
3629
|
*
|
|
3364
3630
|
* Key features:
|
|
3365
|
-
* -
|
|
3366
|
-
* -
|
|
3367
|
-
* -
|
|
3368
|
-
* -
|
|
3369
|
-
* -
|
|
3631
|
+
* - ResizeObserver API wrapper
|
|
3632
|
+
* - Pause/resume/stop functionality
|
|
3633
|
+
* - Automatic cleanup on unmount
|
|
3634
|
+
* - SSR-safe (checks SUPPORTS_OBSERVER)
|
|
3635
|
+
* - Hydration-aware
|
|
3636
|
+
* - Box model options (content-box/border-box)
|
|
3370
3637
|
*
|
|
3371
|
-
*
|
|
3638
|
+
* Perfect for responsive components and size-based rendering.
|
|
3372
3639
|
*/
|
|
3373
3640
|
/**
|
|
3374
|
-
*
|
|
3641
|
+
* A composable that uses the Resize Observer API to detect when an element's
|
|
3642
|
+
* size changes.
|
|
3375
3643
|
*
|
|
3376
|
-
* @param
|
|
3377
|
-
* @
|
|
3378
|
-
* @
|
|
3379
|
-
* @returns
|
|
3644
|
+
* @param target The element to observe.
|
|
3645
|
+
* @param callback The callback to execute when the element's size changes.
|
|
3646
|
+
* @param options The options for the Resize Observer.
|
|
3647
|
+
* @returns An object with methods to control the observer.
|
|
3380
3648
|
*
|
|
3381
|
-
* @see https://
|
|
3649
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver
|
|
3650
|
+
* @see https://0.vuetifyjs.com/composables/system/use-resize-observer
|
|
3382
3651
|
*
|
|
3383
3652
|
* @example
|
|
3384
3653
|
* ```ts
|
|
3385
|
-
* import {
|
|
3654
|
+
* import { ref } from 'vue'
|
|
3655
|
+
* import { useResizeObserver } from '@vuetify/v0'
|
|
3386
3656
|
*
|
|
3387
|
-
* const
|
|
3388
|
-
*
|
|
3389
|
-
*
|
|
3390
|
-
* admin: [['read', 'users']],
|
|
3391
|
-
* editor: [['edit', 'posts']],
|
|
3392
|
-
* },
|
|
3393
|
-
* })
|
|
3394
|
-
* ```
|
|
3395
|
-
*/
|
|
3396
|
-
function createPermissions(_options = {}) {
|
|
3397
|
-
const { adapter = new Vuetify0PermissionAdapter(), permissions = {},...options } = _options;
|
|
3398
|
-
const record = {};
|
|
3399
|
-
for (const role in permissions) {
|
|
3400
|
-
if (!record[role]) record[role] = {};
|
|
3401
|
-
for (const [actions, subjects, condition = true] of permissions[role]) for (const action of toArray(actions)) for (const subject of toArray(subjects)) {
|
|
3402
|
-
if (!record[role][action]) record[role][action] = {};
|
|
3403
|
-
record[role][action][subject] = condition;
|
|
3404
|
-
}
|
|
3405
|
-
}
|
|
3406
|
-
const tokens = createTokens(record, options);
|
|
3407
|
-
function can(id, action, subject, context = {}) {
|
|
3408
|
-
return adapter.can(id, action, subject, context, tokens);
|
|
3409
|
-
}
|
|
3410
|
-
return {
|
|
3411
|
-
...tokens,
|
|
3412
|
-
can
|
|
3413
|
-
};
|
|
3414
|
-
}
|
|
3415
|
-
/**
|
|
3416
|
-
* Creates a new permissions context.
|
|
3657
|
+
* const el = ref<HTMLElement>()
|
|
3658
|
+
* const width = ref(0)
|
|
3659
|
+
* const height = ref(0)
|
|
3417
3660
|
*
|
|
3418
|
-
*
|
|
3419
|
-
*
|
|
3420
|
-
*
|
|
3421
|
-
*
|
|
3661
|
+
* const { pause, resume, isPaused } = useResizeObserver(
|
|
3662
|
+
* el,
|
|
3663
|
+
* (entries) => {
|
|
3664
|
+
* const entry = entries[0]
|
|
3665
|
+
* if (entry) {
|
|
3666
|
+
* width.value = entry.contentRect.width
|
|
3667
|
+
* height.value = entry.contentRect.height
|
|
3668
|
+
* console.log('Size changed:', width.value, 'x', height.value)
|
|
3669
|
+
* }
|
|
3670
|
+
* },
|
|
3671
|
+
* { immediate: true }
|
|
3672
|
+
* )
|
|
3422
3673
|
*
|
|
3423
|
-
*
|
|
3674
|
+
* // Pause observation
|
|
3675
|
+
* pause()
|
|
3424
3676
|
*
|
|
3425
|
-
*
|
|
3426
|
-
*
|
|
3427
|
-
* import { createPermissionsContext } from '@vuetify/v0'
|
|
3428
|
-
*
|
|
3429
|
-
* export const [usePermissions, providePermissions, context] = createPermissionsContext({
|
|
3430
|
-
* namespace: 'app:permissions',
|
|
3431
|
-
* permissions: {
|
|
3432
|
-
* admin: [['read', 'users'], ['edit', 'users']],
|
|
3433
|
-
* editor: [['edit', 'posts']],
|
|
3434
|
-
* },
|
|
3435
|
-
* })
|
|
3677
|
+
* // Resume observation
|
|
3678
|
+
* resume()
|
|
3436
3679
|
* ```
|
|
3437
3680
|
*/
|
|
3438
|
-
function
|
|
3439
|
-
const {
|
|
3440
|
-
const
|
|
3441
|
-
const
|
|
3442
|
-
|
|
3443
|
-
|
|
3681
|
+
function useResizeObserver(target, callback, options = {}) {
|
|
3682
|
+
const { isHydrated } = useHydration();
|
|
3683
|
+
const observer = shallowRef();
|
|
3684
|
+
const isPaused = shallowRef(false);
|
|
3685
|
+
const isActive = toRef(() => !!observer.value);
|
|
3686
|
+
function setup() {
|
|
3687
|
+
if (!isHydrated.value || !SUPPORTS_OBSERVER || !target.value || isPaused.value) return;
|
|
3688
|
+
observer.value = new ResizeObserver((entries) => {
|
|
3689
|
+
callback(entries.map((entry) => ({
|
|
3690
|
+
contentRect: {
|
|
3691
|
+
width: entry.contentRect.width,
|
|
3692
|
+
height: entry.contentRect.height,
|
|
3693
|
+
top: entry.contentRect.top,
|
|
3694
|
+
left: entry.contentRect.left
|
|
3695
|
+
},
|
|
3696
|
+
target: entry.target
|
|
3697
|
+
})));
|
|
3698
|
+
});
|
|
3699
|
+
observer.value.observe(target.value, { box: options.box || "content-box" });
|
|
3700
|
+
if (options.immediate) {
|
|
3701
|
+
const rect = target.value.getBoundingClientRect();
|
|
3702
|
+
callback([{
|
|
3703
|
+
contentRect: {
|
|
3704
|
+
width: rect.width,
|
|
3705
|
+
height: rect.height,
|
|
3706
|
+
top: rect.top,
|
|
3707
|
+
left: rect.left
|
|
3708
|
+
},
|
|
3709
|
+
target: target.value
|
|
3710
|
+
}]);
|
|
3711
|
+
}
|
|
3444
3712
|
}
|
|
3445
|
-
|
|
3713
|
+
watch([isHydrated, target], () => {
|
|
3714
|
+
cleanup();
|
|
3715
|
+
setup();
|
|
3716
|
+
}, { immediate: true });
|
|
3717
|
+
function cleanup() {
|
|
3718
|
+
if (observer.value) {
|
|
3719
|
+
observer.value.disconnect();
|
|
3720
|
+
observer.value = void 0;
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
function pause() {
|
|
3724
|
+
isPaused.value = true;
|
|
3725
|
+
observer.value?.disconnect();
|
|
3726
|
+
}
|
|
3727
|
+
function resume() {
|
|
3728
|
+
isPaused.value = false;
|
|
3729
|
+
setup();
|
|
3730
|
+
}
|
|
3731
|
+
function stop() {
|
|
3732
|
+
cleanup();
|
|
3733
|
+
}
|
|
3734
|
+
onScopeDispose(stop, true);
|
|
3735
|
+
return {
|
|
3736
|
+
isActive: shallowReadonly(isActive),
|
|
3737
|
+
isPaused: shallowReadonly(isPaused),
|
|
3738
|
+
pause,
|
|
3739
|
+
resume,
|
|
3740
|
+
stop
|
|
3741
|
+
};
|
|
3446
3742
|
}
|
|
3447
3743
|
/**
|
|
3448
|
-
*
|
|
3744
|
+
* A convenience composable that uses the Resize Observer API to track an
|
|
3745
|
+
* element's size.
|
|
3449
3746
|
*
|
|
3450
|
-
* @param
|
|
3451
|
-
* @
|
|
3452
|
-
* @template E The type of the permission context.
|
|
3453
|
-
* @returns A new permissions plugin.
|
|
3747
|
+
* @param target The element to observe.
|
|
3748
|
+
* @returns An object with the element's width and height.
|
|
3454
3749
|
*
|
|
3455
|
-
* @see https://0.vuetifyjs.com/composables/
|
|
3750
|
+
* @see https://0.vuetifyjs.com/composables/system/use-resize-observer#use-element-size
|
|
3456
3751
|
*
|
|
3457
3752
|
* @example
|
|
3458
3753
|
* ```ts
|
|
3459
|
-
* import {
|
|
3460
|
-
* import {
|
|
3461
|
-
* import App from './App.vue'
|
|
3754
|
+
* import { ref, watchEffect } from 'vue'
|
|
3755
|
+
* import { useElementSize } from '@vuetify/v0'
|
|
3462
3756
|
*
|
|
3463
|
-
* const
|
|
3757
|
+
* const box = ref<HTMLElement>()
|
|
3758
|
+
* const { width, height } = useElementSize(box)
|
|
3464
3759
|
*
|
|
3465
|
-
*
|
|
3466
|
-
*
|
|
3467
|
-
*
|
|
3468
|
-
*
|
|
3469
|
-
*
|
|
3470
|
-
|
|
3760
|
+
* // Width and height are reactive refs
|
|
3761
|
+
* watchEffect(() => {
|
|
3762
|
+
* console.log('Box size:', width.value, 'x', height.value)
|
|
3763
|
+
* })
|
|
3764
|
+
* ```
|
|
3765
|
+
*/
|
|
3766
|
+
function useElementSize(target) {
|
|
3767
|
+
const width = shallowRef(0);
|
|
3768
|
+
const height = shallowRef(0);
|
|
3769
|
+
const { pause: _pause, resume, stop, isActive, isPaused } = useResizeObserver(target, (entries) => {
|
|
3770
|
+
const entry = entries[0];
|
|
3771
|
+
if (entry) {
|
|
3772
|
+
width.value = entry.contentRect.width;
|
|
3773
|
+
height.value = entry.contentRect.height;
|
|
3774
|
+
}
|
|
3775
|
+
}, { immediate: true });
|
|
3776
|
+
function pause() {
|
|
3777
|
+
width.value = 0;
|
|
3778
|
+
height.value = 0;
|
|
3779
|
+
_pause();
|
|
3780
|
+
}
|
|
3781
|
+
return {
|
|
3782
|
+
width,
|
|
3783
|
+
height,
|
|
3784
|
+
isActive,
|
|
3785
|
+
isPaused,
|
|
3786
|
+
pause,
|
|
3787
|
+
resume,
|
|
3788
|
+
stop
|
|
3789
|
+
};
|
|
3790
|
+
}
|
|
3791
|
+
|
|
3792
|
+
//#endregion
|
|
3793
|
+
//#region src/composables/useOverflow/index.ts
|
|
3794
|
+
/**
|
|
3795
|
+
* @module useOverflow
|
|
3796
|
+
*
|
|
3797
|
+
* @remarks
|
|
3798
|
+
* Composable for computing how many items fit in a container based on available width.
|
|
3799
|
+
* Enables responsive truncation logic for Pagination, Breadcrumbs, and similar components.
|
|
3800
|
+
*
|
|
3801
|
+
* Key features:
|
|
3802
|
+
* - Container width tracking via ResizeObserver
|
|
3803
|
+
* - Two modes: variable-width (per-item) or uniform-width (sample-based)
|
|
3804
|
+
* - Computes capacity (how many items fit)
|
|
3805
|
+
* - SSR-safe with Infinity fallback
|
|
3806
|
+
* - Supports reserved space for nav buttons, ellipsis, etc.
|
|
3807
|
+
*
|
|
3808
|
+
* Use variable mode (default) for items with different widths like Breadcrumbs.
|
|
3809
|
+
* Use uniform mode (itemWidth option) for same-width items like Pagination buttons.
|
|
3810
|
+
*/
|
|
3811
|
+
/**
|
|
3812
|
+
* Creates a new overflow context for computing how many items fit in a container.
|
|
3813
|
+
*
|
|
3814
|
+
* @param options Configuration options
|
|
3815
|
+
* @returns Overflow context with container ref, capacity, and measurement functions
|
|
3816
|
+
*
|
|
3817
|
+
* @example Variable-width mode (Breadcrumbs)
|
|
3818
|
+
* ```vue
|
|
3819
|
+
* <script lang="ts" setup>
|
|
3820
|
+
* import { useTemplateRef } from 'vue'
|
|
3821
|
+
* import { createOverflow } from '@vuetify/v0'
|
|
3822
|
+
*
|
|
3823
|
+
* const containerRef = useTemplateRef('container')
|
|
3824
|
+
* const overflow = createOverflow({
|
|
3825
|
+
* container: containerRef,
|
|
3826
|
+
* gap: 8,
|
|
3827
|
+
* reserved: 40,
|
|
3471
3828
|
* })
|
|
3472
|
-
*
|
|
3829
|
+
* <\/script>
|
|
3473
3830
|
*
|
|
3474
|
-
*
|
|
3831
|
+
* <template>
|
|
3832
|
+
* <div ref="container">
|
|
3833
|
+
* <span
|
|
3834
|
+
* v-for="(item, i) in items.slice(0, overflow.capacity.value)"
|
|
3835
|
+
* :key="i"
|
|
3836
|
+
* :ref="el => overflow.measure(i, el)"
|
|
3837
|
+
* >
|
|
3838
|
+
* {{ item }}
|
|
3839
|
+
* </span>
|
|
3840
|
+
* <span v-if="overflow.isOverflowing.value">...</span>
|
|
3841
|
+
* </div>
|
|
3842
|
+
* </template>
|
|
3843
|
+
* ```
|
|
3844
|
+
*
|
|
3845
|
+
* @example Uniform-width mode (Pagination)
|
|
3846
|
+
* ```ts
|
|
3847
|
+
* const overflow = createOverflow({
|
|
3848
|
+
* container: () => atom.value?.element,
|
|
3849
|
+
* itemWidth: buttonWidth,
|
|
3850
|
+
* reserved: () => buttonWidth.value * 4,
|
|
3851
|
+
* })
|
|
3475
3852
|
* ```
|
|
3476
3853
|
*/
|
|
3477
|
-
function
|
|
3478
|
-
const {
|
|
3479
|
-
const
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3854
|
+
function createOverflow(options = {}) {
|
|
3855
|
+
const { container: _container, gap = 0, reserved = 0, itemWidth, reverse } = options;
|
|
3856
|
+
const container = /* @__PURE__ */ isUndefined(_container) ? shallowRef() : toRef(_container);
|
|
3857
|
+
const widths = shallowRef(/* @__PURE__ */ new Map());
|
|
3858
|
+
const { width } = useElementSize(container);
|
|
3859
|
+
function measure(index, el) {
|
|
3860
|
+
if (!el) {
|
|
3861
|
+
if (widths.value.has(index)) {
|
|
3862
|
+
const next = new Map(widths.value);
|
|
3863
|
+
next.delete(index);
|
|
3864
|
+
widths.value = next;
|
|
3865
|
+
}
|
|
3866
|
+
return;
|
|
3867
|
+
}
|
|
3868
|
+
const style = getComputedStyle(el);
|
|
3869
|
+
const marginX = Number.parseFloat(style.marginLeft) + Number.parseFloat(style.marginRight);
|
|
3870
|
+
const w = el.offsetWidth + marginX;
|
|
3871
|
+
if (widths.value.get(index) !== w) widths.value = new Map(widths.value).set(index, w);
|
|
3872
|
+
}
|
|
3873
|
+
function reset() {
|
|
3874
|
+
widths.value = /* @__PURE__ */ new Map();
|
|
3875
|
+
}
|
|
3876
|
+
const total = computed(() => {
|
|
3877
|
+
const g = toValue(gap);
|
|
3878
|
+
let sum = 0;
|
|
3879
|
+
let count = 0;
|
|
3880
|
+
for (const w of widths.value.values()) {
|
|
3881
|
+
sum += w + (count > 0 ? g : 0);
|
|
3882
|
+
count++;
|
|
3487
3883
|
}
|
|
3884
|
+
return sum;
|
|
3488
3885
|
});
|
|
3886
|
+
return {
|
|
3887
|
+
container,
|
|
3888
|
+
width,
|
|
3889
|
+
capacity: computed(() => {
|
|
3890
|
+
const available = width.value - toValue(reserved);
|
|
3891
|
+
if (width.value === 0) return Infinity;
|
|
3892
|
+
if (available <= 0) return 0;
|
|
3893
|
+
const g = toValue(gap);
|
|
3894
|
+
const uniformWidth = toValue(itemWidth);
|
|
3895
|
+
if (uniformWidth && uniformWidth > 0) {
|
|
3896
|
+
const first = uniformWidth;
|
|
3897
|
+
const subsequent = uniformWidth + g;
|
|
3898
|
+
if (available < first) return 0;
|
|
3899
|
+
return Math.max(1, Math.floor((available - first) / subsequent) + 1);
|
|
3900
|
+
}
|
|
3901
|
+
const entries = [...widths.value.entries()].toSorted((a, b) => a[0] - b[0]);
|
|
3902
|
+
if (toValue(reverse)) entries.reverse();
|
|
3903
|
+
let sum = 0;
|
|
3904
|
+
let count = 0;
|
|
3905
|
+
for (const [, w] of entries) {
|
|
3906
|
+
const next = sum + w + (count > 0 ? g : 0);
|
|
3907
|
+
if (next > available) break;
|
|
3908
|
+
sum = next;
|
|
3909
|
+
count++;
|
|
3910
|
+
}
|
|
3911
|
+
return count;
|
|
3912
|
+
}),
|
|
3913
|
+
total,
|
|
3914
|
+
isOverflowing: toRef(() => {
|
|
3915
|
+
return total.value > width.value - toValue(reserved);
|
|
3916
|
+
}),
|
|
3917
|
+
measure,
|
|
3918
|
+
reset
|
|
3919
|
+
};
|
|
3489
3920
|
}
|
|
3490
3921
|
/**
|
|
3491
|
-
*
|
|
3922
|
+
* Creates an overflow context with dependency injection support.
|
|
3492
3923
|
*
|
|
3493
|
-
* @
|
|
3494
|
-
* @returns
|
|
3924
|
+
* @param options Configuration options including namespace
|
|
3925
|
+
* @returns Trinity tuple: [useContext, provideContext, defaultContext]
|
|
3495
3926
|
*
|
|
3496
|
-
* @
|
|
3927
|
+
* @example
|
|
3928
|
+
* ```ts
|
|
3929
|
+
* // Create injectable context
|
|
3930
|
+
* const [useOverflow, provideOverflow, overflow] = createOverflowContext({
|
|
3931
|
+
* namespace: 'my-overflow',
|
|
3932
|
+
* gap: 8,
|
|
3933
|
+
* reserved: 160,
|
|
3934
|
+
* })
|
|
3935
|
+
*
|
|
3936
|
+
* // In parent component
|
|
3937
|
+
* provideOverflow()
|
|
3938
|
+
*
|
|
3939
|
+
* // In child component
|
|
3940
|
+
* const overflow = useOverflow()
|
|
3941
|
+
* ```
|
|
3942
|
+
*/
|
|
3943
|
+
function createOverflowContext(_options = {}) {
|
|
3944
|
+
const { namespace = "v0:overflow",...options } = _options;
|
|
3945
|
+
const [useOverflowContext, _provideOverflowContext] = createContext(namespace);
|
|
3946
|
+
const context = createOverflow(options);
|
|
3947
|
+
function provideOverflowContext(_context = context, app) {
|
|
3948
|
+
return _provideOverflowContext(_context, app);
|
|
3949
|
+
}
|
|
3950
|
+
return createTrinity(useOverflowContext, provideOverflowContext, context);
|
|
3951
|
+
}
|
|
3952
|
+
/**
|
|
3953
|
+
* Returns the current overflow context from dependency injection.
|
|
3954
|
+
*
|
|
3955
|
+
* @param namespace The namespace for the overflow context. Defaults to `v0:overflow`.
|
|
3956
|
+
* @returns The current overflow context.
|
|
3497
3957
|
*
|
|
3498
3958
|
* @example
|
|
3499
3959
|
* ```vue
|
|
3500
|
-
* <script
|
|
3501
|
-
* import {
|
|
3960
|
+
* <script lang="ts" setup>
|
|
3961
|
+
* import { useOverflow } from '@vuetify/v0'
|
|
3502
3962
|
*
|
|
3503
|
-
*
|
|
3963
|
+
* // Inject overflow context provided by parent
|
|
3964
|
+
* const overflow = useOverflow()
|
|
3504
3965
|
* <\/script>
|
|
3505
3966
|
*
|
|
3506
3967
|
* <template>
|
|
3507
3968
|
* <div>
|
|
3508
|
-
* <p
|
|
3969
|
+
* <p>Capacity: {{ overflow.capacity.value }}</p>
|
|
3509
3970
|
* </div>
|
|
3510
3971
|
* </template>
|
|
3511
3972
|
* ```
|
|
3512
3973
|
*/
|
|
3513
|
-
function
|
|
3974
|
+
function useOverflow(namespace = "v0:overflow") {
|
|
3514
3975
|
return useContext(namespace);
|
|
3515
3976
|
}
|
|
3516
3977
|
|
|
3517
3978
|
//#endregion
|
|
3518
|
-
//#region src/composables/
|
|
3979
|
+
//#region src/composables/usePagination/index.ts
|
|
3519
3980
|
/**
|
|
3520
|
-
* @module
|
|
3981
|
+
* @module usePagination
|
|
3521
3982
|
*
|
|
3522
3983
|
* @remarks
|
|
3523
|
-
*
|
|
3984
|
+
* Lightweight pagination composable for navigating through pages.
|
|
3524
3985
|
*
|
|
3525
3986
|
* Key features:
|
|
3526
|
-
* -
|
|
3527
|
-
* -
|
|
3528
|
-
* -
|
|
3529
|
-
* -
|
|
3530
|
-
*
|
|
3531
|
-
*
|
|
3987
|
+
* - No registry overhead - just a bounded integer
|
|
3988
|
+
* - Direct ref support for v-model compatibility
|
|
3989
|
+
* - Navigation methods: next, prev, first, last
|
|
3990
|
+
* - Computed visible items with ellipsis
|
|
3991
|
+
* - Trinity pattern for dependency injection
|
|
3992
|
+
*
|
|
3993
|
+
* Unlike registry-based composables, pagination tracks a single number
|
|
3994
|
+
* within a range, making it efficient for large page counts.
|
|
3532
3995
|
*/
|
|
3533
3996
|
/**
|
|
3534
|
-
*
|
|
3535
|
-
*
|
|
3536
|
-
* @param registry The selection registry to bind to.
|
|
3537
|
-
* @param model The ref to sync.
|
|
3538
|
-
* @param options The options for the proxy model.
|
|
3539
|
-
* @template Z The type of the selection ticket.
|
|
3540
|
-
* @returns A function to stop the sync.
|
|
3997
|
+
* Creates a pagination instance.
|
|
3541
3998
|
*
|
|
3542
|
-
* @
|
|
3999
|
+
* @param options The options for the pagination instance.
|
|
4000
|
+
* @returns A pagination context with navigation methods.
|
|
3543
4001
|
*
|
|
3544
4002
|
* @example
|
|
3545
4003
|
* ```ts
|
|
3546
|
-
* import {
|
|
4004
|
+
* import { createPagination } from '@vuetify/v0'
|
|
3547
4005
|
*
|
|
3548
|
-
*
|
|
3549
|
-
* const
|
|
3550
|
-
*
|
|
3551
|
-
*
|
|
3552
|
-
* { id: 'item-2', value: 'Item 2' },
|
|
3553
|
-
* ])
|
|
4006
|
+
* // Basic usage
|
|
4007
|
+
* const pagination = createPagination({ size: 100 })
|
|
4008
|
+
* pagination.next()
|
|
4009
|
+
* pagination.items.value // [{ type: 'page', value: 1 }, { type: 'page', value: 2 }, ...]
|
|
3554
4010
|
*
|
|
3555
|
-
*
|
|
4011
|
+
* // With v-model (pass a ref)
|
|
4012
|
+
* const page = ref(1)
|
|
4013
|
+
* const pagination = createPagination({ page, size: 100 })
|
|
4014
|
+
* // Mutating pagination.page or the passed ref syncs both
|
|
3556
4015
|
* ```
|
|
3557
4016
|
*/
|
|
3558
|
-
function
|
|
3559
|
-
const
|
|
3560
|
-
const
|
|
3561
|
-
const
|
|
3562
|
-
|
|
3563
|
-
const
|
|
3564
|
-
|
|
4017
|
+
function createPagination(_options = {}) {
|
|
4018
|
+
const { page: _page = 1, itemsPerPage: _itemsPerPage = 10, size: _size = 0, visible: _visible = 7, ellipsis = "..." } = _options;
|
|
4019
|
+
const page = isRef(_page) ? _page : shallowRef(_page);
|
|
4020
|
+
const pages = computed(() => {
|
|
4021
|
+
const size = toValue(_size);
|
|
4022
|
+
const perPage = toValue(_itemsPerPage);
|
|
4023
|
+
if (size <= 0 || /* @__PURE__ */ isNaN(size)) return 0;
|
|
4024
|
+
return Math.ceil(size / perPage);
|
|
4025
|
+
});
|
|
4026
|
+
function first() {
|
|
4027
|
+
page.value = 1;
|
|
3565
4028
|
}
|
|
3566
|
-
function
|
|
3567
|
-
|
|
3568
|
-
return multiple ? val : val[0];
|
|
4029
|
+
function last() {
|
|
4030
|
+
page.value = pages.value;
|
|
3569
4031
|
}
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
for (const value of modelAsArray) {
|
|
3573
|
-
const ids = registry.browse(value);
|
|
3574
|
-
if (/* @__PURE__ */ isArray(ids)) {
|
|
3575
|
-
for (const id of ids) registry.select(id);
|
|
3576
|
-
pending.delete(value);
|
|
3577
|
-
} else if (ids) {
|
|
3578
|
-
registry.select(ids);
|
|
3579
|
-
pending.delete(value);
|
|
3580
|
-
}
|
|
4032
|
+
function next() {
|
|
4033
|
+
if (page.value < pages.value) page.value++;
|
|
3581
4034
|
}
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
4035
|
+
function prev() {
|
|
4036
|
+
if (page.value > 1) page.value--;
|
|
4037
|
+
}
|
|
4038
|
+
function select(value) {
|
|
4039
|
+
if (value < 1) page.value = 1;
|
|
4040
|
+
else if (value > pages.value) page.value = pages.value;
|
|
4041
|
+
else page.value = value;
|
|
4042
|
+
}
|
|
4043
|
+
const isFirst = computed(() => page.value <= 1);
|
|
4044
|
+
const isLast = computed(() => page.value >= pages.value);
|
|
4045
|
+
const pageStart = computed(() => (page.value - 1) * toValue(_itemsPerPage));
|
|
4046
|
+
const pageStop = computed(() => Math.min(pageStart.value + toValue(_itemsPerPage), toValue(_size)));
|
|
4047
|
+
function toPage(value) {
|
|
4048
|
+
return {
|
|
4049
|
+
type: "page",
|
|
4050
|
+
value
|
|
4051
|
+
};
|
|
4052
|
+
}
|
|
4053
|
+
function toEllipsis() {
|
|
4054
|
+
return ellipsis === false ? false : {
|
|
4055
|
+
type: "ellipsis",
|
|
4056
|
+
value: ellipsis
|
|
4057
|
+
};
|
|
4058
|
+
}
|
|
4059
|
+
function filter(array) {
|
|
4060
|
+
return array.filter(Boolean);
|
|
4061
|
+
}
|
|
4062
|
+
return {
|
|
4063
|
+
page,
|
|
4064
|
+
ellipsis,
|
|
4065
|
+
items: computed(() => {
|
|
4066
|
+
const pageCount = pages.value;
|
|
4067
|
+
const visible = toValue(_visible);
|
|
4068
|
+
const current = page.value;
|
|
4069
|
+
if (pageCount <= 0 || /* @__PURE__ */ isNaN(pageCount) || pageCount > Number.MAX_SAFE_INTEGER) return [];
|
|
4070
|
+
if (visible <= 0) return [];
|
|
4071
|
+
if (visible <= 2) return [toPage(current)];
|
|
4072
|
+
if (pageCount <= visible) return (/* @__PURE__ */ range(pageCount, 1)).map(toPage);
|
|
4073
|
+
if (visible === 3) {
|
|
4074
|
+
const mid = current <= 1 ? 2 : current >= pageCount ? pageCount - 1 : current;
|
|
4075
|
+
return [
|
|
4076
|
+
toPage(1),
|
|
4077
|
+
toPage(mid),
|
|
4078
|
+
toPage(pageCount)
|
|
4079
|
+
];
|
|
4080
|
+
}
|
|
4081
|
+
const boundary = visible - 2;
|
|
4082
|
+
const middle = visible - 4;
|
|
4083
|
+
if (middle <= 0) {
|
|
4084
|
+
if (current <= boundary) return filter([
|
|
4085
|
+
...(/* @__PURE__ */ range(boundary, 1)).map(toPage),
|
|
4086
|
+
toEllipsis(),
|
|
4087
|
+
toPage(pageCount)
|
|
4088
|
+
]);
|
|
4089
|
+
if (current > pageCount - boundary) return filter([
|
|
4090
|
+
toPage(1),
|
|
4091
|
+
toEllipsis(),
|
|
4092
|
+
...(/* @__PURE__ */ range(boundary, pageCount - boundary + 1)).map(toPage)
|
|
4093
|
+
]);
|
|
4094
|
+
return current <= Math.ceil(pageCount / 2) ? filter([
|
|
4095
|
+
toPage(1),
|
|
4096
|
+
toPage(current),
|
|
4097
|
+
toEllipsis(),
|
|
4098
|
+
toPage(pageCount)
|
|
4099
|
+
]) : filter([
|
|
4100
|
+
toPage(1),
|
|
4101
|
+
toEllipsis(),
|
|
4102
|
+
toPage(current),
|
|
4103
|
+
toPage(pageCount)
|
|
4104
|
+
]);
|
|
4105
|
+
}
|
|
4106
|
+
const leftThreshold = boundary - 1;
|
|
4107
|
+
const rightThreshold = pageCount - boundary + 2;
|
|
4108
|
+
if (current <= leftThreshold) return filter([
|
|
4109
|
+
...(/* @__PURE__ */ range(boundary, 1)).map(toPage),
|
|
4110
|
+
toEllipsis(),
|
|
4111
|
+
toPage(pageCount)
|
|
4112
|
+
]);
|
|
4113
|
+
else if (current >= rightThreshold) return filter([
|
|
4114
|
+
toPage(1),
|
|
4115
|
+
toEllipsis(),
|
|
4116
|
+
...(/* @__PURE__ */ range(boundary, pageCount - boundary + 1)).map(toPage)
|
|
4117
|
+
]);
|
|
4118
|
+
else {
|
|
4119
|
+
const start = current - Math.floor(middle / 2);
|
|
4120
|
+
return filter([
|
|
4121
|
+
toPage(1),
|
|
4122
|
+
toEllipsis(),
|
|
4123
|
+
...(/* @__PURE__ */ range(middle, start)).map(toPage),
|
|
4124
|
+
toEllipsis(),
|
|
4125
|
+
toPage(pageCount)
|
|
4126
|
+
]);
|
|
4127
|
+
}
|
|
4128
|
+
}),
|
|
4129
|
+
pageStart,
|
|
4130
|
+
pageStop,
|
|
4131
|
+
isFirst,
|
|
4132
|
+
isLast,
|
|
4133
|
+
first,
|
|
4134
|
+
last,
|
|
4135
|
+
next,
|
|
4136
|
+
prev,
|
|
4137
|
+
select,
|
|
4138
|
+
get itemsPerPage() {
|
|
4139
|
+
return toValue(_itemsPerPage);
|
|
4140
|
+
},
|
|
4141
|
+
get size() {
|
|
4142
|
+
return toValue(_size);
|
|
4143
|
+
},
|
|
4144
|
+
get pages() {
|
|
4145
|
+
return pages.value;
|
|
3604
4146
|
}
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
4147
|
+
};
|
|
4148
|
+
}
|
|
4149
|
+
/**
|
|
4150
|
+
* Creates a pagination context for dependency injection.
|
|
4151
|
+
*
|
|
4152
|
+
* @param options The options including namespace.
|
|
4153
|
+
* @returns A trinity: [usePagination, providePagination, defaultContext]
|
|
4154
|
+
*
|
|
4155
|
+
* @example
|
|
4156
|
+
* ```ts
|
|
4157
|
+
* // With default namespace 'v0:pagination'
|
|
4158
|
+
* const [usePagination, providePaginationContext] = createPaginationContext({ size: 50 })
|
|
4159
|
+
*
|
|
4160
|
+
* // Or with custom namespace
|
|
4161
|
+
* const [usePagination, providePaginationContext] = createPaginationContext({
|
|
4162
|
+
* namespace: 'my-pagination',
|
|
4163
|
+
* size: 50,
|
|
4164
|
+
* })
|
|
4165
|
+
*
|
|
4166
|
+
* // Parent component
|
|
4167
|
+
* providePaginationContext()
|
|
4168
|
+
*
|
|
4169
|
+
* // Child component
|
|
4170
|
+
* const pagination = usePagination()
|
|
4171
|
+
* pagination.next()
|
|
4172
|
+
* ```
|
|
4173
|
+
*/
|
|
4174
|
+
function createPaginationContext(_options = {}) {
|
|
4175
|
+
const { namespace = "v0:pagination",...options } = _options;
|
|
4176
|
+
const [usePaginationContext, _providePaginationContext] = createContext(namespace);
|
|
4177
|
+
const context = createPagination(options);
|
|
4178
|
+
function providePaginationContext(_context = context, app) {
|
|
4179
|
+
return _providePaginationContext(_context, app);
|
|
4180
|
+
}
|
|
4181
|
+
return createTrinity(usePaginationContext, providePaginationContext, context);
|
|
4182
|
+
}
|
|
4183
|
+
/**
|
|
4184
|
+
* Returns the current pagination instance from context.
|
|
4185
|
+
*
|
|
4186
|
+
* @param namespace The namespace. @default 'v0:pagination'
|
|
4187
|
+
* @returns The pagination context.
|
|
4188
|
+
*
|
|
4189
|
+
* @example
|
|
4190
|
+
* ```vue
|
|
4191
|
+
* <script setup>
|
|
4192
|
+
* import { usePagination } from '@vuetify/v0'
|
|
4193
|
+
*
|
|
4194
|
+
* const pagination = usePagination()
|
|
4195
|
+
* <\/script>
|
|
4196
|
+
*
|
|
4197
|
+
* <template>
|
|
4198
|
+
* <button @click="pagination.prev()" :disabled="pagination.isFirst.value">Prev</button>
|
|
4199
|
+
* <button @click="pagination.next()" :disabled="pagination.isLast.value">Next</button>
|
|
4200
|
+
* </template>
|
|
4201
|
+
* ```
|
|
4202
|
+
*/
|
|
4203
|
+
function usePagination(namespace = "v0:pagination") {
|
|
4204
|
+
return useContext(namespace);
|
|
4205
|
+
}
|
|
4206
|
+
|
|
4207
|
+
//#endregion
|
|
4208
|
+
//#region src/composables/usePermissions/adapters/adapter.ts
|
|
4209
|
+
var PermissionAdapter = class {};
|
|
4210
|
+
|
|
4211
|
+
//#endregion
|
|
4212
|
+
//#region src/composables/usePermissions/adapters/v0.ts
|
|
4213
|
+
var Vuetify0PermissionAdapter = class extends PermissionAdapter {
|
|
4214
|
+
constructor() {
|
|
4215
|
+
super();
|
|
3618
4216
|
}
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
4217
|
+
can(role, action, subject, context, permissions) {
|
|
4218
|
+
const access = `${role}.${action}.${subject}`;
|
|
4219
|
+
const ticket = permissions.get(access);
|
|
4220
|
+
if (!ticket || !ticket.value) return false;
|
|
4221
|
+
return /* @__PURE__ */ isFunction(ticket.value) ? ticket.value(context) : ticket.value;
|
|
3624
4222
|
}
|
|
3625
|
-
|
|
3626
|
-
|
|
4223
|
+
};
|
|
4224
|
+
|
|
4225
|
+
//#endregion
|
|
4226
|
+
//#region src/composables/usePermissions/index.ts
|
|
4227
|
+
/**
|
|
4228
|
+
* @module usePermissions
|
|
4229
|
+
*
|
|
4230
|
+
* @see https://0.vuetifyjs.com/composables/plugins/use-permissions
|
|
4231
|
+
*
|
|
4232
|
+
* @remarks
|
|
4233
|
+
* Permission management composable with support for RBAC and ABAC patterns.
|
|
4234
|
+
*
|
|
4235
|
+
* Key features:
|
|
4236
|
+
* - Role-Based Access Control (RBAC) support
|
|
4237
|
+
* - Attribute-Based Access Control (ABAC) with context
|
|
4238
|
+
* - Functional permission conditions
|
|
4239
|
+
* - Token-based permission storage
|
|
4240
|
+
* - Adapter pattern for custom permission systems
|
|
4241
|
+
*
|
|
4242
|
+
* Built on useTokens for flexible permission configuration.
|
|
4243
|
+
*/
|
|
4244
|
+
/**
|
|
4245
|
+
* Creates a new permissions instance.
|
|
4246
|
+
*
|
|
4247
|
+
* @param options The options for the permissions instance.
|
|
4248
|
+
* @template Z The type of the permission ticket.
|
|
4249
|
+
* @template E The type of the permission context.
|
|
4250
|
+
* @returns A new permissions instance.
|
|
4251
|
+
*
|
|
4252
|
+
* @see https://0.vuetifyjs.com/composables/plugins/use-permissions
|
|
4253
|
+
*
|
|
4254
|
+
* @example
|
|
4255
|
+
* ```ts
|
|
4256
|
+
* import { createPermissions } from '@vuetify/v0'
|
|
4257
|
+
*
|
|
4258
|
+
* const [usePermissions, providePermissions] = createPermissions({
|
|
4259
|
+
* namespace: 'v0:permissions',
|
|
4260
|
+
* permissions: {
|
|
4261
|
+
* admin: [['read', 'users']],
|
|
4262
|
+
* editor: [['edit', 'posts']],
|
|
4263
|
+
* },
|
|
4264
|
+
* })
|
|
4265
|
+
* ```
|
|
4266
|
+
*/
|
|
4267
|
+
function createPermissions(_options = {}) {
|
|
4268
|
+
const { adapter = new Vuetify0PermissionAdapter(), permissions = {},...options } = _options;
|
|
4269
|
+
const record = {};
|
|
4270
|
+
for (const role in permissions) {
|
|
4271
|
+
if (!record[role]) record[role] = {};
|
|
4272
|
+
for (const [actions, subjects, condition = true] of permissions[role]) for (const action of toArray(actions)) for (const subject of toArray(subjects)) {
|
|
4273
|
+
if (!record[role][action]) record[role][action] = {};
|
|
4274
|
+
record[role][action][subject] = condition;
|
|
4275
|
+
}
|
|
4276
|
+
}
|
|
4277
|
+
const tokens = createTokens(record, options);
|
|
4278
|
+
function can(id, action, subject, context = {}) {
|
|
4279
|
+
return adapter.can(id, action, subject, context, tokens);
|
|
4280
|
+
}
|
|
4281
|
+
return {
|
|
4282
|
+
...tokens,
|
|
4283
|
+
can
|
|
4284
|
+
};
|
|
4285
|
+
}
|
|
4286
|
+
/**
|
|
4287
|
+
* Creates a new permissions context.
|
|
4288
|
+
*
|
|
4289
|
+
* @param options The options for the permissions context.
|
|
4290
|
+
* @template Z The type of the permission ticket.
|
|
4291
|
+
* @template E The type of the permission context.
|
|
4292
|
+
* @returns A new permissions context.
|
|
4293
|
+
*
|
|
4294
|
+
* @see https://0.vuetifyjs.com/composables/plugins/use-permissions
|
|
4295
|
+
*
|
|
4296
|
+
* @example
|
|
4297
|
+
* ```ts
|
|
4298
|
+
* import { createPermissionsContext } from '@vuetify/v0'
|
|
4299
|
+
*
|
|
4300
|
+
* export const [usePermissions, providePermissions, context] = createPermissionsContext({
|
|
4301
|
+
* namespace: 'app:permissions',
|
|
4302
|
+
* permissions: {
|
|
4303
|
+
* admin: [['read', 'users'], ['edit', 'users']],
|
|
4304
|
+
* editor: [['edit', 'posts']],
|
|
4305
|
+
* },
|
|
4306
|
+
* })
|
|
4307
|
+
* ```
|
|
4308
|
+
*/
|
|
4309
|
+
function createPermissionsContext(_options = {}) {
|
|
4310
|
+
const { namespace = "v0:permissions",...options } = _options;
|
|
4311
|
+
const [usePermissionsContext, _providePermissionsContext] = createContext(namespace);
|
|
4312
|
+
const context = createPermissions(options);
|
|
4313
|
+
function providePermissionsContext(_context = context, app) {
|
|
4314
|
+
return _providePermissionsContext(_context, app);
|
|
4315
|
+
}
|
|
4316
|
+
return createTrinity(usePermissionsContext, providePermissionsContext, context);
|
|
4317
|
+
}
|
|
4318
|
+
/**
|
|
4319
|
+
* Creates a new permissions plugin.
|
|
4320
|
+
*
|
|
4321
|
+
* @param options The options for the permissions plugin.
|
|
4322
|
+
* @template Z The type of the permission ticket.
|
|
4323
|
+
* @template E The type of the permission context.
|
|
4324
|
+
* @returns A new permissions plugin.
|
|
4325
|
+
*
|
|
4326
|
+
* @see https://0.vuetifyjs.com/composables/plugins/use-permissions
|
|
4327
|
+
*
|
|
4328
|
+
* @example
|
|
4329
|
+
* ```ts
|
|
4330
|
+
* import { createApp } from 'vue'
|
|
4331
|
+
* import { createPermissionsPlugin } from '@vuetify/v0'
|
|
4332
|
+
* import App from './App.vue'
|
|
4333
|
+
*
|
|
4334
|
+
* const app = createApp(App)
|
|
4335
|
+
*
|
|
4336
|
+
* app.use(
|
|
4337
|
+
* createPermissionsPlugin({
|
|
4338
|
+
* permissions: {
|
|
4339
|
+
* admin: [['read', 'users']],
|
|
4340
|
+
* editor: [['edit', 'posts']],
|
|
4341
|
+
* },
|
|
4342
|
+
* })
|
|
4343
|
+
* )
|
|
4344
|
+
*
|
|
4345
|
+
* app.mount('#app')
|
|
4346
|
+
* ```
|
|
4347
|
+
*/
|
|
4348
|
+
function createPermissionsPlugin(_options = {}) {
|
|
4349
|
+
const { namespace = "v0:permissions",...options } = _options;
|
|
4350
|
+
const [, providePermissionContext, context] = createPermissionsContext({
|
|
4351
|
+
...options,
|
|
4352
|
+
namespace
|
|
4353
|
+
});
|
|
4354
|
+
return createPlugin({
|
|
4355
|
+
namespace,
|
|
4356
|
+
provide: (app) => {
|
|
4357
|
+
providePermissionContext(context, app);
|
|
4358
|
+
}
|
|
4359
|
+
});
|
|
4360
|
+
}
|
|
4361
|
+
/**
|
|
4362
|
+
* Returns the current permissions instance.
|
|
4363
|
+
*
|
|
4364
|
+
* @template Z The type of the permission ticket.
|
|
4365
|
+
* @returns The current permissions instance.
|
|
4366
|
+
*
|
|
4367
|
+
* @see https://0.vuetifyjs.com/composables/plugins/use-permissions
|
|
4368
|
+
*
|
|
4369
|
+
* @example
|
|
4370
|
+
* ```vue
|
|
4371
|
+
* <script setup lang="ts">
|
|
4372
|
+
* import { usePermissions } from '@vuetify/v0'
|
|
4373
|
+
*
|
|
4374
|
+
* const { can } = usePermissions()
|
|
4375
|
+
* <\/script>
|
|
4376
|
+
*
|
|
4377
|
+
* <template>
|
|
4378
|
+
* <div>
|
|
4379
|
+
* <p v-if="can('admin', 'read', 'users')">Admin access</p>
|
|
4380
|
+
* </div>
|
|
4381
|
+
* </template>
|
|
4382
|
+
* ```
|
|
4383
|
+
*/
|
|
4384
|
+
function usePermissions(namespace = "v0:permissions") {
|
|
4385
|
+
return useContext(namespace);
|
|
3627
4386
|
}
|
|
3628
4387
|
|
|
3629
4388
|
//#endregion
|
|
3630
|
-
//#region src/composables/
|
|
4389
|
+
//#region src/composables/useProxyModel/index.ts
|
|
3631
4390
|
/**
|
|
3632
|
-
* @module
|
|
4391
|
+
* @module useProxyModel
|
|
3633
4392
|
*
|
|
3634
4393
|
* @remarks
|
|
3635
|
-
* Proxy composable for
|
|
4394
|
+
* Proxy composable for bidirectional sync between selection registry and v-model.
|
|
3636
4395
|
*
|
|
3637
4396
|
* Key features:
|
|
3638
|
-
* -
|
|
3639
|
-
* -
|
|
3640
|
-
* - Event-based updates
|
|
4397
|
+
* - Bidirectional synchronization
|
|
4398
|
+
* - Array and single-value modes
|
|
3641
4399
|
* - Automatic cleanup on scope disposal
|
|
3642
|
-
* -
|
|
4400
|
+
* - Perfect for form controls with selection backing
|
|
3643
4401
|
*
|
|
3644
|
-
*
|
|
4402
|
+
* Bridges the gap between selection composables and Vue's v-model.
|
|
3645
4403
|
*/
|
|
3646
4404
|
/**
|
|
3647
|
-
*
|
|
4405
|
+
* Syncs a ref with a selection registry bidirectionally.
|
|
3648
4406
|
*
|
|
3649
|
-
* @param registry The registry
|
|
3650
|
-
* @param
|
|
3651
|
-
* @
|
|
3652
|
-
* @
|
|
4407
|
+
* @param registry The selection registry to bind to.
|
|
4408
|
+
* @param model The ref to sync.
|
|
4409
|
+
* @param options The options for the proxy model.
|
|
4410
|
+
* @template Z The type of the selection ticket.
|
|
4411
|
+
* @returns A function to stop the sync.
|
|
3653
4412
|
*
|
|
3654
|
-
* @see https://0.vuetifyjs.com/composables/
|
|
4413
|
+
* @see https://0.vuetifyjs.com/composables/forms/use-proxy-model
|
|
3655
4414
|
*
|
|
3656
4415
|
* @example
|
|
3657
4416
|
* ```ts
|
|
3658
|
-
* import {
|
|
4417
|
+
* import { createSelection, useProxyModel } from '@vuetify/v0'
|
|
3659
4418
|
*
|
|
3660
|
-
* const
|
|
3661
|
-
* const
|
|
4419
|
+
* const model = ref()
|
|
4420
|
+
* const registry = createSelection({ events: true })
|
|
4421
|
+
* registry.onboard([
|
|
4422
|
+
* { id: 'item-1', value: 'Item 1' },
|
|
4423
|
+
* { id: 'item-2', value: 'Item 2' },
|
|
4424
|
+
* ])
|
|
3662
4425
|
*
|
|
3663
|
-
*
|
|
3664
|
-
* console.log(proxy.size) // 1
|
|
4426
|
+
* const stop = useProxyModel(registry, model)
|
|
3665
4427
|
* ```
|
|
3666
4428
|
*/
|
|
3667
|
-
function
|
|
3668
|
-
const
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
4429
|
+
function useProxyModel(registry, model, options) {
|
|
4430
|
+
const multiple = options?.multiple ?? false;
|
|
4431
|
+
const _transformIn = options?.transformIn;
|
|
4432
|
+
const _transformOut = options?.transformOut;
|
|
4433
|
+
function transformIn(val) {
|
|
4434
|
+
const value = toValue(val);
|
|
4435
|
+
return toArray(/* @__PURE__ */ isFunction(_transformIn) ? _transformIn(value) : value);
|
|
4436
|
+
}
|
|
4437
|
+
function transformOut(val) {
|
|
4438
|
+
if (/* @__PURE__ */ isFunction(_transformOut)) return _transformOut(val);
|
|
4439
|
+
return multiple ? val : val[0];
|
|
4440
|
+
}
|
|
4441
|
+
const modelAsArray = transformIn(model);
|
|
4442
|
+
const pending = new Set(modelAsArray);
|
|
4443
|
+
for (const value of modelAsArray) {
|
|
4444
|
+
const ids = registry.browse(value);
|
|
4445
|
+
if (/* @__PURE__ */ isArray(ids)) {
|
|
4446
|
+
for (const id of ids) registry.select(id);
|
|
4447
|
+
pending.delete(value);
|
|
4448
|
+
} else if (ids) {
|
|
4449
|
+
registry.select(ids);
|
|
4450
|
+
pending.delete(value);
|
|
4451
|
+
}
|
|
4452
|
+
}
|
|
4453
|
+
const registryWatch = watch(registry.selectedValues, (val) => {
|
|
4454
|
+
modelWatch.pause();
|
|
4455
|
+
model.value = transformOut(Array.from(toValue(val)));
|
|
4456
|
+
modelWatch.resume();
|
|
4457
|
+
}, { flush: "sync" });
|
|
4458
|
+
const modelWatch = watch(model, (val) => {
|
|
4459
|
+
registryWatch.pause();
|
|
4460
|
+
const currentIds = new Set(toValue(registry.selectedIds));
|
|
4461
|
+
const targetIds = /* @__PURE__ */ new Set();
|
|
4462
|
+
for (const value of transformIn(val)) {
|
|
4463
|
+
const ids = registry.browse(value);
|
|
4464
|
+
if (/* @__PURE__ */ isArray(ids)) for (const single of ids) targetIds.add(single);
|
|
4465
|
+
else if (ids) targetIds.add(ids);
|
|
4466
|
+
}
|
|
4467
|
+
if (multiple) {
|
|
4468
|
+
for (const id of currentIds.difference(targetIds)) registry.selectedIds.delete(id);
|
|
4469
|
+
for (const id of targetIds.difference(currentIds)) registry.selectedIds.add(id);
|
|
4470
|
+
} else {
|
|
4471
|
+
const next = targetIds.values().next().value;
|
|
4472
|
+
const last = currentIds.values().next().value;
|
|
4473
|
+
if (!/* @__PURE__ */ isUndefined(last)) registry.unselect(last);
|
|
4474
|
+
if (!/* @__PURE__ */ isUndefined(next)) registry.select(next);
|
|
4475
|
+
}
|
|
4476
|
+
registryWatch.resume();
|
|
4477
|
+
}, {
|
|
4478
|
+
flush: "sync",
|
|
4479
|
+
deep: multiple
|
|
3673
4480
|
});
|
|
3674
|
-
function
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
4481
|
+
function onRegister(ticket) {
|
|
4482
|
+
if (!pending.has(ticket.value) || ticket.disabled) return;
|
|
4483
|
+
registryWatch.pause();
|
|
4484
|
+
modelWatch.pause();
|
|
4485
|
+
registry.select(ticket.id);
|
|
4486
|
+
pending.delete(ticket.value);
|
|
4487
|
+
modelWatch.resume();
|
|
4488
|
+
registryWatch.resume();
|
|
3679
4489
|
}
|
|
3680
|
-
registry.on("register:ticket",
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
registry.off("clear:registry", update);
|
|
3689
|
-
}, true);
|
|
3690
|
-
return state;
|
|
4490
|
+
registry.on("register:ticket", onRegister);
|
|
4491
|
+
function stop() {
|
|
4492
|
+
registryWatch();
|
|
4493
|
+
modelWatch();
|
|
4494
|
+
registry.off("register:ticket", onRegister);
|
|
4495
|
+
}
|
|
4496
|
+
onScopeDispose(stop, true);
|
|
4497
|
+
return stop;
|
|
3691
4498
|
}
|
|
3692
4499
|
|
|
3693
4500
|
//#endregion
|
|
@@ -3736,305 +4543,149 @@ function useProxyRegistry(registry, options) {
|
|
|
3736
4543
|
* ticket3.dismiss()
|
|
3737
4544
|
*
|
|
3738
4545
|
* console.log(queue.size) // 2
|
|
3739
|
-
* ```
|
|
3740
|
-
*/
|
|
3741
|
-
function createQueue(_options = {}) {
|
|
3742
|
-
const { timeout: _timeout = 3e3,...options } = _options;
|
|
3743
|
-
const registry = useRegistry({
|
|
3744
|
-
...options,
|
|
3745
|
-
events: true
|
|
3746
|
-
});
|
|
3747
|
-
const timeouts = /* @__PURE__ */ new Map();
|
|
3748
|
-
function startTimeout(ticket) {
|
|
3749
|
-
if (ticket.timeout
|
|
3750
|
-
const timeout = setTimeout(() => {
|
|
3751
|
-
timeouts.delete(ticket.id);
|
|
3752
|
-
registry.unregister(ticket.id);
|
|
3753
|
-
resume();
|
|
3754
|
-
}, ticket.timeout);
|
|
3755
|
-
timeouts.set(ticket.id, timeout);
|
|
3756
|
-
}
|
|
3757
|
-
function clearTimeout(id) {
|
|
3758
|
-
const timeout = timeouts.get(id);
|
|
3759
|
-
if (timeout) {
|
|
3760
|
-
globalThis.clearTimeout(timeout);
|
|
3761
|
-
timeouts.delete(id);
|
|
3762
|
-
}
|
|
3763
|
-
}
|
|
3764
|
-
function register(registration = {}) {
|
|
3765
|
-
const id = registration.id ?? /* @__PURE__ */ genId();
|
|
3766
|
-
const timeout = Object.prototype.hasOwnProperty.call(registration, "timeout") ? registration.timeout : _timeout;
|
|
3767
|
-
const ticket = {
|
|
3768
|
-
...registration,
|
|
3769
|
-
id,
|
|
3770
|
-
timeout,
|
|
3771
|
-
isPaused: registry.size > 0,
|
|
3772
|
-
dismiss: () => unregister(id)
|
|
3773
|
-
};
|
|
3774
|
-
const registered = registry.register(ticket);
|
|
3775
|
-
startTimeout(registered);
|
|
3776
|
-
return registered;
|
|
3777
|
-
}
|
|
3778
|
-
function unregister(id) {
|
|
3779
|
-
const ticket = id === void 0 ? registry.seek("first") : registry.get(id);
|
|
3780
|
-
if (!ticket) return void 0;
|
|
3781
|
-
const wasFirst = ticket.index === 0;
|
|
3782
|
-
clearTimeout(ticket.id);
|
|
3783
|
-
registry.unregister(ticket.id);
|
|
3784
|
-
if (wasFirst) resume();
|
|
3785
|
-
return ticket;
|
|
3786
|
-
}
|
|
3787
|
-
function pause() {
|
|
3788
|
-
const ticket = registry.seek("first");
|
|
3789
|
-
if (!ticket || ticket.isPaused) return void 0;
|
|
3790
|
-
clearTimeout(ticket.id);
|
|
3791
|
-
registry.upsert(ticket.id, { isPaused: true });
|
|
3792
|
-
return ticket;
|
|
3793
|
-
}
|
|
3794
|
-
function resume() {
|
|
3795
|
-
const ticket = registry.seek("first");
|
|
3796
|
-
if (!ticket || ticket.index !== 0 || !ticket.isPaused) return void 0;
|
|
3797
|
-
registry.upsert(ticket.id, { isPaused: false });
|
|
3798
|
-
startTimeout(ticket);
|
|
3799
|
-
return ticket;
|
|
3800
|
-
}
|
|
3801
|
-
function clear() {
|
|
3802
|
-
for (const id of timeouts.keys()) clearTimeout(id);
|
|
3803
|
-
registry.clear();
|
|
3804
|
-
}
|
|
3805
|
-
function dispose() {
|
|
3806
|
-
clear();
|
|
3807
|
-
registry.dispose();
|
|
3808
|
-
}
|
|
3809
|
-
onScopeDispose(dispose, true);
|
|
3810
|
-
return {
|
|
3811
|
-
...registry,
|
|
3812
|
-
register,
|
|
3813
|
-
unregister,
|
|
3814
|
-
pause,
|
|
3815
|
-
resume,
|
|
3816
|
-
clear,
|
|
3817
|
-
dispose,
|
|
3818
|
-
get size() {
|
|
3819
|
-
return registry.size;
|
|
3820
|
-
}
|
|
3821
|
-
};
|
|
3822
|
-
}
|
|
3823
|
-
/**
|
|
3824
|
-
* Creates a new queue context.
|
|
3825
|
-
*
|
|
3826
|
-
* @param namespace The namespace for the queue context.
|
|
3827
|
-
* @param options The options for the queue context.
|
|
3828
|
-
* @template Z The type of the queue ticket.
|
|
3829
|
-
* @template E The type of the queue context.
|
|
3830
|
-
* @returns A new queue context.
|
|
3831
|
-
*
|
|
3832
|
-
* @see https://0.vuetifyjs.com/composables/registration/use-queue
|
|
3833
|
-
*
|
|
3834
|
-
* @example
|
|
3835
|
-
* ```ts
|
|
3836
|
-
* import { createQueueContext } from '@vuetify/v0'
|
|
3837
|
-
*
|
|
3838
|
-
* export const [useQueue, provideQueue] = createQueueContext('v0:queue', {
|
|
3839
|
-
* timeout: 5000,
|
|
3840
|
-
* })
|
|
3841
|
-
* ```
|
|
3842
|
-
*/
|
|
3843
|
-
function createQueueContext(_options) {
|
|
3844
|
-
const { namespace,...options } = _options;
|
|
3845
|
-
const [useQueueContext, _provideQueueContext] = createContext(namespace);
|
|
3846
|
-
const context = createQueue(options);
|
|
3847
|
-
function provideQueueContext(_context = context, app) {
|
|
3848
|
-
return _provideQueueContext(_context, app);
|
|
3849
|
-
}
|
|
3850
|
-
return createTrinity(useQueueContext, provideQueueContext, context);
|
|
3851
|
-
}
|
|
3852
|
-
/**
|
|
3853
|
-
* Returns the current queue instance.
|
|
3854
|
-
*
|
|
3855
|
-
* @param namespace The namespace for the queue context. Defaults to `'v0:queue'`.
|
|
3856
|
-
* @returns The current queue instance.
|
|
3857
|
-
*
|
|
3858
|
-
* @see https://0.vuetifyjs.com/composables/registration/use-queue
|
|
3859
|
-
*
|
|
3860
|
-
* @example
|
|
3861
|
-
* ```vue
|
|
3862
|
-
* <script setup lang="ts">
|
|
3863
|
-
* import { useQueue } from '@vuetify/v0'
|
|
3864
|
-
*
|
|
3865
|
-
* const queue = useQueue()
|
|
3866
|
-
* <\/script>
|
|
3867
|
-
* ```
|
|
3868
|
-
*/
|
|
3869
|
-
function useQueue(namespace = "v0:queue") {
|
|
3870
|
-
return useContext(namespace);
|
|
3871
|
-
}
|
|
3872
|
-
|
|
3873
|
-
//#endregion
|
|
3874
|
-
//#region src/composables/useResizeObserver/index.ts
|
|
3875
|
-
/**
|
|
3876
|
-
* @module useResizeObserver
|
|
3877
|
-
*
|
|
3878
|
-
* @remarks
|
|
3879
|
-
* ResizeObserver composable with lifecycle management.
|
|
3880
|
-
*
|
|
3881
|
-
* Key features:
|
|
3882
|
-
* - ResizeObserver API wrapper
|
|
3883
|
-
* - Pause/resume/stop functionality
|
|
3884
|
-
* - Automatic cleanup on unmount
|
|
3885
|
-
* - SSR-safe (checks SUPPORTS_OBSERVER)
|
|
3886
|
-
* - Hydration-aware
|
|
3887
|
-
* - Box model options (content-box/border-box)
|
|
3888
|
-
*
|
|
3889
|
-
* Perfect for responsive components and size-based rendering.
|
|
3890
|
-
*/
|
|
3891
|
-
/**
|
|
3892
|
-
* A composable that uses the Resize Observer API to detect when an element's
|
|
3893
|
-
* size changes.
|
|
3894
|
-
*
|
|
3895
|
-
* @param target The element to observe.
|
|
3896
|
-
* @param callback The callback to execute when the element's size changes.
|
|
3897
|
-
* @param options The options for the Resize Observer.
|
|
3898
|
-
* @returns An object with methods to control the observer.
|
|
3899
|
-
*
|
|
3900
|
-
* @see https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver
|
|
3901
|
-
* @see https://0.vuetifyjs.com/composables/system/use-resize-observer
|
|
3902
|
-
*
|
|
3903
|
-
* @example
|
|
3904
|
-
* ```ts
|
|
3905
|
-
* import { ref } from 'vue'
|
|
3906
|
-
* import { useResizeObserver } from '@vuetify/v0'
|
|
3907
|
-
*
|
|
3908
|
-
* const el = ref<HTMLElement>()
|
|
3909
|
-
* const width = ref(0)
|
|
3910
|
-
* const height = ref(0)
|
|
3911
|
-
*
|
|
3912
|
-
* const { pause, resume, isPaused } = useResizeObserver(
|
|
3913
|
-
* el,
|
|
3914
|
-
* (entries) => {
|
|
3915
|
-
* const entry = entries[0]
|
|
3916
|
-
* if (entry) {
|
|
3917
|
-
* width.value = entry.contentRect.width
|
|
3918
|
-
* height.value = entry.contentRect.height
|
|
3919
|
-
* console.log('Size changed:', width.value, 'x', height.value)
|
|
3920
|
-
* }
|
|
3921
|
-
* },
|
|
3922
|
-
* { immediate: true }
|
|
3923
|
-
* )
|
|
3924
|
-
*
|
|
3925
|
-
* // Pause observation
|
|
3926
|
-
* pause()
|
|
3927
|
-
*
|
|
3928
|
-
* // Resume observation
|
|
3929
|
-
* resume()
|
|
3930
|
-
* ```
|
|
3931
|
-
*/
|
|
3932
|
-
function useResizeObserver(target, callback, options = {}) {
|
|
3933
|
-
const { isHydrated } = useHydration();
|
|
3934
|
-
const observer = shallowRef();
|
|
3935
|
-
const isPaused = shallowRef(false);
|
|
3936
|
-
function setup() {
|
|
3937
|
-
if (!isHydrated.value || !SUPPORTS_OBSERVER || !target.value || isPaused.value) return;
|
|
3938
|
-
observer.value = new ResizeObserver((entries) => {
|
|
3939
|
-
callback(entries.map((entry) => ({
|
|
3940
|
-
contentRect: {
|
|
3941
|
-
width: entry.contentRect.width,
|
|
3942
|
-
height: entry.contentRect.height,
|
|
3943
|
-
top: entry.contentRect.top,
|
|
3944
|
-
left: entry.contentRect.left
|
|
3945
|
-
},
|
|
3946
|
-
target: entry.target
|
|
3947
|
-
})));
|
|
3948
|
-
});
|
|
3949
|
-
observer.value.observe(target.value, { box: options.box || "content-box" });
|
|
3950
|
-
if (options.immediate) {
|
|
3951
|
-
const rect = target.value.getBoundingClientRect();
|
|
3952
|
-
callback([{
|
|
3953
|
-
contentRect: {
|
|
3954
|
-
width: rect.width,
|
|
3955
|
-
height: rect.height,
|
|
3956
|
-
top: rect.top,
|
|
3957
|
-
left: rect.left
|
|
3958
|
-
},
|
|
3959
|
-
target: target.value
|
|
3960
|
-
}]);
|
|
4546
|
+
* ```
|
|
4547
|
+
*/
|
|
4548
|
+
function createQueue(_options = {}) {
|
|
4549
|
+
const { timeout: _timeout = 3e3,...options } = _options;
|
|
4550
|
+
const registry = useRegistry({
|
|
4551
|
+
...options,
|
|
4552
|
+
events: true
|
|
4553
|
+
});
|
|
4554
|
+
const timeouts = /* @__PURE__ */ new Map();
|
|
4555
|
+
function startTimeout(ticket) {
|
|
4556
|
+
if (/* @__PURE__ */ isUndefined(ticket.timeout) || ticket.timeout < 0 || ticket.isPaused) return;
|
|
4557
|
+
const timeout = setTimeout(() => {
|
|
4558
|
+
timeouts.delete(ticket.id);
|
|
4559
|
+
registry.unregister(ticket.id);
|
|
4560
|
+
resume();
|
|
4561
|
+
}, ticket.timeout);
|
|
4562
|
+
timeouts.set(ticket.id, timeout);
|
|
4563
|
+
}
|
|
4564
|
+
function clearTimeout(id) {
|
|
4565
|
+
const timeout = timeouts.get(id);
|
|
4566
|
+
if (timeout) {
|
|
4567
|
+
globalThis.clearTimeout(timeout);
|
|
4568
|
+
timeouts.delete(id);
|
|
3961
4569
|
}
|
|
3962
4570
|
}
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
4571
|
+
function register(registration = {}) {
|
|
4572
|
+
const id = registration.id ?? /* @__PURE__ */ genId();
|
|
4573
|
+
const timeout = Object.prototype.hasOwnProperty.call(registration, "timeout") ? registration.timeout : _timeout;
|
|
4574
|
+
const ticket = {
|
|
4575
|
+
...registration,
|
|
4576
|
+
id,
|
|
4577
|
+
timeout,
|
|
4578
|
+
isPaused: registry.size > 0,
|
|
4579
|
+
dismiss: () => unregister(id)
|
|
4580
|
+
};
|
|
4581
|
+
const registered = registry.register(ticket);
|
|
4582
|
+
startTimeout(registered);
|
|
4583
|
+
return registered;
|
|
4584
|
+
}
|
|
4585
|
+
function unregister(id) {
|
|
4586
|
+
const ticket = /* @__PURE__ */ isUndefined(id) ? registry.seek("first") : registry.get(id);
|
|
4587
|
+
if (!ticket) return void 0;
|
|
4588
|
+
const wasFirst = ticket.index === 0;
|
|
4589
|
+
clearTimeout(ticket.id);
|
|
4590
|
+
registry.unregister(ticket.id);
|
|
4591
|
+
if (wasFirst) resume();
|
|
4592
|
+
return ticket;
|
|
4593
|
+
}
|
|
4594
|
+
function offboard(ids) {
|
|
4595
|
+
let hadFirst = false;
|
|
4596
|
+
for (const id of ids) {
|
|
4597
|
+
const ticket = registry.get(id);
|
|
4598
|
+
if (!ticket) continue;
|
|
4599
|
+
if (ticket.index === 0) hadFirst = true;
|
|
4600
|
+
clearTimeout(ticket.id);
|
|
3971
4601
|
}
|
|
4602
|
+
registry.offboard(ids);
|
|
4603
|
+
if (hadFirst) resume();
|
|
3972
4604
|
}
|
|
3973
4605
|
function pause() {
|
|
3974
|
-
|
|
3975
|
-
|
|
4606
|
+
const ticket = registry.seek("first");
|
|
4607
|
+
if (!ticket || ticket.isPaused) return void 0;
|
|
4608
|
+
clearTimeout(ticket.id);
|
|
4609
|
+
return registry.upsert(ticket.id, { isPaused: true });
|
|
3976
4610
|
}
|
|
3977
4611
|
function resume() {
|
|
3978
|
-
|
|
3979
|
-
|
|
4612
|
+
const ticket = registry.seek("first");
|
|
4613
|
+
if (!ticket || ticket.index !== 0 || !ticket.isPaused) return void 0;
|
|
4614
|
+
const updated = registry.upsert(ticket.id, { isPaused: false });
|
|
4615
|
+
startTimeout(updated);
|
|
4616
|
+
return updated;
|
|
3980
4617
|
}
|
|
3981
|
-
function
|
|
3982
|
-
|
|
4618
|
+
function clear() {
|
|
4619
|
+
for (const id of timeouts.keys()) clearTimeout(id);
|
|
4620
|
+
registry.clear();
|
|
4621
|
+
}
|
|
4622
|
+
function dispose() {
|
|
4623
|
+
clear();
|
|
4624
|
+
registry.dispose();
|
|
3983
4625
|
}
|
|
3984
|
-
|
|
4626
|
+
onScopeDispose(dispose, true);
|
|
3985
4627
|
return {
|
|
3986
|
-
|
|
4628
|
+
...registry,
|
|
4629
|
+
register,
|
|
4630
|
+
unregister,
|
|
4631
|
+
offboard,
|
|
3987
4632
|
pause,
|
|
3988
4633
|
resume,
|
|
3989
|
-
|
|
4634
|
+
clear,
|
|
4635
|
+
dispose,
|
|
4636
|
+
get size() {
|
|
4637
|
+
return registry.size;
|
|
4638
|
+
}
|
|
3990
4639
|
};
|
|
3991
4640
|
}
|
|
3992
4641
|
/**
|
|
3993
|
-
*
|
|
3994
|
-
* element's size.
|
|
4642
|
+
* Creates a new queue context.
|
|
3995
4643
|
*
|
|
3996
|
-
* @param
|
|
3997
|
-
* @
|
|
4644
|
+
* @param namespace The namespace for the queue context.
|
|
4645
|
+
* @param options The options for the queue context.
|
|
4646
|
+
* @template Z The type of the queue ticket.
|
|
4647
|
+
* @template E The type of the queue context.
|
|
4648
|
+
* @returns A new queue context.
|
|
3998
4649
|
*
|
|
3999
|
-
* @see https://0.vuetifyjs.com/composables/
|
|
4650
|
+
* @see https://0.vuetifyjs.com/composables/registration/use-queue
|
|
4000
4651
|
*
|
|
4001
4652
|
* @example
|
|
4002
4653
|
* ```ts
|
|
4003
|
-
* import {
|
|
4004
|
-
* import { useElementSize } from '@vuetify/v0'
|
|
4005
|
-
*
|
|
4006
|
-
* const box = ref<HTMLElement>()
|
|
4007
|
-
* const { width, height } = useElementSize(box)
|
|
4654
|
+
* import { createQueueContext } from '@vuetify/v0'
|
|
4008
4655
|
*
|
|
4009
|
-
*
|
|
4010
|
-
*
|
|
4011
|
-
* console.log('Box size:', width.value, 'x', height.value)
|
|
4656
|
+
* export const [useQueue, provideQueue] = createQueueContext('v0:queue', {
|
|
4657
|
+
* timeout: 5000,
|
|
4012
4658
|
* })
|
|
4013
4659
|
* ```
|
|
4014
4660
|
*/
|
|
4015
|
-
function
|
|
4016
|
-
const
|
|
4017
|
-
const
|
|
4018
|
-
const
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
width.value = entry.contentRect.width;
|
|
4022
|
-
height.value = entry.contentRect.height;
|
|
4023
|
-
}
|
|
4024
|
-
}, { immediate: true });
|
|
4025
|
-
function pause() {
|
|
4026
|
-
width.value = 0;
|
|
4027
|
-
height.value = 0;
|
|
4028
|
-
_pause();
|
|
4661
|
+
function createQueueContext(_options) {
|
|
4662
|
+
const { namespace,...options } = _options;
|
|
4663
|
+
const [useQueueContext, _provideQueueContext] = createContext(namespace);
|
|
4664
|
+
const context = createQueue(options);
|
|
4665
|
+
function provideQueueContext(_context = context, app) {
|
|
4666
|
+
return _provideQueueContext(_context, app);
|
|
4029
4667
|
}
|
|
4030
|
-
return
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4668
|
+
return createTrinity(useQueueContext, provideQueueContext, context);
|
|
4669
|
+
}
|
|
4670
|
+
/**
|
|
4671
|
+
* Returns the current queue instance.
|
|
4672
|
+
*
|
|
4673
|
+
* @param namespace The namespace for the queue context. Defaults to `'v0:queue'`.
|
|
4674
|
+
* @returns The current queue instance.
|
|
4675
|
+
*
|
|
4676
|
+
* @see https://0.vuetifyjs.com/composables/registration/use-queue
|
|
4677
|
+
*
|
|
4678
|
+
* @example
|
|
4679
|
+
* ```vue
|
|
4680
|
+
* <script setup lang="ts">
|
|
4681
|
+
* import { useQueue } from '@vuetify/v0'
|
|
4682
|
+
*
|
|
4683
|
+
* const queue = useQueue()
|
|
4684
|
+
* <\/script>
|
|
4685
|
+
* ```
|
|
4686
|
+
*/
|
|
4687
|
+
function useQueue(namespace = "v0:queue") {
|
|
4688
|
+
return useContext(namespace);
|
|
4038
4689
|
}
|
|
4039
4690
|
|
|
4040
4691
|
//#endregion
|
|
@@ -4150,13 +4801,13 @@ function createStep(_options = {}) {
|
|
|
4150
4801
|
let index = circular ? wrapped(length, currentIndex + count) : currentIndex + count;
|
|
4151
4802
|
if (!circular && (index < 0 || index >= length)) return;
|
|
4152
4803
|
let id = registry.lookup(index);
|
|
4153
|
-
while (
|
|
4804
|
+
while (!/* @__PURE__ */ isUndefined(id) && toValue(registry.get(id)?.disabled) && hops < length) {
|
|
4154
4805
|
index = circular ? wrapped(length, index + direction) : index + direction;
|
|
4155
4806
|
if (!circular && (index < 0 || index >= length)) return;
|
|
4156
4807
|
id = registry.lookup(index);
|
|
4157
4808
|
hops++;
|
|
4158
4809
|
}
|
|
4159
|
-
if (
|
|
4810
|
+
if (/* @__PURE__ */ isUndefined(id) || hops === length) return;
|
|
4160
4811
|
registry.selectedIds.clear();
|
|
4161
4812
|
registry.select(id);
|
|
4162
4813
|
}
|
|
@@ -4175,7 +4826,6 @@ function createStep(_options = {}) {
|
|
|
4175
4826
|
/**
|
|
4176
4827
|
* Creates a new step context.
|
|
4177
4828
|
*
|
|
4178
|
-
* @param namespace The namespace for the step context.
|
|
4179
4829
|
* @param options The options for the step context.
|
|
4180
4830
|
* @template Z The type of the step ticket.
|
|
4181
4831
|
* @template E The type of the step context.
|
|
@@ -4187,18 +4837,19 @@ function createStep(_options = {}) {
|
|
|
4187
4837
|
* ```ts
|
|
4188
4838
|
* import { createStepContext } from '@vuetify/v0'
|
|
4189
4839
|
*
|
|
4190
|
-
*
|
|
4840
|
+
* // With default namespace 'v0:step'
|
|
4841
|
+
* export const [useStep, provideStep, context] = createStepContext()
|
|
4191
4842
|
*
|
|
4192
4843
|
* // In a parent component:
|
|
4193
|
-
*
|
|
4844
|
+
* provideStep()
|
|
4194
4845
|
*
|
|
4195
4846
|
* // In a child component:
|
|
4196
|
-
* const
|
|
4197
|
-
*
|
|
4847
|
+
* const context = useStep()
|
|
4848
|
+
* context.next() // Progress to next step
|
|
4198
4849
|
* ```
|
|
4199
4850
|
*/
|
|
4200
|
-
function createStepContext(_options) {
|
|
4201
|
-
const { namespace,...options } = _options;
|
|
4851
|
+
function createStepContext(_options = {}) {
|
|
4852
|
+
const { namespace = "v0:step",...options } = _options;
|
|
4202
4853
|
const [useStepContext, _provideStepContext] = createContext(namespace);
|
|
4203
4854
|
const context = createStep(options);
|
|
4204
4855
|
function provideStepContext(_context = context, app) {
|
|
@@ -4277,7 +4928,6 @@ var MemoryAdapter = class {
|
|
|
4277
4928
|
*
|
|
4278
4929
|
* Uses adapter pattern to abstract storage implementation details.
|
|
4279
4930
|
*/
|
|
4280
|
-
const [useStorageContext, provideStorageContext] = createContext("v0:storage");
|
|
4281
4931
|
/**
|
|
4282
4932
|
* Creates a new storage instance.
|
|
4283
4933
|
*
|
|
@@ -4325,7 +4975,7 @@ function createStorage(options = {}) {
|
|
|
4325
4975
|
}
|
|
4326
4976
|
const valueRef = ref(initialValue);
|
|
4327
4977
|
const stop = watch(valueRef, (newValue) => {
|
|
4328
|
-
if (
|
|
4978
|
+
if (/* @__PURE__ */ isNullOrUndefined(newValue)) adapter?.removeItem(prefixedKey);
|
|
4329
4979
|
else adapter?.setItem(prefixedKey, serializer.write(newValue));
|
|
4330
4980
|
}, { deep: true });
|
|
4331
4981
|
watchers.set(prefixedKey, stop);
|
|
@@ -4365,12 +5015,12 @@ function createStorage(options = {}) {
|
|
|
4365
5015
|
}
|
|
4366
5016
|
function createStorageContext(_options = {}) {
|
|
4367
5017
|
const { namespace = "v0:storage",...options } = _options;
|
|
4368
|
-
const [useStorageContext
|
|
5018
|
+
const [useStorageContext, _provideStorageContext] = createContext(namespace);
|
|
4369
5019
|
const context = createStorage(options);
|
|
4370
|
-
function provideStorageContext
|
|
5020
|
+
function provideStorageContext(_context = context, app) {
|
|
4371
5021
|
return _provideStorageContext(_context, app);
|
|
4372
5022
|
}
|
|
4373
|
-
return createTrinity(useStorageContext
|
|
5023
|
+
return createTrinity(useStorageContext, provideStorageContext, context);
|
|
4374
5024
|
}
|
|
4375
5025
|
/**
|
|
4376
5026
|
* Creates a new storage plugin.
|
|
@@ -4395,14 +5045,14 @@ function createStorageContext(_options = {}) {
|
|
|
4395
5045
|
*/
|
|
4396
5046
|
function createStoragePlugin(_options = {}) {
|
|
4397
5047
|
const { namespace = "v0:storage",...options } = _options;
|
|
4398
|
-
const [, provideStorageContext
|
|
5048
|
+
const [, provideStorageContext, context] = createStorageContext({
|
|
4399
5049
|
...options,
|
|
4400
5050
|
namespace
|
|
4401
5051
|
});
|
|
4402
5052
|
return createPlugin({
|
|
4403
5053
|
namespace,
|
|
4404
5054
|
provide: (app) => {
|
|
4405
|
-
provideStorageContext
|
|
5055
|
+
provideStorageContext(context, app);
|
|
4406
5056
|
}
|
|
4407
5057
|
});
|
|
4408
5058
|
}
|
|
@@ -4450,7 +5100,7 @@ var ThemeAdapter = class {
|
|
|
4450
5100
|
const vars = Object.entries(themeColors).map(([key, val]) => ` --${this.prefix}-${key}: ${val};`).join("\n");
|
|
4451
5101
|
css += `[data-theme="${theme}"] {\n${vars}\n}\n`;
|
|
4452
5102
|
}
|
|
4453
|
-
if (
|
|
5103
|
+
if (!/* @__PURE__ */ isUndefined(isDark)) css += `:root {\n color-scheme: ${isDark ? "dark" : "light"};\n}\n`;
|
|
4454
5104
|
return css;
|
|
4455
5105
|
}
|
|
4456
5106
|
};
|
|
@@ -4842,13 +5492,12 @@ function createTimeline(_options = {}) {
|
|
|
4842
5492
|
};
|
|
4843
5493
|
}
|
|
4844
5494
|
/**
|
|
4845
|
-
* Creates a new timeline
|
|
5495
|
+
* Creates a new timeline context.
|
|
4846
5496
|
*
|
|
4847
|
-
* @param
|
|
4848
|
-
* @param options The options for the timeline plugin.
|
|
5497
|
+
* @param options The options for the timeline context.
|
|
4849
5498
|
* @template Z The type of the timeline ticket.
|
|
4850
5499
|
* @template E The type of the timeline context.
|
|
4851
|
-
* @returns A new timeline
|
|
5500
|
+
* @returns A new timeline context.
|
|
4852
5501
|
*
|
|
4853
5502
|
* @see https://0.vuetifyjs.com/composables/registration/use-timeline
|
|
4854
5503
|
*
|
|
@@ -4856,7 +5505,9 @@ function createTimeline(_options = {}) {
|
|
|
4856
5505
|
* ```ts
|
|
4857
5506
|
* import { createTimelineContext } from '@vuetify/v0'
|
|
4858
5507
|
*
|
|
4859
|
-
*
|
|
5508
|
+
* // With default namespace 'v0:timeline'
|
|
5509
|
+
* export const [useTimeline, provideTimeline, context] = createTimelineContext({ size: 5 })
|
|
5510
|
+
*
|
|
4860
5511
|
* context.register({ id: 'example' })
|
|
4861
5512
|
*
|
|
4862
5513
|
* // In a parent component
|
|
@@ -4868,8 +5519,8 @@ function createTimeline(_options = {}) {
|
|
|
4868
5519
|
* console.log(timeline.values()) // [{ id: 'example' }]
|
|
4869
5520
|
* ```
|
|
4870
5521
|
*/
|
|
4871
|
-
function createTimelineContext(_options) {
|
|
4872
|
-
const { namespace,...options } = _options;
|
|
5522
|
+
function createTimelineContext(_options = {}) {
|
|
5523
|
+
const { namespace = "v0:timeline",...options } = _options;
|
|
4873
5524
|
const [useTimelineContext, _provideTimelineContext] = createContext(namespace);
|
|
4874
5525
|
const context = createTimeline(options);
|
|
4875
5526
|
function provideTimelineContext(_context = context, app) {
|
|
@@ -4899,4 +5550,374 @@ function useTimeline(namespace = "v0:timeline") {
|
|
|
4899
5550
|
}
|
|
4900
5551
|
|
|
4901
5552
|
//#endregion
|
|
4902
|
-
|
|
5553
|
+
//#region src/composables/useToggleScope/index.ts
|
|
5554
|
+
/**
|
|
5555
|
+
* @module useToggleScope
|
|
5556
|
+
*
|
|
5557
|
+
* @remarks
|
|
5558
|
+
* Conditionally manages an effect scope based on a reactive boolean condition.
|
|
5559
|
+
* When the source becomes true, creates and runs an effect scope. When false, stops the scope.
|
|
5560
|
+
* All reactive effects created within the scoped function are automatically cleaned up on deactivation.
|
|
5561
|
+
*
|
|
5562
|
+
* Key features:
|
|
5563
|
+
* - Uses Vue's effectScope for efficient reactive effect lifecycle management
|
|
5564
|
+
* - Automatic cleanup when condition becomes false
|
|
5565
|
+
* - Supports optional reset callback for scope restart capability
|
|
5566
|
+
* - Handles rapid toggling and parent scope disposal safely
|
|
5567
|
+
* - SSR-safe (effectScope is part of Vue core)
|
|
5568
|
+
*
|
|
5569
|
+
* Perfect for conditional side effects, feature flags, and performance optimization
|
|
5570
|
+
* by only running reactive effects when needed.
|
|
5571
|
+
*/
|
|
5572
|
+
/**
|
|
5573
|
+
* Conditionally manages an effect scope based on a reactive boolean source.
|
|
5574
|
+
*
|
|
5575
|
+
* @param source A reactive boolean value or getter that controls the scope lifecycle
|
|
5576
|
+
* @param fn The function to run within the effect scope. Can optionally receive controls for manual scope management.
|
|
5577
|
+
* @returns Controls object with isActive state and start/stop/reset methods
|
|
5578
|
+
*
|
|
5579
|
+
* @see https://vuejs.org/api/reactivity-advanced.html#effectscope
|
|
5580
|
+
* @see https://0.vuetifyjs.com/composables/system/use-toggle-scope
|
|
5581
|
+
*
|
|
5582
|
+
* @example
|
|
5583
|
+
* ```ts
|
|
5584
|
+
* import { ref } from 'vue'
|
|
5585
|
+
* import { useToggleScope } from '@vuetify/v0'
|
|
5586
|
+
*
|
|
5587
|
+
* const isEnabled = ref(false)
|
|
5588
|
+
*
|
|
5589
|
+
* const { isActive } = useToggleScope(isEnabled, () => {
|
|
5590
|
+
* // This code runs when isEnabled becomes true
|
|
5591
|
+
* const unwatch = watch(someRef, () => {
|
|
5592
|
+
* console.log('Watching...')
|
|
5593
|
+
* })
|
|
5594
|
+
*
|
|
5595
|
+
* // Cleanup happens automatically when isEnabled becomes false
|
|
5596
|
+
* })
|
|
5597
|
+
*
|
|
5598
|
+
* // Toggle the scope on/off
|
|
5599
|
+
* isEnabled.value = true // Starts the scope
|
|
5600
|
+
* console.log(isActive.value) // true
|
|
5601
|
+
* isEnabled.value = false // Stops and cleans up
|
|
5602
|
+
* console.log(isActive.value) // false
|
|
5603
|
+
* ```
|
|
5604
|
+
*
|
|
5605
|
+
* @example
|
|
5606
|
+
* With manual controls:
|
|
5607
|
+
* ```ts
|
|
5608
|
+
* const isEnabled = ref(true)
|
|
5609
|
+
*
|
|
5610
|
+
* useToggleScope(isEnabled, (controls) => {
|
|
5611
|
+
* // Access controls inside the scope
|
|
5612
|
+
* console.log('Active:', controls.isActive.value)
|
|
5613
|
+
*
|
|
5614
|
+
* // Manually reset the scope if needed
|
|
5615
|
+
* someEvent.on('reset', () => controls.reset())
|
|
5616
|
+
* })
|
|
5617
|
+
* ```
|
|
5618
|
+
*/
|
|
5619
|
+
function useToggleScope(source, fn) {
|
|
5620
|
+
const scope = shallowRef();
|
|
5621
|
+
const isActive = toRef(() => !!scope.value);
|
|
5622
|
+
function start() {
|
|
5623
|
+
if (scope.value) return;
|
|
5624
|
+
scope.value = effectScope();
|
|
5625
|
+
scope.value.run(() => fn.length > 0 ? fn(controls) : fn());
|
|
5626
|
+
}
|
|
5627
|
+
function stop() {
|
|
5628
|
+
scope.value?.stop();
|
|
5629
|
+
scope.value = void 0;
|
|
5630
|
+
}
|
|
5631
|
+
function reset() {
|
|
5632
|
+
stop();
|
|
5633
|
+
start();
|
|
5634
|
+
}
|
|
5635
|
+
const controls = {
|
|
5636
|
+
isActive: shallowReadonly(isActive),
|
|
5637
|
+
start,
|
|
5638
|
+
stop,
|
|
5639
|
+
reset
|
|
5640
|
+
};
|
|
5641
|
+
watch(source, (active) => {
|
|
5642
|
+
if (active && !scope.value) start();
|
|
5643
|
+
else if (!active) stop();
|
|
5644
|
+
}, { immediate: true });
|
|
5645
|
+
onScopeDispose(() => {
|
|
5646
|
+
stop();
|
|
5647
|
+
});
|
|
5648
|
+
return controls;
|
|
5649
|
+
}
|
|
5650
|
+
|
|
5651
|
+
//#endregion
|
|
5652
|
+
//#region src/composables/useVirtual/index.ts
|
|
5653
|
+
/**
|
|
5654
|
+
* @module useVirtual
|
|
5655
|
+
*
|
|
5656
|
+
* @remarks
|
|
5657
|
+
* Virtual scrolling composable for efficiently rendering large lists.
|
|
5658
|
+
*
|
|
5659
|
+
* Key features:
|
|
5660
|
+
* - Renders only visible items (viewport + overscan)
|
|
5661
|
+
* - Dynamic or fixed item heights
|
|
5662
|
+
* - SSR-safe (checks IN_BROWSER)
|
|
5663
|
+
* - Bidirectional scrolling (forward/reverse for chat apps)
|
|
5664
|
+
* - Scroll anchoring (maintains position across data changes)
|
|
5665
|
+
* - Edge detection for infinite scroll
|
|
5666
|
+
* - iOS momentum and elastic scrolling
|
|
5667
|
+
* - Configurable overscan (extra items rendered for smooth scrolling)
|
|
5668
|
+
*
|
|
5669
|
+
* Perfect for large data sets, chat apps, and infinite scroll implementations.
|
|
5670
|
+
*/
|
|
5671
|
+
/**
|
|
5672
|
+
* Virtual scrolling composable for efficiently rendering large lists
|
|
5673
|
+
*
|
|
5674
|
+
* @param items Reactive array of items to virtualize
|
|
5675
|
+
* @param options Configuration options
|
|
5676
|
+
* @returns Virtual scrolling context
|
|
5677
|
+
*
|
|
5678
|
+
* @see https://0.vuetifyjs.com/composables/utilities/use-virtual
|
|
5679
|
+
*
|
|
5680
|
+
* @example
|
|
5681
|
+
* ```vue
|
|
5682
|
+
* <script setup lang="ts">
|
|
5683
|
+
* import { useVirtual } from '@vuetify/v0'
|
|
5684
|
+
*
|
|
5685
|
+
* const items = ref(Array.from({ length: 10000 }, (_, i) => ({ id: i, name: `Item ${i}` })))
|
|
5686
|
+
* <\/script>
|
|
5687
|
+
*
|
|
5688
|
+
* <template>
|
|
5689
|
+
* <div
|
|
5690
|
+
* ref="element"
|
|
5691
|
+
* style="height: 600px; overflow-y: auto;"
|
|
5692
|
+
* @scroll="scroll"
|
|
5693
|
+
* >
|
|
5694
|
+
* <div :style="{ height: `${offset}px` }" />
|
|
5695
|
+
*
|
|
5696
|
+
* <div v-for="item in items" :key="item.index">
|
|
5697
|
+
* {{ item.raw.name }}
|
|
5698
|
+
* </div>
|
|
5699
|
+
*
|
|
5700
|
+
* <div :style="{ height: `${size}px` }" />
|
|
5701
|
+
* </div>
|
|
5702
|
+
* </template>
|
|
5703
|
+
* ```
|
|
5704
|
+
*/
|
|
5705
|
+
function useVirtual(items, _options = {}) {
|
|
5706
|
+
const { itemHeight: _itemHeight, height, overscan = 5, direction = "forward", anchor = "auto", anchorSmooth = true, onStartReached, onEndReached, startThreshold = 0, endThreshold = 0, momentum: momentumOption, elastic: elasticOption } = _options;
|
|
5707
|
+
const element = ref();
|
|
5708
|
+
const itemHeight = shallowRef(Number.parseFloat(String(_itemHeight || 0)));
|
|
5709
|
+
const heights = shallowRef([]);
|
|
5710
|
+
const offsets = shallowRef([]);
|
|
5711
|
+
const first = shallowRef(0);
|
|
5712
|
+
const last = shallowRef(0);
|
|
5713
|
+
const offset = shallowRef(0);
|
|
5714
|
+
const size = shallowRef(0);
|
|
5715
|
+
const viewportHeight = shallowRef(0);
|
|
5716
|
+
const state = shallowRef("ok");
|
|
5717
|
+
const isIOS = IN_BROWSER && /iPad|iPhone|iPod/.test(navigator.userAgent);
|
|
5718
|
+
const momentum = momentumOption ?? isIOS;
|
|
5719
|
+
const elastic = elasticOption ?? isIOS;
|
|
5720
|
+
let raf = -1;
|
|
5721
|
+
let rebuildRaf = -1;
|
|
5722
|
+
let edgeRaf = -1;
|
|
5723
|
+
const cachedViewport = Number.parseInt(String(height)) || 0;
|
|
5724
|
+
let anchorIndex = -1;
|
|
5725
|
+
let anchorOffset = 0;
|
|
5726
|
+
const computedItems = computed(() => items.value.slice(first.value, last.value).map((item, i) => ({
|
|
5727
|
+
raw: item,
|
|
5728
|
+
index: i + first.value
|
|
5729
|
+
})));
|
|
5730
|
+
watch(element, (el) => {
|
|
5731
|
+
if (!IN_BROWSER || !el?.style) return;
|
|
5732
|
+
if (momentum) el.style.webkitOverflowScrolling = "touch";
|
|
5733
|
+
if (!elastic) el.style.overscrollBehavior = "none";
|
|
5734
|
+
});
|
|
5735
|
+
useResizeObserver(element, (entries) => {
|
|
5736
|
+
if (!entries[0]) return;
|
|
5737
|
+
viewportHeight.value = entries[0].contentRect.height;
|
|
5738
|
+
update();
|
|
5739
|
+
});
|
|
5740
|
+
watch(items, (newItems) => {
|
|
5741
|
+
captureAnchor();
|
|
5742
|
+
const length = newItems.length;
|
|
5743
|
+
const newHeights = heights.value.length === length ? heights.value : Array.from({ length }, () => null);
|
|
5744
|
+
if (heights.value !== newHeights) heights.value = newHeights;
|
|
5745
|
+
rebuild();
|
|
5746
|
+
}, { immediate: true });
|
|
5747
|
+
watch(element, () => {
|
|
5748
|
+
if (!element.value) return;
|
|
5749
|
+
if (direction === "reverse" && items.value.length > 0) {
|
|
5750
|
+
const lastIndex = items.value.length - 1;
|
|
5751
|
+
const totalHeight = (offsets.value[lastIndex] || 0) + (heights.value[lastIndex] || itemHeight.value);
|
|
5752
|
+
element.value.scrollTop = totalHeight;
|
|
5753
|
+
}
|
|
5754
|
+
update();
|
|
5755
|
+
});
|
|
5756
|
+
function captureAnchor() {
|
|
5757
|
+
if (!element.value || anchor === "auto") return;
|
|
5758
|
+
if (anchor === "start") {
|
|
5759
|
+
anchorIndex = 0;
|
|
5760
|
+
anchorOffset = 0;
|
|
5761
|
+
} else if (anchor === "end") {
|
|
5762
|
+
anchorIndex = items.value.length - 1;
|
|
5763
|
+
anchorOffset = 0;
|
|
5764
|
+
} else if (/* @__PURE__ */ isFunction(anchor)) {
|
|
5765
|
+
const result = anchor(items.value);
|
|
5766
|
+
if (/* @__PURE__ */ isNumber(result)) {
|
|
5767
|
+
anchorIndex = result;
|
|
5768
|
+
anchorOffset = element.value.scrollTop - (offsets.value[result] || 0);
|
|
5769
|
+
}
|
|
5770
|
+
} else {
|
|
5771
|
+
anchorIndex = first.value;
|
|
5772
|
+
anchorOffset = element.value.scrollTop - (offsets.value[first.value] || 0);
|
|
5773
|
+
}
|
|
5774
|
+
}
|
|
5775
|
+
function restoreAnchor() {
|
|
5776
|
+
if (!element.value || anchorIndex < 0) return;
|
|
5777
|
+
const newScrollTop = (offsets.value[anchorIndex] || 0) + anchorOffset;
|
|
5778
|
+
if (anchorSmooth && direction === "forward") element.value.scrollTo({
|
|
5779
|
+
top: newScrollTop,
|
|
5780
|
+
behavior: "smooth"
|
|
5781
|
+
});
|
|
5782
|
+
else element.value.scrollTop = newScrollTop;
|
|
5783
|
+
anchorIndex = -1;
|
|
5784
|
+
anchorOffset = 0;
|
|
5785
|
+
}
|
|
5786
|
+
function rebuild() {
|
|
5787
|
+
const length = items.value.length;
|
|
5788
|
+
const newOffsets = offsets.value.length === length ? offsets.value : Array.from({ length });
|
|
5789
|
+
let offset$1 = 0;
|
|
5790
|
+
for (let i = 0; i < length; i++) {
|
|
5791
|
+
newOffsets[i] = offset$1;
|
|
5792
|
+
offset$1 += heights.value[i] || itemHeight.value;
|
|
5793
|
+
}
|
|
5794
|
+
if (offsets.value !== newOffsets) offsets.value = newOffsets;
|
|
5795
|
+
update();
|
|
5796
|
+
restoreAnchor();
|
|
5797
|
+
}
|
|
5798
|
+
function findIndex(scrollTop) {
|
|
5799
|
+
const arr = offsets.value;
|
|
5800
|
+
if (arr.length === 0) return 0;
|
|
5801
|
+
let low = 0;
|
|
5802
|
+
let high = arr.length - 1;
|
|
5803
|
+
while (low <= high) {
|
|
5804
|
+
const mid = low + high >> 1;
|
|
5805
|
+
if (arr[mid] <= scrollTop) low = mid + 1;
|
|
5806
|
+
else high = mid - 1;
|
|
5807
|
+
}
|
|
5808
|
+
return Math.max(0, high);
|
|
5809
|
+
}
|
|
5810
|
+
function update() {
|
|
5811
|
+
if (!element.value) return;
|
|
5812
|
+
const viewport = viewportHeight.value || cachedViewport;
|
|
5813
|
+
if (!viewport || !itemHeight.value) return;
|
|
5814
|
+
const scrollTop = element.value.scrollTop || 0;
|
|
5815
|
+
const length = items.value.length;
|
|
5816
|
+
const visibleStart = findIndex(scrollTop);
|
|
5817
|
+
const visibleEnd = findIndex(scrollTop + viewport) + 1;
|
|
5818
|
+
const start = /* @__PURE__ */ clamp(visibleStart - overscan, 0, length);
|
|
5819
|
+
const end = /* @__PURE__ */ clamp(visibleEnd + overscan, start, length);
|
|
5820
|
+
first.value = start;
|
|
5821
|
+
last.value = end;
|
|
5822
|
+
offset.value = offsets.value[start] || 0;
|
|
5823
|
+
const lastIndex = length - 1;
|
|
5824
|
+
const totalHeight = (offsets.value[lastIndex] || 0) + (heights.value[lastIndex] || itemHeight.value);
|
|
5825
|
+
size.value = totalHeight - (offsets.value[end] || totalHeight);
|
|
5826
|
+
}
|
|
5827
|
+
function checkEdges() {
|
|
5828
|
+
if (!element.value) return;
|
|
5829
|
+
const scrollTop = element.value.scrollTop;
|
|
5830
|
+
const scrollHeight = element.value.scrollHeight;
|
|
5831
|
+
const clientHeight = element.value.clientHeight;
|
|
5832
|
+
const distanceFromStart = scrollTop;
|
|
5833
|
+
const distanceFromEnd = scrollHeight - (scrollTop + clientHeight);
|
|
5834
|
+
if (IN_BROWSER) {
|
|
5835
|
+
cancelAnimationFrame(edgeRaf);
|
|
5836
|
+
edgeRaf = requestAnimationFrame(() => {
|
|
5837
|
+
if (onStartReached && distanceFromStart <= startThreshold) onStartReached(distanceFromStart);
|
|
5838
|
+
if (onEndReached && distanceFromEnd <= endThreshold) onEndReached(distanceFromEnd);
|
|
5839
|
+
});
|
|
5840
|
+
} else {
|
|
5841
|
+
if (onStartReached && distanceFromStart <= startThreshold) onStartReached(distanceFromStart);
|
|
5842
|
+
if (onEndReached && distanceFromEnd <= endThreshold) onEndReached(distanceFromEnd);
|
|
5843
|
+
}
|
|
5844
|
+
}
|
|
5845
|
+
function resize(index, height$1) {
|
|
5846
|
+
if (heights.value[index] === height$1) return;
|
|
5847
|
+
heights.value[index] = height$1;
|
|
5848
|
+
if (!itemHeight.value) itemHeight.value = height$1;
|
|
5849
|
+
if (IN_BROWSER) {
|
|
5850
|
+
cancelAnimationFrame(rebuildRaf);
|
|
5851
|
+
rebuildRaf = requestAnimationFrame(rebuild);
|
|
5852
|
+
} else rebuild();
|
|
5853
|
+
}
|
|
5854
|
+
function scroll() {
|
|
5855
|
+
if (IN_BROWSER) {
|
|
5856
|
+
cancelAnimationFrame(raf);
|
|
5857
|
+
raf = requestAnimationFrame(update);
|
|
5858
|
+
checkEdges();
|
|
5859
|
+
} else update();
|
|
5860
|
+
}
|
|
5861
|
+
function scrollTo(index, scrollOptions) {
|
|
5862
|
+
if (!element.value) return;
|
|
5863
|
+
const targetOffset = offsets.value[index] || 0;
|
|
5864
|
+
const behavior = scrollOptions?.behavior ?? "auto";
|
|
5865
|
+
const block = scrollOptions?.block ?? "start";
|
|
5866
|
+
const extraOffset = scrollOptions?.offset ?? 0;
|
|
5867
|
+
let scrollTop = targetOffset + extraOffset;
|
|
5868
|
+
switch (block) {
|
|
5869
|
+
case "center": {
|
|
5870
|
+
const viewport = viewportHeight.value || cachedViewport;
|
|
5871
|
+
const itemH = heights.value[index] || itemHeight.value;
|
|
5872
|
+
scrollTop = targetOffset - viewport / 2 + itemH / 2 + extraOffset;
|
|
5873
|
+
break;
|
|
5874
|
+
}
|
|
5875
|
+
case "end": {
|
|
5876
|
+
const viewport = viewportHeight.value || cachedViewport;
|
|
5877
|
+
const itemH = heights.value[index] || itemHeight.value;
|
|
5878
|
+
scrollTop = targetOffset - viewport + itemH + extraOffset;
|
|
5879
|
+
break;
|
|
5880
|
+
}
|
|
5881
|
+
case "nearest": {
|
|
5882
|
+
const viewport = viewportHeight.value || cachedViewport;
|
|
5883
|
+
const currentScroll = element.value.scrollTop;
|
|
5884
|
+
const itemH = heights.value[index] || itemHeight.value;
|
|
5885
|
+
if (targetOffset < currentScroll) scrollTop = targetOffset + extraOffset;
|
|
5886
|
+
else if (targetOffset + itemH > currentScroll + viewport) scrollTop = targetOffset - viewport + itemH + extraOffset;
|
|
5887
|
+
else return;
|
|
5888
|
+
break;
|
|
5889
|
+
}
|
|
5890
|
+
}
|
|
5891
|
+
if (behavior === "smooth") element.value.scrollTo({
|
|
5892
|
+
top: scrollTop,
|
|
5893
|
+
behavior: "smooth"
|
|
5894
|
+
});
|
|
5895
|
+
else element.value.scrollTop = scrollTop;
|
|
5896
|
+
update();
|
|
5897
|
+
}
|
|
5898
|
+
function reset() {
|
|
5899
|
+
state.value = "ok";
|
|
5900
|
+
anchorIndex = -1;
|
|
5901
|
+
anchorOffset = 0;
|
|
5902
|
+
if (element.value && direction === "reverse" && items.value.length > 0) {
|
|
5903
|
+
const lastIndex = items.value.length - 1;
|
|
5904
|
+
const totalHeight = (offsets.value[lastIndex] || 0) + (heights.value[lastIndex] || itemHeight.value);
|
|
5905
|
+
element.value.scrollTop = totalHeight;
|
|
5906
|
+
}
|
|
5907
|
+
}
|
|
5908
|
+
return {
|
|
5909
|
+
element,
|
|
5910
|
+
items: computedItems,
|
|
5911
|
+
offset: readonly(offset),
|
|
5912
|
+
size: readonly(size),
|
|
5913
|
+
state,
|
|
5914
|
+
scrollTo,
|
|
5915
|
+
scroll,
|
|
5916
|
+
scrollend: scroll,
|
|
5917
|
+
resize,
|
|
5918
|
+
reset
|
|
5919
|
+
};
|
|
5920
|
+
}
|
|
5921
|
+
|
|
5922
|
+
//#endregion
|
|
5923
|
+
export { createFeatures as $, usePagination as A, useHydration as At, createLocalePlugin as B, useContext as Bt, createPermissions as C, createBreakpointsContext as Ct, PermissionAdapter as D, createHydration as Dt, usePermissions as E, createFallbackHydration as Et, useResizeObserver as F, toArray as Ft, useSingle as G, Vuetify0LocaleAdapter as H, useMutationObserver as I, createTrinity as It, useIntersectionObserver as J, useKeydown as K, createLocale as L, createPlugin as Lt, createOverflowContext as M, useEventListener as Mt, useOverflow as N, useWindowEventListener as Nt, createPagination as O, createHydrationContext as Ot, useElementSize as P, toReactive as Pt, useFilter as Q, createLocaleContext as R, createContext as Rt, useProxyModel as S, createBreakpoints as St, createPermissionsPlugin as T, useBreakpoints as Tt, createSingle as U, useLocale as V, createSingleContext as W, createFormContext as X, createForm as Y, useForm as Z, createStepContext as _, createLoggerPlugin as _t, useTimeline as a, useTokens as at, createQueueContext as b, PinoLoggerAdapter as bt, createThemePlugin as c, useGroup as ct, createStorage as d, createSelectionContext as dt, createFeaturesContext as et, createStorageContext as f, useSelection as ft, createStep as g, createLoggerContext as gt, MemoryAdapter as h, createLogger as ht, createTimelineContext as i, createTokensContext as it, createOverflow as j, useDocumentEventListener as jt, createPaginationContext as k, createHydrationPlugin as kt, useTheme as l, useProxyRegistry as lt, useStorage as m, useRegistry as mt, useToggleScope as n, useFeatures as nt, createTheme as o, createGroup as ot, createStoragePlugin as p, createRegistryContext as pt, useElementIntersection as q, createTimeline as r, createTokens as rt, createThemeContext as s, createGroupContext as st, useVirtual as t, createFeaturesPlugin as tt, Vuetify0ThemeAdapter as u, createSelection as ut, useStep as v, useLogger as vt, createPermissionsContext as w, createBreakpointsPlugin as wt, useQueue as x, ConsolaLoggerAdapter as xt, createQueue as y, Vuetify0LoggerAdapter as yt, createLocaleFallback as z, provideContext as zt };
|