@vuetify/v0 0.0.18 → 0.0.21

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 { _ as range, d as isObject, h as isUndefined, l as isNullOrUndefined, m as isSymbol, o as isFunction, p as isString, r as genId, s as isNaN } from "./utilities-JxCe-nF2.mjs";
2
- import { a as SUPPORTS_OBSERVER, s as __LOGGER_ENABLED__, t as IN_BROWSER } from "./globals-BGVqrlN7.mjs";
3
- import { computed, getCurrentInstance, inject, isRef, onScopeDispose, provide, reactive, shallowReactive, shallowReadonly, shallowRef, toRef, toValue, watch } from "vue";
1
+ import { _ as range, c as isNull, d as isObject, h as isUndefined, i as isArray, l as isNullOrUndefined, m as isSymbol, o as isFunction, p as isString, r as genId, s as isNaN, t as clamp } from "./utilities-CjDz-Xvn.mjs";
2
+ import { a as SUPPORTS_OBSERVER, s as __LOGGER_ENABLED__, t as IN_BROWSER } from "./globals-C3JrEDXZ.mjs";
3
+ import { computed, getCurrentInstance, inject, isRef, onScopeDispose, provide, reactive, shallowReactive, shallowReadonly, shallowRef, toRef, toValue, watch, watchEffect } from "vue";
4
4
 
5
5
  //#region src/composables/createContext/index.ts
6
6
  /**
@@ -302,10 +302,12 @@ var Vuetify0LoggerAdapter = class {
302
302
  }
303
303
  timestamp() {
304
304
  if (!IN_BROWSER) return (/* @__PURE__ */ new Date()).toISOString();
305
+ /* v8 ignore next -- defensive fallback, toTimeString always returns valid format */
305
306
  return (/* @__PURE__ */ new Date()).toTimeString().split(" ")[0] ?? "";
306
307
  }
