@vuetify/v0 1.0.0-alpha.4 → 1.0.0-beta.0

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.
Files changed (145) hide show
  1. package/README.md +26 -5
  2. package/dist/{adapter-C9cR4AcY.mjs → adapter-BCVxBtDp.mjs} +11 -11
  3. package/dist/{adapter-Cr7Bl4la.d.mts → adapter-BO75n0Cn.d.mts} +7 -7
  4. package/dist/{adapter-C6JmOyYo.d.mts → adapter-DZeI0ZbW.d.mts} +1 -1
  5. package/dist/{adapter-DgBBe_BW.d.mts → adapter-we-0GbFH.d.mts} +2 -2
  6. package/dist/adapters-BHD1lc-K.mjs +4 -0
  7. package/dist/adapters-BYLWDmYl.mjs +5 -0
  8. package/dist/adapters-BxrTL8E3.mjs +4 -0
  9. package/dist/adapters-CBgBd7VE.mjs +2 -0
  10. package/dist/browser/index.js +2860 -908
  11. package/dist/components/index.d.mts +2 -3
  12. package/dist/components/index.mjs +2 -2
  13. package/dist/{components-BWxMSWr7.mjs → components-_uxZ_Zo3.mjs} +1054 -502
  14. package/dist/composables/index.d.mts +19 -18
  15. package/dist/composables/index.mjs +26 -26
  16. package/dist/{consola-Crr4qbeI.d.mts → consola-CUNHyu8J.d.mts} +1 -1
  17. package/dist/{consola-DYvbPNbF.mjs → consola-cad-VEV2.mjs} +5 -1
  18. package/dist/constants/index.d.mts +1 -1
  19. package/dist/constants/index.mjs +1 -1
  20. package/dist/{createDataTable-DDr2yY-A.mjs → createDataTable-DlFQyBTN.mjs} +192 -101
  21. package/dist/{createGroup-BSGtfBUE.mjs → createGroup-DsKJMuRd.mjs} +2 -2
  22. package/dist/{createRegistry-Db_juawd.mjs → createRegistry-tJ1PPNIr.mjs} +2 -2
  23. package/dist/{createSelection-BeKZMxha.mjs → createSelection-DEqhvdoj.mjs} +2 -2
  24. package/dist/{createSingle-Dms2efxr.mjs → createSingle-ClzWqRCe.mjs} +2 -2
  25. package/dist/{createTokens-B1lwHZvO.mjs → createTokens-mNxs8DQ3.mjs} +3 -3
  26. package/dist/{createTrinity-BTCs1pYx.mjs → createTrinity-z-3q9QWR.mjs} +5 -2
  27. package/dist/data-table/adapters/client/index.d.mts +1 -1
  28. package/dist/data-table/adapters/client/index.mjs +1 -1
  29. package/dist/data-table/adapters/index.d.mts +4 -4
  30. package/dist/data-table/adapters/index.mjs +5 -5
  31. package/dist/data-table/adapters/server/index.d.mts +1 -1
  32. package/dist/data-table/adapters/server/index.mjs +1 -1
  33. package/dist/data-table/adapters/virtual/index.d.mts +1 -1
  34. package/dist/data-table/adapters/virtual/index.mjs +1 -1
  35. package/dist/data-table/index.d.mts +6 -6
  36. package/dist/data-table/index.mjs +5 -5
  37. package/dist/date/index.d.mts +1 -1
  38. package/dist/date/index.mjs +2 -2
  39. package/dist/features/adapters/flagsmith/index.d.mts +1 -1
  40. package/dist/features/adapters/launchdarkly/index.d.mts +1 -1
  41. package/dist/features/adapters/posthog/index.d.mts +1 -1
  42. package/dist/features/adapters/posthog/index.mjs +1 -1
  43. package/dist/features/index.d.mts +2 -2
  44. package/dist/features/index.mjs +1 -1
  45. package/dist/{flagsmith-ygKMImos.d.mts → flagsmith-Cn_KVy6v.d.mts} +1 -1
  46. package/dist/{globals-BAe6tA_x.mjs → globals-DB-krEA-.mjs} +1 -1
  47. package/dist/{index-B3-FstpY.d.mts → index-ATZqco03.d.mts} +95 -2
  48. package/dist/{index-jQPkwo3G.d.mts → index-B3PDLk_Y.d.mts} +879 -454
  49. package/dist/{index-Cw04PFVO.d.mts → index-BIyQ3OxC.d.mts} +2 -168
  50. package/dist/{index-BfpLkDR_.d.mts → index-BN06veYe.d.mts} +947 -225
  51. package/dist/{index-CLR1QMM3.d.mts → index-DHmuwBdv.d.mts} +60 -1
  52. package/dist/index-DWAgQOBa.d.mts +170 -0
  53. package/dist/{index-BA7YU2xY.d.mts → index-ZD3hw2Zo.d.mts} +123 -62
  54. package/dist/index.d.mts +23 -22
  55. package/dist/index.mjs +29 -29
  56. package/dist/json/attributes.json +128 -0
  57. package/dist/json/importMap.json +24 -0
  58. package/dist/json/tags.json +57 -0
  59. package/dist/json/web-types.json +375 -2
  60. package/dist/{launchdarkly-BJR4vbbV.d.mts → launchdarkly-C3Knz7-v.d.mts} +1 -1
  61. package/dist/locale/adapters/index.d.mts +2 -2
  62. package/dist/locale/adapters/index.mjs +2 -2
  63. package/dist/locale/adapters/v0/index.d.mts +1 -1
  64. package/dist/locale/adapters/v0/index.mjs +1 -1
  65. package/dist/locale/adapters/vue-i18n/index.d.mts +1 -1
  66. package/dist/locale/index.d.mts +3 -3
  67. package/dist/locale/index.mjs +2 -2
  68. package/dist/logger/adapters/consola/index.d.mts +1 -1
  69. package/dist/logger/adapters/consola/index.mjs +1 -1
  70. package/dist/logger/adapters/index.d.mts +4 -4
  71. package/dist/logger/adapters/index.mjs +4 -4
  72. package/dist/logger/adapters/pino/index.d.mts +1 -1
  73. package/dist/logger/adapters/pino/index.mjs +1 -1
  74. package/dist/logger/adapters/v0/index.d.mts +1 -1
  75. package/dist/logger/adapters/v0/index.mjs +1 -1
  76. package/dist/logger/index.d.mts +5 -5
  77. package/dist/logger/index.mjs +4 -4
  78. package/dist/notifications/index.d.mts +1 -1
  79. package/dist/notifications/index.mjs +2 -2
  80. package/dist/palettes/ant/generate/index.mjs +6 -1
  81. package/dist/palettes/ant/index.d.mts +1 -1
  82. package/dist/palettes/index.d.mts +3 -3
  83. package/dist/palettes/leonardo/generate/index.mjs +6 -2
  84. package/dist/palettes/material/generate/index.mjs +11 -2
  85. package/dist/palettes/material/index.d.mts +1 -1
  86. package/dist/palettes/md1/index.d.mts +1 -1
  87. package/dist/palettes/md2/index.d.mts +1 -1
  88. package/dist/palettes/radix/index.d.mts +1 -1
  89. package/dist/palettes/tailwind/index.d.mts +1 -1
  90. package/dist/permissions/adapters/index.d.mts +1 -1
  91. package/dist/permissions/adapters/v0/index.d.mts +1 -1
  92. package/dist/permissions/index.d.mts +1 -1
  93. package/dist/permissions/index.mjs +1 -1
  94. package/dist/{pino-NmR7X0s4.d.mts → pino-BaYY_KIN.d.mts} +1 -1
  95. package/dist/{pino-CIcYlW3u.mjs → pino-BscMO3RR.mjs} +5 -2
  96. package/dist/{posthog-B3go2aCh.mjs → posthog-B5s_KGhy.mjs} +1 -1
  97. package/dist/{posthog-CQntUXFO.d.mts → posthog-CR8R2ywM.d.mts} +1 -1
  98. package/dist/rules/index.d.mts +2 -2
  99. package/dist/rules/index.mjs +1 -1
  100. package/dist/{server-DQK305lc.d.mts → server-7vjHxjNc.d.mts} +1 -1
  101. package/dist/{server-ZFXbgQoY.mjs → server-CZ83nTn_.mjs} +1 -1
  102. package/dist/storage/adapters/index.d.mts +2 -2
  103. package/dist/storage/adapters/memory/index.d.mts +1 -1
  104. package/dist/storage/index.d.mts +3 -3
  105. package/dist/storage/index.mjs +1 -1
  106. package/dist/theme/adapters/index.d.mts +1 -1
  107. package/dist/theme/adapters/index.mjs +3 -3
  108. package/dist/theme/adapters/unhead/index.d.mts +1 -1
  109. package/dist/theme/adapters/unhead/index.mjs +1 -1
  110. package/dist/theme/adapters/v0/index.d.mts +1 -1
  111. package/dist/theme/adapters/v0/index.mjs +1 -1
  112. package/dist/theme/index.d.mts +1 -1
  113. package/dist/theme/index.mjs +3 -3
  114. package/dist/types/index.d.mts +2 -2
  115. package/dist/{unhead-dPyVkgiJ.mjs → unhead-CG672gKh.mjs} +1 -1
  116. package/dist/{useClickOutside-B8naChaQ.mjs → useClickOutside-DoOgNtrj.mjs} +1583 -436
  117. package/dist/{useFeatures-x2iKp0C8.mjs → useFeatures-B-A-eOo-.mjs} +4 -4
  118. package/dist/{useLocale-BUms59Bw.mjs → useLocale-CbpoxbVU.mjs} +5 -5
  119. package/dist/{useLogger-DPSSa-YR.mjs → useLogger-CTgAedNH.mjs} +51 -7
  120. package/dist/{useNotifications-BPTmZWQy.mjs → useNotifications-BkKGUl_1.mjs} +6 -6
  121. package/dist/{usePermissions-CTi-3pBk.mjs → usePermissions-BYOrKcnk.mjs} +2 -2
  122. package/dist/{useRules-DNhljkyO.mjs → useRules-mD_DY9dy.mjs} +4 -3
  123. package/dist/{useTheme-3vW3kFNg.mjs → useTheme-B1lUya5B.mjs} +5 -5
  124. package/dist/utilities/index.d.mts +2 -2
  125. package/dist/utilities/index.mjs +116 -2
  126. package/dist/{v0-DadOcxFI.mjs → v0-Bx5Mi7qE.mjs} +1 -1
  127. package/dist/{v0-YJQoTLsp.mjs → v0-CG9o3on7.mjs} +1 -1
  128. package/dist/{v0-CotC1PiS.mjs → v0-CHK79mZX.mjs} +1 -1
  129. package/dist/{v0-DAlKpCR8.mjs → v0-COaQncfZ.mjs} +1 -1
  130. package/dist/{v0-CRGArJim.d.mts → v0-DWZ_N2GG.d.mts} +1 -1
  131. package/dist/{v0-DYsCJPIu.d.mts → v0-QIjffi4E.d.mts} +1 -1
  132. package/dist/{v0-DDELiSI6.d.mts → v0-WzS4GAIp.d.mts} +1 -1
  133. package/dist/{virtual-BqCRw0ly.d.mts → virtual-CrgGA-o6.d.mts} +1 -1
  134. package/dist/{virtual-Dnno49F8.mjs → virtual-UumJ2BQ9.mjs} +1 -1
  135. package/package.json +12 -3
  136. package/dist/adapters-B25KaYU4.mjs +0 -2
  137. package/dist/adapters-BcddLSNs.mjs +0 -4
  138. package/dist/adapters-Dv74Jk6A.mjs +0 -5
  139. package/dist/adapters-t6QC-zE8.mjs +0 -3
  140. /package/dist/{adapter-Dzyrlbz7.d.mts → adapter-B6IrPwib.d.mts} +0 -0
  141. /package/dist/{adapter-o8r-2vUn.d.mts → adapter-BEJXp-ie.d.mts} +0 -0
  142. /package/dist/{index-DWfPppPU.d.mts → index-BOn0AAue.d.mts} +0 -0
  143. /package/dist/{index-DewYtDsc.d.mts → index-C6NnIuDv.d.mts} +0 -0
  144. /package/dist/{index-DJQwYeyx.d.mts → index-PvkxPev3.d.mts} +0 -0
  145. /package/dist/{memory-B0w39kno.d.mts → memory-D-JbdKXg.d.mts} +0 -0
@@ -1,22 +1,22 @@
1
- import { a as SUPPORTS_OBSERVER, i as SUPPORTS_MUTATION_OBSERVER, n as SUPPORTS_INTERSECTION_OBSERVER, r as SUPPORTS_MATCH_MEDIA, t as IN_BROWSER } from "./globals-BAe6tA_x.mjs";
2
- import { clamp, instanceExists, isArray, isElement, isFunction, isNull, isNullOrUndefined, isNumber, isObject, isString, isThenable, isUndefined, mergeDeep, range, resolveIds, resolveIndexes, useId as useId$1 } from "./utilities/index.mjs";
3
- import { i as useContext, t as createTrinity } from "./createTrinity-BTCs1pYx.mjs";
4
- import { a as createPlugin, i as useLogger, o as createPluginContext } from "./useLogger-DPSSa-YR.mjs";
5
- import { t as createRegistry } from "./createRegistry-Db_juawd.mjs";
6
- import { i as createModel, t as createSelection } from "./createSelection-BeKZMxha.mjs";
7
- import { t as createSingle } from "./createSingle-Dms2efxr.mjs";
8
- import "./createTokens-B1lwHZvO.mjs";
9
- import { a as useLocale } from "./useLocale-BUms59Bw.mjs";
10
- import { c as useTimer } from "./useNotifications-BPTmZWQy.mjs";
1
+ import { a as SUPPORTS_OBSERVER, i as SUPPORTS_MUTATION_OBSERVER, n as SUPPORTS_INTERSECTION_OBSERVER, r as SUPPORTS_MATCH_MEDIA, t as IN_BROWSER } from "./globals-DB-krEA-.mjs";
2
+ import { V0Error, clamp, instanceExists, isArray, isElement, isFunction, isNaN, isNull, isNullOrUndefined, isNumber, isObject, isString, isThenable, isUndefined, mergeDeep, range, resolveIds, resolveIndexes, useId as useId$1 } from "./utilities/index.mjs";
3
+ import { i as useContext, n as createContext, t as createTrinity } from "./createTrinity-z-3q9QWR.mjs";
4
+ import { a as createPlugin, i as useLogger, o as createPluginContext } from "./useLogger-CTgAedNH.mjs";
5
+ import { t as createRegistry } from "./createRegistry-tJ1PPNIr.mjs";
6
+ import { i as createModel, t as createSelection } from "./createSelection-DEqhvdoj.mjs";
7
+ import { t as createSingle } from "./createSingle-ClzWqRCe.mjs";
8
+ import "./createTokens-mNxs8DQ3.mjs";
9
+ import { a as useLocale } from "./useLocale-CbpoxbVU.mjs";
10
+ import { c as useTimer } from "./useNotifications-BkKGUl_1.mjs";
11
11
  import { t as toArray } from "./toArray-CD2-OxAz.mjs";
