@vuetify/v0 1.0.0-beta.0 → 1.0.0-beta.2

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 (154) hide show
  1. package/README.md +2 -1
  2. package/dist/{adapter-DZeI0ZbW.d.mts → adapter-CQoi3rdC.d.mts} +1 -1
  3. package/dist/adapter-CVoWg0sw.mjs +99 -0
  4. package/dist/{adapter-oM18AdUc.mjs → adapter-Cj7vxEdB.mjs} +6 -1
  5. package/dist/adapter-Cy7wXVFe.mjs +4 -0
  6. package/dist/{adapter-we-0GbFH.d.mts → adapter-D2ABPTpo.d.mts} +2 -2
  7. package/dist/{adapter-BO75n0Cn.d.mts → adapter-_srNCBEi.d.mts} +3 -3
  8. package/dist/browser/index.js +23639 -23267
  9. package/dist/components/index.d.mts +1 -1
  10. package/dist/components/index.mjs +1 -1
  11. package/dist/{components-_uxZ_Zo3.mjs → components-DcOwIGXt.mjs} +304 -214
  12. package/dist/composables/index.d.mts +20 -20
  13. package/dist/composables/index.mjs +34 -28
  14. package/dist/composables-DxKmYiTj.mjs +4077 -0
  15. package/dist/{consola-CUNHyu8J.d.mts → consola-BPGkJp_q.d.mts} +1 -1
  16. package/dist/constants/index.d.mts +1 -1
  17. package/dist/constants/index.mjs +1 -1
  18. package/dist/{createDataTable-DlFQyBTN.mjs → createDataTable-CBfnCUKU.mjs} +14 -11
  19. package/dist/{createGroup-DsKJMuRd.mjs → createGroup-BTG6xcyp.mjs} +26 -14
  20. package/dist/{adapter-BCVxBtDp.mjs → createPagination-B5dfqY-a.mjs} +3 -98
  21. package/dist/{createRegistry-tJ1PPNIr.mjs → createRegistry-Cb6ftNMU.mjs} +57 -41
  22. package/dist/{createSelection-DEqhvdoj.mjs → createSelection-BMvXj84C.mjs} +7 -4
  23. package/dist/{createSingle-ClzWqRCe.mjs → createSingle-BCUJuV4D.mjs} +3 -3
  24. package/dist/{createTokens-mNxs8DQ3.mjs → createTokens-BSSQAfNX.mjs} +12 -6
  25. package/dist/data-table/adapters/client/index.d.mts +1 -1
  26. package/dist/data-table/adapters/client/index.mjs +1 -1
  27. package/dist/data-table/adapters/index.d.mts +4 -4
  28. package/dist/data-table/adapters/index.mjs +4 -5
  29. package/dist/data-table/adapters/server/index.d.mts +1 -1
  30. package/dist/data-table/adapters/server/index.mjs +1 -1
  31. package/dist/data-table/adapters/virtual/index.d.mts +1 -1
  32. package/dist/data-table/adapters/virtual/index.mjs +1 -1
  33. package/dist/data-table/index.d.mts +5 -5
  34. package/dist/data-table/index.mjs +5 -5
  35. package/dist/date/index.d.mts +1 -1
  36. package/dist/date/index.mjs +36 -33
  37. package/dist/features/adapters/flagsmith/index.d.mts +1 -1
  38. package/dist/features/adapters/flagsmith/index.mjs +47 -1
  39. package/dist/features/adapters/launchdarkly/index.d.mts +1 -1
  40. package/dist/features/adapters/launchdarkly/index.mjs +37 -1
  41. package/dist/features/adapters/posthog/index.d.mts +1 -1
  42. package/dist/features/adapters/posthog/index.mjs +46 -1
  43. package/dist/features/index.d.mts +2 -2
  44. package/dist/features/index.mjs +2 -2
  45. package/dist/{flagsmith-Cn_KVy6v.d.mts → flagsmith-CXzNBqm3.d.mts} +1 -1
  46. package/dist/{globals-DB-krEA-.mjs → globals-C1X-9O7K.mjs} +1 -1
  47. package/dist/{index-DWAgQOBa.d.mts → index-Bmz8PZMY.d.mts} +3 -4
  48. package/dist/{index-BN06veYe.d.mts → index-CKyDkh8J.d.mts} +169 -88
  49. package/dist/{index-B3PDLk_Y.d.mts → index-DLBPM47A.d.mts} +509 -439
  50. package/dist/{index-ZD3hw2Zo.d.mts → index-DRrHHgE7.d.mts} +4 -4
  51. package/dist/{index-ATZqco03.d.mts → index-LMiCHwZl.d.mts} +7 -2
  52. package/dist/{index-BIyQ3OxC.d.mts → index-rJ_lhMYJ.d.mts} +5 -5
  53. package/dist/index.d.mts +24 -24
  54. package/dist/index.mjs +37 -31
  55. package/dist/json/attributes.json +2 -2
  56. package/dist/json/web-types.json +3 -3
  57. package/dist/{launchdarkly-C3Knz7-v.d.mts → launchdarkly-DvhloBts.d.mts} +1 -1
  58. package/dist/locale/adapters/index.d.mts +2 -2
  59. package/dist/locale/adapters/index.mjs +2 -3
  60. package/dist/locale/adapters/v0/index.d.mts +1 -1
  61. package/dist/locale/adapters/v0/index.mjs +1 -1
  62. package/dist/locale/adapters/vue-i18n/index.d.mts +1 -1
  63. package/dist/locale/adapters/vue-i18n/index.mjs +1 -1
  64. package/dist/locale/index.d.mts +3 -3
  65. package/dist/locale/index.mjs +3 -3
  66. package/dist/logger/adapters/consola/index.d.mts +1 -1
  67. package/dist/logger/adapters/index.d.mts +4 -4
  68. package/dist/logger/adapters/index.mjs +1 -2
  69. package/dist/logger/adapters/pino/index.d.mts +1 -1
  70. package/dist/logger/adapters/v0/index.d.mts +1 -1
  71. package/dist/logger/adapters/v0/index.mjs +1 -1
  72. package/dist/logger/index.d.mts +5 -5
  73. package/dist/logger/index.mjs +2 -2
  74. package/dist/notifications/index.d.mts +6 -6
  75. package/dist/notifications/index.mjs +29 -29
  76. package/dist/palettes/ant/index.d.mts +1 -1
  77. package/dist/palettes/index.d.mts +3 -3
  78. package/dist/palettes/material/index.d.mts +1 -1
  79. package/dist/palettes/md1/index.d.mts +1 -1
  80. package/dist/palettes/md2/index.d.mts +1 -1
  81. package/dist/palettes/radix/index.d.mts +1 -1
  82. package/dist/palettes/tailwind/index.d.mts +1 -1
  83. package/dist/permissions/adapters/index.d.mts +1 -1
  84. package/dist/permissions/adapters/index.mjs +1 -2
  85. package/dist/permissions/adapters/v0/index.d.mts +1 -1
  86. package/dist/permissions/adapters/v0/index.mjs +1 -1
  87. package/dist/permissions/index.d.mts +1 -1
  88. package/dist/permissions/index.mjs +2 -2
  89. package/dist/{pino-BaYY_KIN.d.mts → pino-B6BI1AOK.d.mts} +1 -1
  90. package/dist/{posthog-CR8R2ywM.d.mts → posthog-BOT5W7Ds.d.mts} +1 -1
  91. package/dist/rules/index.d.mts +1 -1
  92. package/dist/rules/index.mjs +1 -1
  93. package/dist/{server-CZ83nTn_.mjs → server-D5BJBjB0.mjs} +2 -1
  94. package/dist/{server-7vjHxjNc.d.mts → server-DbtTGb67.d.mts} +1 -1
  95. package/dist/storage/adapters/index.d.mts +2 -2
  96. package/dist/storage/adapters/index.mjs +1 -1
  97. package/dist/storage/adapters/memory/index.d.mts +1 -1
  98. package/dist/storage/adapters/memory/index.mjs +1 -1
  99. package/dist/storage/index.d.mts +3 -3
  100. package/dist/storage/index.mjs +2 -2
  101. package/dist/theme/adapters/index.d.mts +1 -1
  102. package/dist/theme/adapters/index.mjs +3 -4
  103. package/dist/theme/adapters/unhead/index.d.mts +1 -1
  104. package/dist/theme/adapters/unhead/index.mjs +1 -1
  105. package/dist/theme/adapters/v0/index.d.mts +1 -1
  106. package/dist/theme/adapters/v0/index.mjs +1 -1
  107. package/dist/theme/index.d.mts +1 -1
  108. package/dist/theme/index.mjs +5 -4
  109. package/dist/types/index.d.mts +1 -1
  110. package/dist/{unhead-CG672gKh.mjs → unhead-i8ji788n.mjs} +12 -8
  111. package/dist/useEventListener-BK_uaXZv.mjs +103 -0
  112. package/dist/{useFeatures-B-A-eOo-.mjs → useFeatures-BKAK56af.mjs} +4 -8
  113. package/dist/{useLocale-CbpoxbVU.mjs → useLocale-CTwn8uZk.mjs} +4 -5
  114. package/dist/{useLogger-CTgAedNH.mjs → useLogger-D7_hm4e8.mjs} +2 -3
  115. package/dist/{useNotifications-BkKGUl_1.mjs → useNotifications-B2YeotHN.mjs} +15 -8
  116. package/dist/{usePermissions-BYOrKcnk.mjs → usePermissions-CWYXR7ji.mjs} +18 -11
  117. package/dist/{useClickOutside-DoOgNtrj.mjs → useRovingFocus-BdnSUVrD.mjs} +4014 -8138
  118. package/dist/{useRules-mD_DY9dy.mjs → useRules-CB2_tvd0.mjs} +2 -2
  119. package/dist/useStorage-BORz1d1g.mjs +162 -0
  120. package/dist/{useTheme-B1lUya5B.mjs → useTheme-ClUmdmvT.mjs} +12 -9
  121. package/dist/utilities/index.d.mts +2 -2
  122. package/dist/utilities/index.mjs +7 -3
  123. package/dist/{v0-COaQncfZ.mjs → v0-A4GLojAr.mjs} +2 -1
  124. package/dist/{v0-Bx5Mi7qE.mjs → v0-BU39ZO4s.mjs} +21 -10
  125. package/dist/{v0-WzS4GAIp.d.mts → v0-D7NA38Vu.d.mts} +1 -1
  126. package/dist/{v0-CG9o3on7.mjs → v0-DL9JtgCO.mjs} +1 -1
  127. package/dist/{v0-QIjffi4E.d.mts → v0-DotXXGXU.d.mts} +1 -1
  128. package/dist/{v0-CHK79mZX.mjs → v0-SwedwC_b.mjs} +3 -4
  129. package/dist/{v0-DWZ_N2GG.d.mts → v0-rSxMW8FJ.d.mts} +1 -1
  130. package/dist/{virtual-CrgGA-o6.d.mts → virtual-C-NsLue0.d.mts} +1 -1
  131. package/dist/{virtual-UumJ2BQ9.mjs → virtual-DFvOoeG6.mjs} +2 -1
  132. package/package.json +10 -7
  133. package/dist/adapters-BHD1lc-K.mjs +0 -4
  134. package/dist/adapters-BYLWDmYl.mjs +0 -5
  135. package/dist/adapters-BxrTL8E3.mjs +0 -4
  136. package/dist/adapters-CBgBd7VE.mjs +0 -2
  137. package/dist/adapters-D64TORDu.mjs +0 -2
  138. package/dist/flagsmith-Cu93hUIF.mjs +0 -48
  139. package/dist/launchdarkly-CLGtJCsI.mjs +0 -38
  140. package/dist/posthog-B5s_KGhy.mjs +0 -47
  141. /package/dist/{adapter-BEJXp-ie.d.mts → adapter-BYzU4Ey5.d.mts} +0 -0
  142. /package/dist/{adapter-B6IrPwib.d.mts → adapter-C5F2huij.d.mts} +0 -0
  143. /package/dist/{adapter-BSKx9WQ7.mjs → adapter-CTsckKAR.mjs} +0 -0
  144. /package/dist/{adapter-D8zizQVP.mjs → adapter-bwZo7b5p.mjs} +0 -0
  145. /package/dist/{index-DHmuwBdv.d.mts → index-B9YmdGZ3.d.mts} +0 -0
  146. /package/dist/{index-PvkxPev3.d.mts → index-BgCkepP_.d.mts} +0 -0
  147. /package/dist/{index-BOn0AAue.d.mts → index-CROz1VSV.d.mts} +0 -0
  148. /package/dist/{index-H_hl6-4b.d.mts → index-DXMlYoiQ.d.mts} +0 -0
  149. /package/dist/{index-C6NnIuDv.d.mts → index-DidZKN6S.d.mts} +0 -0
  150. /package/dist/{index-CKKoxA9V.d.mts → index-FtE8MXJ_.d.mts} +0 -0
  151. /package/dist/{memory-BMpJcQjQ.mjs → memory-BJjkQzxs.mjs} +0 -0
  152. /package/dist/{memory-D-JbdKXg.d.mts → memory-DbyZeNjg.d.mts} +0 -0
  153. /package/dist/{toArray-CD2-OxAz.mjs → toArray-C89ZaEuF.mjs} +0 -0
  154. /package/dist/{v0-Dvu8fMl3.mjs → v0-UNyykD27.mjs} +0 -0