307
308
  style(level) {
308
309
  if (!this.colors || !IN_BROWSER) return "";
310
+ /* v8 ignore next -- LogLevel union is exhaustive */
309
311
  return {
310
312
  trace: "color: #64748b",
311
313
  debug: "color: #3b82f6",
@@ -560,6 +562,8 @@ function useLogger(namespace = "v0:logger") {
560
562
  /**
561
563
  * @module useRegistry
562
564
  *
565
+ * @see https://0.vuetifyjs.com/composables/registration/use-registry
566
+ *
563
567
  * @remarks
564
568
  * A foundational composable for managing collections of items (tickets) with:
565
569
  * - Unique ID-based access
@@ -616,17 +620,21 @@ function useRegistry(options) {
616
620
  }
617
621
  function on(event, cb) {
618
622
  if (!events) {
619
- logger.warn(`Attempted to register event listener for "${event}" but events are disabled.`);
623
+ logger.warn(`Events are disabled. Initialize with \`useRegistry({ events: true })\` to enable.`);
620
624
  return;
621
625
  }
622
626
  if (!listeners.has(event)) listeners.set(event, /* @__PURE__ */ new Set());
623
627
  listeners.get(event).add(cb);
624
628
  }
625
629
  function off(event, cb) {
630
+ if (!events) {
631
+ logger.warn(`Events are disabled. Initialize with \`useRegistry({ events: true })\` to enable.`);
632
+ return;
633
+ }
626
634
  listeners.get(event)?.delete(cb);
627
635
  }
628
636
  function dispose() {
629
- if (listeners.size > 0) listeners.clear();
637
+ listeners.clear();
630
638
  clear();
631
639
  }
632
640
  function get(id) {
@@ -715,9 +723,9 @@ function useRegistry(options) {
715
723
  return entries$1;
716
724
  }
717
725
  function clear() {
718
- if (collection.size > 0) collection.clear();
719
- if (catalog.size > 0) catalog.clear();
720
- if (directory.size > 0) directory.clear();
726
+ collection.clear();
727
+ catalog.clear();
728
+ directory.clear();
721
729
  invalidate();
722
730
  indexDependentCount = 0;
723
731
  needsReindex = false;
@@ -726,7 +734,7 @@ function useRegistry(options) {
726
734
  }
727
735
  function invalidate() {
728
736
  if (batching) return;
729
- if (cache.size > 0) cache.clear();
737
+ cache.clear();
730
738
  }
731
739
  function queueEmit(event, data) {
732
740
  if (batching) pendingEmits.push({
@@ -741,7 +749,7 @@ function useRegistry(options) {
741
749
  pendingEmits = [];
742
750
  try {
743
751
  const result = fn();
744
- if (cache.size > 0) cache.clear();
752
+ cache.clear();
745
753
  for (const { event, data } of pendingEmits) emit(event, data);
746
754
  return result;
747
755
  } finally {
@@ -752,8 +760,8 @@ function useRegistry(options) {
752
760
  function reindex() {
753
761
  const startIndex = minDirtyIndex === Infinity ? 0 : minDirtyIndex;
754
762
  if (startIndex === 0) {
755
- if (catalog.size > 0) catalog.clear();
756
- if (directory.size > 0) directory.clear();
763
+ catalog.clear();
764
+ directory.clear();
757
765
  }
758
766
  invalidate();
759
767
  let index = 0;
@@ -780,7 +788,7 @@ function useRegistry(options) {
780
788
  const size = collection.size;
781
789
  const id = registration.id ?? /* @__PURE__ */ genId();
782
790
  if (has(id)) {
783
- logger.warn(`Ticket with id "${id}" already exists in the registry. Skipping registration.`);
791
+ logger.warn(`Ticket "${id}" already exists. Use \`upsert()\` to update or check \`has()\` before registering.`);
784
792
  return get(id);
785
793
  }
786
794
  const valueIsUndefined = /* @__PURE__ */ isUndefined(registration.value);
@@ -830,14 +838,18 @@ function useRegistry(options) {
830
838
  }
831
839
  if (removed.length === 0) return;
832
840
  invalidate();
833
- if (events) for (const ticket of removed) emit("unregister:ticket", ticket);
841
+ for (const ticket of removed) queueEmit("unregister:ticket", ticket);
834
842
  needsReindex = true;
835
843
  }
836
844
  function seek(direction = "first", from, predicate) {
837
845
  if (collection.size === 0) return void 0;
838
846
  if (needsReindex) reindex();
847
+ if (!predicate && /* @__PURE__ */ isUndefined(from)) {
848
+ const tickets$1 = values();
849
+ return direction === "first" ? tickets$1[0] : tickets$1.at(-1);
850
+ }
839
851
  const tickets = values();
840
- const index = /* @__PURE__ */ isUndefined(from) ? void 0 : Math.max(0, Math.min(from, tickets.length - 1));
852
+ const index = /* @__PURE__ */ isUndefined(from) ? void 0 : /* @__PURE__ */ clamp(from, 0, tickets.length - 1);
841
853
  if (direction === "last") {
842
854
  const start = /* @__PURE__ */ isUndefined(index) ? tickets.length - 1 : index;
843
855
  for (let i = start; i >= 0; i--) {
@@ -1170,7 +1182,7 @@ function useSelection(namespace = "v0:selection") {
1170
1182
  * ```
1171
1183
  */
1172
1184
  function toArray(value) {
1173
- return /* @__PURE__ */ isNullOrUndefined(value) ? [] : Array.isArray(value) ? value : [value];
1185
+ return /* @__PURE__ */ isNullOrUndefined(value) ? [] : /* @__PURE__ */ isArray(value) ? value : [value];
1174
1186
  }
1175
1187
 
1176
1188
  //#endregion
@@ -1262,7 +1274,8 @@ function useProxyModel(registry, model, options) {
1262
1274
  flush: "sync",
1263
1275
  deep: multiple
1264
1276
  });
1265
- function onRegister(ticket) {
1277
+ function onRegister(data) {
1278
+ const ticket = data;
1266
1279
  if (!pending.has(ticket.value) || ticket.disabled) return;
1267
1280
  registryWatch.pause();
1268
1281
  modelWatch.pause();
@@ -1632,1345 +1645,1350 @@ function useGroup(namespace = "v0:group") {
1632
1645
  }
1633
1646
 
1634
1647
  //#endregion
1635
- //#region src/composables/useHydration/index.ts
1648
+ //#region src/composables/useSingle/index.ts
1636
1649
  /**
1637
- * @module useHydration
1638
- *
1639
- * @see https://0.vuetifyjs.com/composables/plugins/use-hydration
1650
+ * @module useSingle
1640
1651
  *
1641
1652
  * @remarks
1642
- * SSR hydration state management composable.
1653
+ * Single-selection composable that extends useSelection to enforce only one selected item.
1643
1654
  *
1644
1655
  * Key features:
1645
- * - Hydration state detection (browser vs SSR)
1646
- * - Root component detection
1647
- * - Readonly hydration state refs
1648
- * - Plugin installation support
1649
- * - Perfect for hydration-safe rendering
1656
+ * - Auto-clears previous selection when selecting new item
1657
+ * - Singular computed properties (selectedId, selectedItem, selectedIndex, selectedValue)
1658
+ * - Perfect for tabs, radio buttons, theme selectors
1650
1659
  *
1651
- * Essential for composables that need to behave differently during SSR vs client-side.
1660
+ * Inheritance chain: useRegistry useSelection useSingle
1652
1661
  */
1653
1662
  /**
1654
- * Creates a new hydration instance.
1663
+ * Creates a new single selection instance that enforces only one selected item at a time.
1655
1664
  *
1656
- * @returns A new hydration instance.
1665
+ * Extends `createSelection` by automatically clearing previous selections when a new item is selected.
1666
+ * Adds computed singular properties: `selectedId`, `selectedItem`, `selectedIndex`, `selectedValue`.
1657
1667
  *
1658
- * @see https://0.vuetifyjs.com/composables/plugins/use-hydration
1668
+ * @param options The options for the single selection instance.
1669
+ * @template Z The type of the single selection ticket.
1670
+ * @template E The type of the single selection context.
1671
+ * @returns A new single selection instance with single-selection enforcement.
1659
1672
  *
1660
- * @example
1661
- * ```ts
1662
- * import { createHydration } from '@vuetify/v0'
1673
+ * @remarks
1674
+ * **Key Differences from `createSelection`:**
1675
+ * - Automatically clears `selectedIds` before selecting a new item (enforces single selection)
1676
+ * - Provides singular computed properties instead of plural sets
1677
+ * - Perfect for tabs, radio buttons, theme selectors, and other single-choice UI components
1663
1678
  *
1664
- * const hydration = createHydration()
1665
- * console.log(hydration.isHydrated.value) // false
1666
- * hydration.hydrate()
1667
- * console.log(hydration.isHydrated.value) // true
1668
- * ```
1669
- */
1670
- function createHydration() {
1671
- const isHydrated = shallowRef(false);
1672
- function hydrate() {
1673
- isHydrated.value = true;
1674
- }
1675
- return {
1676
- isHydrated: shallowReadonly(isHydrated),
1677
- hydrate
1678
- };
1679
- }
1680
- function createFallbackHydration() {
1681
- return {
1682
- isHydrated: shallowReadonly(shallowRef(true)),
1683
- hydrate: () => {}
1684
- };
1685
- }
1686
- /**
1687
- * Creates a new hydration context trinity.
1679
+ * **Computed Properties:**
1680
+ * - `selectedId`: The ID of the selected item (undefined if none selected)
1681
+ * - `selectedItem`: The selected ticket object (undefined if none selected)
1682
+ * - `selectedIndex`: The index of the selected item (-1 if none selected)
1683
+ * - `selectedValue`: The value of the selected item (undefined if none selected)
1688
1684
  *
1689
- * @param options Options for creating the hydration context.
1690
- * @template E The type of the hydration context.
1691
- * @returns A new hydration context trinity.
1685
+ * **Inheritance Chain:**
1686
+ * `useRegistry` `createSelection` `createSingle` `createStep`
1692
1687
  *
1693
- * @see https://0.vuetifyjs.com/composables/plugins/use-hydration
1688
+ * @see https://0.vuetifyjs.com/composables/selection/use-single
1694
1689
  *
1695
1690
  * @example
1696
1691
  * ```ts
1697
- * import { createHydrationContext } from '@vuetify/v0'
1692
+ * import { createSingle } from '@vuetify/v0'
1698
1693
  *
1699
- * export const [useHydrationContext, provideHydrationContext, context] = createHydrationContext({
1700
- * namespace: 'app:hydration',
1701
- * })
1694
+ * const tabs = createSingle({ mandatory: true })
1695
+ *
1696
+ * tabs.onboard([
1697
+ * { id: 'home', value: 'Home' },
1698
+ * { id: 'about', value: 'About' },
1699
+ * { id: 'contact', value: 'Contact' },
1700
+ * ])
1701
+ *
1702
+ * tabs.first() // Select first tab
1703
+ *
1704
+ * console.log(tabs.selectedId.value) // 'home'
1705
+ * console.log(tabs.selectedIndex.value) // 0
1706
+ *
1707
+ * tabs.select('about') // Switch to about tab
1708
+ * console.log(tabs.selectedId.value) // 'about'
1709
+ * console.log(tabs.selectedIds.size) // 1 (always enforces single selection)
1702
1710
  * ```
1703
1711
  */
1704
- function createHydrationContext(_options = {}) {
1705
- const { namespace = "v0:hydration" } = _options;
1706
- const [useHydrationContext, _provideHydrationContext] = createContext(namespace);
1707
- const context = createHydration();
1708
- function provideHydrationContext(_context = context, app) {
1709
- return _provideHydrationContext(_context, app);
1712
+ function createSingle(_options = {}) {
1713
+ const { mandatory = false, multiple = false, ...options } = _options;
1714
+ const registry = createSelection({
1715
+ ...options,
1716
+ mandatory,
1717
+ multiple
1718
+ });
1719
+ const selectedId = computed(() => registry.selectedIds.values().next().value);
1720
+ const selectedItem = computed(() => registry.selectedItems.value.values().next().value);
1721
+ const selectedIndex = computed(() => selectedItem.value?.index ?? -1);
1722
+ const selectedValue = computed(() => selectedItem.value?.value);
1723
+ function unselect(id) {
1724
+ if (mandatory && registry.selectedIds.size === 1) return;
1725
+ registry.selectedIds.delete(id);
1710
1726
  }
1711
- return createTrinity(useHydrationContext, provideHydrationContext, context);
1727
+ function toggle(id) {
1728
+ if (registry.selectedIds.has(id)) unselect(id);
1729
+ else registry.select(id);
1730
+ }
1731
+ return {
1732
+ ...registry,
1733
+ selectedId,
1734
+ selectedItem,
1735
+ selectedIndex,
1736
+ selectedValue,
1737
+ unselect,
1738
+ toggle,
1739
+ get size() {
1740
+ return registry.size;
1741
+ }
1742
+ };
1712
1743
  }
1713
1744
  /**
1714
- * Creates a new hydration plugin.
1745
+ * Creates a new single selection context.
1715
1746
  *
1716
- * @param options The options for the hydration plugin.
1717
- * @template E The type of the hydration context.
1718
- * @returns A new hydration plugin.
1747
+ * @param options The options for the single selection context.
1748
+ * @template Z The type of the single selection ticket.
1749
+ * @template E The type of the single selection context.
1750
+ * @returns A new single selection context.
1719
1751
  *
1720
- * @see https://0.vuetifyjs.com/composables/plugins/use-hydration
1752
+ * @see https://0.vuetifyjs.com/composables/selection/use-single
1721
1753
  *
1722
1754
  * @example
1723
1755
  * ```ts
1724
- * import { createApp } from 'vue'
1725
- * import { createHydrationPlugin } from '@vuetify/v0'
1726
- * import App from './App.vue'
1756
+ * import { createSingleContext } from '@vuetify/v0'
1727
1757
  *
1728
- * const app = createApp(App)
1758
+ * // With default namespace 'v0:single'
1759
+ * export const [useSingle, provideSingle, context] = createSingleContext()
1729
1760
  *
1730
- * app.use(createHydrationPlugin())
1761
+ * // In a parent component:
1762
+ * provideSingle()
1731
1763
  *
1732
- * app.mount('#app')
1764
+ * // In a child component:
1765
+ * const single = useSingle()
1766
+ * single.select('tab-1')
1733
1767
  * ```
1734
1768
  */
1735
- function createHydrationPlugin(_options = {}) {
1736
- const { namespace = "v0:hydration", ...options } = _options;
1737
- const [, provideHydrationContext, context] = createHydrationContext({
1738
- ...options,
1739
- namespace
1740
- });
1741
- return createPlugin({
1742
- namespace,
1743
- provide: (app) => {
1744
- provideHydrationContext(context, app);
1745
- },
1746
- setup: (app) => {
1747
- app.mixin({ mounted() {
1748
- if (this.$parent !== null) return;
1749
- context.hydrate();
1750
- } });
1751
- }
1752
- });
1769
+ function createSingleContext(_options = {}) {
1770
+ const { namespace = "v0:single", ...options } = _options;
1771
+ const [useSingleContext, _provideSingleContext] = createContext(namespace);
1772
+ const context = createSingle(options);
1773
+ function provideSingleContext(_context = context, app) {
1774
+ return _provideSingleContext(_context, app);
1775
+ }
1776
+ return createTrinity(useSingleContext, provideSingleContext, context);
1753
1777
  }
1754
1778
  /**
1755
- * Returns the current hydration instance.
1779
+ * Returns the current single selection instance.
1756
1780
  *
1757
- * @param namespace The namespace for the hydration context. Defaults to `v0:hydration`.
1758
- * @returns The current hydration instance.
1781
+ * @param namespace The namespace for the single selection context. Defaults to `'v0:single'`.
1782
+ * @returns The current single selection instance.
1759
1783
  *
1760
- * @see https://0.vuetifyjs.com/composables/plugins/use-hydration
1784
+ * @see https://0.vuetifyjs.com/composables/selection/use-single
1761
1785
  *
1762
1786
  * @example
1763
1787
  * ```vue
1764
1788
  * <script setup lang="ts">
1765
- * import { useHydration } from '@vuetify/v0'
1789
+ * import { useSingle } from '@vuetify/v0'
1766
1790
  *
1767
- * const hydration = useHydration()
1791
+ * const tabs = useSingle()
1768
1792
  * <\/script>
1769
1793
  *
1770
1794
  * <template>
1771
1795
  * <div>
1772
- * <p>Is hydrated: {{ hydration.isHydrated.value }}</p>
1796
+ * <p>Selected: {{ tabs.selectedId }}</p>
1773
1797
  * </div>
1774
1798
  * </template>
1775
1799
  * ```
1776
1800
  */
1777
- function useHydration(namespace = "v0:hydration") {
1778
- const fallback = createFallbackHydration();
1779
- if (!getCurrentInstance()) return fallback;
1780
- try {
1781
- return useContext(namespace, fallback);
1782
- } catch {
1783
- return fallback;
1784
- }
1801
+ function useSingle(namespace = "v0:single") {
1802
+ return useContext(namespace);
1785
1803
  }
1786
1804
 
1787
1805
  //#endregion
1788
- //#region src/composables/useResizeObserver/index.ts
1806
+ //#region src/composables/useTokens/index.ts
1789
1807
  /**
1790
- * @module useResizeObserver
1808
+ * @module useTokens
1809
+ *
1810
+ * @see https://0.vuetifyjs.com/composables/registration/use-tokens
1791
1811
  *
1792
1812
  * @remarks
1793
- * ResizeObserver composable with lifecycle management.
1813
+ * Design token registry with alias resolution and W3C Design Tokens format support.
1794
1814
  *
1795
1815
  * Key features:
1796
- * - ResizeObserver API wrapper
1797
- * - Pause/resume/stop functionality
1798
- * - Automatic cleanup on unmount
1799
- * - SSR-safe (checks SUPPORTS_OBSERVER)
1800
- * - Hydration-aware
1801
- * - Box model options (content-box/border-box)
1816
+ * - Alias resolution with circular reference detection
1817
+ * - Nested token flattening with dot notation
1818
+ * - W3C Design Tokens format ($value, $type, $description, $extensions)
1819
+ * - Path-based resolution (e.g., {colors}.blue.500)
1820
+ * - Resolution caching for performance (~28,590 ops/sec)
1802
1821
  *
1803
- * Perfect for responsive components and size-based rendering.
1822
+ * Used by useTheme, useLocale, and useFeatures for token-based configuration.
1804
1823
  */
1805
1824
  /**
1806
- * A composable that uses the Resize Observer API to detect when an element's
1807
- * size changes.
1825
+ * Creates a new token instance.
1808
1826
  *
1809
- * @param target The element to observe.
1810
- * @param callback The callback to execute when the element's size changes.
1811
- * @param options The options for the Resize Observer.
1812
- * @returns An object with methods to control the observer.
1827
+ * @param tokens The tokens to use.
1828
+ * @param options The options for the token instance.
1829
+ * @template Z The type of the token ticket.
1830
+ * @template E The type of the token context.
1831
+ * @returns A new token instance.
1813
1832
  *
1814
- * @see https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver
1815
- * @see https://0.vuetifyjs.com/composables/system/use-resize-observer
1833
+ * @see https://www.designtokens.org/tr/drafts/format/
1834
+ * @see https://0.vuetifyjs.com/composables/registration/use-tokens
1816
1835
  *
1817
1836
  * @example
1818
1837
  * ```ts
1819
- * import { ref } from 'vue'
1820
- * import { useResizeObserver } from '@vuetify/v0'
1821
- *
1822
- * const el = ref<HTMLElement>()
1823
- * const width = ref(0)
1824
- * const height = ref(0)
1838
+ * import { useTokens } from '@vuetify/v0'
1825
1839
  *
1826
- * const { pause, resume, isPaused } = useResizeObserver(
1827
- * el,
1828
- * (entries) => {
1829
- * const entry = entries[0]
1830
- * if (entry) {
1831
- * width.value = entry.contentRect.width
1832
- * height.value = entry.contentRect.height
1833
- * console.log('Size changed:', width.value, 'x', height.value)
1834
- * }
1840
+ * const tokens = useTokens({
1841
+ * colors: {
1842
+ * primary: '#3b82f6',
1843
+ * secondary: '{colors.primary}', // Alias reference
1835
1844
  * },
1836
- * { immediate: true }
1837
- * )
1838
- *
1839
- * // Pause observation
1840
- * pause()
1845
+ * })
1841
1846
  *
1842
- * // Resume observation
1843
- * resume()
1847
+ * console.log(tokens.resolve('{colors.primary}')) // '#3b82f6'
1848
+ * console.log(tokens.resolve('{colors.secondary}')) // '#3b82f6'
1844
1849
  * ```
1845
1850
  */
1846
- function useResizeObserver(target, callback, options = {}) {
1847
- const { isHydrated } = useHydration();
1848
- const observer = shallowRef();
1849
- const isPaused = shallowRef(false);
1850
- const isActive = toRef(() => !!observer.value);
1851
- function setup() {
1852
- if (!isHydrated.value || !SUPPORTS_OBSERVER || !target.value || isPaused.value) return;
1853
- observer.value = new ResizeObserver((entries) => {
1854
- callback(entries.map((entry) => ({
1855
- contentRect: {
1856
- width: entry.contentRect.width,
1857
- height: entry.contentRect.height,
1858
- top: entry.contentRect.top,
1859
- left: entry.contentRect.left
1860
- },
1861
- target: entry.target
1862
- })));
1863
- });
1864
- observer.value.observe(target.value, { box: options.box || "content-box" });
1865
- if (options.immediate) {
1866
- const rect = target.value.getBoundingClientRect();
1867
- callback([{
1868
- contentRect: {
1869
- width: rect.width,
1870
- height: rect.height,
1871
- top: rect.top,
1872
- left: rect.left
1873
- },
1874
- target: target.value
1875
- }]);
1876
- }
1877
- }
1878
- watch([isHydrated, target], () => {
1879
- cleanup();
1880
- setup();
1881
- }, { immediate: true });
1882
- function cleanup() {
1883
- if (observer.value) {
1884
- observer.value.disconnect();
1885
- observer.value = void 0;
1886
- }
1887
- }
1888
- function pause() {
1889
- isPaused.value = true;
1890
- observer.value?.disconnect();
1851
+ function createTokens(tokens = {}, options = {}) {
1852
+ const logger = useLogger();
1853
+ const registry = useRegistry(options);
1854
+ const cache = /* @__PURE__ */ new Map();
1855
+ registry.onboard(flatten(tokens, options.prefix, !!options.flat));
1856
+ function isAlias(token) {
1857
+ return /* @__PURE__ */ isString(token) && token.length > 2 && token[0] === "{" && token.at(-1) === "}";
1891
1858
  }
1892
- function resume() {
1893
- isPaused.value = false;
1894
- setup();
1859
+ function isTokenAlias(value) {
1860
+ return /* @__PURE__ */ isObject(value) && "$value" in value;
1895
1861
  }
1896
- function stop() {
1897
- cleanup();
1862
+ function resolve(token, visited = /* @__PURE__ */ new Set()) {
1863
+ const cacheKey = /* @__PURE__ */ isString(token) ? token : JSON.stringify(token);
1864
+ const cached = cache.get(cacheKey);
1865
+ if (!/* @__PURE__ */ isUndefined(cached)) return cached;
1866
+ const reference = isTokenAlias(token) ? token.$value : token;
1867
+ const isAliasReference = /* @__PURE__ */ isString(reference) && isAlias(reference);
1868
+ const clean = isAliasReference ? reference.slice(1, -1) : String(reference);
1869
+ if (visited.has(clean)) {
1870
+ logger.warn(`Circular alias detected for "${clean}"`);
1871
+ cache.set(cacheKey, void 0);
1872
+ return;
1873
+ }
1874
+ visited.add(clean);
1875
+ let found = registry.get(clean);
1876
+ let segments = [];
1877
+ if (!found && clean.includes(".")) {
1878
+ const parts = clean.split(".");
1879
+ for (let i = parts.length - 1; i > 0; i--) {
1880
+ const prefix = parts.slice(0, i).join(".");
1881
+ const suffix = parts.slice(i);
1882
+ const candidate = registry.get(prefix);
1883
+ if (!/* @__PURE__ */ isUndefined(candidate?.value)) {
1884
+ found = candidate;
1885
+ segments = suffix;
1886
+ break;
1887
+ }
1888
+ }
1889
+ }
1890
+ if (/* @__PURE__ */ isUndefined(found?.value)) {
1891
+ if (isAliasReference) logger.warn(`Alias not found for "${String(reference)}"`);
1892
+ cache.set(cacheKey, void 0);
1893
+ return;
1894
+ }
1895
+ let result;
1896
+ let current = found.value;
1897
+ if (segments.length > 0) {
1898
+ if (isTokenAlias(current)) current = current.$value;
1899
+ for (const segment of segments) {
1900
+ if (!/* @__PURE__ */ isObject(current) || !(segment in current)) {
1901
+ current = void 0;
1902
+ break;
1903
+ }
1904
+ current = current[segment];
1905
+ if (isTokenAlias(current)) current = current.$value;
1906
+ }
1907
+ if (/* @__PURE__ */ isUndefined(current)) {
1908
+ logger.warn(`Path not found inside "${clean}": ${segments.join(".")}`);
1909
+ cache.set(cacheKey, void 0);
1910
+ return;
1911
+ }
1912
+ result = current;
1913
+ } else if (isTokenAlias(current)) {
1914
+ const inner = current.$value;
1915
+ if (/* @__PURE__ */ isString(inner) && isAlias(inner)) return resolve(inner, visited);
1916
+ result = inner;
1917
+ } else if (/* @__PURE__ */ isString(current) && isAlias(current)) return resolve(current, visited);
1918
+ else result = current;
1919
+ cache.set(cacheKey, result);
1920
+ return result;
1898
1921
  }
1899
- onScopeDispose(stop, true);
1900
1922
  return {
1901
- isActive: shallowReadonly(isActive),
1902
- isPaused: shallowReadonly(isPaused),
1903
- pause,
1904
- resume,
1905
- stop
1923
+ ...registry,
1924
+ resolve,
1925
+ isAlias,
1926
+ get size() {
1927
+ return registry.size;
1928
+ }
1906
1929
  };
1907
1930
  }
1908
1931
  /**
1909
- * A convenience composable that uses the Resize Observer API to track an
1910
- * element's size.
1932
+ * Creates a new token context.
1911
1933
  *
1912
- * @param target The element to observe.
1913
- * @returns An object with the element's width and height.
1934
+ * @param namespace The namespace for the token context.
1935
+ * @param tokens The tokens to use.
1936
+ * @template Z The type of the token ticket.
1937
+ * @template E The type of the token context.
1938
+ * @returns A new token context.
1914
1939
  *
1915
- * @see https://0.vuetifyjs.com/composables/system/use-resize-observer#use-element-size
1940
+ * @see https://0.vuetifyjs.com/composables/registration/use-tokens
1916
1941
  *
1917
1942
  * @example
1918
1943
  * ```ts
1919
- * import { ref, watchEffect } from 'vue'
1920
- * import { useElementSize } from '@vuetify/v0'
1921
- *
1922
- * const box = ref<HTMLElement>()
1923
- * const { width, height } = useElementSize(box)
1944
+ * import { createTokensContext } from '@vuetify/v0'
1924
1945
  *
1925
- * // Width and height are reactive refs
1926
- * watchEffect(() => {
1927
- * console.log('Box size:', width.value, 'x', height.value)
1946
+ * export const [useTokens, provideTokens, context] = createTokensContext({
1947
+ * namespace: 'v0:tokens',
1948
+ * tokens: {
1949
+ * colors: {
1950
+ * primary: '#3b82f6',
1951
+ * secondary: '{colors.primary}', // Alias reference
1952
+ * },
1953
+ * },
1928
1954
  * })
1929
1955
  * ```
1930
1956
  */
1931
- function useElementSize(target) {
1932
- const width = shallowRef(0);
1933
- const height = shallowRef(0);
1934
- const { pause: _pause, resume, stop, isActive, isPaused } = useResizeObserver(target, (entries) => {
1935
- const entry = entries[0];
1936
- if (entry) {
1937
- width.value = entry.contentRect.width;
1938
- height.value = entry.contentRect.height;
1939
- }
1940
- }, { immediate: true });
1941
- function pause() {
1942
- width.value = 0;
1943
- height.value = 0;
1944
- _pause();
1957
+ function createTokensContext(_options) {
1958
+ const { namespace = "v0:tokens", tokens = {}, ...options } = _options;
1959
+ const [useTokensContext, _provideTokensContext] = createContext(namespace);
1960
+ const context = createTokens(tokens, options);
1961
+ function provideTokensContext(_context = context, app) {
1962
+ return _provideTokensContext(_context, app);
1945
1963
  }
1946
- return {
1947
- width,
1948
- height,
1949
- isActive,
1950
- isPaused,
1951
- pause,
1952
- resume,
1953
- stop
1954
- };
1964
+ return createTrinity(useTokensContext, provideTokensContext, context);
1955
1965
  }
1956
-
1957
- //#endregion
1958
- //#region src/composables/useOverflow/index.ts
1959
1966
  /**
1960
- * @module useOverflow
1961
- *
1962
- * @remarks
1963
- * Composable for computing how many items fit in a container based on available width.
1964
- * Enables responsive truncation logic for Pagination, Breadcrumbs, and similar components.
1965
- *
1966
- * Key features:
1967
- * - Container width tracking via ResizeObserver
1968
- * - Two modes: variable-width (per-item) or uniform-width (sample-based)
1969
- * - Computes capacity (how many items fit)
1970
- * - SSR-safe with Infinity fallback
1971
- * - Supports reserved space for nav buttons, ellipsis, etc.
1967
+ * Returns the current tokens instance.
1972
1968
  *
1973
- * Use variable mode (default) for items with different widths like Breadcrumbs.
1974
- * Use uniform mode (itemWidth option) for same-width items like Pagination buttons.
1975
- */
1976
- /**
1977
- * Creates a new overflow context for computing how many items fit in a container.
1969
+ * @param namespace The namespace for the tokens context. Defaults to `'v0:tokens'`.
1970
+ * @returns The current tokens instance.
1978
1971
  *
1979
- * @param options Configuration options
1980
- * @returns Overflow context with container ref, capacity, and measurement functions
1972
+ * @see https://0.vuetifyjs.com/composables/registration/use-tokens
1981
1973
  *
1982
- * @example Variable-width mode (Breadcrumbs)
1974
+ * @example
1983
1975
  * ```vue
1984
- * <script lang="ts" setup>
1985
- * import { useTemplateRef } from 'vue'
1986
- * import { createOverflow } from '@vuetify/v0'
1976
+ * <script setup lang="ts">
1977
+ * import { useTokens } from '@vuetify/v0'
1987
1978
  *
1988
- * const containerRef = useTemplateRef('container')
1989
- * const overflow = createOverflow({
1990
- * container: containerRef,
1991
- * gap: 8,
1992
- * reserved: 40,
1993
- * })
1979
+ * const tokens = useTokens()
1994
1980
  * <\/script>
1995
- *
1996
- * <template>
1997
- * <div ref="container">
1998
- * <span
1999
- * v-for="(item, i) in items.slice(0, overflow.capacity.value)"
2000
- * :key="i"
2001
- * :ref="el => overflow.measure(i, el)"
2002
- * >
2003
- * {{ item }}
2004
- * </span>
2005
- * <span v-if="overflow.isOverflowing.value">...</span>
2006
- * </div>
2007
- * </template>
2008
- * ```
2009
- *
2010
- * @example Uniform-width mode (Pagination)
2011
- * ```ts
2012
- * const overflow = createOverflow({
2013
- * container: () => atom.value?.element,
2014
- * itemWidth: buttonWidth,
2015
- * reserved: () => buttonWidth.value * 4,
2016
- * })
2017
1981
  * ```
2018
1982
  */
2019
- function createOverflow(options = {}) {
2020
- const { container: _container, gap = 0, reserved = 0, itemWidth, reverse } = options;
2021
- const container = /* @__PURE__ */ isUndefined(_container) ? shallowRef() : toRef(_container);
2022
- const widths = shallowRef(/* @__PURE__ */ new Map());
2023
- const { width } = useElementSize(container);
2024
- function measure(index, el) {
2025
- if (!el) {
2026
- if (widths.value.has(index)) {
2027
- const next = new Map(widths.value);
2028
- next.delete(index);
2029
- widths.value = next;
1983
+ function useTokens(namespace = "v0:tokens") {
1984
+ return useContext(namespace);
1985
+ }
1986
+ /**
1987
+ * Flattens a nested collection of tokens into a flat array of tokens.
1988
+ * Each token is represented by an object containing its ID & value.
1989
+ * @param tokens The collection of tokens to flatten.
1990
+ * @param prefix An optional prefix to prepend to each token ID.
1991
+ * @returns An array of flattened tokens, each with an ID and value.
1992
+ */
1993
+ function flatten(tokens, prefix = "", flat = false) {
1994
+ const flattened = [];
1995
+ const stack = [{
1996
+ tokens,
1997
+ prefix,
1998
+ flat
1999
+ }];
2000
+ while (stack.length > 0) {
2001
+ const { tokens: currentTokens, prefix: currentPrefix, flat: flat$1 } = stack.pop();
2002
+ const meta = {};
2003
+ for (const k in currentTokens) if (k.startsWith("$")) meta[k] = currentTokens[k];
2004
+ if (Object.keys(meta).length > 0 && currentPrefix) flattened.push({
2005
+ id: currentPrefix,
2006
+ value: meta
2007
+ });
2008
+ for (const key in currentTokens) {
2009
+ if (key.startsWith("$")) continue;
2010
+ const value = currentTokens[key];
2011
+ const id = currentPrefix ? `${currentPrefix}.${key}` : key;
2012
+ if (!/* @__PURE__ */ isObject(value)) {
2013
+ flattened.push({
2014
+ id,
2015
+ value
2016
+ });
2017
+ continue;
2030
2018
  }
2031
- return;
2019
+ if ("$value" in value) {
2020
+ flattened.push({
2021
+ id,
2022
+ value
2023
+ });
2024
+ const inner = value.$value;
2025
+ if (/* @__PURE__ */ isObject(inner) && !flat$1) for (const innerKey in inner) {
2026
+ if (innerKey.startsWith("$")) continue;
2027
+ const child = inner[innerKey];
2028
+ const childId = `${id}.${innerKey}`;
2029
+ if (!/* @__PURE__ */ isObject(child)) flattened.push({
2030
+ id: childId,
2031
+ value: child
2032
+ });
2033
+ else if ("$value" in child) flattened.push({
2034
+ id: childId,
2035
+ value: child
2036
+ });
2037
+ else stack.push({
2038
+ tokens: child,
2039
+ prefix: childId,
2040
+ flat: flat$1
2041
+ });
2042
+ }
2043
+ continue;
2044
+ }
2045
+ if (flat$1) {
2046
+ flattened.push({
2047
+ id,
2048
+ value
2049
+ });
2050
+ continue;
2051
+ }
2052
+ stack.push({
2053
+ tokens: value,
2054
+ prefix: id,
2055
+ flat: flat$1
2056
+ });
2032
2057
  }
2033
- const style = getComputedStyle(el);
2034
- const marginX = Number.parseFloat(style.marginLeft) + Number.parseFloat(style.marginRight);
2035
- const w = el.offsetWidth + marginX;
2036
- if (widths.value.get(index) !== w) widths.value = new Map(widths.value).set(index, w);
2037
2058
  }
2038
- function reset() {
2039
- widths.value = /* @__PURE__ */ new Map();
2059
+ return flattened;
2060
+ }
2061
+
2062
+ //#endregion
2063
+ //#region src/composables/useLocale/adapters/v0.ts
2064
+ /**
2065
+ * Vuetify0.x locale adapter implementation
2066
+ *
2067
+ * This adapter provides translation and number formatting
2068
+ * capabilities using the Intl API and supports both
2069
+ * numbered ({0}, {1}) and named ({name}) variables in translation strings.
2070
+ */
2071
+ var Vuetify0LocaleAdapter = class {
2072
+ t(message, ...params) {
2073
+ let resolvedMessage = message;
2074
+ if (params.length > 0 && /* @__PURE__ */ isObject(params[0])) {
2075
+ const variables = params[0];
2076
+ resolvedMessage = resolvedMessage.replace(/{([a-zA-Z][a-zA-Z0-9_]*)}/g, (match, name) => {
2077
+ return /* @__PURE__ */ isUndefined(variables[name]) ? match : String(variables[name]);
2078
+ });
2079
+ params = params.slice(1);
2080
+ }
2081
+ resolvedMessage = resolvedMessage.replace(/\{(\d+)\}/g, (match, index) => {
2082
+ const idx = Number.parseInt(index, 10);
2083
+ if (!/* @__PURE__ */ isUndefined(params[idx])) return String(params[idx]);
2084
+ return match;
2085
+ });
2086
+ return resolvedMessage;
2040
2087
  }
2041
- const total = computed(() => {
2042
- const g = toValue(gap);
2043
- let sum = 0;
2044
- let count = 0;
2045
- for (const w of widths.value.values()) {
2046
- sum += w + (count > 0 ? g : 0);
2047
- count++;
2088
+ n(value, locale, ...params) {
2089
+ if (!IN_BROWSER || !locale) return value.toString();
2090
+ const options = params[0];
2091
+ return new Intl.NumberFormat(String(locale), options).format(value);
2092
+ }
2093
+ };
2094
+
2095
+ //#endregion
2096
+ //#region src/composables/useLocale/index.ts
2097
+ /**
2098
+ * @module useLocale
2099
+ *
2100
+ * @remarks
2101
+ * Internationalization (i18n) composable with adapter pattern for message translation.
2102
+ *
2103
+ * Key features:
2104
+ * - Locale selection with createSingle
2105
+ * - Token-based message storage with useTokens
2106
+ * - Numbered and named placeholder support ({0}, {name})
2107
+ * - Number formatting with Intl.NumberFormat
2108
+ * - Adapter pattern for integration with i18n providers
2109
+ *
2110
+ * Integrates with createSingle for locale selection and useTokens for message resolution.
2111
+ */
2112
+ /**
2113
+ * Creates a new locale instance.
2114
+ *
2115
+ * @param options The options for the locale instance.
2116
+ * @template Z The type of the locale ticket.
2117
+ * @template E The type of the locale context.
2118
+ * @returns A new locale instance.
2119
+ *
2120
+ * @see https://0.vuetifyjs.com/composables/plugins/use-locale
2121
+ */
2122
+ function createLocale(_options = {}) {
2123
+ const { adapter = new Vuetify0LocaleAdapter(), messages = {}, ...options } = _options;
2124
+ const tokens = createTokens(messages);
2125
+ const registry = createSingle(options);
2126
+ for (const id in messages) {
2127
+ registry.register({ id });
2128
+ if (id === options.default && !registry.selectedId.value) registry.select(id);
2129
+ }
2130
+ function t(key, params, fallback) {
2131
+ const locale = registry.selectedId.value;
2132
+ const args = toArray(params);
2133
+ if (!locale) return adapter.t(fallback ?? key, ...args);
2134
+ const path = `${locale}.${key}`;
2135
+ const message = tokens.get(path)?.value;
2136
+ const template = /* @__PURE__ */ isString(message) ? resolve(locale, message) : fallback ?? key;
2137
+ return adapter.t(template, ...args);
2138
+ }
2139
+ function n(value, ...params) {
2140
+ return adapter.n(value, registry.selectedId.value, ...params);
2141
+ }
2142
+ function resolve(locale, str) {
2143
+ return str.replace(/{([a-zA-Z0-9.-_]+)}/g, (match, key) => {
2144
+ const [prefix, ...rest] = key.split(".");
2145
+ const target = registry.has(prefix) ? prefix : locale;
2146
+ const path = `${target}.${registry.has(prefix) ? rest.join(".") : key}`;
2147
+ const resolved = tokens.get(path)?.value;
2148
+ if (/* @__PURE__ */ isString(resolved)) return resolve(target, resolved);
2149
+ return match;
2150
+ });
2151
+ }
2152
+ return {
2153
+ ...registry,
2154
+ t,
2155
+ n,
2156
+ get size() {
2157
+ return registry.size;
2048
2158
  }
2049
- return sum;
2050
- });
2159
+ };
2160
+ }
2161
+ function createLocaleFallback() {
2051
2162
  return {
2052
- container,
2053
- width,
2054
- capacity: computed(() => {
2055
- const available = width.value - toValue(reserved);
2056
- if (width.value === 0) return Infinity;
2057
- if (available <= 0) return 0;
2058
- const g = toValue(gap);
2059
- const uniformWidth = toValue(itemWidth);
2060
- if (uniformWidth && uniformWidth > 0) {
2061
- const first = uniformWidth;
2062
- const subsequent = uniformWidth + g;
2063
- if (available < first) return 0;
2064
- return Math.max(1, Math.floor((available - first) / subsequent) + 1);
2065
- }
2066
- const entries = [...widths.value.entries()].toSorted((a, b) => a[0] - b[0]);
2067
- if (toValue(reverse)) entries.reverse();
2068
- let sum = 0;
2069
- let count = 0;
2070
- for (const [, w] of entries) {
2071
- const next = sum + w + (count > 0 ? g : 0);
2072
- if (next > available) break;
2073
- sum = next;
2074
- count++;
2075
- }
2076
- return count;
2077
- }),
2078
- total,
2079
- isOverflowing: toRef(() => {
2080
- return total.value > width.value - toValue(reserved);
2081
- }),
2082
- measure,
2083
- reset
2163
+ size: 0,
2164
+ t: (key, _params, fallback) => fallback ?? key,
2165
+ n: String
2084
2166
  };
2085
2167
  }
2086
2168
  /**
2087
- * Creates an overflow context with dependency injection support.
2169
+ * Creates a new locale context.
2088
2170
  *
2089
- * @param options Configuration options including namespace
2090
- * @returns Trinity tuple: [useContext, provideContext, defaultContext]
2171
+ * @param options The options for the locale context.
2172
+ * @template Z The type of the locale ticket.
2173
+ * @template E The type of the locale context.
2174
+ * @returns A new locale context.
2175
+ *
2176
+ * @see https://0.vuetifyjs.com/composables/plugins/use-locale
2091
2177
  *
2092
2178
  * @example
2093
2179
  * ```ts
2094
- * // Create injectable context
2095
- * const [useOverflow, provideOverflow, overflow] = createOverflowContext({
2096
- * namespace: 'my-overflow',
2097
- * gap: 8,
2098
- * reserved: 160,
2180
+ * import { createLocaleContext } from '@vuetify/v0'
2181
+ *
2182
+ * export const [useAppLocale, provideAppLocale, appLocale] = createLocaleContext({
2183
+ * namespace: 'app:locale',
2184
+ * messages: {
2185
+ * en: { hello: 'Hello' },
2186
+ * es: { hello: 'Hola' },
2187
+ * },
2099
2188
  * })
2100
2189
  *
2101
- * // In parent component
2102
- * provideOverflow()
2190
+ * // In a parent component:
2191
+ * provideAppLocale()
2103
2192
  *
2104
- * // In child component
2105
- * const overflow = useOverflow()
2193
+ * // In a child component:
2194
+ * const locale = useAppLocale()
2195
+ * locale.select('es')
2106
2196
  * ```
2107
2197
  */
2108
- function createOverflowContext(_options = {}) {
2109
- const { namespace = "v0:overflow", ...options } = _options;
2110
- const [useOverflowContext, _provideOverflowContext] = createContext(namespace);
2111
- const context = createOverflow(options);
2112
- function provideOverflowContext(_context = context, app) {
2113
- return _provideOverflowContext(_context, app);
2198
+ function createLocaleContext(_options = {}) {
2199
+ const { namespace = "v0:locale", ...options } = _options;
2200
+ const [useLocaleContext, _provideLocaleContext] = createContext(namespace);
2201
+ const context = createLocale(options);
2202
+ function provideLocaleContext(_context = context, app) {
2203
+ return _provideLocaleContext(_context, app);
2114
2204
  }
2115
- return createTrinity(useOverflowContext, provideOverflowContext, context);
2205
+ return createTrinity(useLocaleContext, provideLocaleContext, context);
2206
+ }
2207
+ /**
2208
+ * Creates a new locale plugin.
2209
+ *
2210
+ * @param options The options for the locale plugin.
2211
+ * @template Z The type of the locale ticket.
2212
+ * @template E The type of the locale context.
2213
+ * @template R The type of the token ticket.
2214
+ * @template O The type of the token context.
2215
+ * @returns A new locale plugin.
2216
+ *
2217
+ * @see https://0.vuetifyjs.com/composables/plugins/use-locale
2218
+ */
2219
+ function createLocalePlugin(_options = {}) {
2220
+ const { namespace = "v0:locale", adapter = new Vuetify0LocaleAdapter(), messages = {}, ...options } = _options;
2221
+ const [, provideLocaleContext, context] = createLocaleContext({
2222
+ ...options,
2223
+ namespace,
2224
+ adapter,
2225
+ messages
2226
+ });
2227
+ return createPlugin({
2228
+ namespace,
2229
+ provide: (app) => {
2230
+ provideLocaleContext(context, app);
2231
+ }
2232
+ });
2116
2233
  }
2117
2234
  /**
2118
- * Returns the current overflow context from dependency injection.
2119
- *
2120
- * @param namespace The namespace for the overflow context. Defaults to `v0:overflow`.
2121
- * @returns The current overflow context.
2122
- *
2123
- * @example
2124
- * ```vue
2125
- * <script lang="ts" setup>
2126
- * import { useOverflow } from '@vuetify/v0'
2235
+ * Returns the current locale instance.
2127
2236
  *
2128
- * // Inject overflow context provided by parent
2129
- * const overflow = useOverflow()
2130
- * <\/script>
2237
+ * @returns The current locale instance.
2131
2238
  *
2132
- * <template>
2133
- * <div>
2134
- * <p>Capacity: {{ overflow.capacity.value }}</p>
2135
- * </div>
2136
- * </template>
2137
- * ```
2239
+ * @see https://0.vuetifyjs.com/composables/plugins/use-locale
2138
2240
  */
2139
- function useOverflow(namespace = "v0:overflow") {
2140
- return useContext(namespace);
2241
+ function useLocale(namespace = "v0:locale") {
2242
+ const fallback = createLocaleFallback();
2243
+ if (!getCurrentInstance()) return fallback;
2244
+ try {
2245
+ return useContext(namespace, fallback);
2246
+ } catch {
2247
+ return fallback;
2248
+ }
2141
2249
  }
2142
2250
 
2143
2251
  //#endregion
2144
- //#region src/composables/usePagination/index.ts
2252
+ //#region src/composables/useHydration/index.ts
2145
2253
  /**
2146
- * @module usePagination
2254
+ * @module useHydration
2255
+ *
2256
+ * @see https://0.vuetifyjs.com/composables/plugins/use-hydration
2147
2257
  *
2148
2258
  * @remarks
2149
- * Lightweight pagination composable for navigating through pages.
2259
+ * SSR hydration state management composable.
2150
2260
  *
2151
2261
  * Key features:
2152
- * - No registry overhead - just a bounded integer
2153
- * - Direct ref support for v-model compatibility
2154
- * - Navigation methods: next, prev, first, last
2155
- * - Computed visible items with ellipsis
2156
- * - Trinity pattern for dependency injection
2262
+ * - Hydration state detection (browser vs SSR)
2263
+ * - Root component detection
2264
+ * - Readonly hydration state refs
2265
+ * - Plugin installation support
2266
+ * - Perfect for hydration-safe rendering
2157
2267
  *
2158
- * Unlike registry-based composables, pagination tracks a single number
2159
- * within a range, making it efficient for large page counts.
2268
+ * Essential for composables that need to behave differently during SSR vs client-side.
2160
2269
  */
2161
2270
  /**
2162
- * Creates a pagination instance.
2271
+ * Creates a new hydration instance.
2163
2272
  *
2164
- * @param options The options for the pagination instance.
2165
- * @returns A pagination context with navigation methods.
2273
+ * @returns A new hydration instance.
2274
+ *
2275
+ * @see https://0.vuetifyjs.com/composables/plugins/use-hydration
2166
2276
  *
2167
2277
  * @example
2168
2278
  * ```ts
2169
- * import { createPagination } from '@vuetify/v0'
2170
- *
2171
- * // Basic usage
2172
- * const pagination = createPagination({ size: 100 })
2173
- * pagination.next()
2174
- * pagination.items.value // [{ type: 'page', value: 1 }, { type: 'page', value: 2 }, ...]
2279
+ * import { createHydration } from '@vuetify/v0'
2175
2280
  *
2176
- * // With v-model (pass a ref)
2177
- * const page = ref(1)
2178
- * const pagination = createPagination({ page, size: 100 })
2179
- * // Mutating pagination.page or the passed ref syncs both
2281
+ * const hydration = createHydration()
2282
+ * console.log(hydration.isHydrated.value) // false
2283
+ * hydration.hydrate()
2284
+ * console.log(hydration.isHydrated.value) // true
2180
2285
  * ```
2181
2286
  */
2182
- function createPagination(_options = {}) {
2183
- const { page: _page = 1, itemsPerPage: _itemsPerPage = 10, size: _size = 0, visible: _visible = 7, ellipsis = "..." } = _options;
2184
- const page = isRef(_page) ? _page : shallowRef(_page);
2185
- const pages = computed(() => {
2186
- const size = toValue(_size);
2187
- const perPage = toValue(_itemsPerPage);
2188
- if (size <= 0 || /* @__PURE__ */ isNaN(size)) return 0;
2189
- return Math.ceil(size / perPage);
2190
- });
2191
- function first() {
2192
- page.value = 1;
2193
- }
2194
- function last() {
2195
- page.value = Math.max(1, pages.value);
2196
- }
2197
- function next() {
2198
- if (page.value < pages.value) page.value++;
2199
- }
2200
- function prev() {
2201
- if (page.value > 1) page.value--;
2202
- }
2203
- function select(value) {
2204
- if (value < 1) page.value = 1;
2205
- else if (value > pages.value) page.value = Math.max(1, pages.value);
2206
- else page.value = value;
2207
- }
2208
- const isFirst = computed(() => page.value <= 1);
2209
- const isLast = computed(() => page.value >= pages.value);
2210
- const pageStart = computed(() => (page.value - 1) * toValue(_itemsPerPage));
2211
- const pageStop = computed(() => Math.min(pageStart.value + toValue(_itemsPerPage), toValue(_size)));
2212
- function toPage(value) {
2213
- return {
2214
- type: "page",
2215
- value
2216
- };
2217
- }
2218
- function toEllipsis() {
2219
- return ellipsis === false ? false : {
2220
- type: "ellipsis",
2221
- value: ellipsis
2222
- };
2223
- }
2224
- function filter(array) {
2225
- return array.filter((item) => item !== false);
2287
+ function createHydration() {
2288
+ const isHydrated = shallowRef(false);
2289
+ function hydrate() {
2290
+ isHydrated.value = true;
2226
2291
  }
2227
2292
  return {
2228
- page,
2229
- ellipsis,
2230
- items: computed(() => {
2231
- const pageCount = pages.value;
2232
- const visible = toValue(_visible);
2233
- const current = page.value;
2234
- if (pageCount <= 0 || /* @__PURE__ */ isNaN(pageCount) || pageCount > Number.MAX_SAFE_INTEGER) return [];
2235
- if (visible <= 0) return [];
2236
- if (visible <= 2) return [toPage(current)];
2237
- if (pageCount <= visible) return (/* @__PURE__ */ range(pageCount, 1)).map(toPage);
2238
- if (visible === 3) {
2239
- const mid = current <= 1 ? 2 : current >= pageCount ? pageCount - 1 : current;
2240
- return [
2241
- toPage(1),
2242
- toPage(mid),
2243
- toPage(pageCount)
2244
- ];
2245
- }
2246
- const boundary = visible - 2;
2247
- const middle = visible - 4;
2248
- if (middle <= 0) {
2249
- if (current <= boundary) return filter([
2250
- ...(/* @__PURE__ */ range(boundary, 1)).map(toPage),
2251
- toEllipsis(),
2252
- toPage(pageCount)
2253
- ]);
2254
- if (current > pageCount - boundary) return filter([
2255
- toPage(1),
2256
- toEllipsis(),
2257
- ...(/* @__PURE__ */ range(boundary, pageCount - boundary + 1)).map(toPage)
2258
- ]);
2259
- return current <= Math.ceil(pageCount / 2) ? filter([
2260
- toPage(1),
2261
- toPage(current),
2262
- toEllipsis(),
2263
- toPage(pageCount)
2264
- ]) : filter([
2265
- toPage(1),
2266
- toEllipsis(),
2267
- toPage(current),
2268
- toPage(pageCount)
2269
- ]);
2270
- }
2271
- const leftThreshold = boundary - 1;
2272
- const rightThreshold = pageCount - boundary + 2;
2273
- if (current <= leftThreshold) return filter([
2274
- ...(/* @__PURE__ */ range(boundary, 1)).map(toPage),
2275
- toEllipsis(),
2276
- toPage(pageCount)
2277
- ]);
2278
- else if (current >= rightThreshold) return filter([
2279
- toPage(1),
2280
- toEllipsis(),
2281
- ...(/* @__PURE__ */ range(boundary, pageCount - boundary + 1)).map(toPage)
2282
- ]);
2283
- else {
2284
- const start = current - Math.floor(middle / 2);
2285
- return filter([
2286
- toPage(1),
2287
- toEllipsis(),
2288
- ...(/* @__PURE__ */ range(middle, start)).map(toPage),
2289
- toEllipsis(),
2290
- toPage(pageCount)
2291
- ]);
2292
- }
2293
- }),
2294
- pageStart,
2295
- pageStop,
2296
- isFirst,
2297
- isLast,
2298
- first,
2299
- last,
2300
- next,
2301
- prev,
2302
- select,
2303
- get itemsPerPage() {
2304
- return toValue(_itemsPerPage);
2305
- },
2306
- get size() {
2307
- return toValue(_size);
2308
- },
2309
- get pages() {
2310
- return pages.value;
2311
- }
2293
+ isHydrated: shallowReadonly(isHydrated),
2294
+ hydrate
2295
+ };
2296
+ }
2297
+ function createFallbackHydration() {
2298
+ return {
2299
+ isHydrated: shallowReadonly(shallowRef(true)),
2300
+ hydrate: () => {}
2312
2301
  };
2313
2302
  }
2314
2303
  /**
2315
- * Creates a pagination context for dependency injection.
2304
+ * Creates a new hydration context trinity.
2305
+ *
2306
+ * @param options Options for creating the hydration context.
2307
+ * @template E The type of the hydration context.
2308
+ * @returns A new hydration context trinity.
2309
+ *
2310
+ * @see https://0.vuetifyjs.com/composables/plugins/use-hydration
2311
+ *
2312
+ * @example
2313
+ * ```ts
2314
+ * import { createHydrationContext } from '@vuetify/v0'
2315
+ *
2316
+ * export const [useHydrationContext, provideHydrationContext, context] = createHydrationContext({
2317
+ * namespace: 'app:hydration',
2318
+ * })
2319
+ * ```
2320
+ */
2321
+ function createHydrationContext(_options = {}) {
2322
+ const { namespace = "v0:hydration" } = _options;
2323
+ const [useHydrationContext, _provideHydrationContext] = createContext(namespace);
2324
+ const context = createHydration();
2325
+ function provideHydrationContext(_context = context, app) {
2326
+ return _provideHydrationContext(_context, app);
2327
+ }
2328
+ return createTrinity(useHydrationContext, provideHydrationContext, context);
2329
+ }
2330
+ /**
2331
+ * Creates a new hydration plugin.
2332
+ *
2333
+ * @param options The options for the hydration plugin.
2334
+ * @template E The type of the hydration context.
2335
+ * @returns A new hydration plugin.
2316
2336
  *
2317
- * @param options The options including namespace.
2318
- * @returns A trinity: [usePagination, providePagination, defaultContext]
2337
+ * @see https://0.vuetifyjs.com/composables/plugins/use-hydration
2319
2338
  *
2320
2339
  * @example
2321
2340
  * ```ts
2322
- * // With default namespace 'v0:pagination'
2323
- * const [usePagination, providePaginationContext] = createPaginationContext({ size: 50 })
2341
+ * import { createApp } from 'vue'
2342
+ * import { createHydrationPlugin } from '@vuetify/v0'
2343
+ * import App from './App.vue'
2324
2344
  *
2325
- * // Or with custom namespace
2326
- * const [usePagination, providePaginationContext] = createPaginationContext({
2327
- * namespace: 'my-pagination',
2328
- * size: 50,
2329
- * })
2345
+ * const app = createApp(App)
2330
2346
  *
2331
- * // Parent component
2332
- * providePaginationContext()
2347
+ * app.use(createHydrationPlugin())
2333
2348
  *
2334
- * // Child component
2335
- * const pagination = usePagination()
2336
- * pagination.next()
2349
+ * app.mount('#app')
2337
2350
  * ```
2338
2351
  */
2339
- function createPaginationContext(_options = {}) {
2340
- const { namespace = "v0:pagination", ...options } = _options;
2341
- const [usePaginationContext, _providePaginationContext] = createContext(namespace);
2342
- const context = createPagination(options);
2343
- function providePaginationContext(_context = context, app) {
2344
- return _providePaginationContext(_context, app);
2345
- }
2346
- return createTrinity(usePaginationContext, providePaginationContext, context);
2352
+ function createHydrationPlugin(_options = {}) {
2353
+ const { namespace = "v0:hydration", ...options } = _options;
2354
+ const [, provideHydrationContext, context] = createHydrationContext({
2355
+ ...options,
2356
+ namespace
2357
+ });
2358
+ return createPlugin({
2359
+ namespace,
2360
+ provide: (app) => {
2361
+ provideHydrationContext(context, app);
2362
+ },
2363
+ setup: (app) => {
2364
+ app.mixin({ mounted() {
2365
+ if (!/* @__PURE__ */ isNull(this.$parent)) return;
2366
+ context.hydrate();
2367
+ } });
2368
+ }
2369
+ });
2347
2370
  }
2348
2371
  /**
2349
- * Returns the current pagination instance from context.
2372
+ * Returns the current hydration instance.
2350
2373
  *
2351
- * @param namespace The namespace. @default 'v0:pagination'
2352
- * @returns The pagination context.
2374
+ * @param namespace The namespace for the hydration context. Defaults to `v0:hydration`.
2375
+ * @returns The current hydration instance.
2376
+ *
2377
+ * @see https://0.vuetifyjs.com/composables/plugins/use-hydration
2353
2378
  *
2354
2379
  * @example
2355
2380
  * ```vue
2356
- * <script setup>
2357
- * import { usePagination } from '@vuetify/v0'
2381
+ * <script setup lang="ts">
2382
+ * import { useHydration } from '@vuetify/v0'
2358
2383
  *
2359
- * const pagination = usePagination()
2384
+ * const hydration = useHydration()
2360
2385
  * <\/script>
2361
2386
  *
2362
2387
  * <template>
2363
- * <button @click="pagination.prev()" :disabled="pagination.isFirst.value">Prev</button>
2364
- * <button @click="pagination.next()" :disabled="pagination.isLast.value">Next</button>
2388
+ * <div>
2389
+ * <p>Is hydrated: {{ hydration.isHydrated.value }}</p>
2390
+ * </div>
2365
2391
  * </template>
2366
2392
  * ```
2367
2393
  */
2368
- function usePagination(namespace = "v0:pagination") {
2369
- return useContext(namespace);
2394
+ function useHydration(namespace = "v0:hydration") {
2395
+ const fallback = createFallbackHydration();
2396
+ if (!getCurrentInstance()) return fallback;
2397
+ try {
2398
+ return useContext(namespace, fallback);
2399
+ } catch {
2400
+ return fallback;
2401
+ }
2370
2402
  }
2371
2403
 
2372
2404
  //#endregion
2373
- //#region src/composables/useSingle/index.ts
2405
+ //#region src/composables/useResizeObserver/index.ts
2374
2406
  /**
2375
- * @module useSingle
2407
+ * @module useResizeObserver
2376
2408
  *
2377
2409
  * @remarks
2378
- * Single-selection composable that extends useSelection to enforce only one selected item.
2410
+ * ResizeObserver composable with lifecycle management.
2379
2411
  *
2380
2412
  * Key features:
2381
- * - Auto-clears previous selection when selecting new item
2382
- * - Singular computed properties (selectedId, selectedItem, selectedIndex, selectedValue)
2383
- * - Perfect for tabs, radio buttons, theme selectors
2413
+ * - ResizeObserver API wrapper
2414
+ * - Pause/resume/stop functionality
2415
+ * - Automatic cleanup on unmount
2416
+ * - SSR-safe (checks SUPPORTS_OBSERVER)
2417
+ * - Hydration-aware
2418
+ * - Box model options (content-box/border-box)
2384
2419
  *
2385
- * Inheritance chain: useRegistry useSelection useSingle
2420
+ * Perfect for responsive components and size-based rendering.
2386
2421
  */
2387
2422
  /**
2388
- * Creates a new single selection instance that enforces only one selected item at a time.
2389
- *
2390
- * Extends `createSelection` by automatically clearing previous selections when a new item is selected.
2391
- * Adds computed singular properties: `selectedId`, `selectedItem`, `selectedIndex`, `selectedValue`.
2392
- *
2393
- * @param options The options for the single selection instance.
2394
- * @template Z The type of the single selection ticket.
2395
- * @template E The type of the single selection context.
2396
- * @returns A new single selection instance with single-selection enforcement.
2397
- *
2398
- * @remarks
2399
- * **Key Differences from `createSelection`:**
2400
- * - Automatically clears `selectedIds` before selecting a new item (enforces single selection)
2401
- * - Provides singular computed properties instead of plural sets
2402
- * - Perfect for tabs, radio buttons, theme selectors, and other single-choice UI components
2403
- *
2404
- * **Computed Properties:**
2405
- * - `selectedId`: The ID of the selected item (undefined if none selected)
2406
- * - `selectedItem`: The selected ticket object (undefined if none selected)
2407
- * - `selectedIndex`: The index of the selected item (-1 if none selected)
2408
- * - `selectedValue`: The value of the selected item (undefined if none selected)
2423
+ * A composable that uses the Resize Observer API to detect when an element's
2424
+ * size changes.
2409
2425
  *
2410
- * **Inheritance Chain:**
2411
- * `useRegistry` `createSelection` `createSingle` `createStep`
2426
+ * @param target The element to observe.
2427
+ * @param callback The callback to execute when the element's size changes.
2428
+ * @param options The options for the Resize Observer.
2429
+ * @returns An object with methods to control the observer.
2412
2430
  *
2413
- * @see https://0.vuetifyjs.com/composables/selection/use-single
2431
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver
2432
+ * @see https://0.vuetifyjs.com/composables/system/use-resize-observer
2414
2433
  *
2415
2434
  * @example
2416
2435
  * ```ts
2417
- * import { createSingle } from '@vuetify/v0'
2418
- *
2419
- * const tabs = createSingle({ mandatory: true })
2436
+ * import { ref } from 'vue'
2437
+ * import { useResizeObserver } from '@vuetify/v0'
2420
2438
  *
2421
- * tabs.onboard([
2422
- * { id: 'home', value: 'Home' },
2423
- * { id: 'about', value: 'About' },
2424
- * { id: 'contact', value: 'Contact' },
2425
- * ])
2439
+ * const el = ref<HTMLElement>()
2440
+ * const width = ref(0)
2441
+ * const height = ref(0)
2426
2442
  *
2427
- * tabs.first() // Select first tab
2443
+ * const { pause, resume, isPaused } = useResizeObserver(
2444
+ * el,
2445
+ * (entries) => {
2446
+ * const entry = entries[0]
2447
+ * if (entry) {
2448
+ * width.value = entry.contentRect.width
2449
+ * height.value = entry.contentRect.height
2450
+ * console.log('Size changed:', width.value, 'x', height.value)
2451
+ * }
2452
+ * },
2453
+ * { immediate: true }
2454
+ * )
2428
2455
  *
2429
- * console.log(tabs.selectedId.value) // 'home'
2430
- * console.log(tabs.selectedIndex.value) // 0
2456
+ * // Pause observation
2457
+ * pause()
2431
2458
  *
2432
- * tabs.select('about') // Switch to about tab
2433
- * console.log(tabs.selectedId.value) // 'about'
2434
- * console.log(tabs.selectedIds.size) // 1 (always enforces single selection)
2459
+ * // Resume observation
2460
+ * resume()
2435
2461
  * ```
2436
2462
  */
2437
- function createSingle(_options = {}) {
2438
- const { mandatory = false, multiple = false, ...options } = _options;
2439
- const registry = createSelection({
2440
- ...options,
2441
- mandatory,
2442
- multiple
2443
- });
2444
- const selectedId = computed(() => registry.selectedIds.values().next().value);
2445
- const selectedItem = computed(() => registry.selectedItems.value.values().next().value);
2446
- const selectedIndex = computed(() => selectedItem.value?.index ?? -1);
2447
- const selectedValue = computed(() => selectedItem.value?.value);
2448
- function unselect(id) {
2449
- if (mandatory && registry.selectedIds.size === 1) return;
2450
- registry.selectedIds.delete(id);
2451
- }
2452
- function toggle(id) {
2453
- if (registry.selectedIds.has(id)) unselect(id);
2454
- else registry.select(id);
2455
- }
2456
- return {
2457
- ...registry,
2458
- selectedId,
2459
- selectedItem,
2460
- selectedIndex,
2461
- selectedValue,
2462
- unselect,
2463
- toggle,
2464
- get size() {
2465
- return registry.size;
2463
+ function useResizeObserver(target, callback, options = {}) {
2464
+ const { isHydrated } = useHydration();
2465
+ const observer = shallowRef();
2466
+ const isPaused = shallowRef(false);
2467
+ const isActive = toRef(() => !!observer.value);
2468
+ function setup() {
2469
+ if (/* @__PURE__ */ isNull(observer.value)) return;
2470
+ if (!isHydrated.value || !SUPPORTS_OBSERVER || !target.value || isPaused.value) return;
2471
+ observer.value = new ResizeObserver((entries) => {
2472
+ callback(entries.map((entry) => ({
2473
+ contentRect: {
2474
+ width: entry.contentRect.width,
2475
+ height: entry.contentRect.height,
2476
+ top: entry.contentRect.top,
2477
+ left: entry.contentRect.left
2478
+ },
2479
+ target: entry.target
2480
+ })));
2481
+ if (options.once) stop();
2482
+ });
2483
+ observer.value.observe(target.value, { box: options.box || "content-box" });
2484
+ if (options.immediate) {
2485
+ const rect = target.value.getBoundingClientRect();
2486
+ callback([{
2487
+ contentRect: {
2488
+ width: rect.width,
2489
+ height: rect.height,
2490
+ top: rect.top,
2491
+ left: rect.left
2492
+ },
2493
+ target: target.value
2494
+ }]);
2495
+ }
2496
+ }
2497
+ watchEffect(() => {
2498
+ const hydrated = isHydrated.value;
2499
+ const el = target.value;
2500
+ cleanup();
2501
+ if (hydrated && el) setup();
2502
+ });
2503
+ function cleanup() {
2504
+ if (observer.value) {
2505
+ observer.value.disconnect();
2506
+ observer.value = void 0;
2466
2507
  }
2467
- };
2468
- }
2469
- /**
2470
- * Creates a new single selection context.
2471
- *
2472
- * @param options The options for the single selection context.
2473
- * @template Z The type of the single selection ticket.
2474
- * @template E The type of the single selection context.
2475
- * @returns A new single selection context.
2476
- *
2477
- * @see https://0.vuetifyjs.com/composables/selection/use-single
2478
- *
2479
- * @example
2480
- * ```ts
2481
- * import { createSingleContext } from '@vuetify/v0'
2482
- *
2483
- * // With default namespace 'v0:single'
2484
- * export const [useSingle, provideSingle, context] = createSingleContext()
2485
- *
2486
- * // In a parent component:
2487
- * provideSingle()
2488
- *
2489
- * // In a child component:
2490
- * const single = useSingle()
2491
- * single.select('tab-1')
2492
- * ```
2493
- */
2494
- function createSingleContext(_options = {}) {
2495
- const { namespace = "v0:single", ...options } = _options;
2496
- const [useSingleContext, _provideSingleContext] = createContext(namespace);
2497
- const context = createSingle(options);
2498
- function provideSingleContext(_context = context, app) {
2499
- return _provideSingleContext(_context, app);
2500
2508
  }
2501
- return createTrinity(useSingleContext, provideSingleContext, context);
2509
+ function pause() {
2510
+ isPaused.value = true;
2511
+ observer.value?.disconnect();
2512
+ }
2513
+ function resume() {
2514
+ isPaused.value = false;
2515
+ setup();
2516
+ }
2517
+ function stop() {
2518
+ cleanup();
2519
+ observer.value = null;
2520
+ }
2521
+ onScopeDispose(stop, true);
2522
+ return {
2523
+ isActive: shallowReadonly(isActive),
2524
+ isPaused: shallowReadonly(isPaused),
2525
+ pause,
2526
+ resume,
2527
+ stop
2528
+ };
2502
2529
  }
2503
2530
  /**
2504
- * Returns the current single selection instance.
2531
+ * A convenience composable that uses the Resize Observer API to track an
2532
+ * element's size.
2505
2533
  *
2506
- * @param namespace The namespace for the single selection context. Defaults to `'v0:single'`.
2507
- * @returns The current single selection instance.
2534
+ * @param target The element to observe.
2535
+ * @returns An object with the element's width and height.
2508
2536
  *
2509
- * @see https://0.vuetifyjs.com/composables/selection/use-single
2537
+ * @see https://0.vuetifyjs.com/composables/system/use-resize-observer#use-element-size
2510
2538
  *
2511
2539
  * @example
2512
- * ```vue
2513
- * <script setup lang="ts">
2514
- * import { useSingle } from '@vuetify/v0'
2540
+ * ```ts
2541
+ * import { ref, watchEffect } from 'vue'
2542
+ * import { useElementSize } from '@vuetify/v0'
2515
2543
  *
2516
- * const tabs = useSingle()
2517
- * <\/script>
2544
+ * const box = ref<HTMLElement>()
2545
+ * const { width, height } = useElementSize(box)
2518
2546
  *
2519
- * <template>
2520
- * <div>
2521
- * <p>Selected: {{ tabs.selectedId }}</p>
2522
- * </div>
2523
- * </template>
2547
+ * // Width and height are reactive refs
2548
+ * watchEffect(() => {
2549
+ * console.log('Box size:', width.value, 'x', height.value)
2550
+ * })
2524
2551
  * ```
2525
2552
  */
2526
- function useSingle(namespace = "v0:single") {
2527
- return useContext(namespace);
2553
+ function useElementSize(target) {
2554
+ const width = shallowRef(0);
2555
+ const height = shallowRef(0);
2556
+ const { pause: _pause, resume, stop, isActive, isPaused } = useResizeObserver(target, (entries) => {
2557
+ const entry = entries[0];
2558
+ if (entry) {
2559
+ width.value = entry.contentRect.width;
2560
+ height.value = entry.contentRect.height;
2561
+ }
2562
+ }, { immediate: true });
2563
+ function pause() {
2564
+ width.value = 0;
2565
+ height.value = 0;
2566
+ _pause();
2567
+ }
2568
+ return {
2569
+ width,
2570
+ height,
2571
+ isActive,
2572
+ isPaused,
2573
+ pause,
2574
+ resume,
2575
+ stop
2576
+ };
2528
2577
  }
2529
2578
 
2530
2579
  //#endregion
2531
- //#region src/composables/useTokens/index.ts
2580
+ //#region src/composables/useOverflow/index.ts
2532
2581
  /**
2533
- * @module useTokens
2534
- *
2535
- * @see https://0.vuetifyjs.com/composables/registration/use-tokens
2582
+ * @module useOverflow
2536
2583
  *
2537
2584
  * @remarks
2538
- * Design token registry with alias resolution and W3C Design Tokens format support.
2585
+ * Composable for computing how many items fit in a container based on available width.
2586
+ * Enables responsive truncation logic for Pagination, Breadcrumbs, and similar components.
2539
2587
  *
2540
2588
  * Key features:
2541
- * - Alias resolution with circular reference detection
2542
- * - Nested token flattening with dot notation
2543
- * - W3C Design Tokens format ($value, $type, $description, $extensions)
2544
- * - Path-based resolution (e.g., {colors}.blue.500)
2545
- * - Resolution caching for performance (~28,590 ops/sec)
2589
+ * - Container width tracking via ResizeObserver
2590
+ * - Two modes: variable-width (per-item) or uniform-width (sample-based)
2591
+ * - Computes capacity (how many items fit)
2592
+ * - SSR-safe with Infinity fallback
2593
+ * - Supports reserved space for nav buttons, ellipsis, etc.
2546
2594
  *
2547
- * Used by useTheme, useLocale, and useFeatures for token-based configuration.
2595
+ * Use variable mode (default) for items with different widths like Breadcrumbs.
2596
+ * Use uniform mode (itemWidth option) for same-width items like Pagination buttons.
2548
2597
  */
2549
2598
  /**
2550
- * Creates a new token instance.
2599
+ * Creates a new overflow context for computing how many items fit in a container.
2551
2600
  *
2552
- * @param tokens The tokens to use.
2553
- * @param options The options for the token instance.
2554
- * @template Z The type of the token ticket.
2555
- * @template E The type of the token context.
2556
- * @returns A new token instance.
2601
+ * @param options Configuration options
2602
+ * @returns Overflow context with container ref, capacity, and measurement functions
2557
2603
  *
2558
- * @see https://www.designtokens.org/tr/drafts/format/
2559
- * @see https://0.vuetifyjs.com/composables/registration/use-tokens
2604
+ * @example Variable-width mode (Breadcrumbs)
2605
+ * ```vue
2606
+ * <script lang="ts" setup>
2607
+ * import { useTemplateRef } from 'vue'
2608
+ * import { createOverflow } from '@vuetify/v0'
2560
2609
  *
2561
- * @example
2562
- * ```ts
2563
- * import { useTokens } from '@vuetify/v0'
2610
+ * const containerRef = useTemplateRef('container')
2611
+ * const overflow = createOverflow({
2612
+ * container: containerRef,
2613
+ * gap: 8,
2614
+ * reserved: 40,
2615
+ * })
2616
+ * <\/script>
2564
2617
  *
2565
- * const tokens = useTokens({
2566
- * colors: {
2567
- * primary: '#3b82f6',
2568
- * secondary: '{colors.primary}', // Alias reference
2569
- * },
2570
- * })
2618
+ * <template>
2619
+ * <div ref="container">
2620
+ * <span
2621
+ * v-for="(item, i) in items.slice(0, overflow.capacity.value)"
2622
+ * :key="i"
2623
+ * :ref="el => overflow.measure(i, el)"
2624
+ * >
2625
+ * {{ item }}
2626
+ * </span>
2627
+ * <span v-if="overflow.isOverflowing.value">...</span>
2628
+ * </div>
2629
+ * </template>
2630
+ * ```
2571
2631
  *
2572
- * console.log(tokens.resolve('{colors.primary}')) // '#3b82f6'
2573
- * console.log(tokens.resolve('{colors.secondary}')) // '#3b82f6'
2632
+ * @example Uniform-width mode (Pagination)
2633
+ * ```ts
2634
+ * const overflow = createOverflow({
2635
+ * container: () => atom.value?.element,
2636
+ * itemWidth: buttonWidth,
2637
+ * reserved: () => buttonWidth.value * 4,
2638
+ * })
2574
2639
  * ```
2575
2640
  */
2576
- function createTokens(tokens = {}, options = {}) {
2577
- const logger = useLogger();
2578
- const registry = useRegistry(options);
2579
- const cache = /* @__PURE__ */ new Map();
2580
- registry.onboard(flatten(tokens, options.prefix, !!options.flat));
2581
- function isAlias(token) {
2582
- return /* @__PURE__ */ isString(token) && token.length > 2 && token[0] === "{" && token.at(-1) === "}";
2583
- }
2584
- function isTokenAlias(value) {
2585
- return /* @__PURE__ */ isObject(value) && "$value" in value;
2586
- }
2587
- function resolve(token, visited = /* @__PURE__ */ new Set()) {
2588
- const cacheKey = /* @__PURE__ */ isString(token) ? token : JSON.stringify(token);
2589
- const cached = cache.get(cacheKey);
2590
- if (!/* @__PURE__ */ isUndefined(cached)) return cached;
2591
- const reference = isTokenAlias(token) ? token.$value : token;
2592
- const isAliasReference = /* @__PURE__ */ isString(reference) && isAlias(reference);
2593
- const clean = isAliasReference ? reference.slice(1, -1) : String(reference);
2594
- if (visited.has(clean)) {
2595
- logger.warn(`Circular alias detected for "${clean}"`);
2596
- cache.set(cacheKey, void 0);
2597
- return;
2598
- }
2599
- visited.add(clean);
2600
- let found = registry.get(clean);
2601
- let segments = [];
2602
- if (!found && clean.includes(".")) {
2603
- const parts = clean.split(".");
2604
- for (let i = parts.length - 1; i > 0; i--) {
2605
- const prefix = parts.slice(0, i).join(".");
2606
- const suffix = parts.slice(i);
2607
- const candidate = registry.get(prefix);
2608
- if (!/* @__PURE__ */ isUndefined(candidate?.value)) {
2609
- found = candidate;
2610
- segments = suffix;
2611
- break;
2612
- }
2641
+ function createOverflow(options = {}) {
2642
+ const { container: _container, gap = 0, reserved = 0, itemWidth, reverse } = options;
2643
+ const container = /* @__PURE__ */ isUndefined(_container) ? shallowRef() : toRef(_container);
2644
+ const widths = shallowRef(/* @__PURE__ */ new Map());
2645
+ const { width } = useElementSize(container);
2646
+ function measure(index, el) {
2647
+ if (!el) {
2648
+ if (widths.value.has(index)) {
2649
+ const next = new Map(widths.value);
2650
+ next.delete(index);
2651
+ widths.value = next;
2613
2652
  }
2614
- }
2615
- if (/* @__PURE__ */ isUndefined(found?.value)) {
2616
- if (isAliasReference) logger.warn(`Alias not found for "${String(reference)}"`);
2617
- cache.set(cacheKey, void 0);
2618
2653
  return;
2619
2654
  }
2620
- let result;
2621
- let current = found.value;
2622
- if (segments.length > 0) {
2623
- if (isTokenAlias(current)) current = current.$value;
2624
- for (const segment of segments) {
2625
- if (!/* @__PURE__ */ isObject(current) || !(segment in current)) {
2626
- current = void 0;
2627
- break;
2628
- }
2629
- current = current[segment];
2630
- if (isTokenAlias(current)) current = current.$value;
2655
+ const style = getComputedStyle(el);
2656
+ const marginX = Number.parseFloat(style.marginLeft) + Number.parseFloat(style.marginRight);
2657
+ const w = el.offsetWidth + marginX;
2658
+ if (widths.value.get(index) !== w) widths.value = new Map(widths.value).set(index, w);
2659
+ }
2660
+ function reset() {
2661
+ widths.value = /* @__PURE__ */ new Map();
2662
+ }
2663
+ const total = computed(() => {
2664
+ const g = toValue(gap);
2665
+ let sum = 0;
2666
+ let count = 0;
2667
+ for (const w of widths.value.values()) {
2668
+ sum += w + (count > 0 ? g : 0);
2669
+ count++;
2670
+ }
2671
+ return sum;
2672
+ });
2673
+ return {
2674
+ container,
2675
+ width,
2676
+ capacity: computed(() => {
2677
+ const available = width.value - toValue(reserved);
2678
+ if (width.value === 0) return Infinity;
2679
+ if (available <= 0) return 0;
2680
+ const g = toValue(gap);
2681
+ const uniformWidth = toValue(itemWidth);
2682
+ if (uniformWidth && uniformWidth > 0) {
2683
+ const first = uniformWidth;
2684
+ const subsequent = uniformWidth + g;
2685
+ if (available < first) return 0;
2686
+ return Math.max(1, Math.floor((available - first) / subsequent) + 1);
2631
2687
  }
2632
- if (/* @__PURE__ */ isUndefined(current)) {
2633
- logger.warn(`Path not found inside "${clean}": ${segments.join(".")}`);
2634
- cache.set(cacheKey, void 0);
2635
- return;
2688
+ const entries = [...widths.value.entries()].toSorted((a, b) => a[0] - b[0]);
2689
+ if (toValue(reverse)) entries.reverse();
2690
+ let sum = 0;
2691
+ let count = 0;
2692
+ for (const [, w] of entries) {
2693
+ const next = sum + w + (count > 0 ? g : 0);
2694
+ if (next > available) break;
2695
+ sum = next;
2696
+ count++;
2636
2697
  }
2637
- result = current;
2638
- } else if (isTokenAlias(current)) {
2639
- const inner = current.$value;
2640
- if (/* @__PURE__ */ isString(inner) && isAlias(inner)) return resolve(inner, visited);
2641
- result = inner;
2642
- } else if (/* @__PURE__ */ isString(current) && isAlias(current)) return resolve(current, visited);
2643
- else result = current;
2644
- cache.set(cacheKey, result);
2645
- return result;
2646
- }
2647
- return {
2648
- ...registry,
2649
- resolve,
2650
- isAlias,
2651
- get size() {
2652
- return registry.size;
2653
- }
2698
+ return count;
2699
+ }),
2700
+ total,
2701
+ isOverflowing: toRef(() => {
2702
+ return total.value > width.value - toValue(reserved);
2703
+ }),
2704
+ measure,
2705
+ reset
2654
2706
  };
2655
2707
  }
2656
2708
  /**
2657
- * Creates a new token context.
2658
- *
2659
- * @param namespace The namespace for the token context.
2660
- * @param tokens The tokens to use.
2661
- * @template Z The type of the token ticket.
2662
- * @template E The type of the token context.
2663
- * @returns A new token context.
2709
+ * Creates an overflow context with dependency injection support.
2664
2710
  *
2665
- * @see https://0.vuetifyjs.com/composables/registration/use-tokens
2711
+ * @param options Configuration options including namespace
2712
+ * @returns Trinity tuple: [useContext, provideContext, defaultContext]
2666
2713
  *
2667
2714
  * @example
2668
2715
  * ```ts
2669
- * import { createTokensContext } from '@vuetify/v0'
2670
- *
2671
- * export const [useTokens, provideTokens, context] = createTokensContext({
2672
- * namespace: 'v0:tokens',
2673
- * tokens: {
2674
- * colors: {
2675
- * primary: '#3b82f6',
2676
- * secondary: '{colors.primary}', // Alias reference
2677
- * },
2678
- * },
2716
+ * // Create injectable context
2717
+ * const [useOverflow, provideOverflow, overflow] = createOverflowContext({
2718
+ * namespace: 'my-overflow',
2719
+ * gap: 8,
2720
+ * reserved: 160,
2679
2721
  * })
2722
+ *
2723
+ * // In parent component
2724
+ * provideOverflow()
2725
+ *
2726
+ * // In child component
2727
+ * const overflow = useOverflow()
2680
2728
  * ```
2681
2729
  */
2682
- function createTokensContext(_options) {
2683
- const { namespace = "v0:tokens", tokens = {}, ...options } = _options;
2684
- const [useTokensContext, _provideTokensContext] = createContext(namespace);
2685
- const context = createTokens(tokens, options);
2686
- function provideTokensContext(_context = context, app) {
2687
- return _provideTokensContext(_context, app);
2730
+ function createOverflowContext(_options = {}) {
2731
+ const { namespace = "v0:overflow", ...options } = _options;
2732
+ const [useOverflowContext, _provideOverflowContext] = createContext(namespace);
2733
+ const context = createOverflow(options);
2734
+ function provideOverflowContext(_context = context, app) {
2735
+ return _provideOverflowContext(_context, app);
2688
2736
  }
2689
- return createTrinity(useTokensContext, provideTokensContext, context);
2737
+ return createTrinity(useOverflowContext, provideOverflowContext, context);
2690
2738
  }
2691
2739
  /**
2692
- * Returns the current tokens instance.
2693
- *
2694
- * @param namespace The namespace for the tokens context. Defaults to `'v0:tokens'`.
2695
- * @returns The current tokens instance.
2740
+ * Returns the current overflow context from dependency injection.
2696
2741
  *
2697
- * @see https://0.vuetifyjs.com/composables/registration/use-tokens
2742
+ * @param namespace The namespace for the overflow context. Defaults to `v0:overflow`.
2743
+ * @returns The current overflow context.
2698
2744
  *
2699
2745
  * @example
2700
2746
  * ```vue
2701
- * <script setup lang="ts">
2702
- * import { useTokens } from '@vuetify/v0'
2747
+ * <script lang="ts" setup>
2748
+ * import { useOverflow } from '@vuetify/v0'
2703
2749
  *
2704
- * const tokens = useTokens()
2750
+ * // Inject overflow context provided by parent
2751
+ * const overflow = useOverflow()
2705
2752
  * <\/script>
2753
+ *
2754
+ * <template>
2755
+ * <div>
2756
+ * <p>Capacity: {{ overflow.capacity.value }}</p>
2757
+ * </div>
2758
+ * </template>
2706
2759
  * ```
2707
2760
  */
2708
- function useTokens(namespace = "v0:tokens") {
2761
+ function useOverflow(namespace = "v0:overflow") {
2709
2762
  return useContext(namespace);
2710
2763
  }
2711
- /**
2712
- * Flattens a nested collection of tokens into a flat array of tokens.
2713
- * Each token is represented by an object containing its ID & value.
2714
- * @param tokens The collection of tokens to flatten.
2715
- * @param prefix An optional prefix to prepend to each token ID.
2716
- * @returns An array of flattened tokens, each with an ID and value.
2717
- */
2718
- function flatten(tokens, prefix = "", flat = false) {
2719
- const flattened = [];
2720
- const stack = [{
2721
- tokens,
2722
- prefix,
2723
- flat
2724
- }];
2725
- while (stack.length > 0) {
2726
- const { tokens: currentTokens, prefix: currentPrefix, flat: flat$1 } = stack.pop();
2727
- const meta = {};
2728
- for (const k in currentTokens) if (k.startsWith("$")) meta[k] = currentTokens[k];
2729
- if (Object.keys(meta).length > 0 && currentPrefix) flattened.push({
2730
- id: currentPrefix,
2731
- value: meta
2732
- });
2733
- for (const key in currentTokens) {
2734
- if (key.startsWith("$")) continue;
2735
- const value = currentTokens[key];
2736
- const id = currentPrefix ? `${currentPrefix}.${key}` : key;
2737
- if (!/* @__PURE__ */ isObject(value)) {
2738
- flattened.push({
2739
- id,
2740
- value
2741
- });
2742
- continue;
2743
- }
2744
- if ("$value" in value) {
2745
- flattened.push({
2746
- id,
2747
- value
2748
- });
2749
- const inner = value.$value;
2750
- if (/* @__PURE__ */ isObject(inner) && !flat$1) for (const innerKey in inner) {
2751
- if (innerKey.startsWith("$")) continue;
2752
- const child = inner[innerKey];
2753
- const childId = `${id}.${innerKey}`;
2754
- if (!/* @__PURE__ */ isObject(child)) flattened.push({
2755
- id: childId,
2756
- value: child
2757
- });
2758
- else if ("$value" in child) flattened.push({
2759
- id: childId,
2760
- value: child
2761
- });
2762
- else stack.push({
2763
- tokens: child,
2764
- prefix: childId,
2765
- flat: flat$1
2766
- });
2767
- }
2768
- continue;
2769
- }
2770
- if (flat$1) {
2771
- flattened.push({
2772
- id,
2773
- value
2774
- });
2775
- continue;
2776
- }
2777
- stack.push({
2778
- tokens: value,
2779
- prefix: id,
2780
- flat: flat$1
2781
- });
2782
- }
2783
- }
2784
- return flattened;
2785
- }
2786
-
2787
- //#endregion
2788
- //#region src/composables/useLocale/adapters/v0.ts
2789
- /**
2790
- * Vuetify0.x locale adapter implementation
2791
- *
2792
- * This adapter provides translation and number formatting
2793
- * capabilities using the Intl API and supports both
2794
- * numbered ({0}, {1}) and named ({name}) variables in translation strings.
2795
- */
2796
- var Vuetify0LocaleAdapter = class {
2797
- t(message, ...params) {
2798
- let resolvedMessage = message;
2799
- if (params.length > 0 && /* @__PURE__ */ isObject(params[0])) {
2800
- const variables = params[0];
2801
- resolvedMessage = resolvedMessage.replace(/{([a-zA-Z][a-zA-Z0-9_]*)}/g, (match, name) => {
2802
- return /* @__PURE__ */ isUndefined(variables[name]) ? match : String(variables[name]);
2803
- });
2804
- params = params.slice(1);
2805
- }
2806
- resolvedMessage = resolvedMessage.replace(/\{(\d+)\}/g, (match, index) => {
2807
- const idx = Number.parseInt(index, 10);
2808
- if (!/* @__PURE__ */ isUndefined(params[idx])) return String(params[idx]);
2809
- return match;
2810
- });
2811
- return resolvedMessage;
2812
- }
2813
- n(value, locale, ...params) {
2814
- if (!IN_BROWSER || !locale) return value.toString();
2815
- const options = params[0];
2816
- return new Intl.NumberFormat(String(locale), options).format(value);
2817
- }
2818
- };
2819
2764
 
2820
2765
  //#endregion
2821
- //#region src/composables/useLocale/index.ts
2766
+ //#region src/composables/usePagination/index.ts
2822
2767
  /**
2823
- * @module useLocale
2768
+ * @module usePagination
2824
2769
  *
2825
2770
  * @remarks
2826
- * Internationalization (i18n) composable with adapter pattern for message translation.
2771
+ * Lightweight pagination composable for navigating through pages.
2827
2772
  *
2828
2773
  * Key features:
2829
- * - Locale selection with createSingle
2830
- * - Token-based message storage with useTokens
2831
- * - Numbered and named placeholder support ({0}, {name})
2832
- * - Number formatting with Intl.NumberFormat
2833
- * - Adapter pattern for integration with i18n providers
2774
+ * - No registry overhead - just a bounded integer
2775
+ * - Direct ref support for v-model compatibility
2776
+ * - Navigation methods: next, prev, first, last
2777
+ * - Computed visible items with ellipsis
2778
+ * - Trinity pattern for dependency injection
2834
2779
  *
2835
- * Integrates with createSingle for locale selection and useTokens for message resolution.
2780
+ * Unlike registry-based composables, pagination tracks a single number
2781
+ * within a range, making it efficient for large page counts.
2836
2782
  */
2837
2783
  /**
2838
- * Creates a new locale instance.
2784
+ * Creates a pagination instance.
2839
2785
  *
2840
- * @param options The options for the locale instance.
2841
- * @template Z The type of the locale ticket.
2842
- * @template E The type of the locale context.
2843
- * @returns A new locale instance.
2786
+ * @param options The options for the pagination instance.
2787
+ * @returns A pagination context with navigation methods.
2844
2788
  *
2845
- * @see https://0.vuetifyjs.com/composables/plugins/use-locale
2846
- */
2847
- function createLocale(_options = {}) {
2848
- const { adapter = new Vuetify0LocaleAdapter(), messages = {}, ...options } = _options;
2849
- const tokens = createTokens(messages);
2850
- const registry = createSingle(options);
2851
- for (const id in messages) {
2852
- registry.register({ id });
2853
- if (id === options.default && !registry.selectedId.value) registry.select(id);
2789
+ * @example
2790
+ * ```ts
2791
+ * import { createPagination } from '@vuetify/v0'
2792
+ *
2793
+ * // Basic usage
2794
+ * const pagination = createPagination({ size: 100 })
2795
+ * pagination.next()
2796
+ * pagination.items.value // [{ type: 'page', value: 1 }, { type: 'page', value: 2 }, ...]
2797
+ *
2798
+ * // With v-model (pass a ref)
2799
+ * const page = ref(1)
2800
+ * const pagination = createPagination({ page, size: 100 })
2801
+ * // Mutating pagination.page or the passed ref syncs both
2802
+ * ```
2803
+ */
2804
+ function createPagination(_options = {}) {
2805
+ const { page: _page = 1, itemsPerPage: _itemsPerPage = 10, size: _size = 0, visible: _visible = 7, ellipsis = "..." } = _options;
2806
+ const page = isRef(_page) ? _page : shallowRef(_page);
2807
+ const pages = computed(() => {
2808
+ const size = toValue(_size);
2809
+ const perPage = toValue(_itemsPerPage);
2810
+ if (size <= 0 || /* @__PURE__ */ isNaN(size)) return 0;
2811
+ return Math.ceil(size / perPage);
2812
+ });
2813
+ function first() {
2814
+ page.value = 1;
2854
2815
  }
2855
- function t(key, params, fallback) {
2856
- const locale = registry.selectedId.value;
2857
- const args = toArray(params);
2858
- if (!locale) return adapter.t(fallback ?? key, ...args);
2859
- const path = `${locale}.${key}`;
2860
- const message = tokens.get(path)?.value;
2861
- const template = /* @__PURE__ */ isString(message) ? resolve(locale, message) : fallback ?? key;
2862
- return adapter.t(template, ...args);
2816
+ function last() {
2817
+ page.value = Math.max(1, pages.value);
2863
2818
  }
2864
- function n(value, ...params) {
2865
- return adapter.n(value, registry.selectedId.value, ...params);
2819
+ function next() {
2820
+ if (page.value < pages.value) page.value++;
2866
2821
  }
2867
- function resolve(locale, str) {
2868
- return str.replace(/{([a-zA-Z0-9.-_]+)}/g, (match, key) => {
2869
- const [prefix, ...rest] = key.split(".");
2870
- const target = registry.has(prefix) ? prefix : locale;
2871
- const path = `${target}.${registry.has(prefix) ? rest.join(".") : key}`;
2872
- const resolved = tokens.get(path)?.value;
2873
- if (/* @__PURE__ */ isString(resolved)) return resolve(target, resolved);
2874
- return match;
2875
- });
2822
+ function prev() {
2823
+ if (page.value > 1) page.value--;
2824
+ }
2825
+ function select(value) {
2826
+ if (value < 1) page.value = 1;
2827
+ else if (value > pages.value) page.value = Math.max(1, pages.value);
2828
+ else page.value = value;
2829
+ }
2830
+ const isFirst = computed(() => page.value <= 1);
2831
+ const isLast = computed(() => page.value >= pages.value);
2832
+ const pageStart = computed(() => (page.value - 1) * toValue(_itemsPerPage));
2833
+ const pageStop = computed(() => Math.min(pageStart.value + toValue(_itemsPerPage), toValue(_size)));
2834
+ function toPage(value) {
2835
+ return {
2836
+ type: "page",
2837
+ value
2838
+ };
2839
+ }
2840
+ function toEllipsis() {
2841
+ return ellipsis === false ? false : {
2842
+ type: "ellipsis",
2843
+ value: ellipsis
2844
+ };
2845
+ }
2846
+ function filter(array) {
2847
+ return array.filter((item) => item !== false);
2876
2848
  }
2877
2849
  return {
2878
- ...registry,
2879
- t,
2880
- n,
2850
+ page,
2851
+ ellipsis,
2852
+ items: computed(() => {
2853
+ const pageCount = pages.value;
2854
+ const visible = toValue(_visible);
2855
+ const current = page.value;
2856
+ if (pageCount <= 0 || /* @__PURE__ */ isNaN(pageCount) || pageCount > Number.MAX_SAFE_INTEGER) return [];
2857
+ if (visible <= 0) return [];
2858
+ if (visible <= 2) return [toPage(current)];
2859
+ if (pageCount <= visible) return (/* @__PURE__ */ range(pageCount, 1)).map(toPage);
2860
+ if (visible === 3) {
2861
+ const mid = current <= 1 ? 2 : current >= pageCount ? pageCount - 1 : current;
2862
+ return [
2863
+ toPage(1),
2864
+ toPage(mid),
2865
+ toPage(pageCount)
2866
+ ];
2867
+ }
2868
+ const boundary = visible - 2;
2869
+ const middle = visible - 4;
2870
+ if (middle <= 0) {
2871
+ if (current <= boundary) return filter([
2872
+ ...(/* @__PURE__ */ range(boundary, 1)).map(toPage),
2873
+ toEllipsis(),
2874
+ toPage(pageCount)
2875
+ ]);
2876
+ if (current > pageCount - boundary) return filter([
2877
+ toPage(1),
2878
+ toEllipsis(),
2879
+ ...(/* @__PURE__ */ range(boundary, pageCount - boundary + 1)).map(toPage)
2880
+ ]);
2881
+ return current <= Math.ceil(pageCount / 2) ? filter([
2882
+ toPage(1),
2883
+ toPage(current),
2884
+ toEllipsis(),
2885
+ toPage(pageCount)
2886
+ ]) : filter([
2887
+ toPage(1),
2888
+ toEllipsis(),
2889
+ toPage(current),
2890
+ toPage(pageCount)
2891
+ ]);
2892
+ }
2893
+ const leftThreshold = boundary - 1;
2894
+ const rightThreshold = pageCount - boundary + 2;
2895
+ if (current <= leftThreshold) return filter([
2896
+ ...(/* @__PURE__ */ range(boundary, 1)).map(toPage),
2897
+ toEllipsis(),
2898
+ toPage(pageCount)
2899
+ ]);
2900
+ else if (current >= rightThreshold) return filter([
2901
+ toPage(1),
2902
+ toEllipsis(),
2903
+ ...(/* @__PURE__ */ range(boundary, pageCount - boundary + 1)).map(toPage)
2904
+ ]);
2905
+ else {
2906
+ const start = current - Math.floor(middle / 2);
2907
+ return filter([
2908
+ toPage(1),
2909
+ toEllipsis(),
2910
+ ...(/* @__PURE__ */ range(middle, start)).map(toPage),
2911
+ toEllipsis(),
2912
+ toPage(pageCount)
2913
+ ]);
2914
+ }
2915
+ }),
2916
+ pageStart,
2917
+ pageStop,
2918
+ isFirst,
2919
+ isLast,
2920
+ first,
2921
+ last,
2922
+ next,
2923
+ prev,
2924
+ select,
2925
+ get itemsPerPage() {
2926
+ return toValue(_itemsPerPage);
2927
+ },
2881
2928
  get size() {
2882
- return registry.size;
2929
+ return toValue(_size);
2930
+ },
2931
+ get pages() {
2932
+ return pages.value;
2883
2933
  }
2884
2934
  };
2885
2935
  }
2886
- function createLocaleFallback() {
2887
- return {
2888
- size: 0,
2889
- t: (key, _params, fallback) => fallback ?? key,
2890
- n: String
2891
- };
2892
- }
2893
2936
  /**
2894
- * Creates a new locale context.
2895
- *
2896
- * @param options The options for the locale context.
2897
- * @template Z The type of the locale ticket.
2898
- * @template E The type of the locale context.
2899
- * @returns A new locale context.
2937
+ * Creates a pagination context for dependency injection.
2900
2938
  *
2901
- * @see https://0.vuetifyjs.com/composables/plugins/use-locale
2939
+ * @param options The options including namespace.
2940
+ * @returns A trinity: [usePagination, providePagination, defaultContext]
2902
2941
  *
2903
2942
  * @example
2904
2943
  * ```ts
2905
- * import { createLocaleContext } from '@vuetify/v0'
2944
+ * // With default namespace 'v0:pagination'
2945
+ * const [usePagination, providePaginationContext] = createPaginationContext({ size: 50 })
2906
2946
  *
2907
- * export const [useAppLocale, provideAppLocale, appLocale] = createLocaleContext({
2908
- * namespace: 'app:locale',
2909
- * messages: {
2910
- * en: { hello: 'Hello' },
2911
- * es: { hello: 'Hola' },
2912
- * },
2947
+ * // Or with custom namespace
2948
+ * const [usePagination, providePaginationContext] = createPaginationContext({
2949
+ * namespace: 'my-pagination',
2950
+ * size: 50,
2913
2951
  * })
2914
2952
  *
2915
- * // In a parent component:
2916
- * provideAppLocale()
2953
+ * // Parent component
2954
+ * providePaginationContext()
2917
2955
  *
2918
- * // In a child component:
2919
- * const locale = useAppLocale()
2920
- * locale.select('es')
2956
+ * // Child component
2957
+ * const pagination = usePagination()
2958
+ * pagination.next()
2921
2959
  * ```
2922
2960
  */
2923
- function createLocaleContext(_options = {}) {
2924
- const { namespace = "v0:locale", ...options } = _options;
2925
- const [useLocaleContext, _provideLocaleContext] = createContext(namespace);
2926
- const context = createLocale(options);
2927
- function provideLocaleContext(_context = context, app) {
2928
- return _provideLocaleContext(_context, app);
2961
+ function createPaginationContext(_options = {}) {
2962
+ const { namespace = "v0:pagination", ...options } = _options;
2963
+ const [usePaginationContext, _providePaginationContext] = createContext(namespace);
2964
+ const context = createPagination(options);
2965
+ function providePaginationContext(_context = context, app) {
2966
+ return _providePaginationContext(_context, app);
2929
2967
  }
2930
- return createTrinity(useLocaleContext, provideLocaleContext, context);
2968
+ return createTrinity(usePaginationContext, providePaginationContext, context);
2931
2969
  }
2932
2970
  /**
2933
- * Creates a new locale plugin.
2971
+ * Returns the current pagination instance from context.
2934
2972
  *
2935
- * @param options The options for the locale plugin.
2936
- * @template Z The type of the locale ticket.
2937
- * @template E The type of the locale context.
2938
- * @template R The type of the token ticket.
2939
- * @template O The type of the token context.
2940
- * @returns A new locale plugin.
2973
+ * @param namespace The namespace. @default 'v0:pagination'
2974
+ * @returns The pagination context.
2941
2975
  *
2942
- * @see https://0.vuetifyjs.com/composables/plugins/use-locale
2943
- */
2944
- function createLocalePlugin(_options = {}) {
2945
- const { namespace = "v0:locale", adapter = new Vuetify0LocaleAdapter(), messages = {}, ...options } = _options;
2946
- const [, provideLocaleContext, context] = createLocaleContext({
2947
- ...options,
2948
- namespace,
2949
- adapter,
2950
- messages
2951
- });
2952
- return createPlugin({
2953
- namespace,
2954
- provide: (app) => {
2955
- provideLocaleContext(context, app);
2956
- }
2957
- });
2958
- }
2959
- /**
2960
- * Returns the current locale instance.
2976
+ * @example
2977
+ * ```vue
2978
+ * <script setup lang="ts">
2979
+ * import { usePagination } from '@vuetify/v0'
2961
2980
  *
2962
- * @returns The current locale instance.
2981
+ * const pagination = usePagination()
2982
+ * <\/script>
2963
2983
  *
2964
- * @see https://0.vuetifyjs.com/composables/plugins/use-locale
2984
+ * <template>
2985
+ * <button @click="pagination.prev()" :disabled="pagination.isFirst.value">Prev</button>
2986
+ * <button @click="pagination.next()" :disabled="pagination.isLast.value">Next</button>
2987
+ * </template>
2988
+ * ```
2965
2989
  */
2966
- function useLocale(namespace = "v0:locale") {
2967
- const fallback = createLocaleFallback();
2968
- if (!getCurrentInstance()) return fallback;
2969
- try {
2970
- return useContext(namespace, fallback);
2971
- } catch {
2972
- return fallback;
2973
- }
2990
+ function usePagination(namespace = "v0:pagination") {
2991
+ return useContext(namespace);
2974
2992
  }
2975
2993
 
2976
2994
  //#endregion
@@ -3171,4 +3189,4 @@ function useStep(namespace = "v0:step") {
3171
3189
  }
3172
3190
 
3173
3191
  //#endregion
3174
- export { createGroupContext as A, createLogger as B, useResizeObserver as C, createHydrationPlugin as D, createHydrationContext as E, createSelection as F, PinoLoggerAdapter as G, createLoggerPlugin as H, createSelectionContext as I, createTrinity as J, ConsolaLoggerAdapter as K, useSelection as L, useProxyRegistry as M, useProxyModel as N, useHydration as O, toArray as P, createRegistryContext as R, useElementSize as S, createHydration as T, useLogger as U, createLoggerContext as V, Vuetify0LoggerAdapter as W, provideContext as X, createContext as Y, useContext as Z, createPaginationContext as _, createLocaleContext as a, createOverflowContext as b, useLocale as c, createTokensContext as d, useTokens as f, createPagination as g, useSingle as h, createLocale as i, useGroup as j, createGroup as k, Vuetify0LocaleAdapter as l, createSingleContext as m, createStepContext as n, createLocaleFallback as o, createSingle as p, createPlugin as q, useStep as r, createLocalePlugin as s, createStep as t, createTokens as u, usePagination as v, createFallbackHydration as w, useOverflow as x, createOverflow as y, useRegistry as z };
3192
+ export { createGroupContext as A, createLogger as B, createTokens as C, createSingleContext as D, createSingle as E, createSelection as F, PinoLoggerAdapter as G, createLoggerPlugin as H, createSelectionContext as I, createTrinity as J, ConsolaLoggerAdapter as K, useSelection as L, useProxyRegistry as M, useProxyModel as N, useSingle as O, toArray as P, createRegistryContext as R, Vuetify0LocaleAdapter as S, useTokens as T, useLogger as U, createLoggerContext as V, Vuetify0LoggerAdapter as W, provideContext as X, createContext as Y, useContext as Z, createLocale as _, createPaginationContext as a, createLocalePlugin as b, createOverflowContext as c, useResizeObserver as d, createFallbackHydration as f, useHydration as g, createHydrationPlugin as h, createPagination as i, useGroup as j, createGroup as k, useOverflow as l, createHydrationContext as m, createStepContext as n, usePagination as o, createHydration as p, createPlugin as q, useStep as r, createOverflow as s, createStep as t, useElementSize as u, createLocaleContext as v, createTokensContext as w, useLocale as x, createLocaleFallback as y, useRegistry as z };