12
- import { a as createFilter } from "./adapter-C9cR4AcY.mjs";
13
- import { t as createGroup } from "./createGroup-BSGtfBUE.mjs";
14
- import "./createDataTable-DDr2yY-A.mjs";
15
- import { a as useRules, o as isStandardSchema } from "./useRules-DNhljkyO.mjs";
16
- import "./useFeatures-x2iKp0C8.mjs";
17
- import "./usePermissions-CTi-3pBk.mjs";
12
+ import { a as createFilter } from "./adapter-BCVxBtDp.mjs";
13
+ import { t as createGroup } from "./createGroup-DsKJMuRd.mjs";
14
+ import { a as extractLeaves, t as createDataTable } from "./createDataTable-DlFQyBTN.mjs";
15
+ import { a as useRules, o as isStandardSchema } from "./useRules-mD_DY9dy.mjs";
16
+ import "./useFeatures-B-A-eOo-.mjs";
17
+ import "./usePermissions-BYOrKcnk.mjs";
18
18
  import { t as MemoryStorageAdapter } from "./memory-BMpJcQjQ.mjs";
19
- import "./useTheme-3vW3kFNg.mjs";
19
+ import "./useTheme-B1lUya5B.mjs";
20
20
  import { computed, effectScope, hasInjectionContext, isReadonly, isRef, nextTick, onMounted, onScopeDispose, onWatcherCleanup, reactive, readonly, ref, shallowReactive, shallowReadonly, shallowRef, toRef, toValue, unref, watch, watchEffect } from "vue";
21
21
  //#region src/composables/createBreadcrumbs/index.ts
22
22
  /**
@@ -170,6 +170,123 @@ function useBreadcrumbs(namespace = "v0:breadcrumbs") {
170
170
  return useContext(namespace);
171
171
  }
172
172
  //#endregion
173
+ //#region src/composables/useDelay/index.ts
174
+ /**
175
+ * @module useDelay
176
+ *
177
+ * @see https://0.vuetifyjs.com/composables/system/use-delay
178
+ *
179
+ * @remarks
180
+ * Schedule open and close transitions with configurable delays.
181
+ *
182
+ * Composes `useTimer` for the underlying lifecycle (start / stop / pause / resume,
183
+ * plus reactive `isActive`, `isPaused`, `remaining`) and adds direction tracking
184
+ * (`isOpening`) and promise-based resolution.
185
+ *
186
+ * Key features:
187
+ * - Reactive `openDelay` and `closeDelay` via `MaybeRefOrGetter`
188
+ * - `start(isOpening)` returns a `Promise<boolean>` that resolves once the delay elapses
189
+ * - Per-call `minDelay` floor enforces a minimum delay
190
+ * - Automatic cleanup on scope disposal (timer cleared, pending promise settled with current direction)
191
+ * - SSR-safe
192
+ *
193
+ * @example
194
+ * ```ts
195
+ * import { useDelay } from '@vuetify/v0'
196
+ *
197
+ * const delay = useDelay(isOpening => isVisible.value = isOpening, {
198
+ * openDelay: 300,
199
+ * closeDelay: 200,
200
+ * })
201
+ *
202
+ * delay.start(true)
203
+ * delay.start(false, { minDelay: 500 })
204
+ * ```
205
+ */
206
+ /**
207
+ * Schedule open and close transitions with configurable delays.
208
+ *
209
+ * Composes `useTimer` for the lifecycle (`start` / `stop` / `pause` / `resume` plus
210
+ * reactive `isActive`, `isPaused`, `remaining`) and adds direction tracking
211
+ * (`isOpening`) and promise-based resolution.
212
+ *
213
+ * @param callback Optional callback invoked once a started delay elapses; receives the resolved direction.
214
+ * @param options Reactive delay settings.
215
+ * @returns Lifecycle controls and reactive state.
216
+ *
217
+ * @see https://0.vuetifyjs.com/composables/system/use-delay
218
+ *
219
+ * @example
220
+ * ```ts
221
+ * const delay = useDelay(isOpening => isVisible.value = isOpening, {
222
+ * openDelay: 300,
223
+ * closeDelay: 200,
224
+ * })
225
+ *
226
+ * delay.start(true)
227
+ * delay.start(false, { minDelay: 500 })
228
+ * delay.pause()
229
+ * delay.resume()
230
+ * delay.stop()
231
+ * ```
232
+ */
233
+ function useDelay(callback, options = {}) {
234
+ const { openDelay, closeDelay } = options;
235
+ const isOpening = shallowRef(false);
236
+ let minDelay = 0;
237
+ let resolver;
238
+ function fire() {
239
+ const direction = isOpening.value;
240
+ const resolve = resolver;
241
+ resolver = void 0;
242
+ resolve?.(direction);
243
+ callback?.(direction);
244
+ }
245
+ function duration() {
246
+ const raw = isOpening.value ? toValue(openDelay) : toValue(closeDelay);
247
+ return Math.max(minDelay, raw ?? 0);
248
+ }
249
+ const timer = useTimer(fire, { duration });
250
+ function start(direction, options = {}) {
251
+ const previous = resolver;
252
+ resolver = void 0;
253
+ isOpening.value = direction;
254
+ minDelay = options.minDelay ?? 0;
255
+ previous?.(direction);
256
+ const ms = duration();
257
+ return new Promise((resolve) => {
258
+ resolver = resolve;
259
+ if (ms <= 0) {
260
+ timer.stop();
261
+ fire();
262
+ return;
263
+ }
264
+ timer.start();
265
+ });
266
+ }
267
+ function stop() {
268
+ const resolve = resolver;
269
+ resolver = void 0;
270
+ timer.stop();
271
+ resolve?.(isOpening.value);
272
+ }
273
+ onScopeDispose(() => {
274
+ const resolve = resolver;
275
+ resolver = void 0;
276
+ resolve?.(isOpening.value);
277
+ }, true);
278
+ return {
279
+ start,
280
+ stop,
281
+ pause: timer.pause,
282
+ resume: timer.resume,
283
+ isActive: shallowReadonly(timer.isActive),
284
+ isPaused: shallowReadonly(timer.isPaused),
285
+ remaining: shallowReadonly(timer.remaining),
286
+ isOpening: shallowReadonly(isOpening)
287
+ };
288
+ }
289
+ //#endregion
173
290
  //#region src/composables/usePopover/index.ts
174
291
  /**
175
292
  * @module usePopover
@@ -178,52 +295,50 @@ function useBreadcrumbs(namespace = "v0:breadcrumbs") {
178
295
  *
179
296
  * @remarks
180
297
  * Composable for native popover API behavior with CSS anchor positioning.
181
- * Manages open/close state, anchor styles, content attributes, and
182
- * bidirectional sync between reactive state and native popover events.
298
+ * Manages open/close state, anchor styles, content attributes, bidirectional
299
+ * sync between reactive state and native popover events, and configurable
300
+ * open / close delays via `useDelay`.
183
301
  *
184
302
  * Key features:
185
303
  * - Native popover API (showPopover/hidePopover)
186
304
  * - CSS anchor positioning (position-area, position-try-fallbacks)
305
+ * - Reactive open/close delays via `useDelay`
187
306
  * - Toggle event sync for native state changes
188
307
  * - SSR-safe (no DOM ops outside browser)
189
308
  * - Optional external isOpen ref for v-model integration
190
309
  *
191
- * Perfect for building select, combobox, tooltip, and menu components
192
- * without wrapping the Popover compound component.
193
- *
194
310
  * @example
195
311
  * ```ts
196
312
  * import { usePopover } from '@vuetify/v0'
197
313
  *
198
- * const popover = usePopover()
314
+ * const popover = usePopover({ openDelay: 200, closeDelay: 100 })
199
315
  * popover.open()
200
316
  * popover.toggle()
201
317
  * ```
202
318
  */