@@ -0,0 +1,4077 @@
1
+ import { i as SUPPORTS_MUTATION_OBSERVER, r as SUPPORTS_MATCH_MEDIA, t as IN_BROWSER } from "./globals-C1X-9O7K.mjs";
2
+ import { V0Error, clamp, instanceExists, isArray, isElement, isFunction, isNaN, isNull, isNullOrUndefined, isNumber, isString, isThenable, isUndefined, mergeDeep, 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-D7_hm4e8.mjs";
5
+ import { t as createRegistry } from "./createRegistry-Cb6ftNMU.mjs";
6
+ import { i as createModel } from "./createSelection-BMvXj84C.mjs";
7
+ import { a as useLocale } from "./useLocale-CTwn8uZk.mjs";
8
+ import { t as toArray } from "./toArray-C89ZaEuF.mjs";
9
+ import { n as useEventListener, r as useWindowEventListener } from "./useEventListener-BK_uaXZv.mjs";
10
+ import { J as useResizeObserver, M as ComboboxAdapter, Y as createObserver, b as createInput, nt as useToggleScope, tt as useHydration } from "./useRovingFocus-BdnSUVrD.mjs";
11
+ import { t as createGroup } from "./createGroup-BTG6xcyp.mjs";
12
+ import { a as extractLeaves, t as createDataTable } from "./createDataTable-CBfnCUKU.mjs";
13
+ import { computed, effectScope, isRef, onScopeDispose, onWatcherCleanup, reactive, readonly, ref, shallowReactive, shallowReadonly, shallowRef, toRef, toValue, unref, watch, watchEffect } from "vue";
14
+ //#region src/composables/createCombobox/adapters/server.ts
15
+ /**
16
+ * @module createCombobox/adapters/server
17
+ *
18
+ * @remarks
19
+ * Server-side combobox adapter. All filtering is performed externally.
20
+ * The adapter is a pass-through — it shows all registered items and
21
+ * exposes loading state for the consumer to control.
22
+ *
23
+ * The consumer watches `query` via useComboboxContext() or slot props
24
+ * and provides filtered items reactively.
25
+ */
26
+ var ServerComboboxAdapter = class extends ComboboxAdapter {
27
+ setup(context) {
28
+ const isLoading = shallowRef(false);
29
+ return {
30
+ filtered: toRef(() => {
31
+ return new Set(context.items.value.map((t) => t.id));
32
+ }),
33
+ isLoading,
34
+ isEmpty: toRef(() => context.items.value.length === 0)
35
+ };
36
+ }
37
+ };
38
+ //#endregion
39
+ //#region src/composables/createSortable/index.ts
40
+ /**
41
+ * @module createSortable
42
+ *
43
+ * @see https://0.vuetifyjs.com/composables/data/create-sortable
44
+ *
45
+ * @remarks
46
+ * Headless ordered-list primitive. Extends `createModel` with mutation primitives
47
+ * (`swap`, `reorder`) and an enriched `move:ticket` event on the registry event
48
+ * bus. Honors `disabled` as a real mutation gate at both root and per-ticket scope.
49
+ *
50
+ * Pure logical composable — no DnD, no DOM, no keyboard. Drag-and-drop integration
51
+ * composes with `useDragDrop`; keyboard reorder composes with `useVirtualFocus`.
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * import { createSortable } from '@vuetify/v0'
56
+ * import type { SortableTicketInput } from '@vuetify/v0'
57
+ *
58
+ * interface TaskTicket extends SortableTicketInput {
59
+ * value: { id: number; label: string }
60
+ * }
61
+ *
62
+ * const sortable = createSortable<TaskTicket>()
63
+ * const a = sortable.register({ value: { id: 1, label: 'A' } })
64
+ * const b = sortable.register({ value: { id: 2, label: 'B' } })
65
+ *
66
+ * sortable.move(a.id, 1)
67
+ * sortable.on('move:ticket', ({ ticket, from, to }) => {
68
+ * console.log(`${ticket.value.label} moved ${from} → ${to}`)
69
+ * })
70
+ * ```
71
+ */
72
+ /**
73
+ * Create an ordered-list state primitive.
74
+ *
75
+ * @param options Sortable options.
76
+ * @returns A {@link SortableContext} that extends {@link ModelContext}.
77
+ *
78
+ * @example
79
+ * ```ts
80
+ * const sortable = createSortable<Todo>()
81
+ * sortable.onboard(todos.value.map(value => ({ value })))
82
+ * ```
83
+ */
84
+ function createSortable(_options = {}) {
85
+ const { disabled, ...options } = _options;
86
+ const model = createModel({
87
+ ...options,
88
+ events: true,
89
+ disabled
90
+ });
91
+ const logger = useLogger();
92
+ function move(id, toIndex) {
93
+ if (toValue(disabled)) return void 0;
94
+ const ticket = model.get(id);
95
+ if (!ticket) return void 0;
96
+ if (toValue(ticket.disabled)) return void 0;
97
+ const from = ticket.index;
98
+ const result = model.move(id, toIndex);
99
+ if (result && result.index !== from) model.emit("move:ticket", {
100
+ ticket: result,
101
+ from,
102
+ to: result.index
103
+ });
104
+ return result;
105
+ }
106
+ function swap(a, b) {
107
+ if (toValue(disabled)) return;
108
+ if (a === b) return;
109
+ const ta = model.get(a);
110
+ const tb = model.get(b);
111
+ if (!ta || !tb) return;
112
+ if (toValue(ta.disabled) || toValue(tb.disabled)) return;
113
+ const indexA = ta.index;
114
+ const indexB = tb.index;
115
+ model.batch(() => {
116
+ const ra = model.move(a, indexB);
117
+ if (ra && ra.index !== indexA) model.emit("move:ticket", {
118
+ ticket: ra,
119
+ from: indexA,
120
+ to: ra.index
121
+ });
122
+ const rb = model.move(b, indexA);
123
+ if (rb && rb.index !== indexB) model.emit("move:ticket", {
124
+ ticket: rb,
125
+ from: indexB,
126
+ to: rb.index
127
+ });
128
+ });
129
+ }
130
+ function reorder(ids) {
131
+ if (toValue(disabled)) return;
132
+ const currentSize = model.size;
133
+ if (ids.length !== currentSize) {
134
+ logger.warn(`Reorder expected ${currentSize} ids, got ${ids.length}.`);
135
+ return;
136
+ }
137
+ const known = new Set(model.keys());
138
+ const seen = /* @__PURE__ */ new Set();
139
+ for (const id of ids) {
140
+ if (!known.has(id)) {
141
+ logger.warn(`Reorder contains unknown id "${String(id)}".`);
142
+ return;
143
+ }
144
+ if (seen.has(id)) {
145
+ logger.warn(`Reorder contains duplicate id "${String(id)}".`);
146
+ return;
147
+ }
148
+ seen.add(id);
149
+ }
150
+ const froms = /* @__PURE__ */ new Map();
151
+ for (const id of ids) {
152
+ const ticket = model.get(id);
153
+ if (ticket) froms.set(id, ticket.index);
154
+ }
155
+ model.batch(() => {
156
+ model.reorder(ids);
157
+ for (const [index, id] of ids.entries()) {
158
+ const from = froms.get(id);
159
+ if (from === index) continue;
160
+ const ticket = model.get(id);
161
+ if (ticket) model.emit("move:ticket", {
162
+ ticket,
163
+ from,
164
+ to: index
165
+ });
166
+ }
167
+ });
168
+ }
169
+ return {
170
+ ...model,
171
+ move,
172
+ swap,
173
+ reorder,
174
+ get size() {
175
+ return model.size;
176
+ }
177
+ };
178
+ }
179
+ //#endregion
180
+ //#region src/composables/createDataGrid/editing.ts
181
+ /**
182
+ * @module createDataGrid/editing
183
+ *
184
+ * @remarks
185
+ * Cell editing state management. Tracks active cell, validation errors,
186
+ * and dirty (staged but uncommitted) edits. Does not mutate source data —
187
+ * commit fires a callback for the consumer to handle.
188
+ *
189
+ * When a `registry` is provided, editing state for rows that get
190
+ * unregistered (or wiped via `clear`) is pruned automatically so consumers
191
+ * can never observe `active` pointing at a phantom row or stale entries
192
+ * lingering in `dirty`.
193
+ *
194
+ * @internal Consumed only by `createDataGrid`. This is a fully decoupled 2D
195
+ * cell-edit primitive — it imports nothing from `createDataGrid` /
196
+ * `createDataTable` (only `#v0/utilities`, `#v0/types`, and Vue), so it is a
197
+ * latent composable. It is parameterized over a row type `T` (defaulting to
198
+ * `unknown`), but a type parameter is not an import — the primitive stays
199
+ * fully decoupled from the grid's concrete ticket types and deliberately
200
+ * row-agnostic: `T` is intentionally unconstrained, so do not narrow it to
201
+ * the grid's `Record<string, unknown>` when promoting. Promote it to a
202
+ * standalone `createCellEditing/`
203
+ * composable the moment a second consumer appears (e.g. an editable
204
+ * `createDataTable`). See `.claude/rules/composables.md` §"Sub-modules:
205
+ * inline, private sibling, or promote".
206
+ */
207
+ /**
208
+ * Creates cell editing state for a data grid.
209
+ *
210
+ * @param options Cell editing configuration including columns and commit callback
211
+ * @returns Cell editing state and controls
212
+ *
213
+ * @example
214
+ * ```ts
215
+ * const editing = createCellEditing({
216
+ * columns: [{ id: 'name', editable: true }],
217
+ * registry: table,
218
+ * onEdit (row, column, value) { ... },
219
+ * })
220
+ *
221
+ * editing.edit(1, 'name')
222
+ * editing.commit('Alice')
223
+ * ```
224
+ */
225
+ function createCellEditing(options) {
226
+ const { columns, onEdit, lookup, registry } = options;
227
+ function resolveColumn(column) {
228
+ for (const col of toValue(columns)) if (col.id === column) return col;
229
+ }
230
+ const active = shallowRef(null);
231
+ const error = shallowRef(null);
232
+ const dirty = shallowReactive(/* @__PURE__ */ new Map());
233
+ function edit(row, column) {
234
+ const col = resolveColumn(column);
235
+ if (!col) return;
236
+ if (/* @__PURE__ */ isFunction(col.editable)) {
237
+ const item = lookup?.(row);
238
+ if (/* @__PURE__ */ isUndefined(item) || !col.editable(item)) return;
239
+ } else if (col.editable !== true) return;
240
+ error.value = null;
241
+ active.value = {
242
+ row,
243
+ column
244
+ };
245
+ }
246
+ function commit(value) {
247
+ const cell = active.value;
248
+ if (!cell) return;
249
+ const col = resolveColumn(cell.column);
250
+ if (col?.validate) {
251
+ const result = col.validate(value, lookup?.(cell.row));
252
+ if (result !== true && /* @__PURE__ */ isString(result) && result.length > 0) {
253
+ error.value = result;
254
+ return;
255
+ }
256
+ }
257
+ const entry = dirty.get(cell.row);
258
+ if (entry) {
259
+ entry.delete(cell.column);
260
+ if (entry.size === 0) dirty.delete(cell.row);
261
+ }
262
+ error.value = null;
263
+ active.value = null;
264
+ onEdit?.(cell.row, cell.column, value);
265
+ }
266
+ function cancel() {
267
+ const cell = active.value;
268
+ if (cell) {
269
+ const entry = dirty.get(cell.row);
270
+ if (entry) {
271
+ entry.delete(cell.column);
272
+ if (entry.size === 0) dirty.delete(cell.row);
273
+ }
274
+ }
275
+ error.value = null;
276
+ active.value = null;
277
+ }
278
+ function onUnregister(data) {
279
+ const ticket = data;
280
+ if (active.value?.row === ticket.id) {
281
+ active.value = null;
282
+ error.value = null;
283
+ }
284
+ dirty.delete(ticket.id);
285
+ }
286
+ function onClear() {
287
+ active.value = null;
288
+ error.value = null;
289
+ dirty.clear();
290
+ }
291
+ if (registry) {
292
+ registry.on("unregister:ticket", onUnregister);
293
+ registry.on("clear:registry", onClear);
294
+ onScopeDispose(() => {
295
+ registry.off("unregister:ticket", onUnregister);
296
+ registry.off("clear:registry", onClear);
297
+ }, true);
298
+ }
299
+ return {
300
+ active,
301
+ edit,
302
+ commit,
303
+ cancel,
304
+ error,
305
+ dirty
306
+ };
307
+ }
308
+ //#endregion
309
+ //#region src/composables/createDataGrid/layout.ts
310
+ /**
311
+ * @module createDataGrid/layout
312
+ *
313
+ * @remarks
314
+ * Manages column layout state for data grids: sizing (percentages 0-100),
315
+ * pinning (left/right/scrollable regions), delta-based resizing compatible
316
+ * with the Splitter two-panel model, column reordering, and visibility.
317
+ *
318
+ * Column existence, display order, AND grid layout config (size, pin,
319
+ * min/max, resizable, reorderable) all live on the table's columns registry
320
+ * (passed in as the only argument). `createRegistry` preserves the extra
321
+ * grid fields on each ticket, so layout reads its config straight off the
322
+ * registered ticket — there is no separate defs array. Columns onboarded
323
+ * before OR after the layout is built are picked up identically: an initial
324
+ * loop seeds the already-registered set and a `register:ticket` handler seeds
325
+ * the rest. Removed columns have their pin/size/visibility state pruned via
326
+ * unregister events.
327
+ *
328
+ * Sizing uses percentages so it is compatible with the Splitter component.
329
+ * Offsets are computed per-region (left, scrollable, right) independently
330
+ * and over visible columns only — hidden columns are excluded from the
331
+ * render set without auto-redistribution (headless: the consumer rebalances
332
+ * via `distribute()` or CSS).
333
+ *
334
+ * @internal Private sibling of `createDataGrid`, and intentionally NOT a
335
+ * promotion candidate: it is bound to the table's `DataTableColumnTicket`
336
+ * types, so it is parent-specific glue rather than a reusable primitive. See
337
+ * `.claude/rules/composables.md` §"Sub-modules: inline, private sibling, or
338
+ * promote".
339
+ */
340
+ const DEFAULT_EXTRAS = {
341
+ minSize: 2,
342
+ maxSize: 100,
343
+ resizable: true,
344
+ reorderable: true
345
+ };
346
+ function offsets(cols, reverse = false) {
347
+ let offset = 0;
348
+ const start = reverse ? cols.length - 1 : 0;
349
+ const step = reverse ? -1 : 1;
350
+ const end = reverse ? -1 : cols.length;
351
+ for (let i = start; i !== end; i += step) {
352
+ cols[i].offset = offset;
353
+ offset += cols[i].size;
354
+ }
355
+ }
356
+ /**
357
+ * Creates a column layout manager for a data grid.
358
+ *
359
+ * Reads column existence, display order, AND grid layout config (size, pin,
360
+ * min/max, resizable, reorderable) from the table's columns registry. The
361
+ * grid fields ride on each ticket — `createRegistry` preserves them — so
362
+ * there is no separate defs array. Visibility is layout-local view state
363
+ * (not a ticket field) kept here and reconciled via register / unregister /
364
+ * clear events.
365
+ *
366
+ * Generic over the registry's input/output ticket types so the caller's
367
+ * concrete grid column ticket (`DataGridColumnTicket`) flows through. Layout
368
+ * only ever reads from the registry (`values`, `keys`, `on`, `reorder`,
369
+ * `move`, `lookup`) — it never registers — so the output ticket need only be
370
+ * readable as a {@link GridColumnDef}.
371
+ *
372
+ * @param cols The table's columns registry — source of truth for which
373
+ * columns exist, in what order, and their layout config
374
+ * @returns Column layout state and mutation methods
375
+ */
376
+ function createColumnLayout(cols) {
377
+ const logger = useLogger();
378
+ const configs = /* @__PURE__ */ new Map();
379
+ const group = createGroup({ multiple: true });
380
+ const sizes = shallowReactive(/* @__PURE__ */ new Map());
381
+ const hidden = shallowReactive(/* @__PURE__ */ new Set());
382
+ const order = [];
383
+ function record(id) {
384
+ if (!order.includes(id)) order.push(id);
385
+ }
386
+ /** Even share over the currently-registered leaves: explicit sizes are
387
+ * honored, unsized columns split the remaining percentage. */
388
+ function share(id) {
389
+ const leaves = /* @__PURE__ */ extractLeaves(cols.values());
390
+ let used = 0;
391
+ let unsized = 0;
392
+ for (const leaf of leaves) {
393
+ const size = configs.get(String(leaf.id))?.size;
394
+ if (/* @__PURE__ */ isUndefined(size)) unsized++;
395
+ else used += size;
396
+ }
397
+ const own = configs.get(id)?.size;
398
+ if (!/* @__PURE__ */ isUndefined(own)) return own;
399
+ const remainder = Math.max(0, 100 - used);
400
+ return unsized > 0 ? remainder / unsized : 0;
401
+ }
402
+ function seedLeaf(leaf) {
403
+ const id = String(leaf.id);
404
+ if (!configs.has(id)) configs.set(id, {
405
+ size: leaf.size,
406
+ minSize: leaf.minSize ?? DEFAULT_EXTRAS.minSize,
407
+ maxSize: leaf.maxSize ?? DEFAULT_EXTRAS.maxSize,
408
+ resizable: leaf.resizable ?? DEFAULT_EXTRAS.resizable,
409
+ reorderable: leaf.reorderable ?? DEFAULT_EXTRAS.reorderable,
410
+ pinned: leaf.pinned ?? false
411
+ });
412
+ if (!group.has(id)) group.register({
413
+ id,
414
+ value: id
415
+ });
416
+ if (!sizes.has(id)) sizes.set(id, share(id));
417
+ for (const other of /* @__PURE__ */ extractLeaves(cols.values())) {
418
+ const otherId = String(other.id);
419
+ if (configs.has(otherId) && /* @__PURE__ */ isUndefined(configs.get(otherId)?.size)) sizes.set(otherId, share(otherId));
420
+ }
421
+ const config = configs.get(id);
422
+ if (config.pinned === "left") group.select(id);
423
+ else if (config.pinned === "right") group.mix(id);
424
+ }
425
+ function dropLeaf(id) {
426
+ group.unregister(id);
427
+ configs.delete(id);
428
+ sizes.delete(id);
429
+ hidden.delete(id);
430
+ }
431
+ function leavesOf(ticket) {
432
+ return /* @__PURE__ */ extractLeaves([ticket]);
433
+ }
434
+ for (const ticket of cols.values()) {
435
+ record(String(ticket.id));
436
+ for (const leaf of leavesOf(ticket)) seedLeaf(leaf);
437
+ }
438
+ cols.on("register:ticket", (ticket) => {
439
+ record(String(ticket.id));
440
+ for (const leaf of leavesOf(ticket)) seedLeaf(leaf);
441
+ });
442
+ cols.on("unregister:ticket", (ticket) => {
443
+ const top = order.indexOf(String(ticket.id));
444
+ if (top !== -1) order.splice(top, 1);
445
+ for (const leaf of leavesOf(ticket)) dropLeaf(String(leaf.id));
446
+ });
447
+ cols.on("clear:registry", () => {
448
+ group.clear();
449
+ configs.clear();
450
+ sizes.clear();
451
+ hidden.clear();
452
+ order.length = 0;
453
+ });
454
+ function position(id) {
455
+ if (group.selectedIds.has(id)) return "left";
456
+ if (group.mixed(id)) return "right";
457
+ return false;
458
+ }
459
+ function resolve(includeHidden) {
460
+ const resolved = [];
461
+ let index = 0;
462
+ for (const ticket of /* @__PURE__ */ extractLeaves(cols.values())) {
463
+ const id = String(ticket.id);
464
+ const visible = !hidden.has(id);
465
+ if (!includeHidden && !visible) continue;
466
+ const meta = configs.get(id) ?? DEFAULT_EXTRAS;
467
+ resolved.push({
468
+ id,
469
+ index: index++,
470
+ size: sizes.get(id) ?? 0,
471
+ offset: 0,
472
+ pinned: position(id),
473
+ resizable: meta.resizable,
474
+ reorderable: meta.reorderable,
475
+ minSize: meta.minSize,
476
+ maxSize: meta.maxSize,
477
+ visible
478
+ });
479
+ }
480
+ return resolved;
481
+ }
482
+ function split(resolved) {
483
+ const left = [];
484
+ const scrollable = [];
485
+ const right = [];
486
+ for (const col of resolved) if (col.pinned === "left") left.push(col);
487
+ else if (col.pinned === "right") right.push(col);
488
+ else scrollable.push(col);
489
+ offsets(left);
490
+ offsets(scrollable);
491
+ offsets(right, true);
492
+ return {
493
+ left,
494
+ scrollable,
495
+ right
496
+ };
497
+ }
498
+ const pinned = computed(() => split(resolve(false)));
499
+ const columns = computed(() => {
500
+ const { left, scrollable, right } = pinned.value;
501
+ return [
502
+ ...left,
503
+ ...scrollable,
504
+ ...right
505
+ ];
506
+ });
507
+ const allColumns = computed(() => resolve(true));
508
+ function pin(id, pos) {
509
+ if (!group.has(id)) return;
510
+ group.unselect(id);
511
+ group.unmix(id);
512
+ if (pos === "left") group.select(id);
513
+ else if (pos === "right") group.mix(id);
514
+ }
515
+ function resize(id, delta) {
516
+ const { left, scrollable, right } = pinned.value;
517
+ const all = [
518
+ ...left,
519
+ ...scrollable,
520
+ ...right
521
+ ];
522
+ const target = all.find((c) => c.id === id);
523
+ if (!target || !target.resizable) return;
524
+ const allIndex = all.findIndex((c) => c.id === id);
525
+ if (allIndex === -1 || allIndex === all.length - 1) return;
526
+ const neighbor = all[allIndex + 1];
527
+ const total = target.size + neighbor.size;
528
+ const lower = Math.max(target.minSize, total - neighbor.maxSize);
529
+ const upper = Math.min(target.maxSize, total - neighbor.minSize);
530
+ const clamped = /* @__PURE__ */ clamp(target.size + delta, lower, upper);
531
+ sizes.set(id, clamped);
532
+ sizes.set(neighbor.id, total - clamped);
533
+ }
534
+ function reorder(from, to) {
535
+ if (from === to) return;
536
+ const tops = [...cols.values()];
537
+ const ids = [];
538
+ const leafToTop = [];
539
+ for (const [top, ticket] of tops.entries()) for (const leaf of /* @__PURE__ */ extractLeaves([ticket])) {
540
+ ids.push(String(leaf.id));
541
+ leafToTop.push(top);
542
+ }
543
+ const visible = ids.filter((id) => !hidden.has(id));
544
+ const fromId = visible[from];
545
+ const toId = visible[to];
546
+ if (/* @__PURE__ */ isUndefined(fromId) || /* @__PURE__ */ isUndefined(toId)) return;
547
+ const fromTop = leafToTop[ids.indexOf(fromId)];
548
+ const toTop = leafToTop[ids.indexOf(toId)];
549
+ if (/* @__PURE__ */ isUndefined(fromTop) || /* @__PURE__ */ isUndefined(toTop) || fromTop === toTop) return;
550
+ if (tops[fromTop]?.reorderable === false) return;
551
+ const id = cols.lookup(fromTop);
552
+ if (/* @__PURE__ */ isUndefined(id)) return;
553
+ cols.move(id, toTop);
554
+ }
555
+ function distribute(incoming) {
556
+ const tickets = /* @__PURE__ */ extractLeaves(cols.values());
557
+ if (incoming.length !== tickets.length) {
558
+ logger.warn(`createDataGrid: distribute() expected ${tickets.length} sizes but received ${incoming.length}; ignoring.`);
559
+ return;
560
+ }
561
+ const sum = incoming.reduce((acc, value) => acc + value, 0);
562
+ if (sum <= 0) {
563
+ logger.warn(`createDataGrid: distribute() received sizes summing to ${sum}; expected a positive total.`);
564
+ return;
565
+ }
566
+ const metas = tickets.map((ticket) => configs.get(String(ticket.id)) ?? DEFAULT_EXTRAS);
567
+ let current = incoming.map((value, i) => /* @__PURE__ */ clamp(value / sum * 100, metas[i].minSize, metas[i].maxSize));
568
+ let residual = 100 - current.reduce((acc, value) => acc + value, 0);
569
+ const max = tickets.length * 2;
570
+ for (let pass = 0; pass < max && Math.abs(residual) >= 1e-6; pass++) {
571
+ const room = current.map((value, i) => residual > 0 ? metas[i].maxSize - value : value - metas[i].minSize);
572
+ const available = room.reduce((acc, value) => acc + Math.max(0, value), 0);
573
+ if (available <= 0) break;
574
+ current = current.map((value, i) => room[i] <= 0 ? value : value + residual * (room[i] / available));
575
+ residual = 100 - current.reduce((acc, value) => acc + value, 0);
576
+ }
577
+ if (Math.abs(residual) >= 1e-6) logger.warn(`createDataGrid: distribute() could not reach 100 within min/max constraints (off by ${residual.toFixed(2)})`);
578
+ for (const [i, ticket] of tickets.entries()) sizes.set(String(ticket.id), current[i]);
579
+ }
580
+ function show(id) {
581
+ hidden.delete(id);
582
+ }
583
+ function hide(id) {
584
+ hidden.add(id);
585
+ }
586
+ function toggle(id) {
587
+ if (hidden.has(id)) hidden.delete(id);
588
+ else hidden.add(id);
589
+ }
590
+ function reset() {
591
+ for (const ticket of /* @__PURE__ */ extractLeaves(cols.values())) {
592
+ const id = String(ticket.id);
593
+ sizes.set(id, share(id));
594
+ }
595
+ cols.reorder([...order]);
596
+ hidden.clear();
597
+ for (const ticket of /* @__PURE__ */ extractLeaves(cols.values())) {
598
+ const id = String(ticket.id);
599
+ group.unselect(id);
600
+ group.unmix(id);
601
+ }
602
+ for (const [id, config] of configs) if (config.pinned === "left") group.select(id);
603
+ else if (config.pinned === "right") group.mix(id);
604
+ }
605
+ return {
606
+ pinned,
607
+ columns,
608
+ all: allColumns,
609
+ pin,
610
+ resize,
611
+ reorder,
612
+ distribute,
613
+ show,
614
+ hide,
615
+ toggle,
616
+ reset
617
+ };
618
+ }
619
+ //#endregion
620
+ //#region src/composables/createDataGrid/spanning.ts
621
+ /**
622
+ * @module createDataGrid/spanning
623
+ *
624
+ * @remarks
625
+ * Computes a row span map from visible items. For each cell, determines
626
+ * rowSpan and whether it's hidden (covered by a span from a previous row).
627
+ * Spans do not cross page boundaries. Each row is addressed by a key: either a
628
+ * `key(item, index)` projection (the grid passes the registry ticket id) or,
629
+ * absent that, the row's `itemKey` value. Rows whose resolved key is
630
+ * `undefined` cannot be addressed in the resulting map and are skipped.
631
+ *
632
+ * @internal Consumed only by `createDataGrid`. Fully decoupled (items ref +
633
+ * columns + key + span fn — no grid/table types), so it is a latent composable.
634
+ * Promote it to a standalone `createRowSpanning/` composable on a second
635
+ * consumer. See `.claude/rules/composables.md` §"Sub-modules: inline, private
636
+ * sibling, or promote".
637
+ */
638
+ /**
639
+ * Computes a row span map from visible items.
640
+ *
641
+ * @param options Row spanning configuration
642
+ * @returns A computed map of item ID to column to SpanEntry
643
+ *
644
+ * @example
645
+ * ```ts
646
+ * const spans = createRowSpanning({
647
+ * items: computed(() => grid.items.value),
648
+ * columns: ['group', 'ticker', 'value'],
649
+ * span: (item, column) => column === 'group' ? 3 : 1,
650
+ * })
651
+ *
652
+ * spans.value.get(id)?.get('group') // { rowSpan: 3, hidden: false }
653
+ * ```
654
+ */
655
+ function createRowSpanning(options) {
656
+ const { items, columns, key, itemKey = "id", span } = options;
657
+ return computed(() => {
658
+ const result = /* @__PURE__ */ new Map();
659
+ if (!span) return result;
660
+ const list = items.value;
661
+ const resolved = toValue(columns);
662
+ const covered = Array.from({ length: resolved.length }).fill(0);
663
+ for (let row = 0; row < list.length; row++) {
664
+ const item = list[row];
665
+ const id = key ? key(item, row) : item[itemKey];
666
+ if (/* @__PURE__ */ isUndefined(id)) continue;
667
+ const cells = /* @__PURE__ */ new Map();
668
+ for (const [col, column] of resolved.entries()) if (covered[col] > 0) {
669
+ cells.set(column, {
670
+ rowSpan: 1,
671
+ hidden: true
672
+ });
673
+ covered[col]--;
674
+ } else {
675
+ const raw = span(item, column);
676
+ const rowSpan = Math.min(Math.max(1, /* @__PURE__ */ isNaN(raw) ? 1 : raw), list.length - row);
677
+ cells.set(column, {
678
+ rowSpan,
679
+ hidden: false
680
+ });
681
+ if (rowSpan > 1) covered[col] = rowSpan - 1;
682
+ }
683
+ result.set(id, cells);
684
+ }
685
+ return result;
686
+ });
687
+ }
688
+ //#endregion
689
+ //#region src/composables/createDataGrid/index.ts
690
+ /**
691
+ * @module createDataGrid
692
+ *
693
+ * @see https://0.vuetifyjs.com/composables/data/create-data-grid
694
+ *
695
+ * @remarks
696
+ * Main factory that wires together column layout, cell editing, row ordering,
697
+ * and row spanning on top of a createDataTable pipeline. Row ordering is
698
+ * provided by a createSortable instance synced to the table's row registry
699
+ * via register / unregister events; the ordered ids are applied post-sort,
700
+ * pre-pagination by overriding the `items` projection.
701
+ *
702
+ * Rows are registered through the inherited registry surface (`register`,
703
+ * `onboard`, `unregister`, `clear`) — they are not passed as an `items`
704
+ * option to the factory.
705
+ *
706
+ * Follows the trinity pattern for dependency injection.
707
+ *
708
+ * @example
709
+ * ```ts
710
+ * const grid = createDataGrid()
711
+ *
712
+ * grid.columns.onboard([
713
+ * { id: 'name', sortable: true },
714
+ * { id: 'progress', editable: true },
715
+ * ])
716
+ *
717
+ * grid.onboard(rows.map(value => ({ id: value.id, value })))
718
+ * grid.layout.pin('name', 'left')
719
+ * grid.editing.edit(row.id, 'progress')
720
+ * ```
721
+ */
722
+ /**
723
+ * Creates a data grid instance with layout, editing, row ordering, and spanning
724
+ * layered on top of the createDataTable pipeline.
725
+ *
726
+ * Rows live on the inherited registry surface — register them with
727
+ * `grid.onboard(rows.map(value => ({ id: value.id, value })))` or
728
+ * `grid.register({ id, value })`.
729
+ *
730
+ * @param options Data grid options
731
+ * @returns Data grid context
732
+ *
733
+ * @example
734
+ * ```ts
735
+ * const grid = createDataGrid({ pagination: { initial: 1 } })
736
+ *
737
+ * grid.columns.onboard([
738
+ * { id: 'name', sortable: true },
739
+ * { id: 'progress', editable: true, validate: v => Number(v) >= 0 || 'must be positive' },
740
+ * ])
741
+ *
742
+ * grid.onboard(rows.map(value => ({ id: value.id, value })))
743
+ * grid.layout.pin('name', 'left')
744
+ * grid.rows.move(rowId, 0)
745
+ * grid.editing.edit(row.id, 'progress')
746
+ * ```
747
+ */
748
+ function createDataGrid(_options = {}) {
749
+ const { editing: editingOptions, preserveRowOrder = false, rowSpanning, ...options } = _options;
750
+ const table = createDataTable(options);
751
+ const sortable = createSortable({ reactive: true });
752
+ const dirty = shallowRef(false);
753
+ table.on("register:ticket", (ticket) => {
754
+ sortable.register({
755
+ id: ticket.id,
756
+ value: ticket.id
757
+ });
758
+ });
759
+ table.on("unregister:ticket", (ticket) => {
760
+ sortable.unregister(ticket.id);
761
+ });
762
+ table.on("clear:registry", () => {
763
+ sortable.clear();
764
+ dirty.value = false;
765
+ });
766
+ const order = toRef(() => sortable.keys());
767
+ const sortedIds = computed(() => {
768
+ const buckets = /* @__PURE__ */ new Map();
769
+ for (const ticket of table.values()) {
770
+ const value = ticket.value;
771
+ const queue = buckets.get(value);
772
+ if (queue) queue.push(ticket.id);
773
+ else buckets.set(value, [ticket.id]);
774
+ }
775
+ const ids = [];
776
+ for (const value of table.sortedItems.value) {
777
+ const id = buckets.get(value)?.shift();
778
+ if (!/* @__PURE__ */ isUndefined(id)) ids.push(id);
779
+ }
780
+ return ids;
781
+ });
782
+ function move(id, toIndex) {
783
+ if (!dirty.value) {
784
+ const sorted = sortedIds.value;
785
+ const seen = new Set(sorted);
786
+ const rest = sortable.keys().filter((key) => !seen.has(key));
787
+ sortable.reorder([...sorted, ...rest]);
788
+ }
789
+ sortable.move(id, toIndex);
790
+ dirty.value = true;
791
+ }
792
+ function reset() {
793
+ if (sortable.size > 0) sortable.reorder(table.keys());
794
+ dirty.value = false;
795
+ }
796
+ useToggleScope(() => !toValue(preserveRowOrder), () => {
797
+ watch(table.sort.columns, reset, { flush: "sync" });
798
+ });
799
+ const page = computed(() => {
800
+ if (!dirty.value) {
801
+ const start = table.pagination.pageStart.value;
802
+ return {
803
+ items: table.items.value,
804
+ ids: table.total.value > table.sortedItems.value.length ? sortedIds.value : sortedIds.value.slice(start, start + table.items.value.length)
805
+ };
806
+ }
807
+ const sorted = table.sortedItems.value;
808
+ const ids = sortedIds.value;
809
+ const byId = /* @__PURE__ */ new Map();
810
+ for (let i = 0; i < sorted.length; i++) byId.set(ids[i], sorted[i]);
811
+ const seen = new Set(order.value);
812
+ const items = [];
813
+ const ordered = [];
814
+ for (const id of order.value) {
815
+ const value = byId.get(id);
816
+ if (!/* @__PURE__ */ isUndefined(value)) {
817
+ items.push(value);
818
+ ordered.push(id);
819
+ }
820
+ }
821
+ for (const [i, element] of sorted.entries()) {
822
+ const id = ids[i];
823
+ if (!seen.has(id)) {
824
+ items.push(element);
825
+ ordered.push(id);
826
+ }
827
+ }
828
+ if (table.total.value > sorted.length) return {
829
+ items,
830
+ ids: ordered
831
+ };
832
+ const start = table.pagination.pageStart.value;
833
+ const stop = table.pagination.pageStop.value;
834
+ return {
835
+ items: items.slice(start, stop),
836
+ ids: ordered.slice(start, stop)
837
+ };
838
+ });
839
+ const pageOrderedItems = toRef(() => page.value.items);
840
+ const layout = createColumnLayout(table.columns);
841
+ function lookup(row) {
842
+ return table.get(row)?.value;
843
+ }
844
+ const editing = createCellEditing({
845
+ columns: () => table.leaves.value.filter((col) => col.editable === true || /* @__PURE__ */ isFunction(col.editable)).map((col) => ({
846
+ id: String(col.id),
847
+ editable: col.editable,
848
+ validate: col.validate
849
+ })),
850
+ lookup,
851
+ registry: table,
852
+ onEdit: editingOptions?.onEdit ? (row, column, value) => {
853
+ const item = lookup(row);
854
+ if (!/* @__PURE__ */ isUndefined(item)) editingOptions.onEdit(row, column, value, item);
855
+ } : void 0
856
+ });
857
+ const spans = createRowSpanning({
858
+ items: pageOrderedItems,
859
+ columns: () => table.leaves.value.map((col) => col.id),
860
+ key: (_item, index) => page.value.ids[index],
861
+ span(item, column) {
862
+ const col = table.leaves.value.find((leaf) => String(leaf.id) === column);
863
+ if (col && /* @__PURE__ */ isFunction(col.span)) return col.span(item);
864
+ return rowSpanning?.(item, column) ?? 1;
865
+ }
866
+ });
867
+ return {
868
+ ...table,
869
+ items: pageOrderedItems,
870
+ layout,
871
+ rows: {
872
+ order,
873
+ move,
874
+ reset
875
+ },
876
+ editing,
877
+ spans,
878
+ get size() {
879
+ return table.size;
880
+ }
881
+ };
882
+ }
883
+ /**
884
+ * Creates a data grid context with dependency injection support.
885
+ *
886
+ * @param options Data grid context options including namespace
887
+ * @returns A trinity tuple: [useDataGridContext, provideDataGridContext, defaultContext]
888
+ *
889
+ * @example
890
+ * ```ts
891
+ * const [useDataGrid, provideDataGrid, grid] = createDataGridContext({
892
+ * namespace: 'v0:projects',
893
+ * })
894
+ *
895
+ * grid.columns.onboard(columns)
896
+ * grid.onboard(rows.map(value => ({ id: value.id, value })))
897
+ * provideDataGrid()
898
+ * const ctx = useDataGrid()
899
+ * ```
900
+ */
901
+ function createDataGridContext(_options = {}) {
902
+ const { namespace = "v0:data-grid", ...options } = _options;
903
+ const [useDataGridContext, _provideDataGridContext] = createContext(namespace);
904
+ const context = createDataGrid(options);
905
+ function provideDataGridContext(_context = context, app) {
906
+ return _provideDataGridContext(_context, app);
907
+ }
908
+ return createTrinity(useDataGridContext, provideDataGridContext, context);
909
+ }
910
+ /**
911
+ * Returns the current data grid context from dependency injection.
912
+ *
913
+ * @param namespace The namespace for the data grid context. @default 'v0:data-grid'
914
+ * @returns The current data grid context
915
+ *
916
+ * @example
917
+ * ```ts
918
+ * const grid = useDataGrid<Project>()
919
+ *
920
+ * grid.layout.pin('id', 'left')
921
+ * grid.rows.reset()
922
+ * ```
923
+ */
924
+ function useDataGrid(namespace = "v0:data-grid") {
925
+ return useContext(namespace);
926
+ }
927
+ //#endregion
928
+ //#region src/composables/createKanban/index.ts
929
+ /**
930
+ * @module createKanban
931
+ *
932
+ * @see https://0.vuetifyjs.com/composables/data/create-kanban
933
+ *
934
+ * @remarks
935
+ * Headless data-flow management for two-level sortable boards. Wraps a column
936
+ * sortable; each column ticket carries its own inner sortable on `column.items`.
937
+ * Adds a `transfer` primitive for cross-column moves with reused `disabled`
938
+ * gating and a destination-side `accept` predicate. Pure logic — no DnD, no DOM.
939
+ *
940
+ * @example
941
+ * ```ts
942
+ * import { createKanban } from '@vuetify/v0'
943
+ * import type { SortableTicketInput } from '@vuetify/v0'
944
+ *
945
+ * interface CardTicket extends SortableTicketInput {
946
+ * value: { title: string }
947
+ * }
948
+ *
949
+ * const kanban = createKanban<CardTicket>()
950
+ *
951
+ * const todo = kanban.columns.register({ value: { title: 'Todo' } })
952
+ * const done = kanban.columns.register({ value: { title: 'Done' } })
953
+ *
954
+ * const card = todo.items.register({ value: { title: 'Write spec' } })
955
+ *
956
+ * kanban.on('transfer:ticket', ({ ticket, from, to }) => {
957
+ * console.log(ticket.value.title, from, '→', to)
958
+ * })
959
+ *
960
+ * kanban.transfer(card.id, done.id, 0)
961
+ * ```
962
+ */
963
+ /**
964
+ * Create a headless two-level sortable orchestrator.
965
+ *
966
+ * @param options Kanban options (currently only `disabled`).
967
+ * @returns A {@link KanbanContext}.
968
+ *
969
+ * @example
970
+ * ```ts
971
+ * import { createKanban } from '@vuetify/v0'
972
+ * import type { KanbanColumnTicketInput, SortableTicketInput } from '@vuetify/v0'
973
+ *
974
+ * interface CardTicket extends SortableTicketInput {
975
+ * value: { title: string }
976
+ * }
977
+ *
978
+ * interface BoardColumn extends KanbanColumnTicketInput<CardTicket> {
979
+ * value: { title: string }
980
+ * }
981
+ *
982
+ * const kanban = createKanban<CardTicket, BoardColumn>()
983
+ *
984
+ * const todo = kanban.columns.register({ value: { title: 'Todo' } })
985
+ * const review = kanban.columns.register({
986
+ * value: { title: 'Review' },
987
+ * accept: card => card.value.title.length > 0,
988
+ * })
989
+ *
990
+ * const spec = todo.items.register({ value: { title: 'Write spec' } })
991
+ * kanban.transfer(spec.id, review.id, 0)
992
+ * ```
993
+ */
994
+ function createKanban(_options = {}) {
995
+ const { disabled, ...options } = _options;
996
+ const logger = useLogger();
997
+ const _columns = createSortable({
998
+ ...options,
999
+ disabled
1000
+ });
1001
+ const lookup = createRegistry({ events: true });
1002
+ const bus = createRegistry({ events: true });
1003
+ const on = bus.on;
1004
+ const off = bus.off;
1005
+ const columns = {
1006
+ ..._columns,
1007
+ register(registration) {
1008
+ let ticket;
1009
+ const items = createSortable({ disabled: () => !!toValue(disabled) || !!toValue(ticket?.disabled) });
1010
+ items.on("register:ticket", (t) => {
1011
+ lookup.upsert(t.id, { value: ticket.id });
1012
+ });
1013
+ items.on("unregister:ticket", (t) => {
1014
+ lookup.get(t.id)?.unregister();
1015
+ });
1016
+ ticket = _columns.register({
1017
+ ...registration,
1018
+ items
1019
+ });
1020
+ return ticket;
1021
+ },
1022
+ onboard(registrations) {
1023
+ return _columns.batch(() => registrations.map((reg) => columns.register(reg)));
1024
+ },
1025
+ upsert(id, patch = {}, event) {
1026
+ return /* @__PURE__ */ isUndefined(_columns.get(id)) ? columns.register({
1027
+ ...patch,
1028
+ id
1029
+ }) : _columns.upsert(id, patch, event);
1030
+ },
1031
+ get size() {
1032
+ return _columns.size;
1033
+ }
1034
+ };
1035
+ function isAccepted(accept, ticket, from, toIndex) {
1036
+ if (/* @__PURE__ */ isUndefined(accept)) return true;
1037
+ try {
1038
+ const result = accept(ticket, from, toIndex);
1039
+ if (/* @__PURE__ */ isThenable(result)) {
1040
+ logger.warn("accept predicate returned a thenable; async predicates are not supported — treating as reject");
1041
+ return false;
1042
+ }
1043
+ return Boolean(result);
1044
+ } catch (error) {
1045
+ logger.error("accept predicate threw; treating as reject", error);
1046
+ return false;
1047
+ }
1048
+ }
1049
+ columns.on("unregister:ticket", (column) => {
1050
+ column.items.dispose();
1051
+ for (const t of lookup.values()) if (t.value === column.id) t.unregister();
1052
+ });
1053
+ function transfer(id, toColumnId, toIndex) {
1054
+ const fromColumnId = lookup.get(id)?.value;
1055
+ if (/* @__PURE__ */ isUndefined(fromColumnId)) {
1056
+ logger.warn(`unknown ticket id "${String(id)}"`);
1057
+ return;
1058
+ }
1059
+ const source = columns.get(fromColumnId);
1060
+ if (!source) {
1061
+ logger.warn(`unknown source column id "${String(fromColumnId)}"`);
1062
+ return;
1063
+ }
1064
+ const destination = columns.get(toColumnId);
1065
+ if (!destination) {
1066
+ logger.warn(`unknown destination column id "${String(toColumnId)}"`);
1067
+ return;
1068
+ }
1069
+ const ticket = source.items.get(id);
1070
+ if (!ticket) {
1071
+ logger.warn(`ticket "${String(id)}" missing from source column "${String(fromColumnId)}"`);
1072
+ return;
1073
+ }
1074
+ const fromIndex = ticket.index;
1075
+ if (toColumnId === fromColumnId) return source.items.move(id, toIndex);
1076
+ if (toValue(disabled) || toValue(source.disabled) || toValue(destination.disabled) || toValue(ticket.disabled)) return;
1077
+ if (!isAccepted(destination.accept, ticket, fromColumnId, toIndex)) return void 0;
1078
+ if (!/* @__PURE__ */ isUndefined(destination.items.get(id))) {
1079
+ logger.warn(`id "${String(id)}" already exists in destination column "${String(toColumnId)}"`);
1080
+ return;
1081
+ }
1082
+ let registered;
1083
+ let moved;
1084
+ destination.items.batch(() => {
1085
+ const [input] = source.items.offboard([id]);
1086
+ if (/* @__PURE__ */ isUndefined(input)) return;
1087
+ registered = destination.items.register({
1088
+ ...input,
1089
+ id
1090
+ });
1091
+ moved = registered.index === toIndex ? registered : destination.items.move(registered.id, toIndex);
1092
+ });
1093
+ const final = moved ?? registered;
1094
+ if (final) bus.emit("transfer:ticket", {
1095
+ ticket: final,
1096
+ from: fromColumnId,
1097
+ to: toColumnId,
1098
+ fromIndex,
1099
+ toIndex: final.index
1100
+ });
1101
+ return final;
1102
+ }
1103
+ return {
1104
+ columns,
1105
+ transfer,
1106
+ on,
1107
+ off
1108
+ };
1109
+ }
1110
+ //#endregion
1111
+ //#region src/composables/createOtp/index.ts
1112
+ /**
1113
+ * @module createOtp
1114
+ *
1115
+ * @see https://0.vuetifyjs.com/composables/forms/create-otp
1116
+ *
1117
+ * @remarks
1118
+ * Headless state for a fixed-length one-time-password / verification-code value.
1119
+ * Owns the joined string, per-character pattern matching, length contract,
1120
+ * completion edge detection with a decisional async `onComplete` hook, and
1121
+ * validation through `createInput`. Rendering, focus, and per-element event
1122
+ * wiring are the consumer's responsibility.
1123
+ *
1124
+ * @example
1125
+ * ```ts
1126
+ * import { createOtp } from '@vuetify/v0'
1127
+ *
1128
+ * const otp = createOtp({
1129
+ * length: 6,
1130
+ * pattern: 'numeric',
1131
+ * onComplete: async value => {
1132
+ * const ok = await verify(value)
1133
+ * return ok
1134
+ * },
1135
+ * })
1136
+ *
1137
+ * otp.write(0, '4')
1138
+ * otp.distribute('12345', 1) // returns 5; value becomes '412345'
1139
+ * otp.isComplete.value // true
1140
+ * ```
1141
+ */
1142
+ const PRESETS = {
1143
+ numeric: /^[0-9]$/,
1144
+ alphanumeric: /^[a-zA-Z0-9]$/,
1145
+ alphabetic: /^[a-zA-Z]$/
1146
+ };
1147
+ const WARNED_PATTERNS = /* @__PURE__ */ new WeakSet();
1148
+ function createOtp(_options = {}) {
1149
+ const { value = shallowRef(""), length = 6, pattern = "numeric", disabled = false, readonly: _readonly = false, onComplete, ...options } = _options;
1150
+ const logger = useLogger();
1151
+ const errorRef = shallowRef(false);
1152
+ const errorMessagesRef = shallowRef(void 0);
1153
+ const input = createInput({
1154
+ ...options,
1155
+ value,
1156
+ disabled,
1157
+ readonly: _readonly,
1158
+ error: errorRef,
1159
+ errorMessages: errorMessagesRef
1160
+ });
1161
+ const lengthRef = toRef(() => toValue(length));
1162
+ let lastCompleted = "";
1163
+ function compile(resolved) {
1164
+ if (/* @__PURE__ */ isString(resolved)) return PRESETS[resolved];
1165
+ if (process.env.NODE_ENV !== "production" && !WARNED_PATTERNS.has(resolved)) {
1166
+ WARNED_PATTERNS.add(resolved);
1167
+ if (resolved.test("aa") || resolved.test("00")) logger.warn("createOtp: pattern matches multi-character input; per-character matching may behave unexpectedly");
1168
+ }
1169
+ return resolved;
1170
+ }
1171
+ function accepts(char) {
1172
+ if (char.length !== 1) return false;
1173
+ return compile(toValue(pattern)).test(char);
1174
+ }
1175
+ function filterAccepted(text) {
1176
+ let out = "";
1177
+ for (const ch of text) if (accepts(ch)) out += ch;
1178
+ return out;
1179
+ }
1180
+ const isPending = shallowRef(false);
1181
+ function isLocked() {
1182
+ return toValue(disabled) || toValue(_readonly) || isPending.value;
1183
+ }
1184
+ function clearRejection() {
1185
+ if (errorRef.value || errorMessagesRef.value) {
1186
+ errorRef.value = false;
1187
+ errorMessagesRef.value = void 0;
1188
+ }
1189
+ }
1190
+ function write(index, char) {
1191
+ if (isLocked()) return;
1192
+ const max = toValue(lengthRef);
1193
+ if (index < 0 || index >= max) return;
1194
+ if (char === "") {
1195
+ clearRejection();
1196
+ value.value = value.value.slice(0, index);
1197
+ return;
1198
+ }
1199
+ const first = char[0];
1200
+ if (!accepts(first)) return;
1201
+ clearRejection();
1202
+ const current = value.value;
1203
+ const head = current.slice(0, index);
1204
+ const tail = current.slice(index + 1);
1205
+ value.value = (head + first + tail).slice(0, max);
1206
+ }
1207
+ function distribute(text, index = 0) {
1208
+ if (isLocked()) return 0;
1209
+ const max = toValue(lengthRef);
1210
+ const start = /* @__PURE__ */ clamp(index, 0, max);
1211
+ const filtered = filterAccepted(text);
1212
+ if (filtered.length === 0) return 0;
1213
+ clearRejection();
1214
+ const head = value.value.slice(0, start);
1215
+ const next = (head + filtered).slice(0, max);
1216
+ value.value = next;
1217
+ return next.length - head.length;
1218
+ }
1219
+ function clear() {
1220
+ if (isLocked()) return;
1221
+ clearRejection();
1222
+ lastCompleted = "";
1223
+ value.value = "";
1224
+ }
1225
+ function fill(text) {
1226
+ if (isLocked()) return;
1227
+ const max = toValue(lengthRef);
1228
+ const filtered = filterAccepted(text).slice(0, max);
1229
+ if (text.length === 0 || filtered.length > 0) clearRejection();
1230
+ value.value = filtered;
1231
+ }
1232
+ function reject() {
1233
+ errorRef.value = true;
1234
+ errorMessagesRef.value = ["Invalid code"];
1235
+ lastCompleted = "";
1236
+ value.value = "";
1237
+ }
1238
+ const isComplete = toRef(() => {
1239
+ const max = toValue(lengthRef);
1240
+ const v = value.value;
1241
+ if (v.length !== max) return false;
1242
+ const re = compile(toValue(pattern));
1243
+ for (const ch of v) if (!re.test(ch)) return false;
1244
+ return true;
1245
+ });
1246
+ watch(value, (next) => {
1247
+ if (!isComplete.value) {
1248
+ lastCompleted = "";
1249
+ return;
1250
+ }
1251
+ if (!onComplete) return;
1252
+ if (isLocked()) return;
1253
+ if (next === lastCompleted) return;
1254
+ lastCompleted = next;
1255
+ let result;
1256
+ try {
1257
+ result = onComplete(next);
1258
+ } catch (error) {
1259
+ logger.warn("createOtp: onComplete threw", error);
1260
+ reject();
1261
+ return;
1262
+ }
1263
+ if (/* @__PURE__ */ isThenable(result)) {
1264
+ isPending.value = true;
1265
+ result.then((ok) => {
1266
+ isPending.value = false;
1267
+ if (ok === false) reject();
1268
+ }, (error) => {
1269
+ isPending.value = false;
1270
+ logger.warn("createOtp: onComplete rejected", error);
1271
+ reject();
1272
+ });
1273
+ return;
1274
+ }
1275
+ if (result === false) reject();
1276
+ });
1277
+ return {
1278
+ value,
1279
+ length: lengthRef,
1280
+ input: {
1281
+ ...input,
1282
+ reset() {
1283
+ clearRejection();
1284
+ lastCompleted = "";
1285
+ input.reset();
1286
+ }
1287
+ },
1288
+ isComplete,
1289
+ isValidating: isPending,
1290
+ write,
1291
+ distribute,
1292
+ clear,
1293
+ fill,
1294
+ accepts
1295
+ };
1296
+ }
1297
+ //#endregion
1298
+ //#region src/composables/createTimeline/index.ts
1299
+ /**
1300
+ * @module createTimeline
1301
+ *
1302
+ * @see https://0.vuetifyjs.com/composables/registration/create-timeline
1303
+ *
1304
+ * @remarks
1305
+ * Bounded undo/redo system with overflow management.
1306
+ *
1307
+ * Key features:
1308
+ * - Fixed-size history (default: 10 items)
1309
+ * - Undo/redo stack management
1310
+ * - Overflow queue (preserves oldest items)
1311
+ * - Automatic reindexing after operations
1312
+ * - Perfect for command pattern, history tracking
1313
+ *
1314
+ * Extends createRegistry with temporal navigation capabilities.
1315
+ *
1316
+ * @example
1317
+ * ```ts
1318
+ * import { createTimeline } from '@vuetify/v0'
1319
+ *
1320
+ * const timeline = createTimeline({ size: 5 })
1321
+ * timeline.register({ value: 'action-1' })
1322
+ * timeline.register({ value: 'action-2' })
1323
+ * timeline.undo()
1324
+ * ```
1325
+ */
1326
+ /**
1327
+ * Creates a new timeline instance.
1328
+ *
1329
+ * @param options The options for the timeline instance.
1330
+ * @template Z The type of the timeline ticket.
1331
+ * @template E The type of the timeline context.
1332
+ * @returns A new timeline instance.
1333
+ *
1334
+ * @see https://0.vuetifyjs.com/composables/registration/create-timeline
1335
+ *
1336
+ * @example
1337
+ * ```ts
1338
+ * import { useTimeline } from '@vuetify/v0'
1339
+ *
1340
+ * const timeline = useTimeline({ size: 3 })
1341
+ *
1342
+ * timeline.onboard([{ id: 'one' }, { id: 'two' }, { id: 'three' }])
1343
+ *
1344
+ * console.log(timeline.values()) // [{ id: 'one' }, { id: 'two' }, { id: 'three' }]
1345
+ *
1346
+ * timeline.undo()
1347
+ * console.log(timeline.values()) // [{ id: 'one' }, { id: 'two' }]
1348
+ *
1349
+ * timeline.redo()
1350
+ * console.log(timeline.values()) // [{ id: 'one' }, { id: 'two' }, { id: 'three' }]
1351
+ * ```
1352
+ */
1353
+ function createTimeline(_options = {}) {
1354
+ const { size: _size = 10, ...options } = _options;
1355
+ const size = /* @__PURE__ */ isNaN(_size) ? 10 : Math.max(1, Math.floor(_size));
1356
+ const registry = createRegistry(options);
1357
+ const stack = [];
1358
+ const overflow = [];
1359
+ function register(item) {
1360
+ stack.length = 0;
1361
+ if (registry.size < size) return registry.register({ ...item });
1362
+ const removing = registry.seek("first");
1363
+ if (overflow.length === size) overflow.shift();
1364
+ overflow.push(removing);
1365
+ registry.unregister(removing.id);
1366
+ const ticket = registry.register({ ...item });
1367
+ registry.reindex();
1368
+ return ticket;
1369
+ }
1370
+ function undo() {
1371
+ const item = registry.seek("last");
1372
+ if (!item) return void 0;
1373
+ stack.push(item);
1374
+ registry.unregister(item.id);
1375
+ const restored = overflow.pop();
1376
+ if (restored) {
1377
+ const remaining = registry.values();
1378
+ registry.clear();
1379
+ registry.onboard([restored, ...remaining]);
1380
+ registry.reindex();
1381
+ }
1382
+ return item;
1383
+ }
1384
+ function redo() {
1385
+ if (stack.length === 0) return void 0;
1386
+ const item = stack.pop();
1387
+ if (registry.size >= size) {
1388
+ const oldest = registry.seek("first");
1389
+ if (overflow.length === size) overflow.shift();
1390
+ overflow.push(oldest);
1391
+ registry.unregister(oldest.id);
1392
+ }
1393
+ const ticket = registry.register(item);
1394
+ registry.reindex();
1395
+ return ticket;
1396
+ }
1397
+ return {
1398
+ ...registry,
1399
+ register,
1400
+ undo,
1401
+ redo,
1402
+ get size() {
1403
+ return registry.size;
1404
+ }
1405
+ };
1406
+ }
1407
+ /**
1408
+ * Creates a new timeline context.
1409
+ *
1410
+ * @param options The options for the timeline context.
1411
+ * @template Z The type of the timeline ticket.
1412
+ * @template E The type of the timeline context.
1413
+ * @returns A new timeline context.
1414
+ *
1415
+ * @see https://0.vuetifyjs.com/composables/registration/create-timeline
1416
+ *
1417
+ * @example
1418
+ * ```ts
1419
+ * import { createTimelineContext } from '@vuetify/v0'
1420
+ *
1421
+ * // With default namespace 'v0:timeline'
1422
+ * export const [useTimeline, provideTimeline, context] = createTimelineContext({ size: 5 })
1423
+ *
1424
+ * context.register({ id: 'example' })
1425
+ *
1426
+ * // In a parent component
1427
+ * provideTimeline()
1428
+ *
1429
+ * // In a child component
1430
+ * const timeline = useTimeline()
1431
+ *
1432
+ * console.log(timeline.values()) // [{ id: 'example' }]
1433
+ * ```
1434
+ */
1435
+ function createTimelineContext(_options = {}) {
1436
+ const { namespace = "v0:timeline", ...options } = _options;
1437
+ return createTrinity(namespace, createTimeline(options));
1438
+ }
1439
+ /**
1440
+ * Returns the current timeline instance.
1441
+ *
1442
+ * @param namespace The namespace for the timeline context. Defaults to `'v0:timeline'`.
1443
+ * @returns The current timeline instance.
1444
+ *
1445
+ * @see https://0.vuetifyjs.com/composables/registration/create-timeline
1446
+ *
1447
+ * @example
1448
+ * ```vue
1449
+ * <script setup lang="ts">
1450
+ * import { useTimeline } from '@vuetify/v0'
1451
+ *
1452
+ * const timeline = useTimeline()
1453
+ * <\/script>
1454
+ * ```
1455
+ */
1456
+ function useTimeline(namespace = "v0:timeline") {
1457
+ return useContext(namespace);
1458
+ }
1459
+ //#endregion
1460
+ //#region src/composables/createVirtual/index.ts
1461
+ /**
1462
+ * @module createVirtual
1463
+ *
1464
+ * @see https://0.vuetifyjs.com/composables/data/create-virtual
1465
+ *
1466
+ * @remarks
1467
+ * Virtual scrolling composable for efficiently rendering large lists.
1468
+ *
1469
+ * Key features:
1470
+ * - Trinity pattern for dependency injection (`createVirtualContext`, `useVirtual`)
1471
+ * - Renders only visible items (viewport + overscan)
1472
+ * - Dynamic or fixed item heights
1473
+ * - SSR-safe (checks IN_BROWSER)
1474
+ * - Bidirectional scrolling (forward/reverse for chat apps)
1475
+ * - Scroll anchoring (maintains position across data changes)
1476
+ * - Edge detection for infinite scroll
1477
+ * - iOS momentum and elastic scrolling
1478
+ * - Configurable overscan (extra items rendered for smooth scrolling)
1479
+ *
1480
+ * Perfect for large data sets, chat apps, and infinite scroll implementations.
1481
+ *
1482
+ * @example
1483
+ * ```ts
1484
+ * import { shallowRef } from 'vue'
1485
+ * import { createVirtual } from '@vuetify/v0'
1486
+ *
1487
+ * const items = shallowRef(Array.from({ length: 10000 }, (_, i) => ({ id: i })))
1488
+ * const virtual = createVirtual(items, { itemHeight: 40 })
1489
+ * console.log(virtual.items.value)
1490
+ * ```
1491
+ */
1492
+ /**
1493
+ * Virtual scrolling composable for efficiently rendering large lists
1494
+ *
1495
+ * @param items Reactive array of items to virtualize
1496
+ * @param options Configuration options
1497
+ * @returns Virtual scrolling context
1498
+ *
1499
+ * @see https://0.vuetifyjs.com/composables/data/create-virtual
1500
+ *
1501
+ * @example
1502
+ * ```vue
1503
+ * <script setup lang="ts">
1504
+ * import { createVirtual } from '@vuetify/v0'
1505
+ *
1506
+ * const items = ref(Array.from({ length: 10000 }, (_, i) => ({ id: i, name: `Item ${i}` })))
1507
+ * const virtual = createVirtual(items)
1508
+ * <\/script>
1509
+ *
1510
+ * <template>
1511
+ * <div
1512
+ * ref="virtual.element"
1513
+ * style="height: 600px; overflow-y: auto;"
1514
+ * @scroll="virtual.scroll"
1515
+ * >
1516
+ * <div :style="{ height: `${virtual.offset.value}px` }" />
1517
+ *
1518
+ * <div v-for="item in virtual.items.value" :key="item.index">
1519
+ * {{ item.raw.name }}
1520
+ * </div>
1521
+ *
1522
+ * <div :style="{ height: `${virtual.size.value}px` }" />
1523
+ * </div>
1524
+ * </template>
1525
+ * ```
1526
+ */
1527
+ const FALLBACK_HEIGHT = 40;
1528
+ function createVirtual(items, _options = {}) {
1529
+ const { itemHeight: _itemHeight, height, overscan = 5, direction = "forward", anchor = "auto", anchorSmooth = true, onStartReached, onEndReached, startThreshold = 0, endThreshold = 0, momentum: momentumOption, elastic: elasticOption } = _options;
1530
+ const element = ref();
1531
+ const itemHeight = shallowRef(Number.parseFloat(String(_itemHeight || 0)));
1532
+ function estimate() {
1533
+ return itemHeight.value || FALLBACK_HEIGHT;
1534
+ }
1535
+ const heights = shallowRef([]);
1536
+ const offsets = shallowRef([]);
1537
+ const first = shallowRef(0);
1538
+ const last = shallowRef(0);
1539
+ const offset = shallowRef(0);
1540
+ const size = shallowRef(0);
1541
+ const viewportHeight = shallowRef(0);
1542
+ const state = shallowRef("ok");
1543
+ const isIOS = IN_BROWSER && /iPad|iPhone|iPod/.test(navigator.userAgent);
1544
+ const momentum = momentumOption ?? isIOS;
1545
+ const elastic = elasticOption ?? isIOS;
1546
+ let raf = -1;
1547
+ let rebuildRaf = -1;
1548
+ let edgeRaf = -1;
1549
+ const cachedViewport = Number.parseInt(String(height)) || 0;
1550
+ let anchorIndex = -1;
1551
+ let anchorOffset = 0;
1552
+ const computedItems = computed(() => items.value.slice(first.value, last.value).map((item, i) => ({
1553
+ raw: item,
1554
+ index: i + first.value
1555
+ })));
1556
+ watch(element, (el) => {
1557
+ if (!IN_BROWSER || !el?.style) return;
1558
+ /* v8 ignore start -- browser-only style assignments */
1559
+ if (momentum) el.style.webkitOverflowScrolling = "touch";
1560
+ if (!elastic) el.style.overscrollBehavior = "none";
1561
+ /* v8 ignore stop */
1562
+ });
1563
+ useResizeObserver(element, (entries) => {
1564
+ /* v8 ignore next -- defensive: ResizeObserver always emits at least one entry */
1565
+ if (!entries[0]) return;
1566
+ viewportHeight.value = entries[0].contentRect.height;
1567
+ update();
1568
+ });
1569
+ watch(items, (newItems) => {
1570
+ captureAnchor();
1571
+ const length = newItems.length;
1572
+ const old = heights.value;
1573
+ const newHeights = old.length === length ? old : Array.from({ length }, (_, i) => i < old.length ? old[i] : null);
1574
+ if (heights.value !== newHeights) heights.value = newHeights;
1575
+ rebuild();
1576
+ }, { immediate: true });
1577
+ watch(element, () => {
1578
+ /* v8 ignore next -- defensive: watch fires only when element is present */
1579
+ if (!element.value) return;
1580
+ if (direction === "reverse" && items.value.length > 0) {
1581
+ const lastIndex = items.value.length - 1;
1582
+ const totalHeight = (offsets.value[lastIndex] || 0) + (heights.value[lastIndex] || estimate());
1583
+ element.value.scrollTop = totalHeight;
1584
+ }
1585
+ update();
1586
+ });
1587
+ function captureAnchor() {
1588
+ if (!element.value || anchor === "auto") return;
1589
+ if (anchor === "start") {
1590
+ anchorIndex = 0;
1591
+ anchorOffset = 0;
1592
+ } else if (anchor === "end") {
1593
+ anchorIndex = items.value.length - 1;
1594
+ anchorOffset = 0;
1595
+ } else if (/* @__PURE__ */ isFunction(anchor)) {
1596
+ const result = anchor(items.value);
1597
+ /* v8 ignore next 4 -- function-anchor: tested with valid number return; non-number rejection branch is implicit */
1598
+ if (/* @__PURE__ */ isNumber(result)) {
1599
+ anchorIndex = result;
1600
+ anchorOffset = element.value.scrollTop - (offsets.value[result] || 0);
1601
+ }
1602
+ } else {
1603
+ /* v8 ignore start -- default anchor capture */
1604
+ anchorIndex = first.value;
1605
+ anchorOffset = element.value.scrollTop - (offsets.value[first.value] || 0);
1606
+ }
1607
+ }
1608
+ function restoreAnchor() {
1609
+ if (!element.value || anchorIndex < 0) return;
1610
+ const newScrollTop = (offsets.value[anchorIndex] || 0) + anchorOffset;
1611
+ if (anchorSmooth && direction === "forward") element.value.scrollTo({
1612
+ top: newScrollTop,
1613
+ behavior: "smooth"
1614
+ });
1615
+ else element.value.scrollTop = newScrollTop;
1616
+ /* v8 ignore stop */
1617
+ anchorIndex = -1;
1618
+ anchorOffset = 0;
1619
+ }
1620
+ function rebuild() {
1621
+ const length = items.value.length;
1622
+ const newOffsets = offsets.value.length === length ? offsets.value : Array.from({ length });
1623
+ let offset = 0;
1624
+ for (let i = 0; i < length; i++) {
1625
+ newOffsets[i] = offset;
1626
+ offset += heights.value[i] || estimate();
1627
+ }
1628
+ if (offsets.value !== newOffsets) offsets.value = newOffsets;
1629
+ update();
1630
+ restoreAnchor();
1631
+ }
1632
+ function findIndex(scrollTop) {
1633
+ const arr = offsets.value;
1634
+ if (arr.length === 0) return 0;
1635
+ let low = 0;
1636
+ let high = arr.length - 1;
1637
+ while (low <= high) {
1638
+ const mid = low + high >> 1;
1639
+ if (arr[mid] <= scrollTop) low = mid + 1;
1640
+ else high = mid - 1;
1641
+ }
1642
+ return Math.max(0, high);
1643
+ }
1644
+ function update() {
1645
+ if (!element.value) return;
1646
+ const viewport = viewportHeight.value || cachedViewport;
1647
+ if (!viewport) return;
1648
+ const scrollTop = element.value.scrollTop || 0;
1649
+ const length = items.value.length;
1650
+ const visibleStart = findIndex(scrollTop);
1651
+ const visibleEnd = findIndex(scrollTop + viewport) + 1;
1652
+ const start = /* @__PURE__ */ clamp(visibleStart - overscan, 0, length);
1653
+ const end = /* @__PURE__ */ clamp(visibleEnd + overscan, start, length);
1654
+ first.value = start;
1655
+ last.value = end;
1656
+ offset.value = offsets.value[start] || 0;
1657
+ const lastIndex = length - 1;
1658
+ const totalHeight = (offsets.value[lastIndex] || 0) + (heights.value[lastIndex] || estimate());
1659
+ size.value = totalHeight - (offsets.value[end] || totalHeight);
1660
+ }
1661
+ function checkEdges() {
1662
+ if (!element.value) return;
1663
+ const scrollTop = element.value.scrollTop;
1664
+ const scrollHeight = element.value.scrollHeight;
1665
+ const clientHeight = element.value.clientHeight;
1666
+ const distanceFromStart = scrollTop;
1667
+ const distanceFromEnd = scrollHeight - (scrollTop + clientHeight);
1668
+ if (IN_BROWSER) {
1669
+ cancelAnimationFrame(edgeRaf);
1670
+ edgeRaf = requestAnimationFrame(() => {
1671
+ if (onStartReached && distanceFromStart <= startThreshold) onStartReached(distanceFromStart);
1672
+ if (onEndReached && distanceFromEnd <= endThreshold) onEndReached(distanceFromEnd);
1673
+ });
1674
+ } else {
1675
+ if (onStartReached && distanceFromStart <= startThreshold) onStartReached(distanceFromStart);
1676
+ if (onEndReached && distanceFromEnd <= endThreshold) onEndReached(distanceFromEnd);
1677
+ }
1678
+ /* v8 ignore stop */
1679
+ }
1680
+ function resize(index, height) {
1681
+ /* v8 ignore next -- early bail when height is already correct */
1682
+ if (heights.value[index] === height) return;
1683
+ heights.value[index] = height;
1684
+ /* v8 ignore next -- first-call only: itemHeight bootstrapped from initial measurement */
1685
+ if (!itemHeight.value) itemHeight.value = height;
1686
+ if (IN_BROWSER) {
1687
+ cancelAnimationFrame(rebuildRaf);
1688
+ rebuildRaf = requestAnimationFrame(rebuild);
1689
+ } else rebuild();
1690
+ /* v8 ignore stop */
1691
+ }
1692
+ function scroll() {
1693
+ if (IN_BROWSER) {
1694
+ cancelAnimationFrame(raf);
1695
+ raf = requestAnimationFrame(update);
1696
+ checkEdges();
1697
+ } else update();
1698
+ /* v8 ignore stop */
1699
+ }
1700
+ function scrollTo(index, scrollOptions) {
1701
+ /* v8 ignore next -- defensive: scrollTo only invoked after element mounts */
1702
+ if (!element.value) return;
1703
+ const targetOffset = offsets.value[index] || 0;
1704
+ const behavior = scrollOptions?.behavior ?? "auto";
1705
+ const block = scrollOptions?.block ?? "start";
1706
+ const extraOffset = scrollOptions?.offset ?? 0;
1707
+ let scrollTop = targetOffset + extraOffset;
1708
+ switch (block) {
1709
+ case "center": {
1710
+ const viewport = viewportHeight.value || cachedViewport;
1711
+ const itemH = heights.value[index] || estimate();
1712
+ scrollTop = targetOffset - viewport / 2 + itemH / 2 + extraOffset;
1713
+ break;
1714
+ }
1715
+ case "end": {
1716
+ const viewport = viewportHeight.value || cachedViewport;
1717
+ const itemH = heights.value[index] || estimate();
1718
+ scrollTop = targetOffset - viewport + itemH + extraOffset;
1719
+ break;
1720
+ }
1721
+ case "nearest": {
1722
+ const viewport = viewportHeight.value || cachedViewport;
1723
+ const currentScroll = element.value.scrollTop;
1724
+ const itemH = heights.value[index] || estimate();
1725
+ if (targetOffset < currentScroll) scrollTop = targetOffset + extraOffset;
1726
+ else if (targetOffset + itemH > currentScroll + viewport) scrollTop = targetOffset - viewport + itemH + extraOffset;
1727
+ else return;
1728
+ break;
1729
+ }
1730
+ }
1731
+ if (behavior === "smooth") element.value.scrollTo({
1732
+ top: scrollTop,
1733
+ behavior: "smooth"
1734
+ });
1735
+ else element.value.scrollTop = scrollTop;
1736
+ update();
1737
+ }
1738
+ function reset() {
1739
+ state.value = "ok";
1740
+ anchorIndex = -1;
1741
+ anchorOffset = 0;
1742
+ if (element.value && direction === "reverse" && items.value.length > 0) {
1743
+ const lastIndex = items.value.length - 1;
1744
+ const totalHeight = (offsets.value[lastIndex] || 0) + (heights.value[lastIndex] || estimate());
1745
+ element.value.scrollTop = totalHeight;
1746
+ }
1747
+ }
1748
+ onScopeDispose(() => {
1749
+ if (!IN_BROWSER) return;
1750
+ cancelAnimationFrame(raf);
1751
+ cancelAnimationFrame(rebuildRaf);
1752
+ cancelAnimationFrame(edgeRaf);
1753
+ }, true);
1754
+ return {
1755
+ element,
1756
+ items: computedItems,
1757
+ offset: readonly(offset),
1758
+ size: readonly(size),
1759
+ state,
1760
+ scrollTo,
1761
+ scroll,
1762
+ scrollend: scroll,
1763
+ resize,
1764
+ reset
1765
+ };
1766
+ }
1767
+ /**
1768
+ * Creates a virtual scrolling context with dependency injection support.
1769
+ *
1770
+ * @param items Reactive array of items to virtualize
1771
+ * @param options Configuration options including namespace
1772
+ * @template T The type of the items
1773
+ * @returns Trinity tuple: [useVirtual, provideVirtual, defaultVirtual]
1774
+ *
1775
+ * @see https://0.vuetifyjs.com/composables/data/create-virtual
1776
+ *
1777
+ * @example
1778
+ * ```ts
1779
+ * // Create injectable context
1780
+ * const [useVirtual, provideVirtual, virtual] = createVirtualContext(items, {
1781
+ * namespace: 'my-virtual',
1782
+ * overscan: 10,
1783
+ * })
1784
+ *
1785
+ * // In parent component
1786
+ * provideVirtual()
1787
+ *
1788
+ * // In child component
1789
+ * const virtual = useVirtual()
1790
+ * ```
1791
+ */
1792
+ function createVirtualContext(items, _options = {}) {
1793
+ const { namespace = "v0:virtual", ...options } = _options;
1794
+ return createTrinity(namespace, createVirtual(items, options));
1795
+ }
1796
+ /**
1797
+ * Returns the current virtual context from dependency injection.
1798
+ *
1799
+ * @param namespace The namespace for the virtual context. Defaults to `v0:virtual`.
1800
+ * @template T The type of the items
1801
+ * @returns The current virtual context.
1802
+ *
1803
+ * @throws An error if the virtual context is not found and no default is provided.
1804
+ *
1805
+ * @see https://0.vuetifyjs.com/composables/data/create-virtual
1806
+ *
1807
+ * @example
1808
+ * ```vue
1809
+ * <script lang="ts" setup>
1810
+ * import { createVirtual } from '@vuetify/v0'
1811
+ *
1812
+ * // Inject virtual context provided by parent
1813
+ * const virtual = useVirtual()
1814
+ * <\/script>
1815
+ *
1816
+ * <template>
1817
+ * <div>
1818
+ * <p>Visible items: {{ virtual.items.value.length }}</p>
1819
+ * </div>
1820
+ * </template>
1821
+ * ```
1822
+ */
1823
+ function useVirtual(namespace = "v0:virtual") {
1824
+ return useContext(namespace);
1825
+ }
1826
+ //#endregion
1827
+ //#region src/composables/toHighlight/index.ts
1828
+ /**
1829
+ * @module toHighlight
1830
+ *
1831
+ * @see https://0.vuetifyjs.com/composables/transformers/to-highlight
1832
+ *
1833
+ * @remarks
1834
+ * Pure transformer — no DOM, no state, no registry, no reactivity. Splits text
1835
+ * into matched and unmatched chunks given a query string, an array of query
1836
+ * strings, or pre-computed `[start, end]` match ranges (e.g., from createFilter).
1837
+ * Returns a plain array; wrap the call in `computed()` for reactive recomputation.
1838
+ *
1839
+ * @example
1840
+ * ```ts
1841
+ * import { toHighlight } from '@vuetify/v0'
1842
+ *
1843
+ * const chunks = toHighlight('Hello World', 'World')
1844
+ * // [{ text: 'Hello ', match: false }, { text: 'World', match: true }]
1845
+ * ```
1846
+ */
1847
+ function merge(ranges) {
1848
+ const sorted = ranges.filter((span) => span[0] < span[1]).toSorted((a, b) => a[0] - b[0]);
1849
+ const merged = [];
1850
+ for (const span of sorted) {
1851
+ const last = merged.at(-1);
1852
+ if (last && span[0] <= last[1]) last[1] = Math.max(last[1], span[1]);
1853
+ else merged.push([span[0], span[1]]);
1854
+ }
1855
+ return merged;
1856
+ }
1857
+ function chunk(text, ranges) {
1858
+ const chunks = [];
1859
+ let cursor = 0;
1860
+ for (const [start, end] of ranges) {
1861
+ if (cursor < start) chunks.push({
1862
+ text: text.slice(cursor, start),
1863
+ match: false
1864
+ });
1865
+ chunks.push({
1866
+ text: text.slice(start, end),
1867
+ match: true
1868
+ });
1869
+ cursor = end;
1870
+ }
1871
+ if (cursor < text.length) chunks.push({
1872
+ text: text.slice(cursor),
1873
+ match: false
1874
+ });
1875
+ return chunks;
1876
+ }
1877
+ function find(text, query, matchAll, ignoreCase) {
1878
+ const terms = (/* @__PURE__ */ toArray(query)).filter(Boolean);
1879
+ const haystack = ignoreCase ? text.toLocaleLowerCase() : text;
1880
+ const spans = [];
1881
+ for (const term of terms) {
1882
+ const needle = ignoreCase ? term.toLocaleLowerCase() : term;
1883
+ let index = haystack.indexOf(needle);
1884
+ if (index !== -1) {
1885
+ spans.push([index, index + term.length]);
1886
+ if (matchAll) {
1887
+ index = haystack.indexOf(needle, index + term.length);
1888
+ while (index !== -1) {
1889
+ spans.push([index, index + term.length]);
1890
+ index = haystack.indexOf(needle, index + term.length);
1891
+ }
1892
+ }
1893
+ }
1894
+ }
1895
+ return merge(spans);
1896
+ }
1897
+ /**
1898
+ * Splits text into matched and unmatched chunks.
1899
+ *
1900
+ * Pure transformer — returns a plain array. Wrap the call in `computed()` for
1901
+ * reactive recomputation.
1902
+ *
1903
+ * Priority: `options.matches` (when non-empty) → `query` → no-match fallback.
1904
+ *
1905
+ * @param text The source string to split.
1906
+ * @param query One or more search terms. Empty strings are ignored. Case sensitivity controlled by `options.ignoreCase`.
1907
+ * @param options Optional `matches`, `matchAll`, `ignoreCase`.
1908
+ * @returns A `HighlightChunk[]` array.
1909
+ *
1910
+ * @see https://0.vuetifyjs.com/composables/transformers/to-highlight
1911
+ *
1912
+ * @example
1913
+ * ```ts
1914
+ * import { computed, shallowRef } from 'vue'
1915
+ * import { toHighlight } from '@vuetify/v0'
1916
+ *
1917
+ * const query = shallowRef('World')
1918
+ * const chunks = computed(() => toHighlight('Hello World', query))
1919
+ *
1920
+ * console.log(chunks.value)
1921
+ * // [{ text: 'Hello ', match: false }, { text: 'World', match: true }]
1922
+ * ```
1923
+ */
1924
+ /* @__NO_SIDE_EFFECTS__ */
1925
+ function toHighlight(text, query, options = {}) {
1926
+ const _text = toValue(text);
1927
+ const _query = toValue(query);
1928
+ const _matches = toValue(options.matches);
1929
+ const matchAll = toValue(options.matchAll) ?? false;
1930
+ const ignoreCase = toValue(options.ignoreCase) ?? false;
1931
+ if (_matches?.length) return chunk(_text, merge(_matches));
1932
+ if (_query) {
1933
+ const ranges = find(_text, _query, matchAll, ignoreCase);
1934
+ return ranges.length > 0 ? chunk(_text, ranges) : [{
1935
+ text: _text,
1936
+ match: false
1937
+ }];
1938
+ }
1939
+ return [{
1940
+ text: _text,
1941
+ match: false
1942
+ }];
1943
+ }
1944
+ //#endregion
1945
+ //#region src/composables/toReactive/index.ts
1946
+ /**
1947
+ * @module toReactive
1948
+ *
1949
+ * @see https://0.vuetifyjs.com/composables/transformers/to-reactive
1950
+ *
1951
+ * @remarks
1952
+ * Utility function to convert values and refs into reactive proxies with ref unwrapping.
1953
+ *
1954
+ * Key features:
1955
+ * - Automatic ref unwrapping
1956
+ * - Deep reactive proxying
1957
+ * - Map and Set support with ref unwrapping
1958
+ * - Nested object/array reactivity
1959
+ * - Type preservation
1960
+ *
1961
+ * Perfect for creating reactive versions of plain objects while automatically unwrapping refs.
1962
+ *
1963
+ * @example
1964
+ * ```ts
1965
+ * import { shallowRef } from 'vue'
1966
+ * import { toReactive } from '@vuetify/v0'
1967
+ *
1968
+ * const source = { name: shallowRef('Alice'), age: 30 }
1969
+ * const reactive = toReactive(source)
1970
+ * console.log(reactive.name) // 'Alice' (auto-unwrapped)
1971
+ * ```
1972
+ */
1973
+ function createMapProxy(refObject) {
1974
+ let mapProxy;
1975
+ const mapHandlers = {
1976
+ get: (key) => unref(refObject.value.get(key)),
1977
+ set: (key, value) => {
1978
+ const map = refObject.value;
1979
+ const existingValue = map.get(key);
1980
+ if (isRef(existingValue)) existingValue.value = unref(value);
1981
+ else map.set(key, value);
1982
+ return mapProxy;
1983
+ },
1984
+ has: (key) => refObject.value.has(key),
1985
+ delete: (key) => refObject.value.delete(key),
1986
+ clear: () => refObject.value.clear(),
1987
+ keys: () => refObject.value.keys(),
1988
+ *values() {
1989
+ for (const value of refObject.value.values()) yield unref(value);
1990
+ },
1991
+ *entries() {
1992
+ for (const [key, value] of refObject.value.entries()) yield [key, unref(value)];
1993
+ },
1994
+ forEach: (callback, thisArg) => {
1995
+ for (const [key, value] of refObject.value.entries()) callback.call(thisArg, unref(value), key, mapProxy);
1996
+ }
1997
+ };
1998
+ mapHandlers[Symbol.iterator] = mapHandlers.entries;
1999
+ mapProxy = new Proxy(/* @__PURE__ */ new Map(), { get(_, p) {
2000
+ if (p === "size") return refObject.value.size;
2001
+ if (Object.prototype.hasOwnProperty.call(mapHandlers, p)) return mapHandlers[p];
2002
+ const map = refObject.value;
2003
+ const value = Reflect.get(map, p);
2004
+ return /* @__PURE__ */ isFunction(value) ? value.bind(map) : value;
2005
+ } });
2006
+ return reactive(mapProxy);
2007
+ }
2008
+ function createSetProxy(refObject) {
2009
+ let setProxy;
2010
+ const setHandlers = {
2011
+ add: (value) => {
2012
+ refObject.value.add(value);
2013
+ return setProxy;
2014
+ },
2015
+ has: (value) => refObject.value.has(value),
2016
+ delete: (value) => refObject.value.delete(value),
2017
+ clear: () => refObject.value.clear(),
2018
+ *keys() {
2019
+ for (const value of refObject.value.values()) yield unref(value);
2020
+ },
2021
+ *values() {
2022
+ for (const value of refObject.value.values()) yield unref(value);
2023
+ },
2024
+ *entries() {
2025
+ for (const value of refObject.value.values()) {
2026
+ const unreffedValue = unref(value);
2027
+ yield [unreffedValue, unreffedValue];
2028
+ }
2029
+ },
2030
+ forEach: (callback, thisArg) => {
2031
+ for (const value of refObject.value) {
2032
+ const unreffedValue = unref(value);
2033
+ callback.call(thisArg, unreffedValue, unreffedValue, setProxy);
2034
+ }
2035
+ }
2036
+ };
2037
+ setHandlers[Symbol.iterator] = setHandlers.values;
2038
+ setProxy = new Proxy(/* @__PURE__ */ new Set(), { get(_, p) {
2039
+ if (p === "size") return refObject.value.size;
2040
+ if (Object.prototype.hasOwnProperty.call(setHandlers, p)) return setHandlers[p];
2041
+ const set = refObject.value;
2042
+ const value = Reflect.get(set, p);
2043
+ return /* @__PURE__ */ isFunction(value) ? value.bind(set) : value;
2044
+ } });
2045
+ return reactive(setProxy);
2046
+ }
2047
+ function createObjectProxy(refObject) {
2048
+ return reactive(new Proxy({}, {
2049
+ get(_, p, receiver) {
2050
+ return unref(Reflect.get(refObject.value, p, receiver));
2051
+ },
2052
+ set(_, p, value) {
2053
+ return Reflect.set(refObject.value, p, value);
2054
+ },
2055
+ deleteProperty(_, p) {
2056
+ return Reflect.deleteProperty(refObject.value, p);
2057
+ },
2058
+ has(_, p) {
2059
+ return Reflect.has(refObject.value, p);
2060
+ },
2061
+ ownKeys() {
2062
+ return Reflect.ownKeys(refObject.value);
2063
+ },
2064
+ getOwnPropertyDescriptor(_, p) {
2065
+ const desc = Reflect.getOwnPropertyDescriptor(refObject.value, p);
2066
+ if (!desc) return;
2067
+ const newDesc = {
2068
+ configurable: true,
2069
+ enumerable: desc.enumerable
2070
+ };
2071
+ if ("value" in desc) {
2072
+ newDesc.value = unref(desc.value);
2073
+ newDesc.writable = desc.writable;
2074
+ } else {
2075
+ newDesc.get = desc.get;
2076
+ newDesc.set = desc.set;
2077
+ }
2078
+ return newDesc;
2079
+ }
2080
+ }));
2081
+ }
2082
+ /**
2083
+ * Converts a `MaybeRef` to a `UnwrapNestedRefs`.
2084
+ *
2085
+ * @param objectRef The object to convert.
2086
+ * @template Z The type of the object.
2087
+ * @returns The converted object.
2088
+ *
2089
+ * @see https://0.vuetifyjs.com/composables/transformers/to-reactive
2090
+ *
2091
+ * @example
2092
+ * ```ts
2093
+ * import { ref } from 'vue'
2094
+ * import { toReactive } from '@vuetify/v0'
2095
+ *
2096
+ * const state = ref({ name: 'John', age: 30 })
2097
+ * const rstate = toReactive(state)
2098
+ *
2099
+ * console.log(rstate.name) // John
2100
+ * ```
2101
+ */
2102
+ function toReactive(objectRef) {
2103
+ if (!isRef(objectRef)) return reactive(objectRef);
2104
+ const refObject = objectRef;
2105
+ const target = refObject.value;
2106
+ if (target instanceof Map) return createMapProxy(refObject);
2107
+ if (target instanceof Set) return createSetProxy(refObject);
2108
+ return createObjectProxy(refObject);
2109
+ }
2110
+ //#endregion
2111
+ //#region src/composables/useBreakpoints/index.ts
2112
+ /**
2113
+ * @module useBreakpoints
2114
+ *
2115
+ * @see https://0.vuetifyjs.com/composables/plugins/use-breakpoints
2116
+ *
2117
+ * @remarks
2118
+ * Responsive breakpoint detection composable with window resize handling.
2119
+ *
2120
+ * Key features:
2121
+ * - matchMedia-based detection for zoom-accurate breakpoints
2122
+ * - Six built-in breakpoints (xs, sm, md, lg, xl, xxl) with Vuetify 4 default widths
2123
+ * - `isMobile` flag and configurable `mobileBreakpoint`
2124
+ * - Automatic resize listener with cleanup
2125
+ * - SSR-safe with hydration flush
2126
+ * - Custom breakpoint configuration
2127
+ *
2128
+ * Perfect for responsive layouts and conditional rendering based on screen size.
2129
+ *
2130
+ * @example
2131
+ * ```ts
2132
+ * import { useBreakpoints } from '@vuetify/v0'
2133
+ *
2134
+ * const { current, isMobile, smAndUp } = useBreakpoints()
2135
+ * console.log(current.value) // 'md'
2136
+ * ```
2137
+ */
2138
+ /**
2139
+ * Creates default breakpoint configuration.
2140
+ *
2141
+ * @returns The default breakpoint configuration object.
2142
+ */
2143
+ function createDefaultBreakpoints() {
2144
+ return {
2145
+ mobileBreakpoint: "lg",
2146
+ breakpoints: {
2147
+ xs: 0,
2148
+ sm: 600,
2149
+ md: 840,
2150
+ lg: 1145,
2151
+ xl: 1545,
2152
+ xxl: 2138
2153
+ }
2154
+ };
2155
+ }
2156
+ /**
2157
+ * Creates a new breakpoints instance.
2158
+ *
2159
+ * @param options The options for the breakpoints instance.
2160
+ * @returns A new breakpoints instance.
2161
+ *
2162
+ * @see https://0.vuetifyjs.com/composables/plugins/use-breakpoints
2163
+ *
2164
+ * @example
2165
+ * ```ts
2166
+ * import { createBreakpoints } from '@vuetify/v0'
2167
+ *
2168
+ * const breakpoints = createBreakpoints({
2169
+ * namespace: 'v0:breakpoints',
2170
+ * mobileBreakpoint: 'md',
2171
+ * breakpoints: {
2172
+ * xs: 0,
2173
+ * sm: 600,
2174
+ * md: 840,
2175
+ * lg: 1145,
2176
+ * xl: 1545,
2177
+ * xxl: 2138,
2178
+ * },
2179
+ * })
2180
+ * ```
2181
+ */
2182
+ function createBreakpoints(_options = {}) {
2183
+ const { ssr, ...options } = _options;
2184
+ const { mobileBreakpoint, breakpoints } = /* @__PURE__ */ mergeDeep(createDefaultBreakpoints(), options);
2185
+ const sorted = Object.entries(breakpoints).toSorted((a, b) => a[1] - b[1]);
2186
+ const names = sorted.map(([n]) => n);
2187
+ const mb = /* @__PURE__ */ isNumber(mobileBreakpoint) ? mobileBreakpoint : breakpoints[mobileBreakpoint] ?? breakpoints.md;
2188
+ const initialWidth = ssr ? ssr.clientWidth : IN_BROWSER ? window.innerWidth : 0;
2189
+ const initialHeight = ssr ? ssr.clientHeight ?? 0 : IN_BROWSER ? window.innerHeight : 0;
2190
+ let initialName = "xs";
2191
+ for (let i = sorted.length - 1; i >= 0; i--) if (initialWidth >= sorted[i][1]) {
2192
+ initialName = sorted[i][0];
2193
+ break;
2194
+ }
2195
+ const initialIndex = names.indexOf(initialName);
2196
+ const name = shallowRef(initialName);
2197
+ const width = shallowRef(initialWidth);
2198
+ const height = shallowRef(initialHeight);
2199
+ const isMobile = shallowRef(initialWidth < mb);
2200
+ const xs = shallowRef(initialIndex === 0);
2201
+ const sm = shallowRef(initialIndex === 1);
2202
+ const md = shallowRef(initialIndex === 2);
2203
+ const lg = shallowRef(initialIndex === 3);
2204
+ const xl = shallowRef(initialIndex === 4);
2205
+ const xxl = shallowRef(initialIndex === 5);
2206
+ const smAndUp = shallowRef(initialIndex >= 1);
2207
+ const mdAndUp = shallowRef(initialIndex >= 2);
2208
+ const lgAndUp = shallowRef(initialIndex >= 3);
2209
+ const xlAndUp = shallowRef(initialIndex >= 4);
2210
+ const smAndDown = shallowRef(initialIndex <= 1);
2211
+ const mdAndDown = shallowRef(initialIndex <= 2);
2212
+ const lgAndDown = shallowRef(initialIndex <= 3);
2213
+ const xlAndDown = shallowRef(initialIndex <= 4);
2214
+ function update() {
2215
+ if (!IN_BROWSER) return;
2216
+ width.value = window.innerWidth;
2217
+ height.value = window.innerHeight;
2218
+ let current = "xs";
2219
+ for (let i = sorted.length - 1; i >= 0; i--) {
2220
+ const px = sorted[i][1];
2221
+ if (SUPPORTS_MATCH_MEDIA ? window.matchMedia(`(min-width: ${px}px)`).matches : width.value >= px) {
2222
+ current = sorted[i][0];
2223
+ break;
2224
+ }
2225
+ }
2226
+ name.value = current;
2227
+ const index = names.indexOf(current);
2228
+ isMobile.value = /* @__PURE__ */ isNumber(mb) ? SUPPORTS_MATCH_MEDIA ? !window.matchMedia(`(min-width: ${mb}px)`).matches : width.value < mb : index < names.indexOf(mobileBreakpoint);
2229
+ xs.value = index === 0;
2230
+ sm.value = index === 1;
2231
+ md.value = index === 2;
2232
+ lg.value = index === 3;
2233
+ xl.value = index === 4;
2234
+ xxl.value = index === 5;
2235
+ smAndUp.value = index >= 1;
2236
+ mdAndUp.value = index >= 2;
2237
+ lgAndUp.value = index >= 3;
2238
+ xlAndUp.value = index >= 4;
2239
+ smAndDown.value = index <= 1;
2240
+ mdAndDown.value = index <= 2;
2241
+ lgAndDown.value = index <= 3;
2242
+ xlAndDown.value = index <= 4;
2243
+ }
2244
+ return {
2245
+ breakpoints,
2246
+ mobileBreakpoint,
2247
+ name: readonly(name),
2248
+ width: readonly(width),
2249
+ height: readonly(height),
2250
+ isMobile: readonly(isMobile),
2251
+ xs: readonly(xs),
2252
+ sm: readonly(sm),
2253
+ md: readonly(md),
2254
+ lg: readonly(lg),
2255
+ xl: readonly(xl),
2256
+ xxl: readonly(xxl),
2257
+ smAndUp: readonly(smAndUp),
2258
+ mdAndUp: readonly(mdAndUp),
2259
+ lgAndUp: readonly(lgAndUp),
2260
+ xlAndUp: readonly(xlAndUp),
2261
+ smAndDown: readonly(smAndDown),
2262
+ mdAndDown: readonly(mdAndDown),
2263
+ lgAndDown: readonly(lgAndDown),
2264
+ xlAndDown: readonly(xlAndDown),
2265
+ ssr: !!ssr,
2266
+ update
2267
+ };
2268
+ }
2269
+ function createBreakpointsFallback(options = {}) {
2270
+ if (options.ssr) return createBreakpoints(options);
2271
+ const defaults = createDefaultBreakpoints();
2272
+ return {
2273
+ breakpoints: defaults.breakpoints,
2274
+ mobileBreakpoint: defaults.mobileBreakpoint,
2275
+ name: readonly(shallowRef("xs")),
2276
+ width: readonly(shallowRef(0)),
2277
+ height: readonly(shallowRef(0)),
2278
+ isMobile: readonly(shallowRef(true)),
2279
+ xs: readonly(shallowRef(true)),
2280
+ sm: readonly(shallowRef(false)),
2281
+ md: readonly(shallowRef(false)),
2282
+ lg: readonly(shallowRef(false)),
2283
+ xl: readonly(shallowRef(false)),
2284
+ xxl: readonly(shallowRef(false)),
2285
+ smAndUp: readonly(shallowRef(false)),
2286
+ mdAndUp: readonly(shallowRef(false)),
2287
+ lgAndUp: readonly(shallowRef(false)),
2288
+ xlAndUp: readonly(shallowRef(false)),
2289
+ smAndDown: readonly(shallowRef(true)),
2290
+ mdAndDown: readonly(shallowRef(true)),
2291
+ lgAndDown: readonly(shallowRef(true)),
2292
+ xlAndDown: readonly(shallowRef(true)),
2293
+ ssr: false,
2294
+ update: () => {}
2295
+ };
2296
+ }
2297
+ const [createBreakpointsContext, createBreakpointsPlugin, useBreakpoints] = createPluginContext("v0:breakpoints", (options) => createBreakpoints(options), {
2298
+ fallback: () => createBreakpointsFallback(),
2299
+ setup: (context, app, _options) => {
2300
+ if (IN_BROWSER && !_options?.ssr) context.update();
2301
+ const { mount } = app;
2302
+ app.mount = (...args) => {
2303
+ const vm = mount(...args);
2304
+ const hydration = useHydration();
2305
+ function listener() {
2306
+ context.update();
2307
+ }
2308
+ const unwatch = watch(hydration.isHydrated, (hydrated) => {
2309
+ if (hydrated) listener();
2310
+ }, { immediate: true });
2311
+ const cleanup = useWindowEventListener("resize", listener, { passive: true });
2312
+ onScopeDispose(() => {
2313
+ cleanup();
2314
+ unwatch();
2315
+ }, true);
2316
+ app.mount = mount;
2317
+ return vm;
2318
+ };
2319
+ }
2320
+ });
2321
+ //#endregion
2322
+ //#region src/composables/useDate/index.ts
2323
+ /**
2324
+ * @module useDate
2325
+ *
2326
+ * @see https://0.vuetifyjs.com/composables/plugins/use-date
2327
+ *
2328
+ * @remarks
2329
+ * Date manipulation composable with adapter pattern for date operations.
2330
+ *
2331
+ * Key features:
2332
+ * - Adapter pattern for date library abstraction
2333
+ * - Locale-aware formatting via Intl.DateTimeFormat
2334
+ * - `firstDayOfWeek` derived from locale and propagated to adapter
2335
+ * - Integration with useLocale for automatic locale sync
2336
+ *
2337
+ * @example Using the built-in Temporal adapter
2338
+ * ```ts
2339
+ * import { V0DateAdapter } from '@vuetify/v0/date'
2340
+ * import { createDatePlugin } from '@vuetify/v0'
2341
+ *
2342
+ * const app = createApp(App)
2343
+ * app.use(createDatePlugin({ adapter: new V0DateAdapter() }))
2344
+ *
2345
+ * // In a component:
2346
+ * const { adapter } = useDate()
2347
+ * const today = adapter.date()
2348
+ * const formatted = adapter.format(today, 'fullDate')
2349
+ * ```
2350
+ *
2351
+ * @example Custom adapter
2352
+ * ```ts
2353
+ * import { DateAdapter } from '@vuetify/v0'
2354
+ *
2355
+ * class DateFnsAdapter extends DateAdapter<Date> {
2356
+ * // ... implementation
2357
+ * }
2358
+ *
2359
+ * app.use(createDatePlugin({ adapter: new DateFnsAdapter() }))
2360
+ * ```
2361
+ */
2362
+ /**
2363
+ * Default short locale codes mapped to full Intl locale strings.
2364
+ * Used when useLocale provides a short code without region (e.g., 'en' instead of 'en-US').
2365
+ */
2366
+ const defaultLocales = {
2367
+ en: "en-US",
2368
+ es: "es-ES",
2369
+ fr: "fr-FR",
2370
+ de: "de-DE",
2371
+ it: "it-IT",
2372
+ pt: "pt-BR",
2373
+ ja: "ja-JP",
2374
+ ko: "ko-KR",
2375
+ zh: "zh-CN",
2376
+ ru: "ru-RU",
2377
+ ar: "ar-SA"
2378
+ };
2379
+ /**
2380
+ * Derive firstDayOfWeek from an Intl locale string.
2381
+ * Returns 0-6 (0=Sun) or 0 as fallback.
2382
+ */
2383
+ function deriveFirstDayOfWeek(locale) {
2384
+ try {
2385
+ const info = new Intl.Locale(locale).getWeekInfo?.();
2386
+ /* v8 ignore next -- defensive: getWeekInfo always returns info in Node 22+ */
2387
+ return info ? info.firstDay % 7 : 0;
2388
+ } catch {
2389
+ return 0;
2390
+ }
2391
+ }
2392
+ /**
2393
+ * Creates a new date context.
2394
+ *
2395
+ * @param options Adapter and locale configuration.
2396
+ * @template Z The date type used by the adapter.
2397
+ * @template E The date context type.
2398
+ * @returns A date context.
2399
+ *
2400
+ * @see https://0.vuetifyjs.com/composables/plugins/use-date
2401
+ *
2402
+ * @example
2403
+ * ```ts
2404
+ * import { V0DateAdapter } from '@vuetify/v0/date'
2405
+ *
2406
+ * const { adapter } = createDate({ adapter: new V0DateAdapter() })
2407
+ * const today = adapter.date()
2408
+ *
2409
+ * // With locale options
2410
+ * const { adapter } = createDate({
2411
+ * adapter: new V0DateAdapter(),
2412
+ * locale: 'de-DE',
2413
+ * })
2414
+ * ```
2415
+ */
2416
+ function createDate(options) {
2417
+ const { locales = defaultLocales, adapter, locale: initialLocale, firstDayOfWeek: explicitFirstDay } = options;
2418
+ let selectedId;
2419
+ try {
2420
+ if (/* @__PURE__ */ instanceExists()) selectedId = useLocale().selectedId;
2421
+ } catch {}
2422
+ const locale = computed(() => {
2423
+ const selected = selectedId?.value;
2424
+ if (!/* @__PURE__ */ isNullOrUndefined(selected)) {
2425
+ const str = String(selected);
2426
+ return str.includes("-") ? str : locales[str] ?? str;
2427
+ }
2428
+ const fallback = initialLocale ?? adapter.locale ?? "en-US";
2429
+ return fallback.includes("-") ? fallback : locales[fallback] ?? fallback;
2430
+ });
2431
+ const firstDayOfWeek = computed(() => {
2432
+ if (!/* @__PURE__ */ isUndefined(explicitFirstDay)) return explicitFirstDay;
2433
+ const loc = locale.value;
2434
+ return loc ? deriveFirstDayOfWeek(loc) : 0;
2435
+ });
2436
+ if (/* @__PURE__ */ instanceExists()) onScopeDispose(watchEffect(() => {
2437
+ const loc = locale.value;
2438
+ if (loc && adapter.locale !== loc) adapter.locale = loc;
2439
+ const fdow = firstDayOfWeek.value;
2440
+ if (adapter.firstDayOfWeek !== fdow) adapter.firstDayOfWeek = fdow;
2441
+ }));
2442
+ else {
2443
+ const loc = locale.value;
2444
+ if (loc && adapter.locale !== loc) adapter.locale = loc;
2445
+ const fdow = firstDayOfWeek.value;
2446
+ if (adapter.firstDayOfWeek !== fdow) adapter.firstDayOfWeek = fdow;
2447
+ }
2448
+ return {
2449
+ adapter,
2450
+ locale,
2451
+ firstDayOfWeek
2452
+ };
2453
+ }
2454
+ /**
2455
+ * Creates a new date context trinity.
2456
+ *
2457
+ * @param options Adapter, locale, and namespace configuration.
2458
+ * @template Z The date type used by the adapter.
2459
+ * @template E The date context type.
2460
+ * @returns A trinity [useContext, provideContext, defaultContext].
2461
+ *
2462
+ * @see https://0.vuetifyjs.com/composables/plugins/use-date
2463
+ *
2464
+ * @example
2465
+ * ```ts
2466
+ * import { V0DateAdapter } from '@vuetify/v0/date'
2467
+ *
2468
+ * const [useAppDate, provideAppDate] = createDateContext({
2469
+ * adapter: new V0DateAdapter(),
2470
+ * namespace: 'app:date',
2471
+ * })
2472
+ * ```
2473
+ */
2474
+ function createDateContext(_options) {
2475
+ const { namespace = "v0:date", ...options } = _options;
2476
+ return createTrinity(namespace, createDate(options));
2477
+ }
2478
+ /**
2479
+ * Creates a new date plugin.
2480
+ *
2481
+ * @param options Adapter, locale, and namespace configuration.
2482
+ * @template Z The date type used by the adapter.
2483
+ * @template E The date context type.
2484
+ * @returns A Vue plugin.
2485
+ *
2486
+ * @see https://0.vuetifyjs.com/composables/plugins/use-date
2487
+ *
2488
+ * @example
2489
+ * ```ts
2490
+ * import { V0DateAdapter } from '@vuetify/v0/date'
2491
+ *
2492
+ * const app = createApp(App)
2493
+ * app.use(createDatePlugin({ adapter: new V0DateAdapter() }))
2494
+ *
2495
+ * // With locale options
2496
+ * app.use(createDatePlugin({
2497
+ * adapter: new V0DateAdapter(),
2498
+ * locale: 'de-DE',
2499
+ * }))
2500
+ * ```
2501
+ */
2502
+ function createDatePlugin(_options) {
2503
+ const { namespace = "v0:date", ...options } = _options;
2504
+ const [, provideDateContext, context] = createDateContext({
2505
+ namespace,
2506
+ ...options
2507
+ });
2508
+ return createPlugin({
2509
+ namespace,
2510
+ provide: (app) => {
2511
+ provideDateContext(context, app);
2512
+ }
2513
+ });
2514
+ }
2515
+ /**
2516
+ * Returns the current date context.
2517
+ *
2518
+ * Requires `createDatePlugin` to be installed with an adapter.
2519
+ *
2520
+ * @param namespace The namespace to look up (defaults to 'v0:date').
2521
+ * @template Z The date type used by the adapter.
2522
+ * @template E The date context type.
2523
+ * @returns The current date context.
2524
+ * @throws If called outside a component or without a date plugin installed.
2525
+ *
2526
+ * @see https://0.vuetifyjs.com/composables/plugins/use-date
2527
+ *
2528
+ * @example
2529
+ * ```ts
2530
+ * // main.ts
2531
+ * import { V0DateAdapter } from '@vuetify/v0/date'
2532
+ * import { createDatePlugin } from '@vuetify/v0'
2533
+ *
2534
+ * app.use(createDatePlugin({ adapter: new V0DateAdapter() }))
2535
+ * ```
2536
+ *
2537
+ * @example
2538
+ * ```vue
2539
+ * <script setup lang="ts">
2540
+ * import { useDate } from '@vuetify/v0'
2541
+ *
2542
+ * const { adapter, locale } = useDate()
2543
+ * const today = adapter.date()
2544
+ * <\/script>
2545
+ * ```
2546
+ */
2547
+ function useDate(namespace = "v0:date") {
2548
+ 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() }))", {
2549
+ code: "V0_PLUGIN_MISSING",
2550
+ plugin: "createDatePlugin"
2551
+ });
2552
+ return useContext(namespace);
2553
+ }
2554
+ //#endregion
2555
+ //#region src/composables/useMutationObserver/index.ts
2556
+ /**
2557
+ * @module useMutationObserver
2558
+ *
2559
+ * @see https://0.vuetifyjs.com/composables/system/use-mutation-observer
2560
+ *
2561
+ * @remarks
2562
+ * MutationObserver composable with lifecycle management.
2563
+ *
2564
+ * Key features:
2565
+ * - MutationObserver API wrapper
2566
+ * - `isActive` reactive flag
2567
+ * - `once` option for single-fire observation
2568
+ * - Pause/resume/stop functionality
2569
+ * - Automatic cleanup on unmount
2570
+ * - SSR-safe (checks SUPPORTS_MUTATION_OBSERVER)
2571
+ * - Hydration-aware
2572
+ * - Configurable observation options (childList, attributes, characterData, etc.)
2573
+ *
2574
+ * Perfect for detecting DOM changes and responding to mutations.
2575
+ *
2576
+ * @example
2577
+ * ```ts
2578
+ * import { useTemplateRef } from 'vue'
2579
+ * import { useMutationObserver } from '@vuetify/v0'
2580
+ *
2581
+ * const target = useTemplateRef('target')
2582
+ * useMutationObserver(target, records => {
2583
+ * console.log(records)
2584
+ * }, { childList: true })
2585
+ * ```
2586
+ */
2587
+ /**
2588
+ * A composable that uses the Mutation Observer API to detect changes in the DOM.
2589
+ *
2590
+ * @param target The element to observe.
2591
+ * @param callback The callback to execute when a mutation is observed.
2592
+ * @param options The options for the Mutation Observer.
2593
+ * @returns An object with methods to control the observer.
2594
+ *
2595
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
2596
+ * @see https://0.vuetifyjs.com/composables/system/use-mutation-observer
2597
+ *
2598
+ * @example
2599
+ * ```ts
2600
+ * import { ref } from 'vue'
2601
+ * import { useMutationObserver } from '@vuetify/v0'
2602
+ *
2603
+ * const container = ref<HTMLElement>()
2604
+ *
2605
+ * const { pause, resume, isPaused } = useMutationObserver(
2606
+ * container,
2607
+ * (mutations) => {
2608
+ * mutations.forEach((mutation) => {
2609
+ * if (mutation.type === 'childList') {
2610
+ * console.log('Children changed:', mutation.addedNodes, mutation.removedNodes)
2611
+ * } else if (mutation.type === 'attributes') {
2612
+ * console.log('Attribute changed:', mutation.attributeName)
2613
+ * }
2614
+ * })
2615
+ * },
2616
+ * {
2617
+ * childList: true,
2618
+ * attributes: true,
2619
+ * subtree: true
2620
+ * }
2621
+ * )
2622
+ *
2623
+ * // Pause observation
2624
+ * pause()
2625
+ *
2626
+ * // Resume observation
2627
+ * resume()
2628
+ * ```
2629
+ */
2630
+ function useMutationObserver(target, callback, options = {}) {
2631
+ const observerOptions = {
2632
+ childList: options.childList ?? true,
2633
+ attributes: options.attributes ?? false,
2634
+ characterData: options.characterData ?? false,
2635
+ subtree: options.subtree ?? false,
2636
+ attributeOldValue: options.attributeOldValue ?? false,
2637
+ characterDataOldValue: options.characterDataOldValue ?? false,
2638
+ attributeFilter: options.attributeFilter
2639
+ };
2640
+ return createObserver(target, callback, {
2641
+ supports: SUPPORTS_MUTATION_OBSERVER,
2642
+ once: options.once,
2643
+ create: (cb) => new MutationObserver((mutations) => {
2644
+ cb(mutations.map((m) => ({
2645
+ type: m.type,
2646
+ target: m.target,
2647
+ addedNodes: m.addedNodes,
2648
+ removedNodes: m.removedNodes,
2649
+ previousSibling: m.previousSibling,
2650
+ nextSibling: m.nextSibling,
2651
+ attributeName: m.attributeName,
2652
+ attributeNamespace: m.attributeNamespace,
2653
+ oldValue: m.oldValue
2654
+ })));
2655
+ }),
2656
+ observe: (obs, el) => obs.observe(el, observerOptions),
2657
+ immediate: options.immediate ? (el) => {
2658
+ const emptyNodeList = {
2659
+ length: 0,
2660
+ item: () => null,
2661
+ forEach: () => {},
2662
+ *[Symbol.iterator]() {}
2663
+ };
2664
+ return [{
2665
+ type: "childList",
2666
+ target: el,
2667
+ addedNodes: emptyNodeList,
2668
+ removedNodes: emptyNodeList,
2669
+ previousSibling: null,
2670
+ nextSibling: null,
2671
+ attributeName: null,
2672
+ attributeNamespace: null,
2673
+ oldValue: null
2674
+ }];
2675
+ } : void 0,
2676
+ onceIncludesImmediate: true
2677
+ });
2678
+ }
2679
+ //#endregion
2680
+ //#region src/composables/useDragDrop/adapters/adapter.ts
2681
+ /**
2682
+ * @module useDragDrop/adapters/adapter
2683
+ *
2684
+ * @remarks
2685
+ * Adapter contract and abstract base for useDragDrop. Concrete adapters extend
2686
+ * `DragDropAdapter<Z>` to share the `cleanup` field, `dispose()` lifecycle, and
2687
+ * the `locate()` DOM-walk helper.
2688
+ */
2689
+ /**
2690
+ * Abstract base class for useDragDrop adapters. Provides a `cleanup` slot, a
2691
+ * `dispose()` lifecycle, and a `locate()` helper that walks the DOM to find the
2692
+ * draggable ticket whose `el` ancestors a given event target.
2693
+ *
2694
+ * @example
2695
+ * ```ts
2696
+ * import { DragDropAdapter, type DragDropAdapterContext } from '@vuetify/v0'
2697
+ *
2698
+ * class MyAdapter extends DragDropAdapter {
2699
+ * setup (context: DragDropAdapterContext) {
2700
+ * const stop = useEventListener(document, 'mousedown', event => {
2701
+ * const ticket = this.locate(event.target, context)
2702
+ * if (ticket) context.emit.start(ticket, { x: event.clientX, y: event.clientY }, 'pointer')
2703
+ * })
2704
+ * this.cleanup = () => stop()
2705
+ * }
2706
+ * }
2707
+ * ```
2708
+ */
2709
+ var DragDropAdapter = class {
2710
+ cleanup = null;
2711
+ dispose() {
2712
+ this.cleanup?.();
2713
+ this.cleanup = null;
2714
+ }
2715
+ locate(target, context) {
2716
+ let element = /* @__PURE__ */ isElement(target) ? target : null;
2717
+ while (element) {
2718
+ for (const ticket of context.draggables.values()) if (ticket.el.value === element) return ticket;
2719
+ element = element.parentElement;
2720
+ }
2721
+ return null;
2722
+ }
2723
+ };
2724
+ //#endregion
2725
+ //#region src/composables/useDragDrop/adapters/pointer.ts
2726
+ /**
2727
+ * @module useDragDrop/adapters/pointer
2728
+ *
2729
+ * @remarks
2730
+ * Default pointer adapter for useDragDrop. Listens for `pointerdown` /
2731
+ * `pointermove` / `pointerup` / `pointercancel` on the document and emits the
2732
+ * drag lifecycle. Activation distance is configurable via `threshold`.
2733
+ */
2734
+ /**
2735
+ * Default pointer adapter. Wires `pointerdown` / `pointermove` / `pointerup` /
2736
+ * `pointercancel` on the document and converts them into drag lifecycle calls.
2737
+ *
2738
+ * @example
2739
+ * ```ts
2740
+ * import { PointerAdapter, useDragDrop } from '@vuetify/v0'
2741
+ *
2742
+ * useDragDrop({ adapters: [new PointerAdapter({ threshold: 10 })] })
2743
+ * ```
2744
+ */
2745
+ var PointerAdapter = class extends DragDropAdapter {
2746
+ threshold;
2747
+ constructor(options = {}) {
2748
+ super();
2749
+ this.threshold = options.threshold ?? 0;
2750
+ }
2751
+ setup(context) {
2752
+ const logger = useLogger();
2753
+ if (this.cleanup) {
2754
+ logger.warn("PointerAdapter setup called twice; previous registration will be replaced");
2755
+ this.dispose();
2756
+ }
2757
+ if (!IN_BROWSER) return;
2758
+ let downSource = null;
2759
+ let downOrigin = null;
2760
+ let downId = null;
2761
+ let started = false;
2762
+ function reset() {
2763
+ downSource = null;
2764
+ downOrigin = null;
2765
+ downId = null;
2766
+ started = false;
2767
+ }
2768
+ const onDown = (event) => {
2769
+ if (event.button !== 0) return;
2770
+ if (downSource) return;
2771
+ const ticket = this.locate(event.target, context);
2772
+ if (!ticket) return;
2773
+ downSource = ticket;
2774
+ downOrigin = {
2775
+ x: event.clientX,
2776
+ y: event.clientY
2777
+ };
2778
+ downId = event.pointerId;
2779
+ started = false;
2780
+ };
2781
+ const onMove = (event) => {
2782
+ if (!downSource || !downOrigin || event.pointerId !== downId) return;
2783
+ const point = {
2784
+ x: event.clientX,
2785
+ y: event.clientY
2786
+ };
2787
+ if (!started) {
2788
+ const dx = point.x - downOrigin.x;
2789
+ const dy = point.y - downOrigin.y;
2790
+ if (this.threshold > 0 && Math.hypot(dx, dy) < this.threshold) return;
2791
+ context.emit.start(downSource, downOrigin, "pointer");
2792
+ started = true;
2793
+ }
2794
+ context.emit.move(point);
2795
+ };
2796
+ function onUp(event) {
2797
+ if (event.pointerId !== downId) return;
2798
+ if (downSource && started) context.emit.drop();
2799
+ reset();
2800
+ }
2801
+ function onCancel(event) {
2802
+ if (event.pointerId !== downId) return;
2803
+ if (downSource && started) context.emit.cancel();
2804
+ reset();
2805
+ }
2806
+ const stopDown = useEventListener(document, "pointerdown", onDown);
2807
+ const stopMove = useEventListener(document, "pointermove", onMove);
2808
+ const stopUp = useEventListener(document, "pointerup", onUp);
2809
+ const stopCancel = useEventListener(document, "pointercancel", onCancel);
2810
+ this.cleanup = () => {
2811
+ stopDown();
2812
+ stopMove();
2813
+ stopUp();
2814
+ stopCancel();
2815
+ };
2816
+ }
2817
+ };
2818
+ //#endregion
2819
+ //#region src/composables/useDragDrop/adapters/keyboard.ts
2820
+ /**
2821
+ * @module useDragDrop/adapters/keyboard
2822
+ *
2823
+ * @remarks
2824
+ * Default keyboard adapter for useDragDrop. Listens for `keydown` on the
2825
+ * document. Activation keys (default Space and Enter) start and drop drags;
2826
+ * arrow keys nudge the drag point by `step` px; Escape cancels.
2827
+ */
2828
+ function isEditable(element) {
2829
+ if (/* @__PURE__ */ isNull(element)) return false;
2830
+ const tag = element.tagName;
2831
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
2832
+ if (element.isContentEditable) return true;
2833
+ const role = element.getAttribute("role");
2834
+ return role === "textbox" || role === "searchbox" || role === "combobox" || role === "spinbutton";
2835
+ }
2836
+ /**
2837
+ * Default keyboard adapter. Listens for `keydown` on the document; activation
2838
+ * keys start/drop drags, arrow keys nudge the drag point, Escape cancels.
2839
+ *
2840
+ * @example
2841
+ * ```ts
2842
+ * import { KeyboardAdapter, useDragDrop } from '@vuetify/v0'
2843
+ *
2844
+ * useDragDrop({ adapters: [new KeyboardAdapter({ activate: ['x'], step: 32 })] })
2845
+ * ```
2846
+ */
2847
+ var KeyboardAdapter = class extends DragDropAdapter {
2848
+ activate;
2849
+ step;
2850
+ constructor(options = {}) {
2851
+ super();
2852
+ this.activate = options.activate ?? [" ", "Enter"];
2853
+ this.step = options.step ?? 16;
2854
+ }
2855
+ setup(context) {
2856
+ const logger = useLogger();
2857
+ if (this.cleanup) {
2858
+ logger.warn("KeyboardAdapter setup called twice; previous registration will be replaced");
2859
+ this.dispose();
2860
+ }
2861
+ if (!IN_BROWSER) return;
2862
+ const onKeydown = (event) => {
2863
+ const focused = document.activeElement;
2864
+ if (event.ctrlKey || event.metaKey || event.altKey || event.shiftKey) return;
2865
+ if (isEditable(focused)) return;
2866
+ const ticket = this.locate(focused, context);
2867
+ const isActive = !/* @__PURE__ */ isNull(context.active.value);
2868
+ if (this.activate.includes(event.key)) {
2869
+ if (isActive) {
2870
+ event.preventDefault();
2871
+ context.emit.drop();
2872
+ } else if (ticket) {
2873
+ const el = ticket.el.value;
2874
+ if (!el) {
2875
+ logger.warn("KeyboardAdapter: cannot start drag — element is not mounted");
2876
+ return;
2877
+ }
2878
+ event.preventDefault();
2879
+ const rect = el.getBoundingClientRect();
2880
+ context.emit.start(ticket, {
2881
+ x: rect.left + rect.width / 2,
2882
+ y: rect.top + rect.height / 2
2883
+ }, "keyboard");
2884
+ }
2885
+ return;
2886
+ }
2887
+ if (event.key === "Escape" && isActive) {
2888
+ event.preventDefault();
2889
+ context.emit.cancel();
2890
+ return;
2891
+ }
2892
+ if (isActive && event.key.startsWith("Arrow")) {
2893
+ const active = context.active.value;
2894
+ if (/* @__PURE__ */ isNull(active)) return;
2895
+ const current = active.current;
2896
+ let point = null;
2897
+ switch (event.key) {
2898
+ case "ArrowUp":
2899
+ point = {
2900
+ x: current.x,
2901
+ y: current.y - this.step
2902
+ };
2903
+ break;
2904
+ case "ArrowDown":
2905
+ point = {
2906
+ x: current.x,
2907
+ y: current.y + this.step
2908
+ };
2909
+ break;
2910
+ case "ArrowLeft":
2911
+ point = {
2912
+ x: current.x - this.step,
2913
+ y: current.y
2914
+ };
2915
+ break;
2916
+ case "ArrowRight":
2917
+ point = {
2918
+ x: current.x + this.step,
2919
+ y: current.y
2920
+ };
2921
+ break;
2922
+ }
2923
+ if (point) {
2924
+ event.preventDefault();
2925
+ context.emit.move(point);
2926
+ }
2927
+ }
2928
+ };
2929
+ const stop = useEventListener(document, "keydown", onKeydown);
2930
+ this.cleanup = () => stop();
2931
+ }
2932
+ };
2933
+ //#endregion
2934
+ //#region src/composables/useDragDrop/index.ts
2935
+ /**
2936
+ * @module useDragDrop
2937
+ *
2938
+ * @see https://0.vuetifyjs.com/composables/system/use-drag-drop
2939
+ *
2940
+ * @remarks
2941
+ * Headless drag-and-drop primitive. Owns two registries (draggables and zones)
2942
+ * plus the active-drag state. Pointer and keyboard adapters ship by default;
2943
+ * lifecycle hooks and a plugin array provide extension points.
2944
+ *
2945
+ * @example
2946
+ * ```ts
2947
+ * import { useDragDrop } from '@vuetify/v0'
2948
+ *
2949
+ * const dnd = useDragDrop()
2950
+ * ```
2951
+ */
2952
+ /**
2953
+ * Pure math for resolving where in an oriented zone a pointer would drop.
2954
+ * Internal — consumers rely on the `position.index` / `position.indicator`
2955
+ * fields delivered to `onDrop`.
2956
+ */
2957
+ function resolveDropPosition(point, rects, orientation) {
2958
+ if (rects.length === 0) return null;
2959
+ const axis = orientation === "vertical" ? "y" : "x";
2960
+ const start = orientation === "vertical" ? "top" : "left";
2961
+ const end = orientation === "vertical" ? "bottom" : "right";
2962
+ const coord = point[axis];
2963
+ if (coord < rects[0][start]) return {
2964
+ index: 0,
2965
+ edge: "before",
2966
+ rect: rects[0]
2967
+ };
2968
+ const last = rects.at(-1);
2969
+ if (coord > last[end]) return {
2970
+ index: rects.length,
2971
+ edge: "after",
2972
+ rect: last
2973
+ };
2974
+ for (const [index, rect] of rects.entries()) if (coord >= rect[start] && coord <= rect[end]) {
2975
+ if (coord < rect[start] + (rect[end] - rect[start]) / 2) return {
2976
+ index,
2977
+ edge: "before",
2978
+ rect
2979
+ };
2980
+ return {
2981
+ index: index + 1,
2982
+ edge: "after",
2983
+ rect
2984
+ };
2985
+ }
2986
+ for (let index = 0; index < rects.length - 1; index++) {
2987
+ const a = rects[index];
2988
+ const b = rects[index + 1];
2989
+ if (coord > a[end] && coord < b[start]) {
2990
+ if (coord <= (a[end] + b[start]) / 2) return {
2991
+ index: index + 1,
2992
+ edge: "after",
2993
+ rect: a
2994
+ };
2995
+ return {
2996
+ index: index + 1,
2997
+ edge: "before",
2998
+ rect: b
2999
+ };
3000
+ }
3001
+ }
3002
+ return {
3003
+ index: rects.length,
3004
+ edge: "after",
3005
+ rect: last
3006
+ };
3007
+ }
3008
+ /**
3009
+ * Create a headless drag-and-drop context.
3010
+ *
3011
+ * @param options - Adapters, plugins, and lifecycle hooks. All optional.
3012
+ * @returns A `DragDropContext<Z>` exposing `draggables` / `zones` registries,
3013
+ * the `active` drag, the `isDragging` boolean, and a programmatic `cancel()`.
3014
+ *
3015
+ * @see https://0.vuetifyjs.com/composables/system/use-drag-drop
3016
+ *
3017
+ * @example
3018
+ * ```ts
3019
+ * import { useDragDrop } from '@vuetify/v0'
3020
+ * import { useTemplateRef } from 'vue'
3021
+ *
3022
+ * const dnd = useDragDrop<{ type: 'card', value: string }>()
3023
+ *
3024
+ * const draggable = useTemplateRef<HTMLElement>('draggable')
3025
+ * const dropzone = useTemplateRef<HTMLElement>('dropzone')
3026
+ *
3027
+ * dnd.draggables.register({ el: draggable, type: 'card', value: 'card-1' })
3028
+ * dnd.zones.register({
3029
+ * el: dropzone,
3030
+ * accept: ['card'],
3031
+ * onDrop: (drag, position) => console.log(drag.value, position.index),
3032
+ * })
3033
+ * ```
3034
+ */
3035
+ function useDragDrop(options = {}) {
3036
+ const { plugins = [] } = options;
3037
+ const logger = useLogger();
3038
+ const _draggables = createRegistry({ events: true });
3039
+ const _zones = createRegistry({ events: true });
3040
+ const active = shallowRef(null);
3041
+ const isDragging = toRef(() => !/* @__PURE__ */ isNull(active.value));
3042
+ const scopes = /* @__PURE__ */ new Map();
3043
+ const nodes = /* @__PURE__ */ new Map();
3044
+ function next(fn, ...args) {
3045
+ if (/* @__PURE__ */ isUndefined(fn)) return;
3046
+ try {
3047
+ fn(...args);
3048
+ } catch (error) {
3049
+ logger.error("useDragDrop hook threw", error);
3050
+ }
3051
+ }
3052
+ function safeAccept(accept, drag) {
3053
+ try {
3054
+ if (/* @__PURE__ */ isNull(drag)) return false;
3055
+ if (/* @__PURE__ */ isUndefined(accept)) return true;
3056
+ if (/* @__PURE__ */ isArray(accept)) return accept.includes(drag.type);
3057
+ const result = accept(drag);
3058
+ if (/* @__PURE__ */ isThenable(result)) {
3059
+ logger.warn("useDragDrop accept predicate returned a thenable; async predicates are not supported — treating as reject");
3060
+ return false;
3061
+ }
3062
+ return Boolean(result);
3063
+ } catch (error) {
3064
+ logger.error("useDragDrop accept predicate threw; treating as reject", error);
3065
+ return false;
3066
+ }
3067
+ }
3068
+ const draggables = {
3069
+ ..._draggables,
3070
+ register(registration) {
3071
+ const id = registration.id ?? /* @__PURE__ */ useId$1();
3072
+ const el = toRef(() => toValue(registration.el));
3073
+ const dragging = toRef(() => active.value?.id === id);
3074
+ const input = {
3075
+ ...registration,
3076
+ id,
3077
+ el,
3078
+ isDragging: dragging
3079
+ };
3080
+ return _draggables.register(input);
3081
+ }
3082
+ };
3083
+ const zones = {
3084
+ ..._zones,
3085
+ register(registration) {
3086
+ const id = registration.id ?? /* @__PURE__ */ useId$1();
3087
+ if (_zones.has(id)) return _zones.register(registration);
3088
+ const el = toRef(() => toValue(registration.el));
3089
+ const isOver = toRef(() => active.value?.over === id);
3090
+ const willAccept = toRef(() => safeAccept(registration.accept, active.value));
3091
+ const rects = shallowRef([]);
3092
+ function refresh() {
3093
+ const zoneEl = el.value;
3094
+ rects.value = zoneEl ? Array.from(zoneEl.children).map((child) => child.getBoundingClientRect()) : [];
3095
+ }
3096
+ const scope = effectScope();
3097
+ scopes.set(id, scope);
3098
+ scope.run(() => {
3099
+ if (registration.orientation) {
3100
+ watch(el, refresh, {
3101
+ immediate: true,
3102
+ flush: "post"
3103
+ });
3104
+ useResizeObserver(el, refresh);
3105
+ useMutationObserver(el, refresh, { childList: true });
3106
+ watch(() => active.value?.id, (id) => {
3107
+ if (id) refresh();
3108
+ });
3109
+ }
3110
+ watch(el, (current) => {
3111
+ if (/* @__PURE__ */ isNull(current)) return;
3112
+ nodes.set(current, id);
3113
+ onWatcherCleanup(() => nodes.delete(current));
3114
+ }, { immediate: true });
3115
+ });
3116
+ const indicator = computed(() => {
3117
+ if (!registration.orientation || !isOver.value || !active.value) return null;
3118
+ return resolveDropPosition(active.value.current, rects.value, registration.orientation);
3119
+ });
3120
+ const input = {
3121
+ ...registration,
3122
+ id,
3123
+ el,
3124
+ isOver,
3125
+ willAccept,
3126
+ indicator
3127
+ };
3128
+ return _zones.register(input);
3129
+ }
3130
+ };
3131
+ function at(point) {
3132
+ if (!IN_BROWSER) return null;
3133
+ let element = document.elementFromPoint(point.x, point.y);
3134
+ while (element) {
3135
+ const id = nodes.get(element);
3136
+ if (!/* @__PURE__ */ isUndefined(id)) {
3137
+ const zone = _zones.get(id);
3138
+ if (zone && !toValue(zone.disabled)) return id;
3139
+ }
3140
+ element = element.parentElement;
3141
+ }
3142
+ return null;
3143
+ }
3144
+ function position(zoneId, drag) {
3145
+ const zone = _zones.get(zoneId);
3146
+ const out = { pointer: drag.current };
3147
+ if (zone?.orientation) {
3148
+ const resolved = zone.indicator?.value ?? null;
3149
+ if (resolved) {
3150
+ out.index = resolved.index;
3151
+ out.indicator = resolved;
3152
+ } else out.index = 0;
3153
+ }
3154
+ return out;
3155
+ }
3156
+ function bail(drag, reason = "cancel") {
3157
+ active.value = null;
3158
+ if (!/* @__PURE__ */ isNull(drag.over)) next(_zones.get(drag.over)?.onLeave, drag);
3159
+ next(_draggables.get(drag.id)?.onCancel, drag, reason);
3160
+ next(options.onCancel, drag, reason);
3161
+ }
3162
+ function onStart(source, origin, via) {
3163
+ if (toValue(_draggables.get(source.id)?.disabled)) return;
3164
+ const draft = {
3165
+ id: source.id,
3166
+ type: source.type,
3167
+ value: source.value,
3168
+ origin,
3169
+ current: origin,
3170
+ delta: {
3171
+ x: 0,
3172
+ y: 0
3173
+ },
3174
+ over: null,
3175
+ willAccept: false,
3176
+ via
3177
+ };
3178
+ try {
3179
+ if (_draggables.get(source.id)?.onBeforeStart?.(draft) === false) return;
3180
+ } catch (error) {
3181
+ logger.error("useDragDrop onBeforeStart threw; treating as veto", error);
3182
+ return;
3183
+ }
3184
+ try {
3185
+ if (options.onBeforeStart?.(draft) === false) return;
3186
+ } catch (error) {
3187
+ logger.error("useDragDrop onBeforeStart threw; treating as veto", error);
3188
+ return;
3189
+ }
3190
+ active.value = draft;
3191
+ }
3192
+ function onMove(point) {
3193
+ if (!active.value) return;
3194
+ const over = at(point);
3195
+ const draft = {
3196
+ ...active.value,
3197
+ current: point,
3198
+ delta: {
3199
+ x: point.x - active.value.origin.x,
3200
+ y: point.y - active.value.origin.y
3201
+ },
3202
+ over,
3203
+ willAccept: false
3204
+ };
3205
+ draft.willAccept = !/* @__PURE__ */ isNull(over) && safeAccept(_zones.get(over)?.accept, draft);
3206
+ const previous = active.value.over;
3207
+ active.value = draft;
3208
+ if (previous !== over) {
3209
+ if (!/* @__PURE__ */ isNull(previous)) next(_zones.get(previous)?.onLeave, draft);
3210
+ if (!/* @__PURE__ */ isNull(over)) next(_zones.get(over)?.onEnter, draft);
3211
+ }
3212
+ next(_draggables.get(draft.id)?.onMove, draft);
3213
+ next(options.onMove, draft);
3214
+ }
3215
+ function onDrop() {
3216
+ if (!active.value) return;
3217
+ const drag = active.value;
3218
+ const zoneId = drag.over;
3219
+ if (/* @__PURE__ */ isNull(zoneId)) {
3220
+ bail(drag);
3221
+ return;
3222
+ }
3223
+ const dropAt = position(zoneId, drag);
3224
+ const zone = _zones.get(zoneId);
3225
+ function vetoed(fn, label) {
3226
+ if (!fn) return false;
3227
+ try {
3228
+ return fn(drag, dropAt) === false;
3229
+ } catch (error) {
3230
+ logger.error(`useDragDrop ${label} threw; treating as veto`, error);
3231
+ return true;
3232
+ }
3233
+ }
3234
+ if (vetoed(zone?.onBeforeDrop, "zone onBeforeDrop") || vetoed(options.onBeforeDrop, "options onBeforeDrop")) {
3235
+ bail(drag, "reject");
3236
+ return;
3237
+ }
3238
+ active.value = null;
3239
+ next(zone?.onDrop, drag, dropAt);
3240
+ next(options.onDrop, drag, dropAt);
3241
+ }
3242
+ function cancel() {
3243
+ if (!active.value) return;
3244
+ bail(active.value);
3245
+ }
3246
+ const context = {
3247
+ draggables,
3248
+ zones,
3249
+ active: shallowReadonly(active),
3250
+ isDragging,
3251
+ cancel
3252
+ };
3253
+ const disposers = [];
3254
+ const adapters = options.adapters ?? [new PointerAdapter(), new KeyboardAdapter()];
3255
+ const emit = {
3256
+ start: (source, origin, via) => onStart(source, origin, via),
3257
+ move: (point) => onMove(point),
3258
+ drop: () => onDrop(),
3259
+ cancel: () => cancel()
3260
+ };
3261
+ const adapterContext = {
3262
+ ...context,
3263
+ emit
3264
+ };
3265
+ for (const adapter of adapters) try {
3266
+ adapter.setup(adapterContext);
3267
+ disposers.push(() => adapter.dispose());
3268
+ } catch (error) {
3269
+ logger.error("useDragDrop adapter setup threw; skipping adapter", error);
3270
+ try {
3271
+ adapter.dispose();
3272
+ } catch (disposeError) {
3273
+ logger.error("useDragDrop adapter dispose threw after setup failure", disposeError);
3274
+ }
3275
+ }
3276
+ for (const plugin of plugins) try {
3277
+ const dispose = plugin(context);
3278
+ if (/* @__PURE__ */ isFunction(dispose)) disposers.push(dispose);
3279
+ } catch (error) {
3280
+ logger.error("useDragDrop plugin install threw; skipping plugin", error);
3281
+ }
3282
+ _draggables.on("unregister:ticket", (ticket) => {
3283
+ if (active.value?.id === ticket.id) cancel();
3284
+ });
3285
+ _zones.on("unregister:ticket", (ticket) => {
3286
+ const scope = scopes.get(ticket.id);
3287
+ if (scope) {
3288
+ scope.stop();
3289
+ scopes.delete(ticket.id);
3290
+ }
3291
+ if (active.value?.over === ticket.id) {
3292
+ next(ticket.onLeave, active.value);
3293
+ active.value = {
3294
+ ...active.value,
3295
+ over: null,
3296
+ willAccept: false
3297
+ };
3298
+ }
3299
+ });
3300
+ onScopeDispose(() => {
3301
+ if (!/* @__PURE__ */ isNull(active.value)) cancel();
3302
+ for (const dispose of disposers.toReversed()) try {
3303
+ dispose();
3304
+ } catch (error) {
3305
+ logger.error("useDragDrop disposer threw", error);
3306
+ }
3307
+ for (const scope of scopes.values()) try {
3308
+ scope.stop();
3309
+ } catch (error) {
3310
+ logger.error("useDragDrop zone scope dispose threw", error);
3311
+ }
3312
+ scopes.clear();
3313
+ nodes.clear();
3314
+ try {
3315
+ _draggables.dispose();
3316
+ } catch (error) {
3317
+ logger.error("useDragDrop draggables dispose threw", error);
3318
+ }
3319
+ try {
3320
+ _zones.dispose();
3321
+ } catch (error) {
3322
+ logger.error("useDragDrop zones dispose threw", error);
3323
+ }
3324
+ }, true);
3325
+ return context;
3326
+ }
3327
+ //#endregion
3328
+ //#region src/composables/useHotkey/aliases.ts
3329
+ /**
3330
+ * Key alias mapping for consistent key normalization across the hotkey system.
3331
+ *
3332
+ * Maps user-friendly aliases to canonical key names that match
3333
+ * KeyboardEvent.key values (in lowercase) where possible.
3334
+ */
3335
+ const keyAliasMap = {
3336
+ control: "ctrl",
3337
+ command: "cmd",
3338
+ option: "alt",
3339
+ up: "arrowup",
3340
+ down: "arrowdown",
3341
+ left: "arrowleft",
3342
+ right: "arrowright",
3343
+ esc: "escape",
3344
+ spacebar: " ",
3345
+ space: " ",
3346
+ return: "enter",
3347
+ del: "delete",
3348
+ plus: "+",
3349
+ slash: "/",
3350
+ underscore: "_",
3351
+ minus: "-",
3352
+ hyphen: "-"
3353
+ };
3354
+ /* @__NO_SIDE_EFFECTS__ */
3355
+ function normalizeKey(key) {
3356
+ const lowerKey = key.toLowerCase();
3357
+ return keyAliasMap[lowerKey] ?? lowerKey;
3358
+ }
3359
+ //#endregion
3360
+ //#region src/composables/useHotkey/parsing.ts
3361
+ const logger = /* @__PURE__ */ useLogger();
3362
+ const MODIFIERS = [
3363
+ "ctrl",
3364
+ "shift",
3365
+ "alt",
3366
+ "meta",
3367
+ "cmd"
3368
+ ];
3369
+ /**
3370
+ * Splits a single combination string into individual key parts.
3371
+ *
3372
+ * A combination is a set of keys that must be pressed simultaneously.
3373
+ * e.g. `ctrl+k`, `shift+-`
3374
+ *
3375
+ * @param combination - The combination string to parse
3376
+ * @param isInternal - Whether this is an internal call (suppresses warnings)
3377
+ * @returns Object with keys array and separators array
3378
+ */
3379
+ function splitKeyCombination(combination, isInternal = false) {
3380
+ const emptyResult = {
3381
+ keys: [],
3382
+ separators: []
3383
+ };
3384
+ if (!combination) {
3385
+ if (!isInternal) logger.warn("Invalid hotkey combination: empty string provided");
3386
+ return emptyResult;
3387
+ }
3388
+ if (combination.length > 1 && [
3389
+ "+",
3390
+ "/",
3391
+ "_"
3392
+ ].some((v) => combination.startsWith(v)) && ![
3393
+ "++",
3394
+ "//",
3395
+ "__"
3396
+ ].some((v) => combination.startsWith(v)) || combination.includes("++") || combination.includes("//") || combination.includes("__") || combination === "+" || combination === "/" || combination === "_" || combination.length > 1 && (combination.endsWith("+") || combination.endsWith("/") || combination.endsWith("_")) && combination.at(-2) !== combination.at(-1) || combination === "++" || combination === "--" || combination === "__") {
3397
+ if (!isInternal) logger.warn(`Invalid hotkey combination: "${combination}" has invalid structure`);
3398
+ return emptyResult;
3399
+ }
3400
+ const keys = [];
3401
+ const separators = [];
3402
+ let buffer = "";
3403
+ function flushBuffer(separator) {
3404
+ if (buffer) {
3405
+ if (separator) separators.push(separator);
3406
+ keys.push(/* @__PURE__ */ normalizeKey(buffer));
3407
+ buffer = "";
3408
+ }
3409
+ }
3410
+ for (let i = 0; i < combination.length; i++) {
3411
+ const char = combination[i];
3412
+ const nextChar = combination[i + 1];
3413
+ if ([
3414
+ "+",
3415
+ "/",
3416
+ "_",
3417
+ "-"
3418
+ ].includes(char))
3419
+ /* v8 ignore start -- edge case: doubled special chars */
3420
+ if (char === nextChar) {
3421
+ flushBuffer(char);
3422
+ keys.push(char);
3423
+ i++;
3424
+ } else if ([
3425
+ "+",
3426
+ "/",
3427
+ "_"
3428
+ ].includes(char)) flushBuffer(char);
3429
+ else buffer += char;
3430
+ else buffer += char;
3431
+ }
3432
+ flushBuffer();
3433
+ /* v8 ignore start -- edge case: invalid minus in combination */
3434
+ if (keys.some((key) => key.length > 1 && key.includes("-") && key !== "--")) {
3435
+ if (!isInternal) logger.warn(`Invalid hotkey combination: "${combination}" has invalid structure`);
3436
+ return emptyResult;
3437
+ }
3438
+ if (keys.length === 0 && combination) return {
3439
+ keys: [/* @__PURE__ */ normalizeKey(combination)],
3440
+ separators
3441
+ };
3442
+ /* v8 ignore stop */
3443
+ return {
3444
+ keys,
3445
+ separators
3446
+ };
3447
+ }
3448
+ /**
3449
+ * Splits a hotkey string into its constituent combination groups.
3450
+ *
3451
+ * A sequence is a series of combinations that must be pressed in order.
3452
+ * e.g. `a-b`, `ctrl+k-p`
3453
+ *
3454
+ * @param str - The sequence string to parse
3455
+ * @returns Array of combination strings
3456
+ */
3457
+ function splitKeySequence(str) {
3458
+ if (!str) {
3459
+ logger.warn("Invalid hotkey sequence: empty string provided");
3460
+ return [];
3461
+ }
3462
+ const hasInvalidStart = str.startsWith("-") && ![
3463
+ "-",
3464
+ "---",
3465
+ "--+"
3466
+ ].includes(str);
3467
+ const hasInvalidEnd = str.endsWith("-") && !str.endsWith("+-") && !str.endsWith("_-") && str !== "-" && str !== "---";
3468
+ if (hasInvalidStart || hasInvalidEnd) {
3469
+ logger.warn(`Invalid hotkey sequence: "${str}" contains invalid combinations`);
3470
+ return [];
3471
+ }
3472
+ const result = [];
3473
+ let buffer = "";
3474
+ let i = 0;
3475
+ while (i < str.length) {
3476
+ const char = str[i];
3477
+ if (char === "-") {
3478
+ const prevChar = str[i - 1] ?? "";
3479
+ const prevPrevChar = i > 1 ? str[i - 2] : void 0;
3480
+ if (["+", "_"].includes(prevChar) && !["+", "/"].includes(prevPrevChar ?? "")) {
3481
+ buffer += char;
3482
+ i++;
3483
+ } else {
3484
+ if (buffer) {
3485
+ result.push(buffer);
3486
+ buffer = "";
3487
+ } else result.push("-");
3488
+ i++;
3489
+ }
3490
+ } else {
3491
+ buffer += char;
3492
+ i++;
3493
+ }
3494
+ }
3495
+ if (buffer) result.push(buffer);
3496
+ const collapsed = [];
3497
+ let minusCount = 0;
3498
+ for (const part of result)
3499
+ /* v8 ignore start -- edge case: minus collapsing */
3500
+ if (part === "-") {
3501
+ if (minusCount % 2 === 0) collapsed.push("-");
3502
+ minusCount++;
3503
+ } else {
3504
+ minusCount = 0;
3505
+ collapsed.push(part);
3506
+ }
3507
+ /* v8 ignore start -- edge case: invalid combination in sequence */
3508
+ if (!collapsed.every((s) => splitKeyCombination(s, true).keys.length > 0)) {
3509
+ logger.warn(`Invalid hotkey sequence: "${str}" contains invalid combinations`);
3510
+ return [];
3511
+ }
3512
+ /* v8 ignore stop */
3513
+ return collapsed;
3514
+ }
3515
+ //#endregion
3516
+ //#region src/composables/useHotkey/index.ts
3517
+ /**
3518
+ * @module useHotkey
3519
+ *
3520
+ * @see https://0.vuetifyjs.com/composables/system/use-hotkey
3521
+ *
3522
+ * @remarks
3523
+ * Hotkey listener composable with key combination and sequence support.
3524
+ *
3525
+ * Key features:
3526
+ * - Key combination support (`ctrl+k`, `shift+enter`)
3527
+ * - Key sequence support (`g-h` for GitHub-style shortcuts)
3528
+ * - Platform-aware modifiers (Mac: cmd→meta, others: cmd→ctrl)
3529
+ * - Input focus detection (skip when typing in inputs)
3530
+ * - Sequence timeout with automatic reset
3531
+ * - Key and symbol word alias normalization (`plus` → `+`, `esc` → `escape`, etc.)
3532
+ * - Pause/resume/stop functionality
3533
+ * - Automatic cleanup on scope disposal
3534
+ * - SSR-safe (no-op when not in browser)
3535
+ *
3536
+ * Builds on useEventListener for lifecycle-managed keyboard events.
3537
+ *
3538
+ * @example
3539
+ * ```ts
3540
+ * import { useHotkey } from '@vuetify/v0'
3541
+ *
3542
+ * useHotkey('ctrl+k', () => console.log('opened palette'))
3543
+ * useHotkey('g-h', () => console.log('go home'))
3544
+ * ```
3545
+ */
3546
+ const isMac = IN_BROWSER && /mac|iphone|ipad|ipod/i.test(navigator?.userAgent ?? "");
3547
+ /**
3548
+ * A composable that listens for hotkey combinations and sequences.
3549
+ *
3550
+ * @param keys - The hotkey string (e.g., 'ctrl+k', 'g-h')
3551
+ * @param callback - The function to call when the hotkey is triggered
3552
+ * @param options - Configuration options
3553
+ * @returns An object with state refs and control methods
3554
+ *
3555
+ * @see https://0.vuetifyjs.com/composables/system/use-hotkey
3556
+ *
3557
+ * @example
3558
+ * ```ts
3559
+ * import { useHotkey } from '@vuetify/v0'
3560
+ *
3561
+ * // Simple combination
3562
+ * const { isActive, pause, resume } = useHotkey('ctrl+k', () => {
3563
+ * console.log('Command palette opened')
3564
+ * })
3565
+ *
3566
+ * // Key sequence (GitHub-style)
3567
+ * useHotkey('g-h', () => console.log('Go home'))
3568
+ *
3569
+ * // With options
3570
+ * useHotkey('escape', closeModal, { inputs: true })
3571
+ *
3572
+ * // Pause/resume control
3573
+ * pause() // Temporarily disable
3574
+ * resume() // Re-enable
3575
+ * ```
3576
+ */
3577
+ function useHotkey(keys, callback, options = {}, _platform) {
3578
+ const platformIsMac = _platform?.isMac ?? isMac;
3579
+ const { event = "keydown", inputs = false, preventDefault = true, stopPropagation = false, sequenceTimeout = 1e3 } = options;
3580
+ const isPaused = shallowRef(false);
3581
+ const cleanup = shallowRef(null);
3582
+ const isActive = toRef(() => !/* @__PURE__ */ isNull(cleanup.value));
3583
+ let sequenceTimer;
3584
+ let keyGroups = [];
3585
+ let isSequence = false;
3586
+ let groupIndex = 0;
3587
+ function isInputFocused() {
3588
+ /* v8 ignore next -- IN_BROWSER always true in happy-dom test env */
3589
+ if (!IN_BROWSER) return false;
3590
+ if (toValue(inputs)) return false;
3591
+ const activeElement = document.activeElement;
3592
+ /* v8 ignore next -- defensive: document.activeElement is always set in happy-dom */
3593
+ if (!activeElement) return false;
3594
+ const role = activeElement.getAttribute("role");
3595
+ if (role === "textbox" || role === "searchbox" || role === "combobox" || role === "spinbutton") return true;
3596
+ if (activeElement.tagName === "INPUT") {
3597
+ const type = activeElement.type?.toLowerCase();
3598
+ return [
3599
+ "text",
3600
+ "email",
3601
+ "password",
3602
+ "search",
3603
+ "tel",
3604
+ "url",
3605
+ "number",
3606
+ "date",
3607
+ "datetime-local",
3608
+ "time",
3609
+ "month",
3610
+ "week"
3611
+ ].includes(type);
3612
+ }
3613
+ return activeElement.tagName === "TEXTAREA" || activeElement.isContentEditable;
3614
+ }
3615
+ function resetSequence() {
3616
+ groupIndex = 0;
3617
+ clearTimeout(sequenceTimer);
3618
+ }
3619
+ function handler(e) {
3620
+ const group = keyGroups[groupIndex];
3621
+ if (!group || isInputFocused()) return;
3622
+ if (!matchesKeyGroup(e, group, platformIsMac)) {
3623
+ if (isSequence) resetSequence();
3624
+ return;
3625
+ }
3626
+ if (toValue(stopPropagation)) e.stopPropagation();
3627
+ if (toValue(preventDefault)) e.preventDefault();
3628
+ if (!isSequence) {
3629
+ callback(e);
3630
+ return;
3631
+ }
3632
+ clearTimeout(sequenceTimer);
3633
+ groupIndex++;
3634
+ if (groupIndex === keyGroups.length) {
3635
+ callback(e);
3636
+ resetSequence();
3637
+ return;
3638
+ }
3639
+ sequenceTimer = setTimeout(resetSequence, toValue(sequenceTimeout));
3640
+ }
3641
+ function setup() {
3642
+ if (!IN_BROWSER || isPaused.value) return;
3643
+ const currentKeys = toValue(keys);
3644
+ if (!currentKeys) return;
3645
+ const groups = splitKeySequence(currentKeys.toLowerCase());
3646
+ /* v8 ignore next -- defensive: splitKeySequence rejects empty strings earlier */
3647
+ if (groups.length === 0) return;
3648
+ isSequence = groups.length > 1;
3649
+ keyGroups = groups;
3650
+ resetSequence();
3651
+ cleanup.value = useWindowEventListener(toValue(event), handler);
3652
+ }
3653
+ function dispose() {
3654
+ if (cleanup.value) {
3655
+ cleanup.value();
3656
+ cleanup.value = null;
3657
+ }
3658
+ clearTimeout(sequenceTimer);
3659
+ keyGroups = [];
3660
+ isSequence = false;
3661
+ groupIndex = 0;
3662
+ }
3663
+ function pause() {
3664
+ isPaused.value = true;
3665
+ dispose();
3666
+ }
3667
+ function resume() {
3668
+ isPaused.value = false;
3669
+ setup();
3670
+ }
3671
+ function stop() {
3672
+ dispose();
3673
+ }
3674
+ watch(() => toValue(keys), () => {
3675
+ if (isPaused.value) return;
3676
+ dispose();
3677
+ setup();
3678
+ }, { immediate: true });
3679
+ watch(() => toValue(event), () => {
3680
+ if (isPaused.value || !toValue(keys)) return;
3681
+ dispose();
3682
+ setup();
3683
+ });
3684
+ onScopeDispose(stop, true);
3685
+ return {
3686
+ isActive: shallowReadonly(isActive),
3687
+ isPaused: shallowReadonly(isPaused),
3688
+ pause,
3689
+ resume,
3690
+ stop
3691
+ };
3692
+ }
3693
+ function isModifier(key) {
3694
+ return MODIFIERS.includes(key);
3695
+ }
3696
+ function parseKeyGroup(group) {
3697
+ const { keys: parts } = splitKeyCombination(group.toLowerCase());
3698
+ const modifiers = Object.fromEntries(MODIFIERS.map((m) => [m, false]));
3699
+ /* v8 ignore next 3 -- defensive: parseKeyGroup is not invoked with empty parts */
3700
+ if (parts.length === 0) return {
3701
+ modifiers,
3702
+ actualKey: void 0
3703
+ };
3704
+ let actualKey;
3705
+ for (const part of parts) if (isModifier(part)) modifiers[part] = true;
3706
+ else actualKey = part;
3707
+ return {
3708
+ modifiers,
3709
+ actualKey
3710
+ };
3711
+ }
3712
+ function matchesKeyGroup(e, group, platformIsMac) {
3713
+ const { modifiers, actualKey } = parseKeyGroup(group);
3714
+ const expectCtrl = modifiers.ctrl || !platformIsMac && (modifiers.cmd || modifiers.meta);
3715
+ const expectMeta = platformIsMac && (modifiers.cmd || modifiers.meta);
3716
+ const modifiersMatch = e.ctrlKey === expectCtrl && e.metaKey === expectMeta && e.shiftKey === modifiers.shift && e.altKey === modifiers.alt;
3717
+ if (/* @__PURE__ */ isUndefined(actualKey)) return modifiersMatch;
3718
+ return modifiersMatch && e.key.toLowerCase() === actualKey.toLowerCase();
3719
+ }
3720
+ //#endregion
3721
+ //#region src/composables/useMediaQuery/index.ts
3722
+ /**
3723
+ * @module useMediaQuery
3724
+ *
3725
+ * @see https://0.vuetifyjs.com/composables/system/use-media-query
3726
+ *
3727
+ * @remarks
3728
+ * Reactive media query composable with automatic cleanup.
3729
+ *
3730
+ * Key features:
3731
+ * - Synchronous matchMedia read on client mount (prevents FOUC)
3732
+ * - Window matchMedia integration
3733
+ * - Reactive query string support
3734
+ * - SSR-safe (returns false on server)
3735
+ * - Hydration-aware (defers client updates until hydrated)
3736
+ * - Automatic listener cleanup on scope disposal
3737
+ * - Supports both static and dynamic queries
3738
+ *
3739
+ * Perfect for responsive conditionals beyond breakpoint detection.
3740
+ *
3741
+ * @example
3742
+ * ```ts
3743
+ * import { useMediaQuery } from '@vuetify/v0'
3744
+ *
3745
+ * const { matches } = useMediaQuery('(prefers-color-scheme: dark)')
3746
+ * console.log(matches.value) // true/false
3747
+ * ```
3748
+ */
3749
+ /**
3750
+ * Reactive media query matching.
3751
+ *
3752
+ * @param query CSS media query string (reactive).
3753
+ * @returns The media query context.
3754
+ *
3755
+ * @see https://0.vuetifyjs.com/composables/system/use-media-query
3756
+ *
3757
+ * @example
3758
+ * ```ts
3759
+ * // Static query
3760
+ * const { matches } = useMediaQuery('(prefers-color-scheme: dark)')
3761
+ *
3762
+ * // Dynamic query
3763
+ * const minWidth = ref(768)
3764
+ * const { matches } = useMediaQuery(() => `(min-width: ${minWidth.value}px)`)
3765
+ *
3766
+ * // Manual cleanup
3767
+ * const { matches, stop } = useMediaQuery('(hover: hover)')
3768
+ * stop() // Remove listener early
3769
+ * ```
3770
+ */
3771
+ function useMediaQuery(query) {
3772
+ const resolvedQuery = toRef(() => toValue(query));
3773
+ const initialQuery = toValue(query);
3774
+ const initialMql = IN_BROWSER && SUPPORTS_MATCH_MEDIA ? window.matchMedia(initialQuery) : null;
3775
+ const matches = shallowRef(initialMql?.matches ?? false);
3776
+ const mediaQueryList = shallowRef(initialMql);
3777
+ const hydration = useHydration();
3778
+ let cleanup = null;
3779
+ function listen() {
3780
+ if (!IN_BROWSER || !SUPPORTS_MATCH_MEDIA) return;
3781
+ cleanup?.();
3782
+ cleanup = null;
3783
+ const mql = window.matchMedia(resolvedQuery.value);
3784
+ mediaQueryList.value = mql;
3785
+ matches.value = mql.matches;
3786
+ function handler(e) {
3787
+ matches.value = e.matches;
3788
+ }
3789
+ mql.addEventListener("change", handler);
3790
+ cleanup = () => mql.removeEventListener("change", handler);
3791
+ }
3792
+ const stopWatch = watch([resolvedQuery, () => hydration.isHydrated.value], ([_, hydrated]) => {
3793
+ if (hydrated) listen();
3794
+ }, { immediate: true });
3795
+ function stop() {
3796
+ stopWatch();
3797
+ cleanup?.();
3798
+ cleanup = null;
3799
+ }
3800
+ onScopeDispose(stop, true);
3801
+ return {
3802
+ matches: shallowReadonly(matches),
3803
+ query: resolvedQuery,
3804
+ mediaQueryList: shallowReadonly(mediaQueryList),
3805
+ stop
3806
+ };
3807
+ }
3808
+ /**
3809
+ * Check if the user prefers dark color scheme.
3810
+ *
3811
+ * @returns The media query context.
3812
+ *
3813
+ * @example
3814
+ * ```ts
3815
+ * const { matches: prefersDark } = usePrefersDark()
3816
+ * ```
3817
+ */
3818
+ function usePrefersDark() {
3819
+ return useMediaQuery("(prefers-color-scheme: dark)");
3820
+ }
3821
+ /**
3822
+ * Check if the user prefers reduced motion.
3823
+ *
3824
+ * @returns The media query context.
3825
+ *
3826
+ * @example
3827
+ * ```ts
3828
+ * const { matches: prefersReducedMotion } = usePrefersReducedMotion()
3829
+ * ```
3830
+ */
3831
+ function usePrefersReducedMotion() {
3832
+ return useMediaQuery("(prefers-reduced-motion: reduce)");
3833
+ }
3834
+ /**
3835
+ * Check if the user prefers more contrast.
3836
+ *
3837
+ * @returns The media query context.
3838
+ *
3839
+ * @example
3840
+ * ```ts
3841
+ * const { matches: prefersContrast } = usePrefersContrast()
3842
+ * ```
3843
+ */
3844
+ function usePrefersContrast() {
3845
+ return useMediaQuery("(prefers-contrast: more)");
3846
+ }
3847
+ //#endregion
3848
+ //#region src/composables/useReducedMotion/adapters/adapter.ts
3849
+ var ReducedMotionAdapter = class {
3850
+ /**
3851
+ * Teardown for any side-effects started in `setup`. The plugin registers this on
3852
+ * `app.onUnmount`, so adapter effects are cleaned up even though `setup` runs
3853
+ * outside an effect scope (where `onScopeDispose` would silently no-op). It is
3854
+ * read lazily at app unmount, so adapters may assign it at any point during or
3855
+ * after `setup`.
3856
+ */
3857
+ dispose;
3858
+ };
3859
+ //#endregion
3860
+ //#region src/composables/useReducedMotion/adapters/v0.ts
3861
+ function payload(reduced) {
3862
+ return reduced ? "reduce" : "no-preference";
3863
+ }
3864
+ /**
3865
+ * Default reduced-motion adapter. In the browser it writes
3866
+ * `document.body.dataset.reducedMotion`; on the server it renders the same
3867
+ * attribute through `@unhead` so the value is present in the initial HTML.
3868
+ */
3869
+ var V0ReducedMotionAdapter = class extends ReducedMotionAdapter {
3870
+ setup(app, context) {
3871
+ if (IN_BROWSER) {
3872
+ function sync(reduced) {
3873
+ document.body.dataset.reducedMotion = payload(reduced);
3874
+ }
3875
+ sync(context.isReduced.value);
3876
+ this.dispose = watch(context.isReduced, sync);
3877
+ } else {
3878
+ const head = app._context?.provides?.usehead ?? app._context?.provides?.head;
3879
+ if (head?.push) {
3880
+ const entry = head.push({ bodyAttrs: { "data-reduced-motion": payload(context.isReduced.value) } });
3881
+ const stop = watch(context.isReduced, (reduced) => entry.patch?.({ bodyAttrs: { "data-reduced-motion": payload(reduced) } }));
3882
+ this.dispose = () => {
3883
+ stop();
3884
+ entry.dispose?.();
3885
+ };
3886
+ }
3887
+ }
3888
+ }
3889
+ };
3890
+ //#endregion
3891
+ //#region src/composables/useReducedMotion/index.ts
3892
+ /**
3893
+ * @module useReducedMotion
3894
+ *
3895
+ * @see https://0.vuetifyjs.com/composables/plugins/use-reduced-motion
3896
+ *
3897
+ * @remarks
3898
+ * Reduced-motion plugin composable for respecting or overriding the
3899
+ * `prefers-reduced-motion` media query.
3900
+ *
3901
+ * Key features:
3902
+ * - Three-way `mode`: `'system'` (honor OS setting), `'always'` (force reduce),
3903
+ * `'never'` (force full motion)
3904
+ * - Reactive `isReduced` boolean — true when motion should be minimized
3905
+ * - When installed as a plugin, the default `V0ReducedMotionAdapter` writes
3906
+ * `document.body.dataset.reducedMotion = 'reduce' | 'no-preference'` in the browser.
3907
+ * On the server it renders the same attribute via `@unhead` with the configured
3908
+ * mode — the OS preference is only readable in the browser, so in `'system'` mode
3909
+ * the server value is always `'no-preference'` until client takeover; pair the
3910
+ * attribute selector with a native `@media (prefers-reduced-motion: reduce)` rule
3911
+ * for pre-hydration coverage
3912
+ * - SSR-safe: no `window`/`document` access on the server
3913
+ *
3914
+ * @example
3915
+ * ```ts
3916
+ * import { createReducedMotionPlugin, useReducedMotion } from '@vuetify/v0'
3917
+ *
3918
+ * // install once
3919
+ * app.use(createReducedMotionPlugin({ mode: 'system' }))
3920
+ *
3921
+ * // consume anywhere
3922
+ * const motion = useReducedMotion()
3923
+ * console.log(motion.isReduced.value) // true | false
3924
+ * motion.select('never') // override
3925
+ * ```
3926
+ */
3927
+ /**
3928
+ * Creates a standalone reduced-motion context.
3929
+ *
3930
+ * @param options Initial options.
3931
+ * @returns A `ReducedMotionContext`.
3932
+ *
3933
+ * @see https://0.vuetifyjs.com/composables/plugins/use-reduced-motion
3934
+ *
3935
+ * @example
3936
+ * ```ts
3937
+ * import { createReducedMotion } from '@vuetify/v0'
3938
+ *
3939
+ * const motion = createReducedMotion({ mode: 'system' })
3940
+ *
3941
+ * console.log(motion.isReduced.value) // true | false
3942
+ * motion.select('always')
3943
+ * ```
3944
+ */
3945
+ function createReducedMotion(options = {}) {
3946
+ const selectedMode = shallowRef(options.mode ?? "system");
3947
+ const media = usePrefersReducedMotion();
3948
+ const isReduced = toRef(() => {
3949
+ if (selectedMode.value === "always") return true;
3950
+ if (selectedMode.value === "never") return false;
3951
+ return media.matches.value;
3952
+ });
3953
+ function select(value) {
3954
+ selectedMode.value = value;
3955
+ }
3956
+ return {
3957
+ selectedMode: shallowReadonly(selectedMode),
3958
+ isReduced,
3959
+ select,
3960
+ dispose: media.stop
3961
+ };
3962
+ }
3963
+ function createReducedMotionFallback() {
3964
+ return {
3965
+ selectedMode: shallowReadonly(shallowRef("system")),
3966
+ isReduced: shallowReadonly(shallowRef(false)),
3967
+ select: () => {},
3968
+ dispose: () => {}
3969
+ };
3970
+ }
3971
+ const [createReducedMotionContext, createReducedMotionPlugin, useReducedMotion] = createPluginContext("v0:reduced-motion", (options) => createReducedMotion(options), {
3972
+ fallback: () => createReducedMotionFallback(),
3973
+ persist: (context) => context.selectedMode.value,
3974
+ restore: (context, saved) => {
3975
+ if (saved === "system" || saved === "always" || saved === "never") context.select(saved);
3976
+ },
3977
+ setup: (context, app, { adapter = new V0ReducedMotionAdapter() }) => {
3978
+ app.onUnmount(() => context.dispose());
3979
+ adapter.setup(app, context);
3980
+ app.onUnmount(() => adapter.dispose?.());
3981
+ }
3982
+ });
3983
+ //#endregion
3984
+ //#region src/composables/useRtl/adapters/adapter.ts
3985
+ var RtlAdapter = class {
3986
+ /**
3987
+ * Teardown registered by `setup`. Called once via `app.onUnmount` on the
3988
+ * plugin path and available as a reachable handle on standalone paths.
3989
+ */
3990
+ dispose;
3991
+ };
3992
+ //#endregion
3993
+ //#region src/composables/useRtl/adapters/v0.ts
3994
+ var V0RtlAdapter = class extends RtlAdapter {
3995
+ setup(app, context, target) {
3996
+ if (IN_BROWSER) {
3997
+ if (/* @__PURE__ */ isNull(target)) return;
3998
+ const targetEl = target instanceof HTMLElement ? target : /* @__PURE__ */ isString(target) ? document.querySelector(target) : document.documentElement;
3999
+ if (!targetEl) return;
4000
+ targetEl.dir = context.isRtl.value ? "rtl" : "ltr";
4001
+ this.dispose = watch(context.isRtl, (value) => {
4002
+ targetEl.dir = value ? "rtl" : "ltr";
4003
+ });
4004
+ } else {
4005
+ const head = app._context?.provides?.usehead ?? app._context?.provides?.head;
4006
+ if (head?.push) {
4007
+ const entry = head.push({ htmlAttrs: { dir: context.isRtl.value ? "rtl" : "ltr" } });
4008
+ if (entry?.dispose) this.dispose = entry.dispose;
4009
+ }
4010
+ }
4011
+ }
4012
+ };
4013
+ //#endregion
4014
+ //#region src/composables/useRtl/index.ts
4015
+ /**
4016
+ * @module useRtl
4017
+ *
4018
+ * @see https://0.vuetifyjs.com/composables/plugins/use-rtl
4019
+ *
4020
+ * @remarks
4021
+ * RTL (right-to-left) direction composable with adapter pattern.
4022
+ *
4023
+ * Key features:
4024
+ * - Reactive boolean direction state
4025
+ * - Plugin trinity pattern (createRtlContext, createRtlPlugin, useRtl)
4026
+ * - Adapter pattern for DOM integration (dir attribute)
4027
+ * - Subtree overrides via context provision
4028
+ *
4029
+ * Independent from useLocale — Vuetify connects them via adapter.
4030
+ *
4031
+ * @example
4032
+ * ```ts
4033
+ * import { useRtl } from '@vuetify/v0'
4034
+ *
4035
+ * const { isRtl, toggle } = useRtl()
4036
+ * toggle() // flip direction
4037
+ * ```
4038
+ */
4039
+ /**
4040
+ * Creates a new RTL direction instance.
4041
+ *
4042
+ * @param options The options for the RTL instance.
4043
+ * @returns A new RTL context with reactive direction state.
4044
+ *
4045
+ * @see https://0.vuetifyjs.com/composables/plugins/use-rtl
4046
+ */
4047
+ function createRtl(options = {}) {
4048
+ const isRtl = shallowRef(options.default ?? false);
4049
+ function toggle() {
4050
+ isRtl.value = !isRtl.value;
4051
+ }
4052
+ return {
4053
+ isRtl,
4054
+ toggle,
4055
+ dispose: () => {}
4056
+ };
4057
+ }
4058
+ function createRtlFallback() {
4059
+ return {
4060
+ isRtl: shallowRef(false),
4061
+ toggle: () => {},
4062
+ dispose: () => {}
4063
+ };
4064
+ }
4065
+ const [createRtlContext, createRtlPlugin, useRtl] = createPluginContext("v0:rtl", (options) => createRtl(options), {
4066
+ fallback: () => createRtlFallback(),
4067
+ setup: (context, app, { adapter = new V0RtlAdapter(), target }) => {
4068
+ adapter.setup(app, context, target);
4069
+ app.onUnmount(() => adapter.dispose?.());
4070
+ },
4071
+ persist: (ctx) => ctx.isRtl.value,
4072
+ restore: (ctx, saved) => {
4073
+ if (typeof saved === "boolean") ctx.isRtl.value = saved;
4074
+ }
4075
+ });
4076
+ //#endregion
4077
+ export { createBreakpointsPlugin as A, createOtp as B, useMutationObserver as C, useDate as D, createDatePlugin as E, createVirtualContext as F, createSortable as G, createDataGrid as H, useVirtual as I, ServerComboboxAdapter as K, createTimeline as L, toReactive as M, toHighlight as N, createBreakpoints as O, createVirtual as P, createTimelineContext as R, DragDropAdapter as S, createDateContext as T, createDataGridContext as U, createKanban as V, useDataGrid as W, usePrefersReducedMotion as _, useRtl as a, KeyboardAdapter as b, createReducedMotion as c, useReducedMotion as d, V0ReducedMotionAdapter as f, usePrefersDark as g, usePrefersContrast as h, createRtlPlugin as i, useBreakpoints as j, createBreakpointsContext as k, createReducedMotionContext as l, useMediaQuery as m, createRtlContext as n, V0RtlAdapter as o, ReducedMotionAdapter as p, createRtlFallback as r, RtlAdapter as s, createRtl as t, createReducedMotionPlugin as u, useHotkey as v, createDate as w, PointerAdapter as x, useDragDrop as y, useTimeline as z };