@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.
@@ -1,6 +1,6 @@
1
- import { a as isNull, c as isObject, d as isUndefined, f as mergeDeep, i as isFunction, n as isArray, o as isNullOrUndefined, r as isBoolean, s as isNumber, t as genId, u as isString } from "./utilities-C26nB74O.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-Dh0BVnN0.mjs";
3
- import { computed, getCurrentInstance, getCurrentScope, inject, isRef, onMounted, onScopeDispose, onUnmounted, provide, reactive, readonly, ref, shallowReactive, shallowReadonly, shallowRef, toRef, toValue, unref, watch } from "vue";
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 (context === void 0) throw new Error(`Context "${String(key)}" not found. Ensure it's provided by an ancestor.`);
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
- return useContext(namespace);
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
- window.addEventListener("resize", listener, { passive: true });
829
+ const cleanup = useWindowEventListener("resize", listener, { passive: true });
729
830
  onScopeDispose(() => {
730
- window.removeEventListener("resize", listener);
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 === void 0) {
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
- if (catalog.size > 0) catalog.clear();
1395
- if (directory.size > 0) directory.clear();
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 (ticket.index !== index) {
1400
- ticket.index = index;
1401
- if (ticket.valueIsIndex) ticket.value = index;
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
- reindex();
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) => this.register(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
- * export const [useItems, provideItems, items] = createRegistryContext('items')
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
- * export const [useCheckboxes, provideCheckboxes, checkboxes] = createSelectionContext('checkboxes')
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/useGroup/index.ts
1805
+ //#region src/composables/useProxyRegistry/index.ts
1730
1806
  /**
1731
- * @module useGroup
1807
+ * @module useProxyRegistry
1732
1808
  *
1733
1809
  * @remarks
1734
- * Multi-selection composable that extends useSelection with batch operation support.
1810
+ * Proxy composable for reactive registry keys, values, entries, and size.
1735
1811
  *
1736
1812
  * Key features:
1737
- * - Batch operations (select/unselect/toggle accept ID | ID[])
1738
- * - selectedIndexes computed Set for position-based tracking
1739
- * - Perfect for checkboxes, multi-select dropdowns, filter panels
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
- * Inheritance chain: useRegistry useSelection useGroup
1742
- * Extended by: useFeatures
1819
+ * Perfect for exposing registry data as reactive computed properties.
1743
1820
  */
1744
1821
  /**
1745
- * Creates a new group instance with batch selection operations.
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
- * **Used By:**
1774
- * - `createFeatures` for feature flag management with multiple selections
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/selection/use-group
1829
+ * @see https://0.vuetifyjs.com/composables/registration/use-proxy-registry
1777
1830
  *
1778
1831
  * @example
1779
1832
  * ```ts
1780
- * import { createGroup } from '@vuetify/v0'
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
- * // Toggle operations
1797
- * checkboxes.toggle(['option-a', 'option-b'])
1798
- * console.log(checkboxes.selectedIds) // Set { 'option-b', 'option-c' }
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 registry = createSelection({
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(registry.selectedItems.value).map((item) => item?.index));
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)) registry.select(id);
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)) registry.unselect(id);
1999
+ for (const id of toArray(ids)) selection.unselect(id);
1816
2000
  }
1817
2001
  function toggle(ids) {
1818
- for (const id of toArray(ids)) registry.toggle(id);
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
- ...registry,
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 registry.size;
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
- * export const [useMyGroup, provideMyGroup, myGroup] = createGroupContext('my-group')
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 (cached !== void 0) return cached;
2214
+ if (!/* @__PURE__ */ isUndefined(cached)) return cached;
1952
2215
  const reference = isTokenAlias(token) ? token.$value : token;
1953
- const clean = /* @__PURE__ */ isString(reference) && isAlias(reference) ? reference.slice(1, -1) : String(reference);
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 !== void 0) {
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 === void 0) {
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 (current === void 0) {
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/selection/use-filter
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 || null;
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
- * export const [useMyForm, provideMyForm, myForm] = createFormContext('my-form', {
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
- onUnmounted(stop);
2997
+ onScopeDispose(stop, true);
2729
2998
  return {
2730
- isIntersecting: readonly(isIntersecting),
2731
- isPaused: readonly(isPaused),
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: readonly(isIntersecting),
2785
- intersectionRatio: readonly(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
- * - Auto-starts when in component scope
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 { startListening, stopListening } = useKeydown([
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
- * startListening()
2827
- * stopListening()
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
- const keyHandlers = Array.isArray(handlers) ? handlers : [handlers];
3104
+ let cleanup = null;
3105
+ const isActive = toRef(() => !!cleanup);
2832
3106
  function onKeydown(event) {
2833
- const handler = keyHandlers.find((h$1) => h$1.key === event.key);
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 startListening() {
2841
- document.addEventListener("keydown", onKeydown);
3115
+ function start() {
3116
+ if (cleanup) return;
3117
+ cleanup = useDocumentEventListener("keydown", onKeydown);
2842
3118
  }
2843
- function stopListening() {
2844
- document.removeEventListener("keydown", onKeydown);
3119
+ function stop() {
3120
+ if (!cleanup) return;
3121
+ cleanup();
3122
+ cleanup = null;
2845
3123
  }
2846
- if (getCurrentScope()) onMounted(startListening);
2847
- onScopeDispose(stopListening, true);
3124
+ cleanup = useDocumentEventListener("keydown", onKeydown);
3125
+ onScopeDispose(stop);
2848
3126
  return {
2849
- startListening,
2850
- stopListening
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
- * export const [useTabs, provideTabs, tabs] = createSingleContext('tabs', { mandatory: true })
3244
+ * // With default namespace 'v0:single'
3245
+ * export const [useSingle, provideSingle, context] = createSingleContext()
2967
3246
  *
2968
3247
  * // In a parent component:
2969
- * provideTabs()
3248
+ * provideSingle()
2970
3249
  *
2971
3250
  * // In a child component:
2972
- * const tabs = useTabs()
2973
- * tabs.select('tab-1')
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] === void 0 ? match : String(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] !== void 0) return String(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, { flat: true });
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, ...params) {
3359
+ function t(key, params, fallback) {
3084
3360
  const locale = registry.selectedId.value;
3085
- if (!locale) return key;
3086
- const message = (registry.get(locale)?.value)?.[key];
3087
- const template = /* @__PURE__ */ isString(message) ? resolve(locale, message) : key;
3088
- return adapter.t(template, ...params);
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 path = rest.join(".");
3097
- const prefixTicket = registry.get(prefix);
3098
- const target = prefixTicket ? prefix : locale;
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
- return useContext(namespace);
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
- onUnmounted(stop);
3612
+ onScopeDispose(stop, true);
3328
3613
  return {
3329
- isPaused: readonly(isPaused),
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/usePermissions/adapters/adapter.ts
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 usePermissions
3358
- *
3359
- * @see https://0.vuetifyjs.com/composables/plugins/use-permissions
3625
+ * @module useResizeObserver
3360
3626
  *
3361
3627
  * @remarks
3362
- * Permission management composable with support for RBAC and ABAC patterns.
3628
+ * ResizeObserver composable with lifecycle management.
3363
3629
  *
3364
3630
  * Key features:
3365
- * - Role-Based Access Control (RBAC) support
3366
- * - Attribute-Based Access Control (ABAC) with context
3367
- * - Functional permission conditions
3368
- * - Token-based permission storage
3369
- * - Adapter pattern for custom permission systems
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
- * Built on useTokens for flexible permission configuration.
3638
+ * Perfect for responsive components and size-based rendering.
3372
3639
  */
3373
3640
  /**
3374
- * Creates a new permissions instance.
3641
+ * A composable that uses the Resize Observer API to detect when an element's
3642
+ * size changes.
3375
3643
  *
3376
- * @param options The options for the permissions instance.
3377
- * @template Z The type of the permission ticket.
3378
- * @template E The type of the permission context.
3379
- * @returns A new permissions instance.
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://0.vuetifyjs.com/composables/plugins/use-permissions
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 { createPermissions } from '@vuetify/v0'
3654
+ * import { ref } from 'vue'
3655
+ * import { useResizeObserver } from '@vuetify/v0'
3386
3656
  *
3387
- * const [usePermissions, providePermissions] = createPermissions({
3388
- * namespace: 'v0:permissions',
3389
- * permissions: {
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
- * @param options The options for the permissions context.
3419
- * @template Z The type of the permission ticket.
3420
- * @template E The type of the permission context.
3421
- * @returns A new permissions context.
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
- * @see https://0.vuetifyjs.com/composables/plugins/use-permissions
3674
+ * // Pause observation
3675
+ * pause()
3424
3676
  *
3425
- * @example
3426
- * ```ts
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 createPermissionsContext(_options = {}) {
3439
- const { namespace = "v0:permissions",...options } = _options;
3440
- const [usePermissionsContext, _providePermissionsContext] = createContext(namespace);
3441
- const context = createPermissions(options);
3442
- function providePermissionsContext(_context = context, app) {
3443
- return _providePermissionsContext(_context, app);
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
- return createTrinity(usePermissionsContext, providePermissionsContext, context);
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
- * Creates a new permissions plugin.
3744
+ * A convenience composable that uses the Resize Observer API to track an
3745
+ * element's size.
3449
3746
  *
3450
- * @param options The options for the permissions plugin.
3451
- * @template Z The type of the permission ticket.
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/plugins/use-permissions
3750
+ * @see https://0.vuetifyjs.com/composables/system/use-resize-observer#use-element-size
3456
3751
  *
3457
3752
  * @example
3458
3753
  * ```ts
3459
- * import { createApp } from 'vue'
3460
- * import { createPermissionsPlugin } from '@vuetify/v0'
3461
- * import App from './App.vue'
3754
+ * import { ref, watchEffect } from 'vue'
3755
+ * import { useElementSize } from '@vuetify/v0'
3462
3756
  *
3463
- * const app = createApp(App)
3757
+ * const box = ref<HTMLElement>()
3758
+ * const { width, height } = useElementSize(box)
3464
3759
  *
3465
- * app.use(
3466
- * createPermissionsPlugin({
3467
- * permissions: {
3468
- * admin: [['read', 'users']],
3469
- * editor: [['edit', 'posts']],
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
- * app.mount('#app')
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 createPermissionsPlugin(_options = {}) {
3478
- const { namespace = "v0:permissions",...options } = _options;
3479
- const [, providePermissionContext, context] = createPermissionsContext({
3480
- ...options,
3481
- namespace
3482
- });
3483
- return createPlugin({
3484
- namespace,
3485
- provide: (app) => {
3486
- providePermissionContext(context, app);
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
- * Returns the current permissions instance.
3922
+ * Creates an overflow context with dependency injection support.
3492
3923
  *
3493
- * @template Z The type of the permission ticket.
3494
- * @returns The current permissions instance.
3924
+ * @param options Configuration options including namespace
3925
+ * @returns Trinity tuple: [useContext, provideContext, defaultContext]
3495
3926
  *
3496
- * @see https://0.vuetifyjs.com/composables/plugins/use-permissions
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 setup lang="ts">
3501
- * import { usePermissions } from '@vuetify/v0'
3960
+ * <script lang="ts" setup>
3961
+ * import { useOverflow } from '@vuetify/v0'
3502
3962
  *
3503
- * const { can } = usePermissions()
3963
+ * // Inject overflow context provided by parent
3964
+ * const overflow = useOverflow()
3504
3965
  * <\/script>
3505
3966
  *
3506
3967
  * <template>
3507
3968
  * <div>
3508
- * <p v-if="can('admin', 'read', 'users')">Admin access</p>
3969
+ * <p>Capacity: {{ overflow.capacity.value }}</p>
3509
3970
  * </div>
3510
3971
  * </template>
3511
3972
  * ```
3512
3973
  */
3513
- function usePermissions(namespace = "v0:permissions") {
3974
+ function useOverflow(namespace = "v0:overflow") {
3514
3975
  return useContext(namespace);
3515
3976
  }
3516
3977
 
3517
3978
  //#endregion
3518
- //#region src/composables/useProxyModel/index.ts
3979
+ //#region src/composables/usePagination/index.ts
3519
3980
  /**
3520
- * @module useProxyModel
3981
+ * @module usePagination
3521
3982
  *
3522
3983
  * @remarks
3523
- * Proxy composable for bidirectional sync between selection registry and v-model.
3984
+ * Lightweight pagination composable for navigating through pages.
3524
3985
  *
3525
3986
  * Key features:
3526
- * - Bidirectional synchronization
3527
- * - Array and single-value modes
3528
- * - Automatic cleanup on scope disposal
3529
- * - Perfect for form controls with selection backing
3530
- *
3531
- * Bridges the gap between selection composables and Vue's v-model.
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
- * Syncs a ref with a selection registry bidirectionally.
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
- * @see https://0.vuetifyjs.com/composables/forms/use-proxy-model
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 { createSelection, useProxyModel } from '@vuetify/v0'
4004
+ * import { createPagination } from '@vuetify/v0'
3547
4005
  *
3548
- * const model = ref()
3549
- * const registry = createSelection({ events: true })
3550
- * registry.onboard([
3551
- * { id: 'item-1', value: 'Item 1' },
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
- * const stop = useProxyModel(registry, model)
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 useProxyModel(registry, model, options) {
3559
- const multiple = options?.multiple ?? false;
3560
- const _transformIn = options?.transformIn;
3561
- const _transformOut = options?.transformOut;
3562
- function transformIn(val) {
3563
- const value = toValue(val);
3564
- return toArray(/* @__PURE__ */ isFunction(_transformIn) ? _transformIn(value) : value);
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 transformOut(val) {
3567
- if (/* @__PURE__ */ isFunction(_transformOut)) return _transformOut(val);
3568
- return multiple ? val : val[0];
4029
+ function last() {
4030
+ page.value = pages.value;
3569
4031
  }
3570
- const modelAsArray = transformIn(model);
3571
- const pending = new Set(modelAsArray);
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
- const registryWatch = watch(registry.selectedValues, (val) => {
3583
- modelWatch.pause();
3584
- model.value = transformOut(Array.from(toValue(val)));
3585
- modelWatch.resume();
3586
- }, { flush: "sync" });
3587
- const modelWatch = watch(model, (val) => {
3588
- registryWatch.pause();
3589
- const currentIds = new Set(toValue(registry.selectedIds));
3590
- const targetIds = /* @__PURE__ */ new Set();
3591
- for (const value of transformIn(val)) {
3592
- const ids = registry.browse(value);
3593
- if (/* @__PURE__ */ isArray(ids)) for (const single of ids) targetIds.add(single);
3594
- else if (ids) targetIds.add(ids);
3595
- }
3596
- if (multiple) {
3597
- for (const id of currentIds.difference(targetIds)) registry.selectedIds.delete(id);
3598
- for (const id of targetIds.difference(currentIds)) registry.selectedIds.add(id);
3599
- } else {
3600
- const next = targetIds.values().next().value;
3601
- const last = currentIds.values().next().value;
3602
- if (last !== void 0) registry.unselect(last);
3603
- if (next !== void 0) registry.select(next);
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
- registryWatch.resume();
3606
- }, {
3607
- flush: "sync",
3608
- deep: multiple
3609
- });
3610
- function onRegister(ticket) {
3611
- if (!pending.has(ticket.value) || ticket.disabled) return;
3612
- registryWatch.pause();
3613
- modelWatch.pause();
3614
- registry.select(ticket.id);
3615
- pending.delete(ticket.value);
3616
- modelWatch.resume();
3617
- registryWatch.resume();
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
- registry.on("register:ticket", onRegister);
3620
- function stop() {
3621
- registryWatch();
3622
- modelWatch();
3623
- registry.off("register:ticket", onRegister);
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
- onScopeDispose(stop, true);
3626
- return stop;
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/useProxyRegistry/index.ts
4389
+ //#region src/composables/useProxyModel/index.ts
3631
4390
  /**
3632
- * @module useProxyRegistry
4391
+ * @module useProxyModel
3633
4392
  *
3634
4393
  * @remarks
3635
- * Proxy composable for reactive registry keys, values, entries, and size.
4394
+ * Proxy composable for bidirectional sync between selection registry and v-model.
3636
4395
  *
3637
4396
  * Key features:
3638
- * - Reactive proxy for registry data
3639
- * - Deep or shallow reactivity options
3640
- * - Event-based updates
4397
+ * - Bidirectional synchronization
4398
+ * - Array and single-value modes
3641
4399
  * - Automatic cleanup on scope disposal
3642
- * - Transforms Map-based registry into reactive refs
4400
+ * - Perfect for form controls with selection backing
3643
4401
  *
3644
- * Perfect for exposing registry data as reactive computed properties.
4402
+ * Bridges the gap between selection composables and Vue's v-model.
3645
4403
  */
3646
4404
  /**
3647
- * Creates a proxy registry that provides reactive objects for registry data.
4405
+ * Syncs a ref with a selection registry bidirectionally.
3648
4406
  *
3649
- * @param registry The registry instance to proxy.
3650
- * @param options The options for the proxy registry.
3651
- * @template Z The type of the registry ticket.
3652
- * @returns A proxy registry with reactive objects.
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/registration/use-proxy-registry
4413
+ * @see https://0.vuetifyjs.com/composables/forms/use-proxy-model
3655
4414
  *
3656
4415
  * @example
3657
4416
  * ```ts
3658
- * import { useRegistry, useProxyRegistry } from '@vuetify/v0'
4417
+ * import { createSelection, useProxyModel } from '@vuetify/v0'
3659
4418
  *
3660
- * const registry = useRegistry({ events: true })
3661
- * const proxy = useProxyRegistry(registry)
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
- * registry.register({ value: 'Item 1' })
3664
- * console.log(proxy.size) // 1
4426
+ * const stop = useProxyModel(registry, model)
3665
4427
  * ```
3666
4428
  */
3667
- function useProxyRegistry(registry, options) {
3668
- const state = (options?.deep ? reactive : shallowReactive)({
3669
- keys: registry.keys(),
3670
- values: registry.values(),
3671
- entries: registry.entries(),
3672
- size: registry.size
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 update() {
3675
- state.keys = registry.keys();
3676
- state.values = registry.values();
3677
- state.entries = registry.entries();
3678
- state.size = registry.size;
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", update);
3681
- registry.on("unregister:ticket", update);
3682
- registry.on("update:ticket", update);
3683
- registry.on("clear:registry", update);
3684
- onScopeDispose(() => {
3685
- registry.off("register:ticket", update);
3686
- registry.off("unregister:ticket", update);
3687
- registry.off("update:ticket", update);
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 === void 0 || ticket.timeout === -1 || ticket.isPaused) return;
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
- watch([isHydrated, target], () => {
3964
- cleanup();
3965
- setup();
3966
- }, { immediate: true });
3967
- function cleanup() {
3968
- if (observer.value) {
3969
- observer.value.disconnect();
3970
- observer.value = void 0;
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
- isPaused.value = true;
3975
- observer.value?.disconnect();
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
- isPaused.value = false;
3979
- setup();
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 stop() {
3982
- cleanup();
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
- onUnmounted(stop);
4626
+ onScopeDispose(dispose, true);
3985
4627
  return {
3986
- isPaused: readonly(isPaused),
4628
+ ...registry,
4629
+ register,
4630
+ unregister,
4631
+ offboard,
3987
4632
  pause,
3988
4633
  resume,
3989
- stop
4634
+ clear,
4635
+ dispose,
4636
+ get size() {
4637
+ return registry.size;
4638
+ }
3990
4639
  };
3991
4640
  }
3992
4641
  /**
3993
- * A convenience composable that uses the Resize Observer API to track an
3994
- * element's size.
4642
+ * Creates a new queue context.
3995
4643
  *
3996
- * @param target The element to observe.
3997
- * @returns An object with the element's width and height.
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/system/use-resize-observer#use-element-size
4650
+ * @see https://0.vuetifyjs.com/composables/registration/use-queue
4000
4651
  *
4001
4652
  * @example
4002
4653
  * ```ts
4003
- * import { ref, watchEffect } from 'vue'
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
- * // Width and height are reactive refs
4010
- * watchEffect(() => {
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 useElementSize(target) {
4016
- const width = shallowRef(0);
4017
- const height = shallowRef(0);
4018
- const { pause: _pause, resume, stop, isPaused } = useResizeObserver(target, (entries) => {
4019
- const entry = entries[0];
4020
- if (entry) {
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
- width,
4032
- height,
4033
- isPaused,
4034
- pause,
4035
- resume,
4036
- stop
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 (id !== void 0 && toValue(registry.get(id)?.disabled) && hops < length) {
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 (id === void 0 || hops === length) return;
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
- * export const [useWizard, provideWizard, wizard] = createStepContext('wizard')
4840
+ * // With default namespace 'v0:step'
4841
+ * export const [useStep, provideStep, context] = createStepContext()
4191
4842
  *
4192
4843
  * // In a parent component:
4193
- * provideWizard()
4844
+ * provideStep()
4194
4845
  *
4195
4846
  * // In a child component:
4196
- * const wizard = useWizard()
4197
- * wizard.next() // Progress to next step
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 (newValue === void 0 || newValue === null) adapter?.removeItem(prefixedKey);
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$1, _provideStorageContext] = createContext(namespace);
5018
+ const [useStorageContext, _provideStorageContext] = createContext(namespace);
4369
5019
  const context = createStorage(options);
4370
- function provideStorageContext$1(_context = context, app) {
5020
+ function provideStorageContext(_context = context, app) {
4371
5021
  return _provideStorageContext(_context, app);
4372
5022
  }
4373
- return createTrinity(useStorageContext$1, provideStorageContext$1, context);
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$1, context] = createStorageContext({
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$1(context, app);
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 (isDark !== void 0) css += `:root {\n color-scheme: ${isDark ? "dark" : "light"};\n}\n`;
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 plugin.
5495
+ * Creates a new timeline context.
4846
5496
  *
4847
- * @param namespace The namespace for the timeline plugin.
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 plugin.
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
- * export const [useTimeline, provideTimeline, context] = createTimelineContext('v0:timeline', { size: 5 })
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
- export { useTokens as $, PermissionAdapter as A, createContext as At, useKeydown as B, useQueue as C, createHydrationContext as Ct, createPermissionsContext as D, toArray as Dt, createPermissions as E, toReactive as Et, useLocale as F, useForm as G, useIntersectionObserver as H, Vuetify0LocaleAdapter as I, createFeaturesContext as J, useFilter as K, createSingle as L, createLocale as M, useContext as Mt, createLocaleContext as N, createPermissionsPlugin as O, createTrinity as Ot, createLocalePlugin as P, createTokensContext as Q, createSingleContext as R, createQueueContext as S, createHydration as St, useProxyModel as T, useHydration as Tt, createForm as U, useElementIntersection as V, createFormContext as W, useFeatures as X, createFeaturesPlugin as Y, createTokens as Z, createStepContext as _, useWindowEventListener as _t, createThemeContext as a, useSelection as at, useResizeObserver as b, createBreakpointsPlugin as bt, Vuetify0ThemeAdapter as c, createLogger as ct, createStoragePlugin as d, useLogger as dt, createGroup as et, provideStorageContext as f, Vuetify0LoggerAdapter as ft, createStep as g, useEventListener as gt, MemoryAdapter as h, useDocumentEventListener as ht, createTheme as i, createSelectionContext as it, useMutationObserver as j, provideContext as jt, usePermissions as k, createPlugin as kt, createStorage as l, createLoggerContext as lt, useStorageContext as m, ConsolaLoggerAdapter as mt, createTimelineContext as n, useGroup as nt, createThemePlugin as o, createRegistryContext as ot, useStorage as p, PinoLoggerAdapter as pt, createFeatures as q, useTimeline as r, createSelection as rt, useTheme as s, useRegistry as st, createTimeline as t, createGroupContext as tt, createStorageContext as u, createLoggerPlugin as ut, useStep as v, createBreakpoints as vt, useProxyRegistry as w, createHydrationPlugin as wt, createQueue as x, useBreakpoints as xt, useElementSize as y, createBreakpointsContext as yt, useSingle as z };
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 };