203
319
  function usePopover(options = {}) {
204
- const { id: _id, positionArea = "bottom", positionTry = "most-width bottom", showDelay = 0, hideDelay = 0 } = options;
320
+ const { id: _id, positionArea = "bottom", positionTry = "most-width bottom", openDelay, closeDelay } = options;
205
321
  const id = _id ?? /* @__PURE__ */ useId$1();
206
322
  const isOpen = options.isOpen ?? shallowRef(false);
207
- const showTimer = showDelay > 0 ? useTimer(() => {
208
- isOpen.value = true;
209
- }, { duration: showDelay }) : void 0;
210
- const hideTimer = hideDelay > 0 ? useTimer(() => {
211
- isOpen.value = false;
212
- }, { duration: hideDelay }) : void 0;
323
+ const delay = useDelay((direction) => {
324
+ isOpen.value = direction;
325
+ }, {
326
+ openDelay,
327
+ closeDelay
328
+ });
213
329
  function open() {
214
- hideTimer?.stop();
215
- if (showTimer) showTimer.start();
216
- else isOpen.value = true;
330
+ delay.start(true);
217
331
  }
218
332
  function close() {
219
- showTimer?.stop();
220
- if (hideTimer) hideTimer.start();
221
- else isOpen.value = false;
333
+ delay.start(false);
222
334
  }
223
335
  function toggle() {
224
336
  if (isOpen.value) close();
225
337
  else open();
226
338
  }
339
+ function cancel() {
340
+ delay.stop();
341
+ }
227
342
  const anchorStyles = toRef(() => ({ anchorName: `--${id}` }));
228
343
  const contentAttrs = toRef(() => ({
229
344
  id,
@@ -241,7 +356,7 @@ function usePopover(options = {}) {
241
356
  function attach(el) {
242
357
  onMounted(() => {
243
358
  const element = toValue(el);
244
- if (isOpen.value) element?.showPopover();
359
+ if (isOpen.value) element?.showPopover?.();
245
360
  });
246
361
  watch(isOpen, (value) => {
247
362
  const element = toValue(el);
@@ -262,6 +377,7 @@ function usePopover(options = {}) {
262
377
  open,
263
378
  close,
264
379
  toggle,
380
+ cancel,
265
381
  anchorStyles,
266
382
  contentAttrs,
267
383
  contentStyles,
@@ -785,44 +901,1042 @@ function createCombobox(options = {}) {
785
901
  };
786
902
  }
787
903
  /**
788
- * Creates a combobox context with dependency injection support.
789
- *
790
- * @param options - Combobox options plus an optional namespace for DI.
791
- * @returns A context trinity: `[useCombobox, provideCombobox, defaultCombobox]`.
904
+ * Creates a combobox context with dependency injection support.
905
+ *
906
+ * @param options - Combobox options plus an optional namespace for DI.
907
+ * @returns A context trinity: `[useCombobox, provideCombobox, defaultCombobox]`.
908
+ *
909
+ * @see https://0.vuetifyjs.com/composables/forms/create-combobox
910
+ *
911
+ * @example
912
+ * ```ts
913
+ * import { createComboboxContext } from '@vuetify/v0'
914
+ *
915
+ * const [useCombobox, provideCombobox, combobox] = createComboboxContext({
916
+ * namespace: 'my-combobox',
917
+ * strict: true,
918
+ * })
919
+ * ```
920
+ */
921
+ function createComboboxContext(options = {}) {
922
+ const { namespace = "v0:combobox", ...rest } = options;
923
+ return createTrinity(namespace, createCombobox(rest));
924
+ }
925
+ /**
926
+ * Injects the current combobox context from a parent provider.
927
+ *
928
+ * @param namespace - DI namespace. Defaults to `'v0:combobox'`.
929
+ * @returns The combobox context from the nearest provider.
930
+ *
931
+ * @see https://0.vuetifyjs.com/composables/forms/create-combobox
932
+ *
933
+ * @example
934
+ * ```ts
935
+ * import { useCombobox } from '@vuetify/v0'
936
+ *
937
+ * const combobox = useCombobox()
938
+ * combobox.query.value = 'search term'
939
+ * ```
940
+ */
941
+ function useCombobox(namespace = "v0:combobox") {
942
+ return useContext(namespace);
943
+ }
944
+ //#endregion
945
+ //#region src/composables/createSortable/index.ts
946
+ /**
947
+ * @module createSortable
948
+ *
949
+ * @see https://0.vuetifyjs.com/composables/data/create-sortable
950
+ *
951
+ * @remarks
952
+ * Headless ordered-list primitive. Extends `createModel` with mutation primitives
953
+ * (`swap`, `reorder`) and an enriched `move:ticket` event on the registry event
954
+ * bus. Honors `disabled` as a real mutation gate at both root and per-ticket scope.
955
+ *
956
+ * Pure logical composable — no DnD, no DOM, no keyboard. Drag-and-drop integration
957
+ * composes with `useDragDrop`; keyboard reorder composes with `useVirtualFocus`.
958
+ *
959
+ * @example
960
+ * ```ts
961
+ * import { createSortable } from '@vuetify/v0'
962
+ * import type { SortableTicketInput } from '@vuetify/v0'
963
+ *
964
+ * interface TaskTicket extends SortableTicketInput {
965
+ * value: { id: number; label: string }
966
+ * }
967
+ *
968
+ * const sortable = createSortable<TaskTicket>()
969
+ * const a = sortable.register({ value: { id: 1, label: 'A' } })
970
+ * const b = sortable.register({ value: { id: 2, label: 'B' } })
971
+ *
972
+ * sortable.move(a.id, 1)
973
+ * sortable.on('move:ticket', ({ ticket, from, to }) => {
974
+ * console.log(`${ticket.value.label} moved ${from} → ${to}`)
975
+ * })
976
+ * ```
977
+ */
978
+ /**
979
+ * Create an ordered-list state primitive.
980
+ *
981
+ * @param options Sortable options.
982
+ * @returns A {@link SortableContext} that extends {@link ModelContext}.
983
+ *
984
+ * @example
985
+ * ```ts
986
+ * const sortable = createSortable<Todo>()
987
+ * sortable.onboard(todos.value.map(value => ({ value })))
988
+ * ```
989
+ */
990
+ function createSortable(_options = {}) {
991
+ const { disabled, ...options } = _options;
992
+ const model = createModel({
993
+ ...options,
994
+ events: true,
995
+ disabled
996
+ });
997
+ const logger = useLogger();
998
+ function move(id, toIndex) {
999
+ if (toValue(disabled)) return void 0;
1000
+ const ticket = model.get(id);
1001
+ if (!ticket) return void 0;
1002
+ if (toValue(ticket.disabled)) return void 0;
1003
+ const from = ticket.index;
1004
+ const result = model.move(id, toIndex);
1005
+ if (result && result.index !== from) model.emit("move:ticket", {
1006
+ ticket: result,
1007
+ from,
1008
+ to: result.index
1009
+ });
1010
+ return result;
1011
+ }
1012
+ function swap(a, b) {
1013
+ if (toValue(disabled)) return;
1014
+ if (a === b) return;
1015
+ const ta = model.get(a);
1016
+ const tb = model.get(b);
1017
+ if (!ta || !tb) return;
1018
+ if (toValue(ta.disabled) || toValue(tb.disabled)) return;
1019
+ const indexA = ta.index;
1020
+ const indexB = tb.index;
1021
+ model.batch(() => {
1022
+ const ra = model.move(a, indexB);
1023
+ if (ra && ra.index !== indexA) model.emit("move:ticket", {
1024
+ ticket: ra,
1025
+ from: indexA,
1026
+ to: ra.index
1027
+ });
1028
+ const rb = model.move(b, indexA);
1029
+ if (rb && rb.index !== indexB) model.emit("move:ticket", {
1030
+ ticket: rb,
1031
+ from: indexB,
1032
+ to: rb.index
1033
+ });
1034
+ });
1035
+ }
1036
+ function reorder(ids) {
1037
+ if (toValue(disabled)) return;
1038
+ const currentSize = model.size;
1039
+ if (ids.length !== currentSize) {
1040
+ logger.warn(`Reorder expected ${currentSize} ids, got ${ids.length}.`);
1041
+ return;
1042
+ }
1043
+ const known = new Set(model.keys());
1044
+ const seen = /* @__PURE__ */ new Set();
1045
+ for (const id of ids) {
1046
+ if (!known.has(id)) {
1047
+ logger.warn(`Reorder contains unknown id "${String(id)}".`);
1048
+ return;
1049
+ }
1050
+ if (seen.has(id)) {
1051
+ logger.warn(`Reorder contains duplicate id "${String(id)}".`);
1052
+ return;
1053
+ }
1054
+ seen.add(id);
1055
+ }
1056
+ const froms = /* @__PURE__ */ new Map();
1057
+ for (const id of ids) {
1058
+ const ticket = model.get(id);
1059
+ if (ticket) froms.set(id, ticket.index);
1060
+ }
1061
+ model.batch(() => {
1062
+ model.reorder(ids);
1063
+ for (const [index, id] of ids.entries()) {
1064
+ const from = froms.get(id);
1065
+ if (from === index) continue;
1066
+ const ticket = model.get(id);
1067
+ if (ticket) model.emit("move:ticket", {
1068
+ ticket,
1069
+ from,
1070
+ to: index
1071
+ });
1072
+ }
1073
+ });
1074
+ }
1075
+ return {
1076
+ ...model,
1077
+ move,
1078
+ swap,
1079
+ reorder,
1080
+ get size() {
1081
+ return model.size;
1082
+ }
1083
+ };
1084
+ }
1085
+ //#endregion
1086
+ //#region src/composables/useToggleScope/index.ts
1087
+ /**
1088
+ * @module useToggleScope
1089
+ *
1090
+ * @see https://0.vuetifyjs.com/composables/system/use-toggle-scope
1091
+ *
1092
+ * @remarks
1093
+ * Conditionally manages an effect scope based on a reactive boolean condition.
1094
+ * When the source becomes true, creates and runs an effect scope. When false, stops the scope.
1095
+ * All reactive effects created within the scoped function are automatically cleaned up on deactivation.
1096
+ *
1097
+ * Key features:
1098
+ * - Uses Vue's effectScope for efficient reactive effect lifecycle management
1099
+ * - Automatic cleanup when condition becomes false
1100
+ * - Supports optional reset callback for scope restart capability
1101
+ * - Handles rapid toggling and parent scope disposal safely
1102
+ * - SSR-safe (effectScope is part of Vue core)
1103
+ *
1104
+ * Perfect for conditional side effects, feature flags, and performance optimization
1105
+ * by only running reactive effects when needed.
1106
+ *
1107
+ * @example
1108
+ * ```ts
1109
+ * import { shallowRef } from 'vue'
1110
+ * import { useToggleScope } from '@vuetify/v0'
1111
+ *
1112
+ * const isEnabled = shallowRef(false)
1113
+ * const { isActive } = useToggleScope(isEnabled, () => {
1114
+ * // effects run while isEnabled is true
1115
+ * })
1116
+ * ```
1117
+ */
1118
+ /**
1119
+ * Conditionally manages an effect scope based on a reactive boolean source.
1120
+ *
1121
+ * @param source A reactive boolean value or getter that controls the scope lifecycle
1122
+ * @param fn The function to run within the effect scope. Can optionally receive controls for manual scope management.
1123
+ * @returns Controls object with isActive state and start/stop/reset methods
1124
+ *
1125
+ * @see https://vuejs.org/api/reactivity-advanced.html#effectscope
1126
+ * @see https://0.vuetifyjs.com/composables/system/use-toggle-scope
1127
+ *
1128
+ * @example
1129
+ * ```ts
1130
+ * import { ref } from 'vue'
1131
+ * import { useToggleScope } from '@vuetify/v0'
1132
+ *
1133
+ * const isEnabled = ref(false)
1134
+ *
1135
+ * const { isActive } = useToggleScope(isEnabled, () => {
1136
+ * // This code runs when isEnabled becomes true
1137
+ * const unwatch = watch(someRef, () => {
1138
+ * console.log('Watching...')
1139
+ * })
1140
+ *
1141
+ * // Cleanup happens automatically when isEnabled becomes false
1142
+ * })
1143
+ *
1144
+ * // Toggle the scope on/off
1145
+ * isEnabled.value = true // Starts the scope
1146
+ * console.log(isActive.value) // true
1147
+ * isEnabled.value = false // Stops and cleans up
1148
+ * console.log(isActive.value) // false
1149
+ * ```
1150
+ *
1151
+ * @example
1152
+ * With manual controls:
1153
+ * ```ts
1154
+ * const isEnabled = ref(true)
1155
+ *
1156
+ * useToggleScope(isEnabled, (controls) => {
1157
+ * // Access controls inside the scope
1158
+ * console.log('Active:', controls.isActive.value)
1159
+ *
1160
+ * // Manually reset the scope if needed
1161
+ * someEvent.on('reset', () => controls.reset())
1162
+ * })
1163
+ * ```
1164
+ */
1165
+ function useToggleScope(source, fn) {
1166
+ const scope = shallowRef();
1167
+ const isActive = toRef(() => !!scope.value);
1168
+ function start() {
1169
+ if (scope.value) return;
1170
+ scope.value = effectScope();
1171
+ scope.value.run(() => fn.length > 0 ? fn(controls) : fn());
1172
+ }
1173
+ function stop() {
1174
+ scope.value?.stop();
1175
+ scope.value = void 0;
1176
+ }
1177
+ function reset() {
1178
+ stop();
1179
+ start();
1180
+ }
1181
+ const controls = {
1182
+ isActive: shallowReadonly(isActive),
1183
+ start,
1184
+ stop,
1185
+ reset
1186
+ };
1187
+ watch(source, (active) => {
1188
+ if (active && !scope.value) start();
1189
+ else if (!active) stop();
1190
+ }, { immediate: true });
1191
+ onScopeDispose(stop, true);
1192
+ return controls;
1193
+ }
1194
+ //#endregion
1195
+ //#region src/composables/createDataGrid/editing.ts
1196
+ /**
1197
+ * @module createDataGrid/editing
1198
+ *
1199
+ * @remarks
1200
+ * Cell editing state management. Tracks active cell, validation errors,
1201
+ * and dirty (staged but uncommitted) edits. Does not mutate source data —
1202
+ * commit fires a callback for the consumer to handle.
1203
+ *
1204
+ * When a `registry` is provided, editing state for rows that get
1205
+ * unregistered (or wiped via `clear`) is pruned automatically so consumers
1206
+ * can never observe `active` pointing at a phantom row or stale entries
1207
+ * lingering in `dirty`.
1208
+ *
1209
+ * @internal Consumed only by `createDataGrid`. This is a fully decoupled 2D
1210
+ * cell-edit primitive — it imports nothing from `createDataGrid` /
1211
+ * `createDataTable` (only `#v0/utilities`, `#v0/types`, and Vue), so it is a
1212
+ * latent composable. It is parameterized over a row type `T` (defaulting to
1213
+ * `unknown`), but a type parameter is not an import — the primitive stays
1214
+ * fully decoupled from the grid's concrete ticket types and deliberately
1215
+ * row-agnostic: `T` is intentionally unconstrained, so do not narrow it to
1216
+ * the grid's `Record<string, unknown>` when promoting. Promote it to a
1217
+ * standalone `createCellEditing/`
1218
+ * composable the moment a second consumer appears (e.g. an editable
1219
+ * `createDataTable`). See `.claude/rules/composables.md` §"Sub-modules:
1220
+ * inline, private sibling, or promote".
1221
+ */
1222
+ /**
1223
+ * Creates cell editing state for a data grid.
1224
+ *
1225
+ * @param options Cell editing configuration including columns and commit callback
1226
+ * @returns Cell editing state and controls
1227
+ *
1228
+ * @example
1229
+ * ```ts
1230
+ * const editing = createCellEditing({
1231
+ * columns: [{ id: 'name', editable: true }],
1232
+ * registry: table,
1233
+ * onEdit (row, column, value) { ... },
1234
+ * })
1235
+ *
1236
+ * editing.edit(1, 'name')
1237
+ * editing.commit('Alice')
1238
+ * ```
1239
+ */
1240
+ function createCellEditing(options) {
1241
+ const { columns, onEdit, lookup, registry } = options;
1242
+ function resolveColumn(column) {
1243
+ for (const col of toValue(columns)) if (col.id === column) return col;
1244
+ }
1245
+ const active = shallowRef(null);
1246
+ const error = shallowRef(null);
1247
+ const dirty = shallowReactive(/* @__PURE__ */ new Map());
1248
+ function edit(row, column) {
1249
+ const col = resolveColumn(column);
1250
+ if (!col) return;
1251
+ if (/* @__PURE__ */ isFunction(col.editable)) {
1252
+ const item = lookup?.(row);
1253
+ if (/* @__PURE__ */ isUndefined(item) || !col.editable(item)) return;
1254
+ } else if (col.editable !== true) return;
1255
+ error.value = null;
1256
+ active.value = {
1257
+ row,
1258
+ column
1259
+ };
1260
+ }
1261
+ function commit(value) {
1262
+ const cell = active.value;
1263
+ if (!cell) return;
1264
+ const col = resolveColumn(cell.column);
1265
+ if (col?.validate) {
1266
+ const result = col.validate(value, lookup?.(cell.row));
1267
+ if (result !== true && /* @__PURE__ */ isString(result) && result.length > 0) {
1268
+ error.value = result;
1269
+ return;
1270
+ }
1271
+ }
1272
+ const entry = dirty.get(cell.row);
1273
+ if (entry) {
1274
+ entry.delete(cell.column);
1275
+ if (entry.size === 0) dirty.delete(cell.row);
1276
+ }
1277
+ error.value = null;
1278
+ active.value = null;
1279
+ onEdit?.(cell.row, cell.column, value);
1280
+ }
1281
+ function cancel() {
1282
+ const cell = active.value;
1283
+ if (cell) {
1284
+ const entry = dirty.get(cell.row);
1285
+ if (entry) {
1286
+ entry.delete(cell.column);
1287
+ if (entry.size === 0) dirty.delete(cell.row);
1288
+ }
1289
+ }
1290
+ error.value = null;
1291
+ active.value = null;
1292
+ }
1293
+ function onUnregister(data) {
1294
+ const ticket = data;
1295
+ if (active.value?.row === ticket.id) {
1296
+ active.value = null;
1297
+ error.value = null;
1298
+ }
1299
+ dirty.delete(ticket.id);
1300
+ }
1301
+ function onClear() {
1302
+ active.value = null;
1303
+ error.value = null;
1304
+ dirty.clear();
1305
+ }
1306
+ if (registry) {
1307
+ registry.on("unregister:ticket", onUnregister);
1308
+ registry.on("clear:registry", onClear);
1309
+ onScopeDispose(() => {
1310
+ registry.off("unregister:ticket", onUnregister);
1311
+ registry.off("clear:registry", onClear);
1312
+ }, true);
1313
+ }
1314
+ return {
1315
+ active,
1316
+ edit,
1317
+ commit,
1318
+ cancel,
1319
+ error,
1320
+ dirty
1321
+ };
1322
+ }
1323
+ //#endregion
1324
+ //#region src/composables/createDataGrid/layout.ts
1325
+ /**
1326
+ * @module createDataGrid/layout
1327
+ *
1328
+ * @remarks
1329
+ * Manages column layout state for data grids: sizing (percentages 0-100),
1330
+ * pinning (left/right/scrollable regions), delta-based resizing compatible
1331
+ * with the Splitter two-panel model, column reordering, and visibility.
1332
+ *
1333
+ * Column existence, display order, AND grid layout config (size, pin,
1334
+ * min/max, resizable, reorderable) all live on the table's columns registry
1335
+ * (passed in as the only argument). `createRegistry` preserves the extra
1336
+ * grid fields on each ticket, so layout reads its config straight off the
1337
+ * registered ticket — there is no separate defs array. Columns onboarded
1338
+ * before OR after the layout is built are picked up identically: an initial
1339
+ * loop seeds the already-registered set and a `register:ticket` handler seeds
1340
+ * the rest. Removed columns have their pin/size/visibility state pruned via
1341
+ * unregister events.
1342
+ *
1343
+ * Sizing uses percentages so it is compatible with the Splitter component.
1344
+ * Offsets are computed per-region (left, scrollable, right) independently
1345
+ * and over visible columns only — hidden columns are excluded from the
1346
+ * render set without auto-redistribution (headless: the consumer rebalances
1347
+ * via `distribute()` or CSS).
1348
+ *
1349
+ * @internal Private sibling of `createDataGrid`, and intentionally NOT a
1350
+ * promotion candidate: it is bound to the table's `DataTableColumnTicket`
1351
+ * types, so it is parent-specific glue rather than a reusable primitive. See
1352
+ * `.claude/rules/composables.md` §"Sub-modules: inline, private sibling, or
1353
+ * promote".
1354
+ */
1355
+ const DEFAULT_EXTRAS = {
1356
+ minSize: 2,
1357
+ maxSize: 100,
1358
+ resizable: true,
1359
+ reorderable: true
1360
+ };
1361
+ function offsets(cols, reverse = false) {
1362
+ let offset = 0;
1363
+ const start = reverse ? cols.length - 1 : 0;
1364
+ const step = reverse ? -1 : 1;
1365
+ const end = reverse ? -1 : cols.length;
1366
+ for (let i = start; i !== end; i += step) {
1367
+ cols[i].offset = offset;
1368
+ offset += cols[i].size;
1369
+ }
1370
+ }
1371
+ /**
1372
+ * Creates a column layout manager for a data grid.
1373
+ *
1374
+ * Reads column existence, display order, AND grid layout config (size, pin,
1375
+ * min/max, resizable, reorderable) from the table's columns registry. The
1376
+ * grid fields ride on each ticket — `createRegistry` preserves them — so
1377
+ * there is no separate defs array. Visibility is layout-local view state
1378
+ * (not a ticket field) kept here and reconciled via register / unregister /
1379
+ * clear events.
1380
+ *
1381
+ * Generic over the registry's input/output ticket types so the caller's
1382
+ * concrete grid column ticket (`DataGridColumnTicket`) flows through. Layout
1383
+ * only ever reads from the registry (`values`, `keys`, `on`, `reorder`,
1384
+ * `move`, `lookup`) — it never registers — so the output ticket need only be
1385
+ * readable as a {@link GridColumnDef}.
1386
+ *
1387
+ * @param cols The table's columns registry — source of truth for which
1388
+ * columns exist, in what order, and their layout config
1389
+ * @returns Column layout state and mutation methods
1390
+ */
1391
+ function createColumnLayout(cols) {
1392
+ const logger = useLogger();
1393
+ const configs = /* @__PURE__ */ new Map();
1394
+ const group = createGroup({ multiple: true });
1395
+ const sizes = shallowReactive(/* @__PURE__ */ new Map());
1396
+ const hidden = shallowReactive(/* @__PURE__ */ new Set());
1397
+ const order = [];
1398
+ function record(id) {
1399
+ if (!order.includes(id)) order.push(id);
1400
+ }
1401
+ /** Even share over the currently-registered leaves: explicit sizes are
1402
+ * honored, unsized columns split the remaining percentage. */
1403
+ function share(id) {
1404
+ const leaves = /* @__PURE__ */ extractLeaves(cols.values());
1405
+ let used = 0;
1406
+ let unsized = 0;
1407
+ for (const leaf of leaves) {
1408
+ const size = configs.get(String(leaf.id))?.size;
1409
+ if (/* @__PURE__ */ isUndefined(size)) unsized++;
1410
+ else used += size;
1411
+ }
1412
+ const own = configs.get(id)?.size;
1413
+ if (!/* @__PURE__ */ isUndefined(own)) return own;
1414
+ const remainder = Math.max(0, 100 - used);
1415
+ return unsized > 0 ? remainder / unsized : 0;
1416
+ }
1417
+ function seedLeaf(leaf) {
1418
+ const id = String(leaf.id);
1419
+ if (!configs.has(id)) configs.set(id, {
1420
+ size: leaf.size,
1421
+ minSize: leaf.minSize ?? DEFAULT_EXTRAS.minSize,
1422
+ maxSize: leaf.maxSize ?? DEFAULT_EXTRAS.maxSize,
1423
+ resizable: leaf.resizable ?? DEFAULT_EXTRAS.resizable,
1424
+ reorderable: leaf.reorderable ?? DEFAULT_EXTRAS.reorderable,
1425
+ pinned: leaf.pinned ?? false
1426
+ });
1427
+ if (!group.has(id)) group.register({
1428
+ id,
1429
+ value: id
1430
+ });
1431
+ if (!sizes.has(id)) sizes.set(id, share(id));
1432
+ for (const other of /* @__PURE__ */ extractLeaves(cols.values())) {
1433
+ const otherId = String(other.id);
1434
+ if (configs.has(otherId) && /* @__PURE__ */ isUndefined(configs.get(otherId)?.size)) sizes.set(otherId, share(otherId));
1435
+ }
1436
+ const config = configs.get(id);
1437
+ if (config.pinned === "left") group.select(id);
1438
+ else if (config.pinned === "right") group.mix(id);
1439
+ }
1440
+ function dropLeaf(id) {
1441
+ group.unregister(id);
1442
+ configs.delete(id);
1443
+ sizes.delete(id);
1444
+ hidden.delete(id);
1445
+ }
1446
+ function leavesOf(ticket) {
1447
+ return /* @__PURE__ */ extractLeaves([ticket]);
1448
+ }
1449
+ for (const ticket of cols.values()) {
1450
+ record(String(ticket.id));
1451
+ for (const leaf of leavesOf(ticket)) seedLeaf(leaf);
1452
+ }
1453
+ cols.on("register:ticket", (ticket) => {
1454
+ record(String(ticket.id));
1455
+ for (const leaf of leavesOf(ticket)) seedLeaf(leaf);
1456
+ });
1457
+ cols.on("unregister:ticket", (ticket) => {
1458
+ const top = order.indexOf(String(ticket.id));
1459
+ if (top !== -1) order.splice(top, 1);
1460
+ for (const leaf of leavesOf(ticket)) dropLeaf(String(leaf.id));
1461
+ });
1462
+ cols.on("clear:registry", () => {
1463
+ group.clear();
1464
+ configs.clear();
1465
+ sizes.clear();
1466
+ hidden.clear();
1467
+ order.length = 0;
1468
+ });
1469
+ function position(id) {
1470
+ if (group.selectedIds.has(id)) return "left";
1471
+ if (group.mixed(id)) return "right";
1472
+ return false;
1473
+ }
1474
+ function resolve(includeHidden) {
1475
+ const resolved = [];
1476
+ let index = 0;
1477
+ for (const ticket of /* @__PURE__ */ extractLeaves(cols.values())) {
1478
+ const id = String(ticket.id);
1479
+ const visible = !hidden.has(id);
1480
+ if (!includeHidden && !visible) continue;
1481
+ const meta = configs.get(id) ?? DEFAULT_EXTRAS;
1482
+ resolved.push({
1483
+ id,
1484
+ index: index++,
1485
+ size: sizes.get(id) ?? 0,
1486
+ offset: 0,
1487
+ pinned: position(id),
1488
+ resizable: meta.resizable,
1489
+ reorderable: meta.reorderable,
1490
+ minSize: meta.minSize,
1491
+ maxSize: meta.maxSize,
1492
+ visible
1493
+ });
1494
+ }
1495
+ return resolved;
1496
+ }
1497
+ function split(resolved) {
1498
+ const left = [];
1499
+ const scrollable = [];
1500
+ const right = [];
1501
+ for (const col of resolved) if (col.pinned === "left") left.push(col);
1502
+ else if (col.pinned === "right") right.push(col);
1503
+ else scrollable.push(col);
1504
+ offsets(left);
1505
+ offsets(scrollable);
1506
+ offsets(right, true);
1507
+ return {
1508
+ left,
1509
+ scrollable,
1510
+ right
1511
+ };
1512
+ }
1513
+ const pinned = computed(() => split(resolve(false)));
1514
+ const columns = computed(() => {
1515
+ const { left, scrollable, right } = pinned.value;
1516
+ return [
1517
+ ...left,
1518
+ ...scrollable,
1519
+ ...right
1520
+ ];
1521
+ });
1522
+ const allColumns = computed(() => resolve(true));
1523
+ function pin(id, pos) {
1524
+ if (!group.has(id)) return;
1525
+ group.unselect(id);
1526
+ group.unmix(id);
1527
+ if (pos === "left") group.select(id);
1528
+ else if (pos === "right") group.mix(id);
1529
+ }
1530
+ function resize(id, delta) {
1531
+ const { left, scrollable, right } = pinned.value;
1532
+ const all = [
1533
+ ...left,
1534
+ ...scrollable,
1535
+ ...right
1536
+ ];
1537
+ const target = all.find((c) => c.id === id);
1538
+ if (!target || !target.resizable) return;
1539
+ const allIndex = all.findIndex((c) => c.id === id);
1540
+ if (allIndex === -1 || allIndex === all.length - 1) return;
1541
+ const neighbor = all[allIndex + 1];
1542
+ const total = target.size + neighbor.size;
1543
+ const lower = Math.max(target.minSize, total - neighbor.maxSize);
1544
+ const upper = Math.min(target.maxSize, total - neighbor.minSize);
1545
+ const clamped = /* @__PURE__ */ clamp(target.size + delta, lower, upper);
1546
+ sizes.set(id, clamped);
1547
+ sizes.set(neighbor.id, total - clamped);
1548
+ }
1549
+ function reorder(from, to) {
1550
+ if (from === to) return;
1551
+ const tops = [...cols.values()];
1552
+ const ids = [];
1553
+ const leafToTop = [];
1554
+ for (const [top, ticket] of tops.entries()) for (const leaf of /* @__PURE__ */ extractLeaves([ticket])) {
1555
+ ids.push(String(leaf.id));
1556
+ leafToTop.push(top);
1557
+ }
1558
+ const visible = ids.filter((id) => !hidden.has(id));
1559
+ const fromId = visible[from];
1560
+ const toId = visible[to];
1561
+ if (/* @__PURE__ */ isUndefined(fromId) || /* @__PURE__ */ isUndefined(toId)) return;
1562
+ const fromTop = leafToTop[ids.indexOf(fromId)];
1563
+ const toTop = leafToTop[ids.indexOf(toId)];
1564
+ if (/* @__PURE__ */ isUndefined(fromTop) || /* @__PURE__ */ isUndefined(toTop) || fromTop === toTop) return;
1565
+ if (tops[fromTop]?.reorderable === false) return;
1566
+ const id = cols.lookup(fromTop);
1567
+ if (/* @__PURE__ */ isUndefined(id)) return;
1568
+ cols.move(id, toTop);
1569
+ }
1570
+ function distribute(incoming) {
1571
+ const tickets = /* @__PURE__ */ extractLeaves(cols.values());
1572
+ if (incoming.length !== tickets.length) {
1573
+ logger.warn(`createDataGrid: distribute() expected ${tickets.length} sizes but received ${incoming.length}; ignoring.`);
1574
+ return;
1575
+ }
1576
+ const sum = incoming.reduce((acc, value) => acc + value, 0);
1577
+ if (sum <= 0) {
1578
+ logger.warn(`createDataGrid: distribute() received sizes summing to ${sum}; expected a positive total.`);
1579
+ return;
1580
+ }
1581
+ const metas = tickets.map((ticket) => configs.get(String(ticket.id)) ?? DEFAULT_EXTRAS);
1582
+ let current = incoming.map((value, i) => /* @__PURE__ */ clamp(value / sum * 100, metas[i].minSize, metas[i].maxSize));
1583
+ let residual = 100 - current.reduce((acc, value) => acc + value, 0);
1584
+ const max = tickets.length * 2;
1585
+ for (let pass = 0; pass < max && Math.abs(residual) >= 1e-6; pass++) {
1586
+ const room = current.map((value, i) => residual > 0 ? metas[i].maxSize - value : value - metas[i].minSize);
1587
+ const available = room.reduce((acc, value) => acc + Math.max(0, value), 0);
1588
+ if (available <= 0) break;
1589
+ current = current.map((value, i) => room[i] <= 0 ? value : value + residual * (room[i] / available));
1590
+ residual = 100 - current.reduce((acc, value) => acc + value, 0);
1591
+ }
1592
+ if (Math.abs(residual) >= 1e-6) logger.warn(`createDataGrid: distribute() could not reach 100 within min/max constraints (off by ${residual.toFixed(2)})`);
1593
+ for (const [i, ticket] of tickets.entries()) sizes.set(String(ticket.id), current[i]);
1594
+ }
1595
+ function show(id) {
1596
+ hidden.delete(id);
1597
+ }
1598
+ function hide(id) {
1599
+ hidden.add(id);
1600
+ }
1601
+ function toggle(id) {
1602
+ if (hidden.has(id)) hidden.delete(id);
1603
+ else hidden.add(id);
1604
+ }
1605
+ function reset() {
1606
+ for (const ticket of /* @__PURE__ */ extractLeaves(cols.values())) {
1607
+ const id = String(ticket.id);
1608
+ sizes.set(id, share(id));
1609
+ }
1610
+ cols.reorder([...order]);
1611
+ hidden.clear();
1612
+ for (const ticket of /* @__PURE__ */ extractLeaves(cols.values())) {
1613
+ const id = String(ticket.id);
1614
+ group.unselect(id);
1615
+ group.unmix(id);
1616
+ }
1617
+ for (const [id, config] of configs) if (config.pinned === "left") group.select(id);
1618
+ else if (config.pinned === "right") group.mix(id);
1619
+ }
1620
+ return {
1621
+ pinned,
1622
+ columns,
1623
+ all: allColumns,
1624
+ pin,
1625
+ resize,
1626
+ reorder,
1627
+ distribute,
1628
+ show,
1629
+ hide,
1630
+ toggle,
1631
+ reset
1632
+ };
1633
+ }
1634
+ //#endregion
1635
+ //#region src/composables/createDataGrid/spanning.ts
1636
+ /**
1637
+ * @module createDataGrid/spanning
1638
+ *
1639
+ * @remarks
1640
+ * Computes a row span map from visible items. For each cell, determines
1641
+ * rowSpan and whether it's hidden (covered by a span from a previous row).
1642
+ * Spans do not cross page boundaries. Each row is addressed by a key: either a
1643
+ * `key(item, index)` projection (the grid passes the registry ticket id) or,
1644
+ * absent that, the row's `itemKey` value. Rows whose resolved key is
1645
+ * `undefined` cannot be addressed in the resulting map and are skipped.
1646
+ *
1647
+ * @internal Consumed only by `createDataGrid`. Fully decoupled (items ref +
1648
+ * columns + key + span fn — no grid/table types), so it is a latent composable.
1649
+ * Promote it to a standalone `createRowSpanning/` composable on a second
1650
+ * consumer. See `.claude/rules/composables.md` §"Sub-modules: inline, private
1651
+ * sibling, or promote".
1652
+ */
1653
+ /**
1654
+ * Computes a row span map from visible items.
1655
+ *
1656
+ * @param options Row spanning configuration
1657
+ * @returns A computed map of item ID to column to SpanEntry
1658
+ *
1659
+ * @example
1660
+ * ```ts
1661
+ * const spans = createRowSpanning({
1662
+ * items: computed(() => grid.items.value),
1663
+ * columns: ['group', 'ticker', 'value'],
1664
+ * span: (item, column) => column === 'group' ? 3 : 1,
1665
+ * })
1666
+ *
1667
+ * spans.value.get(id)?.get('group') // { rowSpan: 3, hidden: false }
1668
+ * ```
1669
+ */
1670
+ function createRowSpanning(options) {
1671
+ const { items, columns, key, itemKey = "id", span } = options;
1672
+ return computed(() => {
1673
+ const result = /* @__PURE__ */ new Map();
1674
+ if (!span) return result;
1675
+ const list = items.value;
1676
+ const resolved = toValue(columns);
1677
+ const covered = Array.from({ length: resolved.length }).fill(0);
1678
+ for (let row = 0; row < list.length; row++) {
1679
+ const item = list[row];
1680
+ const id = key ? key(item, row) : item[itemKey];
1681
+ if (/* @__PURE__ */ isUndefined(id)) continue;
1682
+ const cells = /* @__PURE__ */ new Map();
1683
+ for (const [col, column] of resolved.entries()) if (covered[col] > 0) {
1684
+ cells.set(column, {
1685
+ rowSpan: 1,
1686
+ hidden: true
1687
+ });
1688
+ covered[col]--;
1689
+ } else {
1690
+ const raw = span(item, column);
1691
+ const rowSpan = Math.min(Math.max(1, /* @__PURE__ */ isNaN(raw) ? 1 : raw), list.length - row);
1692
+ cells.set(column, {
1693
+ rowSpan,
1694
+ hidden: false
1695
+ });
1696
+ if (rowSpan > 1) covered[col] = rowSpan - 1;
1697
+ }
1698
+ result.set(id, cells);
1699
+ }
1700
+ return result;
1701
+ });
1702
+ }
1703
+ //#endregion
1704
+ //#region src/composables/createDataGrid/index.ts
1705
+ /**
1706
+ * @module createDataGrid
1707
+ *
1708
+ * @see https://0.vuetifyjs.com/composables/data/create-data-grid
1709
+ *
1710
+ * @remarks
1711
+ * Main factory that wires together column layout, cell editing, row ordering,
1712
+ * and row spanning on top of a createDataTable pipeline. Row ordering is
1713
+ * provided by a createSortable instance synced to the table's row registry
1714
+ * via register / unregister events; the ordered ids are applied post-sort,
1715
+ * pre-pagination by overriding the `items` projection.
1716
+ *
1717
+ * Rows are registered through the inherited registry surface (`register`,
1718
+ * `onboard`, `unregister`, `clear`) — they are not passed as an `items`
1719
+ * option to the factory.
1720
+ *
1721
+ * Follows the trinity pattern for dependency injection.
1722
+ *
1723
+ * @example
1724
+ * ```ts
1725
+ * const grid = createDataGrid()
1726
+ *
1727
+ * grid.columns.onboard([
1728
+ * { id: 'name', sortable: true },
1729
+ * { id: 'progress', editable: true },
1730
+ * ])
1731
+ *
1732
+ * grid.onboard(rows.map(value => ({ id: value.id, value })))
1733
+ * grid.layout.pin('name', 'left')
1734
+ * grid.editing.edit(row.id, 'progress')
1735
+ * ```
1736
+ */
1737
+ /**
1738
+ * Creates a data grid instance with layout, editing, row ordering, and spanning
1739
+ * layered on top of the createDataTable pipeline.
1740
+ *
1741
+ * Rows live on the inherited registry surface — register them with
1742
+ * `grid.onboard(rows.map(value => ({ id: value.id, value })))` or
1743
+ * `grid.register({ id, value })`.
1744
+ *
1745
+ * @param options Data grid options
1746
+ * @returns Data grid context
1747
+ *
1748
+ * @example
1749
+ * ```ts
1750
+ * const grid = createDataGrid({ pagination: { initial: 1 } })
1751
+ *
1752
+ * grid.columns.onboard([
1753
+ * { id: 'name', sortable: true },
1754
+ * { id: 'progress', editable: true, validate: v => Number(v) >= 0 || 'must be positive' },
1755
+ * ])
1756
+ *
1757
+ * grid.onboard(rows.map(value => ({ id: value.id, value })))
1758
+ * grid.layout.pin('name', 'left')
1759
+ * grid.rows.move(rowId, 0)
1760
+ * grid.editing.edit(row.id, 'progress')
1761
+ * ```
1762
+ */
1763
+ function createDataGrid(_options = {}) {
1764
+ const { editing: editingOptions, preserveRowOrder = false, rowSpanning, ...options } = _options;
1765
+ const table = createDataTable(options);
1766
+ const sortable = createSortable({ reactive: true });
1767
+ const dirty = shallowRef(false);
1768
+ table.on("register:ticket", (ticket) => {
1769
+ sortable.register({
1770
+ id: ticket.id,
1771
+ value: ticket.id
1772
+ });
1773
+ });
1774
+ table.on("unregister:ticket", (ticket) => {
1775
+ sortable.unregister(ticket.id);
1776
+ });
1777
+ table.on("clear:registry", () => {
1778
+ sortable.clear();
1779
+ dirty.value = false;
1780
+ });
1781
+ const order = toRef(() => sortable.keys());
1782
+ const sortedIds = computed(() => {
1783
+ const buckets = /* @__PURE__ */ new Map();
1784
+ for (const ticket of table.values()) {
1785
+ const value = ticket.value;
1786
+ const queue = buckets.get(value);
1787
+ if (queue) queue.push(ticket.id);
1788
+ else buckets.set(value, [ticket.id]);
1789
+ }
1790
+ const ids = [];
1791
+ for (const value of table.sortedItems.value) {
1792
+ const id = buckets.get(value)?.shift();
1793
+ if (!/* @__PURE__ */ isUndefined(id)) ids.push(id);
1794
+ }
1795
+ return ids;
1796
+ });
1797
+ function move(id, toIndex) {
1798
+ if (!dirty.value) {
1799
+ const sorted = sortedIds.value;
1800
+ const seen = new Set(sorted);
1801
+ const rest = sortable.keys().filter((key) => !seen.has(key));
1802
+ sortable.reorder([...sorted, ...rest]);
1803
+ }
1804
+ sortable.move(id, toIndex);
1805
+ dirty.value = true;
1806
+ }
1807
+ function reset() {
1808
+ if (sortable.size > 0) sortable.reorder(table.keys());
1809
+ dirty.value = false;
1810
+ }
1811
+ useToggleScope(() => !toValue(preserveRowOrder), () => {
1812
+ watch(table.sort.columns, reset, { flush: "sync" });
1813
+ });
1814
+ const page = computed(() => {
1815
+ if (!dirty.value) {
1816
+ const start = table.pagination.pageStart.value;
1817
+ return {
1818
+ items: table.items.value,
1819
+ ids: table.total.value > table.sortedItems.value.length ? sortedIds.value : sortedIds.value.slice(start, start + table.items.value.length)
1820
+ };
1821
+ }
1822
+ const sorted = table.sortedItems.value;
1823
+ const ids = sortedIds.value;
1824
+ const byId = /* @__PURE__ */ new Map();
1825
+ for (let i = 0; i < sorted.length; i++) byId.set(ids[i], sorted[i]);
1826
+ const seen = new Set(order.value);
1827
+ const items = [];
1828
+ const ordered = [];
1829
+ for (const id of order.value) {
1830
+ const value = byId.get(id);
1831
+ if (!/* @__PURE__ */ isUndefined(value)) {
1832
+ items.push(value);
1833
+ ordered.push(id);
1834
+ }
1835
+ }
1836
+ for (const [i, element] of sorted.entries()) {
1837
+ const id = ids[i];
1838
+ if (!seen.has(id)) {
1839
+ items.push(element);
1840
+ ordered.push(id);
1841
+ }
1842
+ }
1843
+ if (table.total.value > sorted.length) return {
1844
+ items,
1845
+ ids: ordered
1846
+ };
1847
+ const start = table.pagination.pageStart.value;
1848
+ const stop = table.pagination.pageStop.value;
1849
+ return {
1850
+ items: items.slice(start, stop),
1851
+ ids: ordered.slice(start, stop)
1852
+ };
1853
+ });
1854
+ const pageOrderedItems = toRef(() => page.value.items);
1855
+ const layout = createColumnLayout(table.columns);
1856
+ function lookup(row) {
1857
+ return table.get(row)?.value;
1858
+ }
1859
+ const editing = createCellEditing({
1860
+ columns: () => table.leaves.value.filter((col) => col.editable === true || /* @__PURE__ */ isFunction(col.editable)).map((col) => ({
1861
+ id: String(col.id),
1862
+ editable: col.editable,
1863
+ validate: col.validate
1864
+ })),
1865
+ lookup,
1866
+ registry: table,
1867
+ onEdit: editingOptions?.onEdit ? (row, column, value) => {
1868
+ const item = lookup(row);
1869
+ if (!/* @__PURE__ */ isUndefined(item)) editingOptions.onEdit(row, column, value, item);
1870
+ } : void 0
1871
+ });
1872
+ const spans = createRowSpanning({
1873
+ items: pageOrderedItems,
1874
+ columns: () => table.leaves.value.map((col) => col.id),
1875
+ key: (_item, index) => page.value.ids[index],
1876
+ span(item, column) {
1877
+ const col = table.leaves.value.find((leaf) => String(leaf.id) === column);
1878
+ if (col && /* @__PURE__ */ isFunction(col.span)) return col.span(item);
1879
+ return rowSpanning?.(item, column) ?? 1;
1880
+ }
1881
+ });
1882
+ return {
1883
+ ...table,
1884
+ items: pageOrderedItems,
1885
+ layout,
1886
+ rows: {
1887
+ order,
1888
+ move,
1889
+ reset
1890
+ },
1891
+ editing,
1892
+ spans,
1893
+ get size() {
1894
+ return table.size;
1895
+ }
1896
+ };
1897
+ }
1898
+ /**
1899
+ * Creates a data grid context with dependency injection support.
792
1900
  *
793
- * @see https://0.vuetifyjs.com/composables/forms/create-combobox
1901
+ * @param options Data grid context options including namespace
1902
+ * @returns A trinity tuple: [useDataGridContext, provideDataGridContext, defaultContext]
794
1903
  *
795
1904
  * @example
796
1905
  * ```ts
797
- * import { createComboboxContext } from '@vuetify/v0'
798
- *
799
- * const [useCombobox, provideCombobox, combobox] = createComboboxContext({
800
- * namespace: 'my-combobox',
801
- * strict: true,
1906
+ * const [useDataGrid, provideDataGrid, grid] = createDataGridContext({
1907
+ * namespace: 'v0:projects',
802
1908
  * })
1909
+ *
1910
+ * grid.columns.onboard(columns)
1911
+ * grid.onboard(rows.map(value => ({ id: value.id, value })))
1912
+ * provideDataGrid()
1913
+ * const ctx = useDataGrid()
803
1914
  * ```
804
1915
  */
805
- function createComboboxContext(options = {}) {
806
- const { namespace = "v0:combobox", ...rest } = options;
807
- return createTrinity(namespace, createCombobox(rest));
1916
+ function createDataGridContext(_options = {}) {
1917
+ const { namespace = "v0:data-grid", ...options } = _options;
1918
+ const [useDataGridContext, _provideDataGridContext] = createContext(namespace);
1919
+ const context = createDataGrid(options);
1920
+ function provideDataGridContext(_context = context, app) {
1921
+ return _provideDataGridContext(_context, app);
1922
+ }
1923
+ return createTrinity(useDataGridContext, provideDataGridContext, context);
808
1924
  }
809
1925
  /**
810
- * Injects the current combobox context from a parent provider.
811
- *
812
- * @param namespace - DI namespace. Defaults to `'v0:combobox'`.
813
- * @returns The combobox context from the nearest provider.
1926
+ * Returns the current data grid context from dependency injection.
814
1927
  *
815
- * @see https://0.vuetifyjs.com/composables/forms/create-combobox
1928
+ * @param namespace The namespace for the data grid context. @default 'v0:data-grid'
1929
+ * @returns The current data grid context
816
1930
  *
817
1931
  * @example
818
1932
  * ```ts
819
- * import { useCombobox } from '@vuetify/v0'
1933
+ * const grid = useDataGrid<Project>()
820
1934
  *
821
- * const combobox = useCombobox()
822
- * combobox.query.value = 'search term'
1935
+ * grid.layout.pin('id', 'left')
1936
+ * grid.rows.reset()
823
1937
  * ```
824
1938
  */
825
- function useCombobox(namespace = "v0:combobox") {
1939
+ function useDataGrid(namespace = "v0:data-grid") {
826
1940
  return useContext(namespace);
827
1941
  }
828
1942
  //#endregion
@@ -1220,147 +2334,6 @@ function createInput(options) {
1220
2334
  };
1221
2335
  }
1222
2336
  //#endregion
1223
- //#region src/composables/createSortable/index.ts
1224
- /**
1225
- * @module createSortable
1226
- *
1227
- * @see https://0.vuetifyjs.com/composables/data/create-sortable
1228
- *
1229
- * @remarks
1230
- * Headless ordered-list primitive. Extends `createModel` with mutation primitives
1231
- * (`swap`, `reorder`) and an enriched `move:ticket` event on the registry event
1232
- * bus. Honors `disabled` as a real mutation gate at both root and per-ticket scope.
1233
- *
1234
- * Pure logical composable — no DnD, no DOM, no keyboard. Drag-and-drop integration
1235
- * composes with `useDragDrop`; keyboard reorder composes with `useVirtualFocus`.
1236
- *
1237
- * @example
1238
- * ```ts
1239
- * import { createSortable } from '@vuetify/v0'
1240
- * import type { SortableTicketInput } from '@vuetify/v0'
1241
- *
1242
- * interface TaskTicket extends SortableTicketInput {
1243
- * value: { id: number; label: string }
1244
- * }
1245
- *
1246
- * const sortable = createSortable<TaskTicket>()
1247
- * const a = sortable.register({ value: { id: 1, label: 'A' } })
1248
- * const b = sortable.register({ value: { id: 2, label: 'B' } })
1249
- *
1250
- * sortable.move(a.id, 1)
1251
- * sortable.on('move:ticket', ({ ticket, from, to }) => {
1252
- * console.log(`${ticket.value.label} moved ${from} → ${to}`)
1253
- * })
1254
- * ```
1255
- */
1256
- /**
1257
- * Create an ordered-list state primitive.
1258
- *
1259
- * @param options Sortable options.
1260
- * @returns A {@link SortableContext} that extends {@link ModelContext}.
1261
- *
1262
- * @example
1263
- * ```ts
1264
- * const sortable = createSortable<Todo>()
1265
- * sortable.onboard(todos.value.map(value => ({ value })))
1266
- * ```
1267
- */
1268
- function createSortable(_options = {}) {
1269
- const { disabled, ...options } = _options;
1270
- const model = createModel({
1271
- ...options,
1272
- events: true,
1273
- disabled
1274
- });
1275
- const logger = useLogger();
1276
- function move(id, toIndex) {
1277
- if (toValue(disabled)) return void 0;
1278
- const ticket = model.get(id);
1279
- if (!ticket) return void 0;
1280
- if (toValue(ticket.disabled)) return void 0;
1281
- const from = ticket.index;
1282
- const result = model.move(id, toIndex);
1283
- if (result && result.index !== from) model.emit("move:ticket", {
1284
- ticket: result,
1285
- from,
1286
- to: result.index
1287
- });
1288
- return result;
1289
- }
1290
- function swap(a, b) {
1291
- if (toValue(disabled)) return;
1292
- if (a === b) return;
1293
- const ta = model.get(a);
1294
- const tb = model.get(b);
1295
- if (!ta || !tb) return;
1296
- if (toValue(ta.disabled) || toValue(tb.disabled)) return;
1297
- const indexA = ta.index;
1298
- const indexB = tb.index;
1299
- model.batch(() => {
1300
- const ra = model.move(a, indexB);
1301
- if (ra && ra.index !== indexA) model.emit("move:ticket", {
1302
- ticket: ra,
1303
- from: indexA,
1304
- to: ra.index
1305
- });
1306
- const rb = model.move(b, indexA);
1307
- if (rb && rb.index !== indexB) model.emit("move:ticket", {
1308
- ticket: rb,
1309
- from: indexB,
1310
- to: rb.index
1311
- });
1312
- });
1313
- }
1314
- function reorder(ids) {
1315
- if (toValue(disabled)) return;
1316
- const currentSize = model.size;
1317
- if (ids.length !== currentSize) {
1318
- logger.warn(`Reorder expected ${currentSize} ids, got ${ids.length}.`);
1319
- return;
1320
- }
1321
- const known = new Set(model.keys());
1322
- const seen = /* @__PURE__ */ new Set();
1323
- for (const id of ids) {
1324
- if (!known.has(id)) {
1325
- logger.warn(`Reorder contains unknown id "${String(id)}".`);
1326
- return;
1327
- }
1328
- if (seen.has(id)) {
1329
- logger.warn(`Reorder contains duplicate id "${String(id)}".`);
1330
- return;
1331
- }
1332
- seen.add(id);
1333
- }
1334
- const froms = /* @__PURE__ */ new Map();
1335
- for (const id of ids) {
1336
- const ticket = model.get(id);
1337
- if (ticket) froms.set(id, ticket.index);
1338
- }
1339
- model.batch(() => {
1340
- model.reorder(ids);
1341
- for (const [index, id] of ids.entries()) {
1342
- const from = froms.get(id);
1343
- if (from === index) continue;
1344
- const ticket = model.get(id);
1345
- if (ticket) model.emit("move:ticket", {
1346
- ticket,
1347
- from,
1348
- to: index
1349
- });
1350
- }
1351
- });
1352
- }
1353
- return {
1354
- ...model,
1355
- move,
1356
- swap,
1357
- reorder,
1358
- get size() {
1359
- return model.size;
1360
- }
1361
- };
1362
- }
1363
- //#endregion
1364
2337
  //#region src/composables/createKanban/index.ts
1365
2338
  /**
1366
2339
  * @module createKanban
@@ -2405,6 +3378,193 @@ function createNumberField(options = {}) {
2405
3378
  };
2406
3379
  }
2407
3380
  //#endregion
3381
+ //#region src/composables/createOtp/index.ts
3382
+ /**
3383
+ * @module createOtp
3384
+ *
3385
+ * @see https://0.vuetifyjs.com/composables/forms/create-otp
3386
+ *
3387
+ * @remarks
3388
+ * Headless state for a fixed-length one-time-password / verification-code value.
3389
+ * Owns the joined string, per-character pattern matching, length contract,
3390
+ * completion edge detection with a decisional async `onComplete` hook, and
3391
+ * validation through `createInput`. Rendering, focus, and per-element event
3392
+ * wiring are the consumer's responsibility.
3393
+ *
3394
+ * @example
3395
+ * ```ts
3396
+ * import { createOtp } from '@vuetify/v0'
3397
+ *
3398
+ * const otp = createOtp({
3399
+ * length: 6,
3400
+ * pattern: 'numeric',
3401
+ * onComplete: async value => {
3402
+ * const ok = await verify(value)
3403
+ * return ok
3404
+ * },
3405
+ * })
3406
+ *
3407
+ * otp.write(0, '4')
3408
+ * otp.distribute('12345', 1) // returns 5; value becomes '412345'
3409
+ * otp.isComplete.value // true
3410
+ * ```
3411
+ */
3412
+ const PRESETS = {
3413
+ numeric: /^[0-9]$/,
3414
+ alphanumeric: /^[a-zA-Z0-9]$/,
3415
+ alphabetic: /^[a-zA-Z]$/
3416
+ };
3417
+ const WARNED_PATTERNS = /* @__PURE__ */ new WeakSet();
3418
+ function createOtp(options = {}) {
3419
+ const { value = shallowRef(""), length = 6, pattern = "numeric", disabled = false, readonly: _readonly = false, onComplete, ...inputOptions } = options;
3420
+ const logger = useLogger();
3421
+ const errorRef = shallowRef(false);
3422
+ const errorMessagesRef = shallowRef(void 0);
3423
+ const input = createInput({
3424
+ ...inputOptions,
3425
+ value,
3426
+ disabled,
3427
+ readonly: _readonly,
3428
+ error: errorRef,
3429
+ errorMessages: errorMessagesRef
3430
+ });
3431
+ const lengthRef = toRef(() => toValue(length));
3432
+ let lastCompleted = "";
3433
+ function compile(resolved) {
3434
+ if (/* @__PURE__ */ isString(resolved)) return PRESETS[resolved];
3435
+ if (process.env.NODE_ENV !== "production" && !WARNED_PATTERNS.has(resolved)) {
3436
+ WARNED_PATTERNS.add(resolved);
3437
+ if (resolved.test("aa") || resolved.test("00")) logger.warn("createOtp: pattern matches multi-character input; per-character matching may behave unexpectedly");
3438
+ }
3439
+ return resolved;
3440
+ }
3441
+ function accepts(char) {
3442
+ if (char.length !== 1) return false;
3443
+ return compile(toValue(pattern)).test(char);
3444
+ }
3445
+ function filterAccepted(text) {
3446
+ let out = "";
3447
+ for (const ch of text) if (accepts(ch)) out += ch;
3448
+ return out;
3449
+ }
3450
+ const isPending = shallowRef(false);
3451
+ function isLocked() {
3452
+ return toValue(disabled) || toValue(_readonly) || isPending.value;
3453
+ }
3454
+ function clearRejection() {
3455
+ if (errorRef.value || errorMessagesRef.value) {
3456
+ errorRef.value = false;
3457
+ errorMessagesRef.value = void 0;
3458
+ }
3459
+ }
3460
+ function write(index, char) {
3461
+ if (isLocked()) return;
3462
+ const max = toValue(lengthRef);
3463
+ if (index < 0 || index >= max) return;
3464
+ if (char === "") {
3465
+ clearRejection();
3466
+ value.value = value.value.slice(0, index);
3467
+ return;
3468
+ }
3469
+ const first = char[0];
3470
+ if (!accepts(first)) return;
3471
+ clearRejection();
3472
+ const current = value.value;
3473
+ const head = current.slice(0, index);
3474
+ const tail = current.slice(index + 1);
3475
+ value.value = (head + first + tail).slice(0, max);
3476
+ }
3477
+ function distribute(text, index = 0) {
3478
+ if (isLocked()) return 0;
3479
+ const max = toValue(lengthRef);
3480
+ const start = /* @__PURE__ */ clamp(index, 0, max);
3481
+ const filtered = filterAccepted(text);
3482
+ if (filtered.length === 0) return 0;
3483
+ clearRejection();
3484
+ const head = value.value.slice(0, start);
3485
+ const next = (head + filtered).slice(0, max);
3486
+ value.value = next;
3487
+ return next.length - head.length;
3488
+ }
3489
+ function clear() {
3490
+ if (isLocked()) return;
3491
+ clearRejection();
3492
+ lastCompleted = "";
3493
+ value.value = "";
3494
+ }
3495
+ function fill(text) {
3496
+ if (isLocked()) return;
3497
+ const max = toValue(lengthRef);
3498
+ const filtered = filterAccepted(text).slice(0, max);
3499
+ if (text.length === 0 || filtered.length > 0) clearRejection();
3500
+ value.value = filtered;
3501
+ }
3502
+ function reject() {
3503
+ errorRef.value = true;
3504
+ errorMessagesRef.value = ["Invalid code"];
3505
+ lastCompleted = "";
3506
+ value.value = "";
3507
+ }
3508
+ const isComplete = toRef(() => {
3509
+ const max = toValue(lengthRef);
3510
+ const v = value.value;
3511
+ if (v.length !== max) return false;
3512
+ const re = compile(toValue(pattern));
3513
+ for (const ch of v) if (!re.test(ch)) return false;
3514
+ return true;
3515
+ });
3516
+ watch(value, (next) => {
3517
+ if (!isComplete.value) {
3518
+ lastCompleted = "";
3519
+ return;
3520
+ }
3521
+ if (!onComplete) return;
3522
+ if (isLocked()) return;
3523
+ if (next === lastCompleted) return;
3524
+ lastCompleted = next;
3525
+ let result;
3526
+ try {
3527
+ result = onComplete(next);
3528
+ } catch (error) {
3529
+ logger.warn("createOtp: onComplete threw", error);
3530
+ reject();
3531
+ return;
3532
+ }
3533
+ if (/* @__PURE__ */ isThenable(result)) {
3534
+ isPending.value = true;
3535
+ result.then((ok) => {
3536
+ isPending.value = false;
3537
+ if (ok === false) reject();
3538
+ }, (error) => {
3539
+ isPending.value = false;
3540
+ logger.warn("createOtp: onComplete rejected", error);
3541
+ reject();
3542
+ });
3543
+ return;
3544
+ }
3545
+ if (result === false) reject();
3546
+ });
3547
+ return {
3548
+ value,
3549
+ length: lengthRef,
3550
+ input: {
3551
+ ...input,
3552
+ reset() {
3553
+ clearRejection();
3554
+ lastCompleted = "";
3555
+ input.reset();
3556
+ }
3557
+ },
3558
+ isComplete,
3559
+ isValidating: isPending,
3560
+ write,
3561
+ distribute,
3562
+ clear,
3563
+ fill,
3564
+ accepts
3565
+ };
3566
+ }
3567
+ //#endregion
2408
3568
  //#region src/composables/useHydration/index.ts
2409
3569
  /**
2410
3570
  * @module useHydration
@@ -2808,7 +3968,7 @@ function useElementSize(target) {
2808
3968
  return {
2809
3969
  width: shallowReadonly(width),
2810
3970
  height: shallowReadonly(height),
2811
- isActive,
3971
+ isActive: shallowReadonly(isActive),
2812
3972
  isPaused: shallowReadonly(isPaused),
2813
3973
  pause,
2814
3974
  resume,
@@ -2836,6 +3996,20 @@ function useElementSize(target) {
2836
3996
  * Use variable mode (default) for items with different widths like Breadcrumbs.
2837
3997
  * Use uniform mode (itemWidth option) for same-width items like Pagination buttons.
2838
3998
  *
3999
+ * ## Item registration contract
4000
+ *
4001
+ * Every child item must call `measure(index, el)` on mount and
4002
+ * `measure(index, undefined)` on unmount, regardless of mode. The width arg
4003
+ * is recorded in both modes but **only consumed by uniform mode for
4004
+ * isOverflowing's count** — capacity in uniform mode is derived purely
4005
+ * from `itemWidth`. Variable mode uses the per-item widths for both
4006
+ * capacity and isOverflowing.
4007
+ *
4008
+ * Consumers that compute their own capacity outside this composable (e.g.,
4009
+ * with a separate registry) still need the measure() calls if they want
4010
+ * `isOverflowing` to reflect reality — without them the count source is
4011
+ * empty and the signal is permanently false.
4012
+ *
2839
4013
  * @example
2840
4014
  * ```ts
2841
4015
  * import { useTemplateRef } from 'vue'
@@ -2906,8 +4080,8 @@ function createOverflow(options = {}) {
2906
4080
  /* v8 ignore start -- browser-only measurement code */
2907
4081
  if (!IN_BROWSER) return;
2908
4082
  const style = getComputedStyle(el);
2909
- const marginX = Number.parseFloat(style.marginLeft) + Number.parseFloat(style.marginRight);
2910
- const w = el.offsetWidth + marginX;
4083
+ const marginX = (Number.parseFloat(style.marginLeft) || 0) + (Number.parseFloat(style.marginRight) || 0);
4084
+ const w = (el.offsetWidth || 0) + marginX;
2911
4085
  if (widths.value.get(index) !== w) widths.value = new Map(widths.value).set(index, w);
2912
4086
  /* v8 ignore stop */
2913
4087
  }
@@ -2924,36 +4098,42 @@ function createOverflow(options = {}) {
2924
4098
  }
2925
4099
  return sum;
2926
4100
  });
4101
+ const capacity = computed(() => {
4102
+ const available = width.value - toValue(reserved);
4103
+ if (width.value === 0) return Infinity;
4104
+ if (available <= 0) return 0;
4105
+ const g = toValue(gap);
4106
+ const uniformWidth = toValue(itemWidth) ?? 0;
4107
+ if (!/* @__PURE__ */ isUndefined(itemWidth) && uniformWidth <= 0) return Infinity;
4108
+ if (uniformWidth > 0) {
4109
+ const subsequent = uniformWidth + g;
4110
+ if (available < uniformWidth) return 0;
4111
+ return Math.max(1, Math.floor((available - uniformWidth) / subsequent) + 1);
4112
+ }
4113
+ const entries = [...widths.value.entries()].toSorted((a, b) => a[0] - b[0]);
4114
+ if (toValue(reverse)) entries.reverse();
4115
+ let sum = 0;
4116
+ let count = 0;
4117
+ for (const [, w] of entries) {
4118
+ const next = sum + w + (count > 0 ? g : 0);
4119
+ if (next > available) break;
4120
+ sum = next;
4121
+ count++;
4122
+ }
4123
+ return count;
4124
+ });
2927
4125
  return {
2928
4126
  container,
2929
- width,
2930
- capacity: computed(() => {
2931
- const available = width.value - toValue(reserved);
2932
- if (width.value === 0) return Infinity;
2933
- if (available <= 0) return 0;
2934
- const g = toValue(gap);
2935
- const uniformWidth = toValue(itemWidth) ?? 0;
2936
- if (!/* @__PURE__ */ isUndefined(itemWidth) && uniformWidth <= 0) return Infinity;
2937
- if (uniformWidth > 0) {
2938
- const subsequent = uniformWidth + g;
2939
- if (available < uniformWidth) return 0;
2940
- return Math.max(1, Math.floor((available - uniformWidth) / subsequent) + 1);
2941
- }
2942
- const entries = [...widths.value.entries()].toSorted((a, b) => a[0] - b[0]);
2943
- if (toValue(reverse)) entries.reverse();
2944
- let sum = 0;
2945
- let count = 0;
2946
- for (const [, w] of entries) {
2947
- const next = sum + w + (count > 0 ? g : 0);
2948
- if (next > available) break;
2949
- sum = next;
2950
- count++;
2951
- }
2952
- return count;
2953
- }),
4127
+ width,
4128
+ capacity,
2954
4129
  total,
2955
4130
  isOverflowing: toRef(() => {
2956
4131
  if (width.value === 0) return false;
4132
+ if (!/* @__PURE__ */ isUndefined(itemWidth)) {
4133
+ const cap = capacity.value;
4134
+ if (cap === Infinity) return false;
4135
+ return widths.value.size > cap;
4136
+ }
2957
4137
  return total.value > width.value - toValue(reserved);
2958
4138
  }),
2959
4139
  measure,
@@ -4226,6 +5406,124 @@ function useVirtual(namespace = "v0:virtual") {
4226
5406
  return useContext(namespace);
4227
5407
  }
4228
5408
  //#endregion
5409
+ //#region src/composables/toHighlight/index.ts
5410
+ /**
5411
+ * @module toHighlight
5412
+ *
5413
+ * @see https://0.vuetifyjs.com/composables/transformers/to-highlight
5414
+ *
5415
+ * @remarks
5416
+ * Pure transformer — no DOM, no state, no registry, no reactivity. Splits text
5417
+ * into matched and unmatched chunks given a query string, an array of query
5418
+ * strings, or pre-computed `[start, end]` match ranges (e.g., from createFilter).
5419
+ * Returns a plain array; wrap the call in `computed()` for reactive recomputation.
5420
+ *
5421
+ * @example
5422
+ * ```ts
5423
+ * import { toHighlight } from '@vuetify/v0'
5424
+ *
5425
+ * const chunks = toHighlight('Hello World', 'World')
5426
+ * // [{ text: 'Hello ', match: false }, { text: 'World', match: true }]
5427
+ * ```
5428
+ */
5429
+ function merge(ranges) {
5430
+ const sorted = ranges.filter((span) => span[0] < span[1]).toSorted((a, b) => a[0] - b[0]);
5431
+ const merged = [];
5432
+ for (const span of sorted) {
5433
+ const last = merged.at(-1);
5434
+ if (last && span[0] <= last[1]) last[1] = Math.max(last[1], span[1]);
5435
+ else merged.push([span[0], span[1]]);
5436
+ }
5437
+ return merged;
5438
+ }
5439
+ function chunk(text, ranges) {
5440
+ const chunks = [];
5441
+ let cursor = 0;
5442
+ for (const [start, end] of ranges) {
5443
+ if (cursor < start) chunks.push({
5444
+ text: text.slice(cursor, start),
5445
+ match: false
5446
+ });
5447
+ chunks.push({
5448
+ text: text.slice(start, end),
5449
+ match: true
5450
+ });
5451
+ cursor = end;
5452
+ }
5453
+ if (cursor < text.length) chunks.push({
5454
+ text: text.slice(cursor),
5455
+ match: false
5456
+ });
5457
+ return chunks;
5458
+ }
5459
+ function find(text, query, matchAll, ignoreCase) {
5460
+ const terms = (/* @__PURE__ */ toArray(query)).filter(Boolean);
5461
+ const haystack = ignoreCase ? text.toLocaleLowerCase() : text;
5462
+ const spans = [];
5463
+ for (const term of terms) {
5464
+ const needle = ignoreCase ? term.toLocaleLowerCase() : term;
5465
+ let index = haystack.indexOf(needle);
5466
+ if (index !== -1) {
5467
+ spans.push([index, index + term.length]);
5468
+ if (matchAll) {
5469
+ index = haystack.indexOf(needle, index + term.length);
5470
+ while (index !== -1) {
5471
+ spans.push([index, index + term.length]);
5472
+ index = haystack.indexOf(needle, index + term.length);
5473
+ }
5474
+ }
5475
+ }
5476
+ }
5477
+ return merge(spans);
5478
+ }
5479
+ /**
5480
+ * Splits text into matched and unmatched chunks.
5481
+ *
5482
+ * Pure transformer — returns a plain array. Wrap the call in `computed()` for
5483
+ * reactive recomputation.
5484
+ *
5485
+ * Priority: `options.matches` (when non-empty) → `query` → no-match fallback.
5486
+ *
5487
+ * @param text The source string to split.
5488
+ * @param query One or more search terms. Empty strings are ignored. Case sensitivity controlled by `options.ignoreCase`.
5489
+ * @param options Optional `matches`, `matchAll`, `ignoreCase`.
5490
+ * @returns A `HighlightChunk[]` array.
5491
+ *
5492
+ * @see https://0.vuetifyjs.com/composables/transformers/to-highlight
5493
+ *
5494
+ * @example
5495
+ * ```ts
5496
+ * import { computed, shallowRef } from 'vue'
5497
+ * import { toHighlight } from '@vuetify/v0'
5498
+ *
5499
+ * const query = shallowRef('World')
5500
+ * const chunks = computed(() => toHighlight('Hello World', query))
5501
+ *
5502
+ * console.log(chunks.value)
5503
+ * // [{ text: 'Hello ', match: false }, { text: 'World', match: true }]
5504
+ * ```
5505
+ */
5506
+ /* @__NO_SIDE_EFFECTS__ */
5507
+ function toHighlight(text, query, options = {}) {
5508
+ const _text = toValue(text);
5509
+ const _query = toValue(query);
5510
+ const _matches = toValue(options.matches);
5511
+ const matchAll = toValue(options.matchAll) ?? false;
5512
+ const ignoreCase = toValue(options.ignoreCase) ?? false;
5513
+ if (_matches?.length) return chunk(_text, merge(_matches));
5514
+ if (_query) {
5515
+ const ranges = find(_text, _query, matchAll, ignoreCase);
5516
+ return ranges.length > 0 ? chunk(_text, ranges) : [{
5517
+ text: _text,
5518
+ match: false
5519
+ }];
5520
+ }
5521
+ return [{
5522
+ text: _text,
5523
+ match: false
5524
+ }];
5525
+ }
5526
+ //#endregion
4229
5527
  //#region src/composables/toReactive/index.ts
4230
5528
  /**
4231
5529
  * @module toReactive
@@ -4829,127 +6127,13 @@ function createDatePlugin(options) {
4829
6127
  * ```
4830
6128
  */
4831
6129
  function useDate(namespace = "v0:date") {
4832
- if (!/* @__PURE__ */ instanceExists()) throw new Error("[v0] useDate() must be called inside a Vue component with createDatePlugin installed.\n\nExample:\n import { V0DateAdapter } from '@vuetify/v0/date'\n import { createDatePlugin } from '@vuetify/v0'\n\n app.use(createDatePlugin({ adapter: new V0DateAdapter() }))");
6130
+ if (!/* @__PURE__ */ instanceExists()) throw new V0Error("[v0] useDate() must be called inside a Vue component with createDatePlugin installed.\n\nExample:\n import { V0DateAdapter } from '@vuetify/v0/date'\n import { createDatePlugin } from '@vuetify/v0'\n\n app.use(createDatePlugin({ adapter: new V0DateAdapter() }))", {
6131
+ code: "V0_PLUGIN_MISSING",
6132
+ plugin: "createDatePlugin"
6133
+ });
4833
6134
  return useContext(namespace);
4834
6135
  }
4835
6136
  //#endregion
4836
- //#region src/composables/useDelay/index.ts
4837
- /**
4838
- * @module useDelay
4839
- *
4840
- * @see https://0.vuetifyjs.com/composables/system/use-delay
4841
- *
4842
- * @remarks
4843
- * Schedule open and close transitions with configurable delays.
4844
- *
4845
- * Composes `useTimer` for the underlying lifecycle (start / stop / pause / resume,
4846
- * plus reactive `isActive`, `isPaused`, `remaining`) and adds direction tracking
4847
- * (`isOpening`) and promise-based resolution.
4848
- *
4849
- * Key features:
4850
- * - Reactive `openDelay` and `closeDelay` via `MaybeRefOrGetter`
4851
- * - `start(isOpening)` returns a `Promise<boolean>` that resolves once the delay elapses
4852
- * - Per-call `minDelay` floor enforces a minimum delay
4853
- * - Automatic cleanup on scope disposal (timer cleared, pending promise settled with current direction)
4854
- * - SSR-safe
4855
- *
4856
- * @example
4857
- * ```ts
4858
- * import { useDelay } from '@vuetify/v0'
4859
- *
4860
- * const delay = useDelay(isOpening => isVisible.value = isOpening, {
4861
- * openDelay: 300,
4862
- * closeDelay: 200,
4863
- * })
4864
- *
4865
- * delay.start(true)
4866
- * delay.start(false, { minDelay: 500 })
4867
- * ```
4868
- */
4869
- /**
4870
- * Schedule open and close transitions with configurable delays.
4871
- *
4872
- * Composes `useTimer` for the lifecycle (`start` / `stop` / `pause` / `resume` plus
4873
- * reactive `isActive`, `isPaused`, `remaining`) and adds direction tracking
4874
- * (`isOpening`) and promise-based resolution.
4875
- *
4876
- * @param callback Optional callback invoked once a started delay elapses; receives the resolved direction.
4877
- * @param options Reactive delay settings.
4878
- * @returns Lifecycle controls and reactive state.
4879
- *
4880
- * @see https://0.vuetifyjs.com/composables/system/use-delay
4881
- *
4882
- * @example
4883
- * ```ts
4884
- * const delay = useDelay(isOpening => isVisible.value = isOpening, {
4885
- * openDelay: 300,
4886
- * closeDelay: 200,
4887
- * })
4888
- *
4889
- * delay.start(true)
4890
- * delay.start(false, { minDelay: 500 })
4891
- * delay.pause()
4892
- * delay.resume()
4893
- * delay.stop()
4894
- * ```
4895
- */
4896
- function useDelay(callback, options = {}) {
4897
- const { openDelay, closeDelay } = options;
4898
- const isOpening = shallowRef(false);
4899
- let minDelay = 0;
4900
- let resolver;
4901
- function fire() {
4902
- const direction = isOpening.value;
4903
- const resolve = resolver;
4904
- resolver = void 0;
4905
- resolve?.(direction);
4906
- callback?.(direction);
4907
- }
4908
- function duration() {
4909
- const raw = isOpening.value ? toValue(openDelay) : toValue(closeDelay);
4910
- return Math.max(minDelay, raw ?? 0);
4911
- }
4912
- const timer = useTimer(fire, { duration });
4913
- function start(direction, options = {}) {
4914
- const previous = resolver;
4915
- resolver = void 0;
4916
- isOpening.value = direction;
4917
- minDelay = options.minDelay ?? 0;
4918
- previous?.(direction);
4919
- const ms = duration();
4920
- return new Promise((resolve) => {
4921
- resolver = resolve;
4922
- if (ms <= 0) {
4923
- timer.stop();
4924
- fire();
4925
- return;
4926
- }
4927
- timer.start();
4928
- });
4929
- }
4930
- function stop() {
4931
- const resolve = resolver;
4932
- resolver = void 0;
4933
- timer.stop();
4934
- resolve?.(isOpening.value);
4935
- }
4936
- onScopeDispose(() => {
4937
- const resolve = resolver;
4938
- resolver = void 0;
4939
- resolve?.(isOpening.value);
4940
- }, true);
4941
- return {
4942
- start,
4943
- stop,
4944
- pause: timer.pause,
4945
- resume: timer.resume,
4946
- isActive: shallowReadonly(timer.isActive),
4947
- isPaused: shallowReadonly(timer.isPaused),
4948
- remaining: shallowReadonly(timer.remaining),
4949
- isOpening: shallowReadonly(isOpening)
4950
- };
4951
- }
4952
- //#endregion
4953
6137
  //#region src/composables/useMutationObserver/index.ts
4954
6138
  /**
4955
6139
  * @module useMutationObserver
@@ -7473,114 +8657,77 @@ function createStorageFallback() {
7473
8657
  }
7474
8658
  const [createStorageContext, createStoragePlugin, useStorage] = createPluginContext("v0:storage", (options) => createStorage(options), { fallback: () => createStorageFallback() });
7475
8659
  //#endregion
7476
- //#region src/composables/useToggleScope/index.ts
8660
+ //#region src/composables/useTooltip/index.ts
7477
8661
  /**
7478
- * @module useToggleScope
8662
+ * @module useTooltip
7479
8663
  *
7480
- * @see https://0.vuetifyjs.com/composables/system/use-toggle-scope
8664
+ * @see https://0.vuetifyjs.com/composables/plugins/use-tooltip
7481
8665
  *
7482
8666
  * @remarks
7483
- * Conditionally manages an effect scope based on a reactive boolean condition.
7484
- * When the source becomes true, creates and runs an effect scope. When false, stops the scope.
7485
- * All reactive effects created within the scoped function are automatically cleaned up on deactivation.
7486
- *
7487
- * Key features:
7488
- * - Uses Vue's effectScope for efficient reactive effect lifecycle management
7489
- * - Automatic cleanup when condition becomes false
7490
- * - Supports optional reset callback for scope restart capability
7491
- * - Handles rapid toggling and parent scope disposal safely
7492
- * - SSR-safe (effectScope is part of Vue core)
8667
+ * Region-scoped tooltip coordination plugin. Holds shared delay defaults and
8668
+ * a small registry of currently-open tooltips so individual tooltip instances
8669
+ * can skip their open delay when another tooltip is already visible (the
8670
+ * "warmup" pattern from React Aria / Radix).
7493
8671
  *
7494
- * Perfect for conditional side effects, feature flags, and performance optimization
7495
- * by only running reactive effects when needed.
8672
+ * Use the plugin form (`app.use(createTooltipPlugin())`) for app-wide
8673
+ * defaults.
7496
8674
  *
7497
8675
  * @example
7498
8676
  * ```ts
7499
- * import { shallowRef } from 'vue'
7500
- * import { useToggleScope } from '@vuetify/v0'
8677
+ * import { createTooltipPlugin } from '@vuetify/v0'
7501
8678
  *
7502
- * const isEnabled = shallowRef(false)
7503
- * const { isActive } = useToggleScope(isEnabled, () => {
7504
- * // effects run while isEnabled is true
7505
- * })
8679
+ * app.use(createTooltipPlugin({ openDelay: 500 }))
7506
8680
  * ```
7507
8681
  */
8682
+ function createTooltip(options = {}) {
8683
+ const openDelay = toRef(() => toValue(options.openDelay) ?? 700);
8684
+ const closeDelay = toRef(() => toValue(options.closeDelay) ?? 150);
8685
+ const skipDelay = toRef(() => toValue(options.skipDelay) ?? 300);
8686
+ const disabled = toRef(() => toValue(options.disabled) ?? false);
8687
+ const registry = createRegistry({
8688
+ reactive: true,
8689
+ events: true
8690
+ });
8691
+ let lastClosedAt = null;
8692
+ registry.on("unregister:ticket", () => {
8693
+ lastClosedAt = Date.now();
8694
+ });
8695
+ const isAnyOpen = toRef(() => registry.size > 0);
8696
+ function shouldSkipOpenDelay() {
8697
+ if (isAnyOpen.value) return true;
8698
+ if (/* @__PURE__ */ isNull(lastClosedAt)) return false;
8699
+ return Date.now() - lastClosedAt < skipDelay.value;
8700
+ }
8701
+ onScopeDispose(() => registry.dispose(), true);
8702
+ return {
8703
+ openDelay,
8704
+ closeDelay,
8705
+ skipDelay,
8706
+ disabled,
8707
+ isAnyOpen,
8708
+ shouldSkipOpenDelay,
8709
+ register: registry.register,
8710
+ unregister: registry.unregister
8711
+ };
8712
+ }
7508
8713
  /**
7509
- * Conditionally manages an effect scope based on a reactive boolean source.
7510
- *
7511
- * @param source A reactive boolean value or getter that controls the scope lifecycle
7512
- * @param fn The function to run within the effect scope. Can optionally receive controls for manual scope management.
7513
- * @returns Controls object with isActive state and start/stop/reset methods
7514
- *
7515
- * @see https://vuejs.org/api/reactivity-advanced.html#effectscope
7516
- * @see https://0.vuetifyjs.com/composables/system/use-toggle-scope
7517
- *
7518
- * @example
7519
- * ```ts
7520
- * import { ref } from 'vue'
7521
- * import { useToggleScope } from '@vuetify/v0'
7522
- *
7523
- * const isEnabled = ref(false)
7524
- *
7525
- * const { isActive } = useToggleScope(isEnabled, () => {
7526
- * // This code runs when isEnabled becomes true
7527
- * const unwatch = watch(someRef, () => {
7528
- * console.log('Watching...')
7529
- * })
7530
- *
7531
- * // Cleanup happens automatically when isEnabled becomes false
7532
- * })
7533
- *
7534
- * // Toggle the scope on/off
7535
- * isEnabled.value = true // Starts the scope
7536
- * console.log(isActive.value) // true
7537
- * isEnabled.value = false // Stops and cleans up
7538
- * console.log(isActive.value) // false
7539
- * ```
8714
+ * Synthesized fallback used when `useTooltip()` is called without
8715
+ * `app.use(createTooltipPlugin())`. Returns a fresh context with the
8716
+ * documented defaults `<Tooltip.Root>` keeps working but warmup
8717
+ * coordination is per-instance instead of region-wide.
7540
8718
  *
7541
8719
  * @example
7542
- * With manual controls:
7543
8720
  * ```ts
7544
- * const isEnabled = ref(true)
7545
- *
7546
- * useToggleScope(isEnabled, (controls) => {
7547
- * // Access controls inside the scope
7548
- * console.log('Active:', controls.isActive.value)
8721
+ * import { createTooltipFallback } from '@vuetify/v0'
7549
8722
  *
7550
- * // Manually reset the scope if needed
7551
- * someEvent.on('reset', () => controls.reset())
7552
- * })
8723
+ * const tooltip = createTooltipFallback()
8724
+ * console.log(tooltip.openDelay.value) // 700
7553
8725
  * ```
7554
8726
  */
7555
- function useToggleScope(source, fn) {
7556
- const scope = shallowRef();
7557
- const isActive = toRef(() => !!scope.value);
7558
- function start() {
7559
- if (scope.value) return;
7560
- scope.value = effectScope();
7561
- scope.value.run(() => fn.length > 0 ? fn(controls) : fn());
7562
- }
7563
- function stop() {
7564
- scope.value?.stop();
7565
- scope.value = void 0;
7566
- }
7567
- function reset() {
7568
- stop();
7569
- start();
7570
- }
7571
- const controls = {
7572
- isActive: shallowReadonly(isActive),
7573
- start,
7574
- stop,
7575
- reset
7576
- };
7577
- watch(source, (active) => {
7578
- if (active && !scope.value) start();
7579
- else if (!active) stop();
7580
- }, { immediate: true });
7581
- onScopeDispose(stop, true);
7582
- return controls;
8727
+ function createTooltipFallback() {
8728
+ return createTooltip();
7583
8729
  }
8730
+ const [createTooltipContext, createTooltipPlugin, useTooltip] = createPluginContext("v0:tooltip", createTooltip, { fallback: () => createTooltipFallback() });
7584
8731
  //#endregion
7585
8732
  //#region src/composables/useEventListener/index.ts
7586
8733
  /**
@@ -7957,4 +9104,4 @@ function useClickOutside(target, handler, options = {}) {
7957
9104
  };
7958
9105
  }
7959
9106
  //#endregion
7960
- export { useTimeline as $, useIntersectionObserver as A, createForm as At, createDateContext as B, usePopover as Bt, usePresence as C, createNested as Ct, usePrefersReducedMotion as D, createSortable as Dt, usePrefersDark as E, createKanban as Et, PointerAdapter as F, useCombobox as Ft, createBreakpointsPlugin as G, useDate as H, createBreadcrumbsContext as Ht, DragDropAdapter as I, ServerComboboxAdapter as It, createVirtual as J, useBreakpoints as K, useMutationObserver as L, ClientComboboxAdapter as Lt, useHotkey as M, useForm as Mt, useDragDrop as N, createCombobox as Nt, useLazy as O, createInput as Ot, KeyboardAdapter as P, createComboboxContext as Pt, createTimelineContext as Q, useDelay as R, ComboboxAdapter as Rt, useProxyModel as S, createNumeric as St, usePrefersContrast as T, useNested as Tt, createBreakpoints as U, useBreadcrumbs as Ut, createDatePlugin as V, createBreadcrumbs as Vt, createBreakpointsContext as W, useVirtual as X, createVirtualContext as Y, createTimeline as Z, createRtlPlugin as _, createHydration as _t, useToggleScope as a, createRatingContext as at, useRovingFocus as b, useHydration as bt, createStoragePlugin as c, createProgressContext as ct, createStackContext as d, createOverflowContext as dt, createStep as et, createStackPlugin as f, useOverflow as ft, createRtlFallback as g, createFallbackHydration as gt, createRtlContext as h, toElement as ht, useWindowEventListener as i, createRating as it, useImage as j, createFormContext as jt, useElementIntersection as k, createValidation as kt, useStorage as l, useProgress as lt, createRtl as m, useResizeObserver as mt, useDocumentEventListener as n, useStep as nt, createStorage as o, useRating as ot, useStack as p, useElementSize as pt, toReactive as q, useEventListener as r, createSlider as rt, createStorageContext as s, createProgress as st, useClickOutside as t, createStepContext as tt, createStack as u, createOverflow as ut, useRtl as v, createHydrationContext as vt, useMediaQuery as w, createNestedContext as wt, useRaf as x, createNumberField as xt, V0RtlAdapter as y, createHydrationPlugin as yt, createDate as z, useVirtualFocus as zt };
9107
+ export { useVirtual as $, usePrefersReducedMotion as A, createKanban as At, useMutationObserver as B, createSortable as Bt, useRovingFocus as C, useHydration as Ct, useMediaQuery as D, createNested as Dt, usePresence as E, createNumeric as Et, useHotkey as F, useForm as Ft, createBreakpoints as G, ClientComboboxAdapter as Gt, createDateContext as H, createComboboxContext as Ht, useDragDrop as I, createDataGrid as It, useBreakpoints as J, usePopover as Jt, createBreakpointsContext as K, ComboboxAdapter as Kt, KeyboardAdapter as L, createDataGridContext as Lt, useElementIntersection as M, createValidation as Mt, useIntersectionObserver as N, createForm as Nt, usePrefersContrast as O, createNestedContext as Ot, useImage as P, createFormContext as Pt, createVirtualContext as Q, useBreadcrumbs as Qt, PointerAdapter as R, useDataGrid as Rt, V0RtlAdapter as S, createHydrationPlugin as St, useProxyModel as T, createNumberField as Tt, createDatePlugin as U, useCombobox as Ut, createDate as V, createCombobox as Vt, useDate as W, ServerComboboxAdapter as Wt, toHighlight as X, createBreadcrumbs as Xt, toReactive as Y, useDelay as Yt, createVirtual as Z, createBreadcrumbsContext as Zt, createRtl as _, useResizeObserver as _t, createTooltipContext as a, useStep as at, createRtlPlugin as b, createHydration as bt, useTooltip as c, createRatingContext as ct, createStoragePlugin as d, createProgressContext as dt, createTimeline as et, useStorage as f, useProgress as ft, useStack as g, useElementSize as gt, createStackPlugin as h, useOverflow as ht, useWindowEventListener as i, createStepContext as it, useLazy as j, createInput as jt, usePrefersDark as k, useNested as kt, createStorage as l, useRating as lt, createStackContext as m, createOverflowContext as mt, useDocumentEventListener as n, useTimeline as nt, createTooltipFallback as o, createSlider as ot, createStack as p, createOverflow as pt, createBreakpointsPlugin as q, useVirtualFocus as qt, useEventListener as r, createStep as rt, createTooltipPlugin as s, createRating as st, useClickOutside as t, createTimelineContext as tt, createStorageContext as u, createProgress as ut, createRtlContext as v, toElement as vt, useRaf as w, createOtp as wt, useRtl as x, createHydrationContext as xt, createRtlFallback as y, createFallbackHydration as yt, DragDropAdapter as z, useToggleScope as zt };