@vuetify/v0 1.0.0-alpha.5 → 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -5
- package/dist/{adapter-BFSJsyv7.mjs → adapter-BCVxBtDp.mjs} +1 -1
- package/dist/{adapter-CspReV3g.d.mts → adapter-DZeI0ZbW.d.mts} +1 -1
- package/dist/{adapter-oM18AdUc.mjs → adapter-DdTval2R.mjs} +1 -1
- package/dist/{adapter-DDJHLC8K.d.mts → adapter-we-0GbFH.d.mts} +2 -2
- package/dist/adapters-B350nY4t.mjs +4 -0
- package/dist/adapters-BYLWDmYl.mjs +5 -0
- package/dist/adapters-DKWBbvCe.mjs +4 -0
- package/dist/adapters-Da2jxCo7.mjs +2 -0
- package/dist/browser/index.js +2532 -916
- package/dist/components/index.d.mts +2 -3
- package/dist/components/index.mjs +2 -2
- package/dist/{components-Dn4Kbi0i.mjs → components-B210m9Ye.mjs} +1053 -493
- package/dist/composables/index.d.mts +8 -8
- package/dist/composables/index.mjs +26 -26
- package/dist/{consola-DYvbPNbF.mjs → consola-cad-VEV2.mjs} +5 -1
- package/dist/constants/index.mjs +1 -1
- package/dist/{createDataTable-H-PuVigU.mjs → createDataTable-B-Fo-uuL.mjs} +6 -6
- package/dist/{createGroup-aHt_kvtU.mjs → createGroup-CpaW7zEJ.mjs} +26 -14
- package/dist/{createRegistry-CWpvSbCe.mjs → createRegistry-8FryYQC-.mjs} +56 -41
- package/dist/{createSelection-MRyUZyW2.mjs → createSelection-DPOvmJ5q.mjs} +2 -2
- package/dist/{createSingle-BSciC9EJ.mjs → createSingle-CYi0RuOy.mjs} +4 -4
- package/dist/{createTokens-CQ9C7GTV.mjs → createTokens-DaJ_SYE7.mjs} +12 -5
- package/dist/{createTrinity-BTCs1pYx.mjs → createTrinity-z-3q9QWR.mjs} +5 -2
- package/dist/data-table/adapters/client/index.mjs +1 -1
- package/dist/data-table/adapters/index.mjs +5 -5
- package/dist/data-table/adapters/server/index.mjs +1 -1
- package/dist/data-table/adapters/virtual/index.mjs +1 -1
- package/dist/data-table/index.d.mts +1 -1
- package/dist/data-table/index.mjs +5 -5
- package/dist/date/index.mjs +1 -1
- package/dist/features/adapters/flagsmith/index.d.mts +1 -1
- package/dist/features/adapters/launchdarkly/index.d.mts +1 -1
- package/dist/features/adapters/posthog/index.d.mts +1 -1
- package/dist/features/adapters/posthog/index.mjs +1 -1
- package/dist/features/index.d.mts +2 -2
- package/dist/features/index.mjs +1 -1
- package/dist/{flagsmith-Cd44vxff.d.mts → flagsmith-Cn_KVy6v.d.mts} +1 -1
- package/dist/{globals-BRdxsC2M.mjs → globals-BBqmW3dM.mjs} +1 -1
- package/dist/{index-C0ewabdY.d.mts → index-BIyQ3OxC.d.mts} +1 -1
- package/dist/{index-CyUCELZC.d.mts → index-BN06veYe.d.mts} +678 -154
- package/dist/{index-CJdG9yY1.d.mts → index-BpDzW6zC.d.mts} +879 -454
- package/dist/{index-CLR1QMM3.d.mts → index-DHmuwBdv.d.mts} +60 -1
- package/dist/{index-C_t6t0Z-.d.mts → index-DWAgQOBa.d.mts} +1 -1
- package/dist/{index-BmEu85PI.d.mts → index-ZD3hw2Zo.d.mts} +2 -2
- package/dist/{index-BLaKZq0Y.d.mts → index-y-dyBzAF.d.mts} +96 -2
- package/dist/index.d.mts +11 -11
- package/dist/index.mjs +29 -29
- package/dist/json/attributes.json +128 -0
- package/dist/json/importMap.json +24 -0
- package/dist/json/tags.json +57 -0
- package/dist/json/web-types.json +375 -2
- package/dist/{launchdarkly-De701hkf.d.mts → launchdarkly-C3Knz7-v.d.mts} +1 -1
- package/dist/locale/adapters/index.d.mts +2 -2
- package/dist/locale/adapters/index.mjs +2 -2
- package/dist/locale/adapters/v0/index.d.mts +1 -1
- package/dist/locale/adapters/v0/index.mjs +1 -1
- package/dist/locale/adapters/vue-i18n/index.d.mts +1 -1
- package/dist/locale/index.d.mts +3 -3
- package/dist/locale/index.mjs +2 -2
- package/dist/logger/adapters/consola/index.mjs +1 -1
- package/dist/logger/adapters/index.mjs +4 -4
- package/dist/logger/adapters/pino/index.mjs +1 -1
- package/dist/logger/adapters/v0/index.mjs +1 -1
- package/dist/logger/index.d.mts +1 -1
- package/dist/logger/index.mjs +4 -4
- package/dist/notifications/index.d.mts +1 -1
- package/dist/notifications/index.mjs +2 -2
- package/dist/palettes/ant/generate/index.mjs +6 -1
- package/dist/palettes/ant/index.d.mts +1 -1
- package/dist/palettes/index.d.mts +3 -3
- package/dist/palettes/leonardo/generate/index.mjs +6 -2
- package/dist/palettes/material/generate/index.mjs +11 -2
- package/dist/palettes/material/index.d.mts +1 -1
- package/dist/palettes/md1/index.d.mts +1 -1
- package/dist/palettes/md2/index.d.mts +1 -1
- package/dist/palettes/radix/index.d.mts +1 -1
- package/dist/palettes/tailwind/index.d.mts +1 -1
- package/dist/permissions/adapters/index.d.mts +1 -1
- package/dist/permissions/adapters/v0/index.d.mts +1 -1
- package/dist/permissions/index.d.mts +1 -1
- package/dist/permissions/index.mjs +1 -1
- package/dist/{pino-CIcYlW3u.mjs → pino-BscMO3RR.mjs} +5 -2
- package/dist/{posthog-CIrmvke4.d.mts → posthog-CR8R2ywM.d.mts} +1 -1
- package/dist/{posthog-0umvvA48.mjs → posthog-DPe6ewqN.mjs} +1 -1
- package/dist/rules/index.d.mts +2 -2
- package/dist/rules/index.mjs +1 -1
- package/dist/{server-BNSiVoMe.mjs → server-CZ83nTn_.mjs} +1 -1
- package/dist/storage/index.d.mts +1 -1
- package/dist/storage/index.mjs +1 -1
- package/dist/theme/adapters/index.d.mts +1 -1
- package/dist/theme/adapters/index.mjs +4 -4
- package/dist/theme/adapters/unhead/index.d.mts +1 -1
- package/dist/theme/adapters/unhead/index.mjs +1 -1
- package/dist/theme/adapters/v0/index.d.mts +1 -1
- package/dist/theme/adapters/v0/index.mjs +1 -1
- package/dist/theme/index.d.mts +1 -1
- package/dist/theme/index.mjs +3 -3
- package/dist/types/index.d.mts +2 -2
- package/dist/{unhead-_cXZcx0j.mjs → unhead-l-ZLWtSR.mjs} +2 -2
- package/dist/{useClickOutside-DbOLg1uw.mjs → useClickOutside-BurEbsu3.mjs} +1409 -559
- package/dist/{useFeatures-Dhnr-Lg2.mjs → useFeatures-BWC23LA6.mjs} +4 -4
- package/dist/{useLocale-Sh-2sNiG.mjs → useLocale-yXLswxYa.mjs} +5 -5
- package/dist/{useLogger-1_ZeBY12.mjs → useLogger-JwRA-DZ7.mjs} +50 -6
- package/dist/{useNotifications-CAVQ7hK6.mjs → useNotifications-8qt26Ha9.mjs} +4 -4
- package/dist/{usePermissions-D9Oituwy.mjs → usePermissions-Bc1-N-lO.mjs} +11 -5
- package/dist/{useRules-DK4AeNeC.mjs → useRules-Dg-pFO8r.mjs} +2 -2
- package/dist/{useTheme-FMDQC81h.mjs → useTheme-TpMOisy2.mjs} +5 -5
- package/dist/utilities/index.d.mts +2 -2
- package/dist/utilities/index.mjs +117 -3
- package/dist/{v0-Ch-O6YVd.mjs → v0-BfZc5TNn.mjs} +1 -1
- package/dist/{v0-C42ssNIy.mjs → v0-C7j3bzRU.mjs} +1 -1
- package/dist/{v0-CEfbyyp2.mjs → v0-COaQncfZ.mjs} +1 -1
- package/dist/{v0-aYLq6qmT.d.mts → v0-WzS4GAIp.d.mts} +1 -1
- package/dist/{v0-D0imQlSD.mjs → v0-eRmUaRpH.mjs} +2 -2
- package/dist/{virtual-Cq5aRp7m.mjs → virtual-UumJ2BQ9.mjs} +1 -1
- package/package.json +12 -3
- package/dist/adapters-4t-NhR11.mjs +0 -5
- package/dist/adapters-BcoA0zCS.mjs +0 -4
- package/dist/adapters-BjaPK635.mjs +0 -2
- package/dist/adapters-DvG-9z9q.mjs +0 -3
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import { a as SUPPORTS_OBSERVER, i as SUPPORTS_MUTATION_OBSERVER, n as SUPPORTS_INTERSECTION_OBSERVER, r as SUPPORTS_MATCH_MEDIA, t as IN_BROWSER } from "./globals-
|
|
2
|
-
import { clamp, instanceExists, isArray, isElement, isFunction, isNull, isNullOrUndefined, isNumber, isObject, isString, isThenable, isUndefined, mergeDeep, range, resolveIds, resolveIndexes, useId as useId$1 } from "./utilities/index.mjs";
|
|
3
|
-
import { i as useContext, t as createTrinity } from "./createTrinity-
|
|
4
|
-
import { a as createPlugin, i as useLogger, o as createPluginContext } from "./useLogger-
|
|
5
|
-
import { t as createRegistry } from "./createRegistry-
|
|
6
|
-
import { i as createModel, t as createSelection } from "./createSelection-
|
|
7
|
-
import { t as createSingle } from "./createSingle-
|
|
8
|
-
import "./createTokens-
|
|
9
|
-
import { a as useLocale } from "./useLocale-
|
|
10
|
-
import { c as useTimer } from "./useNotifications-
|
|
1
|
+
import { a as SUPPORTS_OBSERVER, i as SUPPORTS_MUTATION_OBSERVER, n as SUPPORTS_INTERSECTION_OBSERVER, r as SUPPORTS_MATCH_MEDIA, t as IN_BROWSER } from "./globals-BBqmW3dM.mjs";
|
|
2
|
+
import { V0Error, clamp, instanceExists, isArray, isElement, isFunction, isNaN, isNull, isNullOrUndefined, isNumber, isObject, isString, isThenable, isUndefined, mergeDeep, range, resolveIds, resolveIndexes, useId as useId$1 } from "./utilities/index.mjs";
|
|
3
|
+
import { i as useContext, n as createContext, t as createTrinity } from "./createTrinity-z-3q9QWR.mjs";
|
|
4
|
+
import { a as createPlugin, i as useLogger, o as createPluginContext } from "./useLogger-JwRA-DZ7.mjs";
|
|
5
|
+
import { t as createRegistry } from "./createRegistry-8FryYQC-.mjs";
|
|
6
|
+
import { i as createModel, t as createSelection } from "./createSelection-DPOvmJ5q.mjs";
|
|
7
|
+
import { t as createSingle } from "./createSingle-CYi0RuOy.mjs";
|
|
8
|
+
import "./createTokens-DaJ_SYE7.mjs";
|
|
9
|
+
import { a as useLocale } from "./useLocale-yXLswxYa.mjs";
|
|
10
|
+
import { c as useTimer } from "./useNotifications-8qt26Ha9.mjs";
|
|
11
11
|
import { t as toArray } from "./toArray-CD2-OxAz.mjs";
|
|
12
|
-
import { a as createFilter } from "./adapter-
|
|
13
|
-
import { t as createGroup } from "./createGroup-
|
|
14
|
-
import "./createDataTable-
|
|
15
|
-
import { a as useRules, o as isStandardSchema } from "./useRules-
|
|
16
|
-
import "./useFeatures-
|
|
17
|
-
import "./usePermissions-
|
|
12
|
+
import { a as createFilter } from "./adapter-BCVxBtDp.mjs";
|
|
13
|
+
import { t as createGroup } from "./createGroup-CpaW7zEJ.mjs";
|
|
14
|
+
import { a as extractLeaves, t as createDataTable } from "./createDataTable-B-Fo-uuL.mjs";
|
|
15
|
+
import { a as useRules, o as isStandardSchema } from "./useRules-Dg-pFO8r.mjs";
|
|
16
|
+
import "./useFeatures-BWC23LA6.mjs";
|
|
17
|
+
import "./usePermissions-Bc1-N-lO.mjs";
|
|
18
18
|
import { t as MemoryStorageAdapter } from "./memory-BMpJcQjQ.mjs";
|
|
19
|
-
import "./useTheme-
|
|
19
|
+
import "./useTheme-TpMOisy2.mjs";
|
|
20
20
|
import { computed, effectScope, hasInjectionContext, isReadonly, isRef, nextTick, onMounted, onScopeDispose, onWatcherCleanup, reactive, readonly, ref, shallowReactive, shallowReadonly, shallowRef, toRef, toValue, unref, watch, watchEffect } from "vue";
|
|
21
21
|
//#region src/composables/createBreadcrumbs/index.ts
|
|
22
22
|
/**
|
|
@@ -170,6 +170,123 @@ function useBreadcrumbs(namespace = "v0:breadcrumbs") {
|
|
|
170
170
|
return useContext(namespace);
|
|
171
171
|
}
|
|
172
172
|
//#endregion
|
|
173
|
+
//#region src/composables/useDelay/index.ts
|
|
174
|
+
/**
|
|
175
|
+
* @module useDelay
|
|
176
|
+
*
|
|
177
|
+
* @see https://0.vuetifyjs.com/composables/system/use-delay
|
|
178
|
+
*
|
|
179
|
+
* @remarks
|
|
180
|
+
* Schedule open and close transitions with configurable delays.
|
|
181
|
+
*
|
|
182
|
+
* Composes `useTimer` for the underlying lifecycle (start / stop / pause / resume,
|
|
183
|
+
* plus reactive `isActive`, `isPaused`, `remaining`) and adds direction tracking
|
|
184
|
+
* (`isOpening`) and promise-based resolution.
|
|
185
|
+
*
|
|
186
|
+
* Key features:
|
|
187
|
+
* - Reactive `openDelay` and `closeDelay` via `MaybeRefOrGetter`
|
|
188
|
+
* - `start(isOpening)` returns a `Promise<boolean>` that resolves once the delay elapses
|
|
189
|
+
* - Per-call `minDelay` floor enforces a minimum delay
|
|
190
|
+
* - Automatic cleanup on scope disposal (timer cleared, pending promise settled with current direction)
|
|
191
|
+
* - SSR-safe
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* ```ts
|
|
195
|
+
* import { useDelay } from '@vuetify/v0'
|
|
196
|
+
*
|
|
197
|
+
* const delay = useDelay(isOpening => isVisible.value = isOpening, {
|
|
198
|
+
* openDelay: 300,
|
|
199
|
+
* closeDelay: 200,
|
|
200
|
+
* })
|
|
201
|
+
*
|
|
202
|
+
* delay.start(true)
|
|
203
|
+
* delay.start(false, { minDelay: 500 })
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
206
|
+
/**
|
|
207
|
+
* Schedule open and close transitions with configurable delays.
|
|
208
|
+
*
|
|
209
|
+
* Composes `useTimer` for the lifecycle (`start` / `stop` / `pause` / `resume` plus
|
|
210
|
+
* reactive `isActive`, `isPaused`, `remaining`) and adds direction tracking
|
|
211
|
+
* (`isOpening`) and promise-based resolution.
|
|
212
|
+
*
|
|
213
|
+
* @param callback Optional callback invoked once a started delay elapses; receives the resolved direction.
|
|
214
|
+
* @param options Reactive delay settings.
|
|
215
|
+
* @returns Lifecycle controls and reactive state.
|
|
216
|
+
*
|
|
217
|
+
* @see https://0.vuetifyjs.com/composables/system/use-delay
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* ```ts
|
|
221
|
+
* const delay = useDelay(isOpening => isVisible.value = isOpening, {
|
|
222
|
+
* openDelay: 300,
|
|
223
|
+
* closeDelay: 200,
|
|
224
|
+
* })
|
|
225
|
+
*
|
|
226
|
+
* delay.start(true)
|
|
227
|
+
* delay.start(false, { minDelay: 500 })
|
|
228
|
+
* delay.pause()
|
|
229
|
+
* delay.resume()
|
|
230
|
+
* delay.stop()
|
|
231
|
+
* ```
|
|
232
|
+
*/
|
|
233
|
+
function useDelay(callback, options = {}) {
|
|
234
|
+
const { openDelay, closeDelay } = options;
|
|
235
|
+
const isOpening = shallowRef(false);
|
|
236
|
+
let minDelay = 0;
|
|
237
|
+
let resolver;
|
|
238
|
+
function fire() {
|
|
239
|
+
const direction = isOpening.value;
|
|
240
|
+
const resolve = resolver;
|
|
241
|
+
resolver = void 0;
|
|
242
|
+
resolve?.(direction);
|
|
243
|
+
callback?.(direction);
|
|
244
|
+
}
|
|
245
|
+
function duration() {
|
|
246
|
+
const raw = isOpening.value ? toValue(openDelay) : toValue(closeDelay);
|
|
247
|
+
return Math.max(minDelay, raw ?? 0);
|
|
248
|
+
}
|
|
249
|
+
const timer = useTimer(fire, { duration });
|
|
250
|
+
function start(direction, options = {}) {
|
|
251
|
+
const previous = resolver;
|
|
252
|
+
resolver = void 0;
|
|
253
|
+
isOpening.value = direction;
|
|
254
|
+
minDelay = options.minDelay ?? 0;
|
|
255
|
+
previous?.(direction);
|
|
256
|
+
const ms = duration();
|
|
257
|
+
return new Promise((resolve) => {
|
|
258
|
+
resolver = resolve;
|
|
259
|
+
if (ms <= 0) {
|
|
260
|
+
timer.stop();
|
|
261
|
+
fire();
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
timer.start();
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
function stop() {
|
|
268
|
+
const resolve = resolver;
|
|
269
|
+
resolver = void 0;
|
|
270
|
+
timer.stop();
|
|
271
|
+
resolve?.(isOpening.value);
|
|
272
|
+
}
|
|
273
|
+
onScopeDispose(() => {
|
|
274
|
+
const resolve = resolver;
|
|
275
|
+
resolver = void 0;
|
|
276
|
+
resolve?.(isOpening.value);
|
|
277
|
+
}, true);
|
|
278
|
+
return {
|
|
279
|
+
start,
|
|
280
|
+
stop,
|
|
281
|
+
pause: timer.pause,
|
|
282
|
+
resume: timer.resume,
|
|
283
|
+
isActive: shallowReadonly(timer.isActive),
|
|
284
|
+
isPaused: shallowReadonly(timer.isPaused),
|
|
285
|
+
remaining: shallowReadonly(timer.remaining),
|
|
286
|
+
isOpening: shallowReadonly(isOpening)
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
//#endregion
|
|
173
290
|
//#region src/composables/usePopover/index.ts
|
|
174
291
|
/**
|
|
175
292
|
* @module usePopover
|
|
@@ -178,52 +295,50 @@ function useBreadcrumbs(namespace = "v0:breadcrumbs") {
|
|
|
178
295
|
*
|
|
179
296
|
* @remarks
|
|
180
297
|
* Composable for native popover API behavior with CSS anchor positioning.
|
|
181
|
-
* Manages open/close state, anchor styles, content attributes,
|
|
182
|
-
*
|
|
298
|
+
* Manages open/close state, anchor styles, content attributes, bidirectional
|
|
299
|
+
* sync between reactive state and native popover events, and configurable
|
|
300
|
+
* open / close delays via `useDelay`.
|
|
183
301
|
*
|
|
184
302
|
* Key features:
|
|
185
303
|
* - Native popover API (showPopover/hidePopover)
|
|
186
304
|
* - CSS anchor positioning (position-area, position-try-fallbacks)
|
|
305
|
+
* - Reactive open/close delays via `useDelay`
|
|
187
306
|
* - Toggle event sync for native state changes
|
|
188
307
|
* - SSR-safe (no DOM ops outside browser)
|
|
189
308
|
* - Optional external isOpen ref for v-model integration
|
|
190
309
|
*
|
|
191
|
-
* Perfect for building select, combobox, tooltip, and menu components
|
|
192
|
-
* without wrapping the Popover compound component.
|
|
193
|
-
*
|
|
194
310
|
* @example
|
|
195
311
|
* ```ts
|
|
196
312
|
* import { usePopover } from '@vuetify/v0'
|
|
197
313
|
*
|
|
198
|
-
* const popover = usePopover()
|
|
314
|
+
* const popover = usePopover({ openDelay: 200, closeDelay: 100 })
|
|
199
315
|
* popover.open()
|
|
200
316
|
* popover.toggle()
|
|
201
317
|
* ```
|
|
202
318
|
*/
|
|
203
319
|
function usePopover(options = {}) {
|
|
204
|
-
const { id: _id, positionArea = "bottom", positionTry = "most-width bottom",
|
|
320
|
+
const { id: _id, positionArea = "bottom", positionTry = "most-width bottom", openDelay, closeDelay } = options;
|
|
205
321
|
const id = _id ?? /* @__PURE__ */ useId$1();
|
|
206
322
|
const isOpen = options.isOpen ?? shallowRef(false);
|
|
207
|
-
const
|
|
208
|
-
isOpen.value =
|
|
209
|
-
}, {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}
|
|
323
|
+
const delay = useDelay((direction) => {
|
|
324
|
+
isOpen.value = direction;
|
|
325
|
+
}, {
|
|
326
|
+
openDelay,
|
|
327
|
+
closeDelay
|
|
328
|
+
});
|
|
213
329
|
function open() {
|
|
214
|
-
|
|
215
|
-
if (showTimer) showTimer.start();
|
|
216
|
-
else isOpen.value = true;
|
|
330
|
+
delay.start(true);
|
|
217
331
|
}
|
|
218
332
|
function close() {
|
|
219
|
-
|
|
220
|
-
if (hideTimer) hideTimer.start();
|
|
221
|
-
else isOpen.value = false;
|
|
333
|
+
delay.start(false);
|
|
222
334
|
}
|
|
223
335
|
function toggle() {
|
|
224
336
|
if (isOpen.value) close();
|
|
225
337
|
else open();
|
|
226
338
|
}
|
|
339
|
+
function cancel() {
|
|
340
|
+
delay.stop();
|
|
341
|
+
}
|
|
227
342
|
const anchorStyles = toRef(() => ({ anchorName: `--${id}` }));
|
|
228
343
|
const contentAttrs = toRef(() => ({
|
|
229
344
|
id,
|
|
@@ -241,7 +356,7 @@ function usePopover(options = {}) {
|
|
|
241
356
|
function attach(el) {
|
|
242
357
|
onMounted(() => {
|
|
243
358
|
const element = toValue(el);
|
|
244
|
-
if (isOpen.value) element?.showPopover();
|
|
359
|
+
if (isOpen.value) element?.showPopover?.();
|
|
245
360
|
});
|
|
246
361
|
watch(isOpen, (value) => {
|
|
247
362
|
const element = toValue(el);
|
|
@@ -262,6 +377,7 @@ function usePopover(options = {}) {
|
|
|
262
377
|
open,
|
|
263
378
|
close,
|
|
264
379
|
toggle,
|
|
380
|
+
cancel,
|
|
265
381
|
anchorStyles,
|
|
266
382
|
contentAttrs,
|
|
267
383
|
contentStyles,
|
|
@@ -826,136 +942,1134 @@ function useCombobox(namespace = "v0:combobox") {
|
|
|
826
942
|
return useContext(namespace);
|
|
827
943
|
}
|
|
828
944
|
//#endregion
|
|
829
|
-
//#region src/composables/
|
|
945
|
+
//#region src/composables/createSortable/index.ts
|
|
830
946
|
/**
|
|
831
|
-
* @module
|
|
947
|
+
* @module createSortable
|
|
832
948
|
*
|
|
833
|
-
* @see https://0.vuetifyjs.com/composables/
|
|
949
|
+
* @see https://0.vuetifyjs.com/composables/data/create-sortable
|
|
834
950
|
*
|
|
835
951
|
* @remarks
|
|
836
|
-
*
|
|
837
|
-
*
|
|
838
|
-
*
|
|
839
|
-
* - Pure registry of ValidationContext instances
|
|
840
|
-
* - Validations auto-register via useForm() injection
|
|
841
|
-
* - Form-level submit, reset, and aggregate state
|
|
842
|
-
* - Exposes disabled/readonly refs for component consumption
|
|
843
|
-
* - Trinity pattern for DI
|
|
952
|
+
* Headless ordered-list primitive. Extends `createModel` with mutation primitives
|
|
953
|
+
* (`swap`, `reorder`) and an enriched `move:ticket` event on the registry event
|
|
954
|
+
* bus. Honors `disabled` as a real mutation gate at both root and per-ticket scope.
|
|
844
955
|
*
|
|
845
|
-
*
|
|
846
|
-
*
|
|
956
|
+
* Pure logical composable — no DnD, no DOM, no keyboard. Drag-and-drop integration
|
|
957
|
+
* composes with `useDragDrop`; keyboard reorder composes with `useVirtualFocus`.
|
|
847
958
|
*
|
|
848
959
|
* @example
|
|
849
960
|
* ```ts
|
|
850
|
-
* import {
|
|
851
|
-
*
|
|
852
|
-
* const form = createForm()
|
|
853
|
-
* const isValid = await form.validate()
|
|
854
|
-
* if (isValid) form.submit()
|
|
855
|
-
* ```
|
|
856
|
-
*/
|
|
857
|
-
/**
|
|
858
|
-
* Creates a new form instance.
|
|
859
|
-
*
|
|
860
|
-
* A form is a pure registry of validation contexts. Validations register
|
|
861
|
-
* themselves via `useForm()` injection. The form coordinates submit, reset,
|
|
862
|
-
* and aggregate state across all registered validations.
|
|
863
|
-
*
|
|
864
|
-
* @param options The options for the form instance.
|
|
865
|
-
* @returns A new form context.
|
|
866
|
-
*
|
|
867
|
-
* @see https://0.vuetifyjs.com/composables/forms/create-form
|
|
961
|
+
* import { createSortable } from '@vuetify/v0'
|
|
962
|
+
* import type { SortableTicketInput } from '@vuetify/v0'
|
|
868
963
|
*
|
|
869
|
-
*
|
|
870
|
-
*
|
|
871
|
-
*
|
|
872
|
-
* import { shallowRef } from 'vue'
|
|
964
|
+
* interface TaskTicket extends SortableTicketInput {
|
|
965
|
+
* value: { id: number; label: string }
|
|
966
|
+
* }
|
|
873
967
|
*
|
|
874
|
-
* const
|
|
968
|
+
* const sortable = createSortable<TaskTicket>()
|
|
969
|
+
* const a = sortable.register({ value: { id: 1, label: 'A' } })
|
|
970
|
+
* const b = sortable.register({ value: { id: 2, label: 'B' } })
|
|
875
971
|
*
|
|
876
|
-
*
|
|
877
|
-
*
|
|
878
|
-
*
|
|
879
|
-
* value: email,
|
|
880
|
-
* rules: ['required', 'email'],
|
|
972
|
+
* sortable.move(a.id, 1)
|
|
973
|
+
* sortable.on('move:ticket', ({ ticket, from, to }) => {
|
|
974
|
+
* console.log(`${ticket.value.label} moved ${from} → ${to}`)
|
|
881
975
|
* })
|
|
882
|
-
*
|
|
883
|
-
* form.register({ value: validation })
|
|
884
|
-
*
|
|
885
|
-
* await form.submit()
|
|
886
|
-
* form.reset()
|
|
887
976
|
* ```
|
|
888
977
|
*/
|
|
889
|
-
function createForm(options = {}) {
|
|
890
|
-
const { disabled = false, readonly = false, ..._options } = options;
|
|
891
|
-
const registry = createRegistry({
|
|
892
|
-
..._options,
|
|
893
|
-
reactive: true
|
|
894
|
-
});
|
|
895
|
-
const isValidating = computed(() => {
|
|
896
|
-
for (const ticket of registry.values()) if (ticket.value.isValidating.value) return true;
|
|
897
|
-
return false;
|
|
898
|
-
});
|
|
899
|
-
const isValid = computed(() => {
|
|
900
|
-
let hasNull = false;
|
|
901
|
-
let hasFields = false;
|
|
902
|
-
for (const ticket of registry.values()) {
|
|
903
|
-
hasFields = true;
|
|
904
|
-
if (ticket.value.isValid.value === false) return false;
|
|
905
|
-
if (/* @__PURE__ */ isNull(ticket.value.isValid.value)) hasNull = true;
|
|
906
|
-
}
|
|
907
|
-
if (!hasFields) return null;
|
|
908
|
-
return hasNull ? null : true;
|
|
909
|
-
});
|
|
910
|
-
function register(registration) {
|
|
911
|
-
return registry.register(registration);
|
|
912
|
-
}
|
|
913
|
-
function reset() {
|
|
914
|
-
for (const ticket of registry.values()) ticket.value.reset();
|
|
915
|
-
}
|
|
916
|
-
async function submit(id) {
|
|
917
|
-
const ids = id ? /* @__PURE__ */ toArray(id) : [...registry.keys()];
|
|
918
|
-
return (await Promise.all(ids.map(async (key) => {
|
|
919
|
-
const ticket = registry.get(key);
|
|
920
|
-
if (!ticket) return true;
|
|
921
|
-
return ticket.value.validate();
|
|
922
|
-
}))).every(Boolean);
|
|
923
|
-
}
|
|
924
|
-
return {
|
|
925
|
-
...registry,
|
|
926
|
-
register,
|
|
927
|
-
submit,
|
|
928
|
-
reset,
|
|
929
|
-
disabled,
|
|
930
|
-
readonly,
|
|
931
|
-
isValid,
|
|
932
|
-
isValidating,
|
|
933
|
-
get size() {
|
|
934
|
-
return registry.size;
|
|
935
|
-
}
|
|
936
|
-
};
|
|
937
|
-
}
|
|
938
978
|
/**
|
|
939
|
-
*
|
|
940
|
-
*
|
|
941
|
-
* @param options The options for the form context.
|
|
942
|
-
* @returns A Trinity tuple: [useFormContext, provideFormContext, formContext]
|
|
979
|
+
* Create an ordered-list state primitive.
|
|
943
980
|
*
|
|
944
|
-
* @
|
|
981
|
+
* @param options Sortable options.
|
|
982
|
+
* @returns A {@link SortableContext} that extends {@link ModelContext}.
|
|
945
983
|
*
|
|
946
984
|
* @example
|
|
947
985
|
* ```ts
|
|
948
|
-
*
|
|
949
|
-
*
|
|
950
|
-
* export const [useMyForm, provideMyForm, myForm] = createFormContext()
|
|
986
|
+
* const sortable = createSortable<Todo>()
|
|
987
|
+
* sortable.onboard(todos.value.map(value => ({ value })))
|
|
951
988
|
* ```
|
|
952
989
|
*/
|
|
953
|
-
function
|
|
954
|
-
const {
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
990
|
+
function createSortable(_options = {}) {
|
|
991
|
+
const { disabled, ...options } = _options;
|
|
992
|
+
const model = createModel({
|
|
993
|
+
...options,
|
|
994
|
+
events: true,
|
|
995
|
+
disabled
|
|
996
|
+
});
|
|
997
|
+
const logger = useLogger();
|
|
998
|
+
function move(id, toIndex) {
|
|
999
|
+
if (toValue(disabled)) return void 0;
|
|
1000
|
+
const ticket = model.get(id);
|
|
1001
|
+
if (!ticket) return void 0;
|
|
1002
|
+
if (toValue(ticket.disabled)) return void 0;
|
|
1003
|
+
const from = ticket.index;
|
|
1004
|
+
const result = model.move(id, toIndex);
|
|
1005
|
+
if (result && result.index !== from) model.emit("move:ticket", {
|
|
1006
|
+
ticket: result,
|
|
1007
|
+
from,
|
|
1008
|
+
to: result.index
|
|
1009
|
+
});
|
|
1010
|
+
return result;
|
|
1011
|
+
}
|
|
1012
|
+
function swap(a, b) {
|
|
1013
|
+
if (toValue(disabled)) return;
|
|
1014
|
+
if (a === b) return;
|
|
1015
|
+
const ta = model.get(a);
|
|
1016
|
+
const tb = model.get(b);
|
|
1017
|
+
if (!ta || !tb) return;
|
|
1018
|
+
if (toValue(ta.disabled) || toValue(tb.disabled)) return;
|
|
1019
|
+
const indexA = ta.index;
|
|
1020
|
+
const indexB = tb.index;
|
|
1021
|
+
model.batch(() => {
|
|
1022
|
+
const ra = model.move(a, indexB);
|
|
1023
|
+
if (ra && ra.index !== indexA) model.emit("move:ticket", {
|
|
1024
|
+
ticket: ra,
|
|
1025
|
+
from: indexA,
|
|
1026
|
+
to: ra.index
|
|
1027
|
+
});
|
|
1028
|
+
const rb = model.move(b, indexA);
|
|
1029
|
+
if (rb && rb.index !== indexB) model.emit("move:ticket", {
|
|
1030
|
+
ticket: rb,
|
|
1031
|
+
from: indexB,
|
|
1032
|
+
to: rb.index
|
|
1033
|
+
});
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
function reorder(ids) {
|
|
1037
|
+
if (toValue(disabled)) return;
|
|
1038
|
+
const currentSize = model.size;
|
|
1039
|
+
if (ids.length !== currentSize) {
|
|
1040
|
+
logger.warn(`Reorder expected ${currentSize} ids, got ${ids.length}.`);
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
const known = new Set(model.keys());
|
|
1044
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1045
|
+
for (const id of ids) {
|
|
1046
|
+
if (!known.has(id)) {
|
|
1047
|
+
logger.warn(`Reorder contains unknown id "${String(id)}".`);
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
if (seen.has(id)) {
|
|
1051
|
+
logger.warn(`Reorder contains duplicate id "${String(id)}".`);
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
seen.add(id);
|
|
1055
|
+
}
|
|
1056
|
+
const froms = /* @__PURE__ */ new Map();
|
|
1057
|
+
for (const id of ids) {
|
|
1058
|
+
const ticket = model.get(id);
|
|
1059
|
+
if (ticket) froms.set(id, ticket.index);
|
|
1060
|
+
}
|
|
1061
|
+
model.batch(() => {
|
|
1062
|
+
model.reorder(ids);
|
|
1063
|
+
for (const [index, id] of ids.entries()) {
|
|
1064
|
+
const from = froms.get(id);
|
|
1065
|
+
if (from === index) continue;
|
|
1066
|
+
const ticket = model.get(id);
|
|
1067
|
+
if (ticket) model.emit("move:ticket", {
|
|
1068
|
+
ticket,
|
|
1069
|
+
from,
|
|
1070
|
+
to: index
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
return {
|
|
1076
|
+
...model,
|
|
1077
|
+
move,
|
|
1078
|
+
swap,
|
|
1079
|
+
reorder,
|
|
1080
|
+
get size() {
|
|
1081
|
+
return model.size;
|
|
1082
|
+
}
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
//#endregion
|
|
1086
|
+
//#region src/composables/useToggleScope/index.ts
|
|
1087
|
+
/**
|
|
1088
|
+
* @module useToggleScope
|
|
1089
|
+
*
|
|
1090
|
+
* @see https://0.vuetifyjs.com/composables/system/use-toggle-scope
|
|
1091
|
+
*
|
|
1092
|
+
* @remarks
|
|
1093
|
+
* Conditionally manages an effect scope based on a reactive boolean condition.
|
|
1094
|
+
* When the source becomes true, creates and runs an effect scope. When false, stops the scope.
|
|
1095
|
+
* All reactive effects created within the scoped function are automatically cleaned up on deactivation.
|
|
1096
|
+
*
|
|
1097
|
+
* Key features:
|
|
1098
|
+
* - Uses Vue's effectScope for efficient reactive effect lifecycle management
|
|
1099
|
+
* - Automatic cleanup when condition becomes false
|
|
1100
|
+
* - Supports optional reset callback for scope restart capability
|
|
1101
|
+
* - Handles rapid toggling and parent scope disposal safely
|
|
1102
|
+
* - SSR-safe (effectScope is part of Vue core)
|
|
1103
|
+
*
|
|
1104
|
+
* Perfect for conditional side effects, feature flags, and performance optimization
|
|
1105
|
+
* by only running reactive effects when needed.
|
|
1106
|
+
*
|
|
1107
|
+
* @example
|
|
1108
|
+
* ```ts
|
|
1109
|
+
* import { shallowRef } from 'vue'
|
|
1110
|
+
* import { useToggleScope } from '@vuetify/v0'
|
|
1111
|
+
*
|
|
1112
|
+
* const isEnabled = shallowRef(false)
|
|
1113
|
+
* const { isActive } = useToggleScope(isEnabled, () => {
|
|
1114
|
+
* // effects run while isEnabled is true
|
|
1115
|
+
* })
|
|
1116
|
+
* ```
|
|
1117
|
+
*/
|
|
1118
|
+
/**
|
|
1119
|
+
* Conditionally manages an effect scope based on a reactive boolean source.
|
|
1120
|
+
*
|
|
1121
|
+
* @param source A reactive boolean value or getter that controls the scope lifecycle
|
|
1122
|
+
* @param fn The function to run within the effect scope. Can optionally receive controls for manual scope management.
|
|
1123
|
+
* @returns Controls object with isActive state and start/stop/reset methods
|
|
1124
|
+
*
|
|
1125
|
+
* @see https://vuejs.org/api/reactivity-advanced.html#effectscope
|
|
1126
|
+
* @see https://0.vuetifyjs.com/composables/system/use-toggle-scope
|
|
1127
|
+
*
|
|
1128
|
+
* @example
|
|
1129
|
+
* ```ts
|
|
1130
|
+
* import { ref } from 'vue'
|
|
1131
|
+
* import { useToggleScope } from '@vuetify/v0'
|
|
1132
|
+
*
|
|
1133
|
+
* const isEnabled = ref(false)
|
|
1134
|
+
*
|
|
1135
|
+
* const { isActive } = useToggleScope(isEnabled, () => {
|
|
1136
|
+
* // This code runs when isEnabled becomes true
|
|
1137
|
+
* const unwatch = watch(someRef, () => {
|
|
1138
|
+
* console.log('Watching...')
|
|
1139
|
+
* })
|
|
1140
|
+
*
|
|
1141
|
+
* // Cleanup happens automatically when isEnabled becomes false
|
|
1142
|
+
* })
|
|
1143
|
+
*
|
|
1144
|
+
* // Toggle the scope on/off
|
|
1145
|
+
* isEnabled.value = true // Starts the scope
|
|
1146
|
+
* console.log(isActive.value) // true
|
|
1147
|
+
* isEnabled.value = false // Stops and cleans up
|
|
1148
|
+
* console.log(isActive.value) // false
|
|
1149
|
+
* ```
|
|
1150
|
+
*
|
|
1151
|
+
* @example
|
|
1152
|
+
* With manual controls:
|
|
1153
|
+
* ```ts
|
|
1154
|
+
* const isEnabled = ref(true)
|
|
1155
|
+
*
|
|
1156
|
+
* useToggleScope(isEnabled, (controls) => {
|
|
1157
|
+
* // Access controls inside the scope
|
|
1158
|
+
* console.log('Active:', controls.isActive.value)
|
|
1159
|
+
*
|
|
1160
|
+
* // Manually reset the scope if needed
|
|
1161
|
+
* someEvent.on('reset', () => controls.reset())
|
|
1162
|
+
* })
|
|
1163
|
+
* ```
|
|
1164
|
+
*/
|
|
1165
|
+
function useToggleScope(source, fn) {
|
|
1166
|
+
const scope = shallowRef();
|
|
1167
|
+
const isActive = toRef(() => !!scope.value);
|
|
1168
|
+
function start() {
|
|
1169
|
+
if (scope.value) return;
|
|
1170
|
+
scope.value = effectScope();
|
|
1171
|
+
scope.value.run(() => fn.length > 0 ? fn(controls) : fn());
|
|
1172
|
+
}
|
|
1173
|
+
function stop() {
|
|
1174
|
+
scope.value?.stop();
|
|
1175
|
+
scope.value = void 0;
|
|
1176
|
+
}
|
|
1177
|
+
function reset() {
|
|
1178
|
+
stop();
|
|
1179
|
+
start();
|
|
1180
|
+
}
|
|
1181
|
+
const controls = {
|
|
1182
|
+
isActive: shallowReadonly(isActive),
|
|
1183
|
+
start,
|
|
1184
|
+
stop,
|
|
1185
|
+
reset
|
|
1186
|
+
};
|
|
1187
|
+
watch(source, (active) => {
|
|
1188
|
+
if (active && !scope.value) start();
|
|
1189
|
+
else if (!active) stop();
|
|
1190
|
+
}, { immediate: true });
|
|
1191
|
+
onScopeDispose(stop, true);
|
|
1192
|
+
return controls;
|
|
1193
|
+
}
|
|
1194
|
+
//#endregion
|
|
1195
|
+
//#region src/composables/createDataGrid/editing.ts
|
|
1196
|
+
/**
|
|
1197
|
+
* @module createDataGrid/editing
|
|
1198
|
+
*
|
|
1199
|
+
* @remarks
|
|
1200
|
+
* Cell editing state management. Tracks active cell, validation errors,
|
|
1201
|
+
* and dirty (staged but uncommitted) edits. Does not mutate source data —
|
|
1202
|
+
* commit fires a callback for the consumer to handle.
|
|
1203
|
+
*
|
|
1204
|
+
* When a `registry` is provided, editing state for rows that get
|
|
1205
|
+
* unregistered (or wiped via `clear`) is pruned automatically so consumers
|
|
1206
|
+
* can never observe `active` pointing at a phantom row or stale entries
|
|
1207
|
+
* lingering in `dirty`.
|
|
1208
|
+
*
|
|
1209
|
+
* @internal Consumed only by `createDataGrid`. This is a fully decoupled 2D
|
|
1210
|
+
* cell-edit primitive — it imports nothing from `createDataGrid` /
|
|
1211
|
+
* `createDataTable` (only `#v0/utilities`, `#v0/types`, and Vue), so it is a
|
|
1212
|
+
* latent composable. It is parameterized over a row type `T` (defaulting to
|
|
1213
|
+
* `unknown`), but a type parameter is not an import — the primitive stays
|
|
1214
|
+
* fully decoupled from the grid's concrete ticket types and deliberately
|
|
1215
|
+
* row-agnostic: `T` is intentionally unconstrained, so do not narrow it to
|
|
1216
|
+
* the grid's `Record<string, unknown>` when promoting. Promote it to a
|
|
1217
|
+
* standalone `createCellEditing/`
|
|
1218
|
+
* composable the moment a second consumer appears (e.g. an editable
|
|
1219
|
+
* `createDataTable`). See `.claude/rules/composables.md` §"Sub-modules:
|
|
1220
|
+
* inline, private sibling, or promote".
|
|
1221
|
+
*/
|
|
1222
|
+
/**
|
|
1223
|
+
* Creates cell editing state for a data grid.
|
|
1224
|
+
*
|
|
1225
|
+
* @param options Cell editing configuration including columns and commit callback
|
|
1226
|
+
* @returns Cell editing state and controls
|
|
1227
|
+
*
|
|
1228
|
+
* @example
|
|
1229
|
+
* ```ts
|
|
1230
|
+
* const editing = createCellEditing({
|
|
1231
|
+
* columns: [{ id: 'name', editable: true }],
|
|
1232
|
+
* registry: table,
|
|
1233
|
+
* onEdit (row, column, value) { ... },
|
|
1234
|
+
* })
|
|
1235
|
+
*
|
|
1236
|
+
* editing.edit(1, 'name')
|
|
1237
|
+
* editing.commit('Alice')
|
|
1238
|
+
* ```
|
|
1239
|
+
*/
|
|
1240
|
+
function createCellEditing(options) {
|
|
1241
|
+
const { columns, onEdit, lookup, registry } = options;
|
|
1242
|
+
function resolveColumn(column) {
|
|
1243
|
+
for (const col of toValue(columns)) if (col.id === column) return col;
|
|
1244
|
+
}
|
|
1245
|
+
const active = shallowRef(null);
|
|
1246
|
+
const error = shallowRef(null);
|
|
1247
|
+
const dirty = shallowReactive(/* @__PURE__ */ new Map());
|
|
1248
|
+
function edit(row, column) {
|
|
1249
|
+
const col = resolveColumn(column);
|
|
1250
|
+
if (!col) return;
|
|
1251
|
+
if (/* @__PURE__ */ isFunction(col.editable)) {
|
|
1252
|
+
const item = lookup?.(row);
|
|
1253
|
+
if (/* @__PURE__ */ isUndefined(item) || !col.editable(item)) return;
|
|
1254
|
+
} else if (col.editable !== true) return;
|
|
1255
|
+
error.value = null;
|
|
1256
|
+
active.value = {
|
|
1257
|
+
row,
|
|
1258
|
+
column
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
function commit(value) {
|
|
1262
|
+
const cell = active.value;
|
|
1263
|
+
if (!cell) return;
|
|
1264
|
+
const col = resolveColumn(cell.column);
|
|
1265
|
+
if (col?.validate) {
|
|
1266
|
+
const result = col.validate(value, lookup?.(cell.row));
|
|
1267
|
+
if (result !== true && /* @__PURE__ */ isString(result) && result.length > 0) {
|
|
1268
|
+
error.value = result;
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
const entry = dirty.get(cell.row);
|
|
1273
|
+
if (entry) {
|
|
1274
|
+
entry.delete(cell.column);
|
|
1275
|
+
if (entry.size === 0) dirty.delete(cell.row);
|
|
1276
|
+
}
|
|
1277
|
+
error.value = null;
|
|
1278
|
+
active.value = null;
|
|
1279
|
+
onEdit?.(cell.row, cell.column, value);
|
|
1280
|
+
}
|
|
1281
|
+
function cancel() {
|
|
1282
|
+
const cell = active.value;
|
|
1283
|
+
if (cell) {
|
|
1284
|
+
const entry = dirty.get(cell.row);
|
|
1285
|
+
if (entry) {
|
|
1286
|
+
entry.delete(cell.column);
|
|
1287
|
+
if (entry.size === 0) dirty.delete(cell.row);
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
error.value = null;
|
|
1291
|
+
active.value = null;
|
|
1292
|
+
}
|
|
1293
|
+
function onUnregister(data) {
|
|
1294
|
+
const ticket = data;
|
|
1295
|
+
if (active.value?.row === ticket.id) {
|
|
1296
|
+
active.value = null;
|
|
1297
|
+
error.value = null;
|
|
1298
|
+
}
|
|
1299
|
+
dirty.delete(ticket.id);
|
|
1300
|
+
}
|
|
1301
|
+
function onClear() {
|
|
1302
|
+
active.value = null;
|
|
1303
|
+
error.value = null;
|
|
1304
|
+
dirty.clear();
|
|
1305
|
+
}
|
|
1306
|
+
if (registry) {
|
|
1307
|
+
registry.on("unregister:ticket", onUnregister);
|
|
1308
|
+
registry.on("clear:registry", onClear);
|
|
1309
|
+
onScopeDispose(() => {
|
|
1310
|
+
registry.off("unregister:ticket", onUnregister);
|
|
1311
|
+
registry.off("clear:registry", onClear);
|
|
1312
|
+
}, true);
|
|
1313
|
+
}
|
|
1314
|
+
return {
|
|
1315
|
+
active,
|
|
1316
|
+
edit,
|
|
1317
|
+
commit,
|
|
1318
|
+
cancel,
|
|
1319
|
+
error,
|
|
1320
|
+
dirty
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
//#endregion
|
|
1324
|
+
//#region src/composables/createDataGrid/layout.ts
|
|
1325
|
+
/**
|
|
1326
|
+
* @module createDataGrid/layout
|
|
1327
|
+
*
|
|
1328
|
+
* @remarks
|
|
1329
|
+
* Manages column layout state for data grids: sizing (percentages 0-100),
|
|
1330
|
+
* pinning (left/right/scrollable regions), delta-based resizing compatible
|
|
1331
|
+
* with the Splitter two-panel model, column reordering, and visibility.
|
|
1332
|
+
*
|
|
1333
|
+
* Column existence, display order, AND grid layout config (size, pin,
|
|
1334
|
+
* min/max, resizable, reorderable) all live on the table's columns registry
|
|
1335
|
+
* (passed in as the only argument). `createRegistry` preserves the extra
|
|
1336
|
+
* grid fields on each ticket, so layout reads its config straight off the
|
|
1337
|
+
* registered ticket — there is no separate defs array. Columns onboarded
|
|
1338
|
+
* before OR after the layout is built are picked up identically: an initial
|
|
1339
|
+
* loop seeds the already-registered set and a `register:ticket` handler seeds
|
|
1340
|
+
* the rest. Removed columns have their pin/size/visibility state pruned via
|
|
1341
|
+
* unregister events.
|
|
1342
|
+
*
|
|
1343
|
+
* Sizing uses percentages so it is compatible with the Splitter component.
|
|
1344
|
+
* Offsets are computed per-region (left, scrollable, right) independently
|
|
1345
|
+
* and over visible columns only — hidden columns are excluded from the
|
|
1346
|
+
* render set without auto-redistribution (headless: the consumer rebalances
|
|
1347
|
+
* via `distribute()` or CSS).
|
|
1348
|
+
*
|
|
1349
|
+
* @internal Private sibling of `createDataGrid`, and intentionally NOT a
|
|
1350
|
+
* promotion candidate: it is bound to the table's `DataTableColumnTicket`
|
|
1351
|
+
* types, so it is parent-specific glue rather than a reusable primitive. See
|
|
1352
|
+
* `.claude/rules/composables.md` §"Sub-modules: inline, private sibling, or
|
|
1353
|
+
* promote".
|
|
1354
|
+
*/
|
|
1355
|
+
const DEFAULT_EXTRAS = {
|
|
1356
|
+
minSize: 2,
|
|
1357
|
+
maxSize: 100,
|
|
1358
|
+
resizable: true,
|
|
1359
|
+
reorderable: true
|
|
1360
|
+
};
|
|
1361
|
+
function offsets(cols, reverse = false) {
|
|
1362
|
+
let offset = 0;
|
|
1363
|
+
const start = reverse ? cols.length - 1 : 0;
|
|
1364
|
+
const step = reverse ? -1 : 1;
|
|
1365
|
+
const end = reverse ? -1 : cols.length;
|
|
1366
|
+
for (let i = start; i !== end; i += step) {
|
|
1367
|
+
cols[i].offset = offset;
|
|
1368
|
+
offset += cols[i].size;
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
/**
|
|
1372
|
+
* Creates a column layout manager for a data grid.
|
|
1373
|
+
*
|
|
1374
|
+
* Reads column existence, display order, AND grid layout config (size, pin,
|
|
1375
|
+
* min/max, resizable, reorderable) from the table's columns registry. The
|
|
1376
|
+
* grid fields ride on each ticket — `createRegistry` preserves them — so
|
|
1377
|
+
* there is no separate defs array. Visibility is layout-local view state
|
|
1378
|
+
* (not a ticket field) kept here and reconciled via register / unregister /
|
|
1379
|
+
* clear events.
|
|
1380
|
+
*
|
|
1381
|
+
* Generic over the registry's input/output ticket types so the caller's
|
|
1382
|
+
* concrete grid column ticket (`DataGridColumnTicket`) flows through. Layout
|
|
1383
|
+
* only ever reads from the registry (`values`, `keys`, `on`, `reorder`,
|
|
1384
|
+
* `move`, `lookup`) — it never registers — so the output ticket need only be
|
|
1385
|
+
* readable as a {@link GridColumnDef}.
|
|
1386
|
+
*
|
|
1387
|
+
* @param cols The table's columns registry — source of truth for which
|
|
1388
|
+
* columns exist, in what order, and their layout config
|
|
1389
|
+
* @returns Column layout state and mutation methods
|
|
1390
|
+
*/
|
|
1391
|
+
function createColumnLayout(cols) {
|
|
1392
|
+
const logger = useLogger();
|
|
1393
|
+
const configs = /* @__PURE__ */ new Map();
|
|
1394
|
+
const group = createGroup({ multiple: true });
|
|
1395
|
+
const sizes = shallowReactive(/* @__PURE__ */ new Map());
|
|
1396
|
+
const hidden = shallowReactive(/* @__PURE__ */ new Set());
|
|
1397
|
+
const order = [];
|
|
1398
|
+
function record(id) {
|
|
1399
|
+
if (!order.includes(id)) order.push(id);
|
|
1400
|
+
}
|
|
1401
|
+
/** Even share over the currently-registered leaves: explicit sizes are
|
|
1402
|
+
* honored, unsized columns split the remaining percentage. */
|
|
1403
|
+
function share(id) {
|
|
1404
|
+
const leaves = /* @__PURE__ */ extractLeaves(cols.values());
|
|
1405
|
+
let used = 0;
|
|
1406
|
+
let unsized = 0;
|
|
1407
|
+
for (const leaf of leaves) {
|
|
1408
|
+
const size = configs.get(String(leaf.id))?.size;
|
|
1409
|
+
if (/* @__PURE__ */ isUndefined(size)) unsized++;
|
|
1410
|
+
else used += size;
|
|
1411
|
+
}
|
|
1412
|
+
const own = configs.get(id)?.size;
|
|
1413
|
+
if (!/* @__PURE__ */ isUndefined(own)) return own;
|
|
1414
|
+
const remainder = Math.max(0, 100 - used);
|
|
1415
|
+
return unsized > 0 ? remainder / unsized : 0;
|
|
1416
|
+
}
|
|
1417
|
+
function seedLeaf(leaf) {
|
|
1418
|
+
const id = String(leaf.id);
|
|
1419
|
+
if (!configs.has(id)) configs.set(id, {
|
|
1420
|
+
size: leaf.size,
|
|
1421
|
+
minSize: leaf.minSize ?? DEFAULT_EXTRAS.minSize,
|
|
1422
|
+
maxSize: leaf.maxSize ?? DEFAULT_EXTRAS.maxSize,
|
|
1423
|
+
resizable: leaf.resizable ?? DEFAULT_EXTRAS.resizable,
|
|
1424
|
+
reorderable: leaf.reorderable ?? DEFAULT_EXTRAS.reorderable,
|
|
1425
|
+
pinned: leaf.pinned ?? false
|
|
1426
|
+
});
|
|
1427
|
+
if (!group.has(id)) group.register({
|
|
1428
|
+
id,
|
|
1429
|
+
value: id
|
|
1430
|
+
});
|
|
1431
|
+
if (!sizes.has(id)) sizes.set(id, share(id));
|
|
1432
|
+
for (const other of /* @__PURE__ */ extractLeaves(cols.values())) {
|
|
1433
|
+
const otherId = String(other.id);
|
|
1434
|
+
if (configs.has(otherId) && /* @__PURE__ */ isUndefined(configs.get(otherId)?.size)) sizes.set(otherId, share(otherId));
|
|
1435
|
+
}
|
|
1436
|
+
const config = configs.get(id);
|
|
1437
|
+
if (config.pinned === "left") group.select(id);
|
|
1438
|
+
else if (config.pinned === "right") group.mix(id);
|
|
1439
|
+
}
|
|
1440
|
+
function dropLeaf(id) {
|
|
1441
|
+
group.unregister(id);
|
|
1442
|
+
configs.delete(id);
|
|
1443
|
+
sizes.delete(id);
|
|
1444
|
+
hidden.delete(id);
|
|
1445
|
+
}
|
|
1446
|
+
function leavesOf(ticket) {
|
|
1447
|
+
return /* @__PURE__ */ extractLeaves([ticket]);
|
|
1448
|
+
}
|
|
1449
|
+
for (const ticket of cols.values()) {
|
|
1450
|
+
record(String(ticket.id));
|
|
1451
|
+
for (const leaf of leavesOf(ticket)) seedLeaf(leaf);
|
|
1452
|
+
}
|
|
1453
|
+
cols.on("register:ticket", (ticket) => {
|
|
1454
|
+
record(String(ticket.id));
|
|
1455
|
+
for (const leaf of leavesOf(ticket)) seedLeaf(leaf);
|
|
1456
|
+
});
|
|
1457
|
+
cols.on("unregister:ticket", (ticket) => {
|
|
1458
|
+
const top = order.indexOf(String(ticket.id));
|
|
1459
|
+
if (top !== -1) order.splice(top, 1);
|
|
1460
|
+
for (const leaf of leavesOf(ticket)) dropLeaf(String(leaf.id));
|
|
1461
|
+
});
|
|
1462
|
+
cols.on("clear:registry", () => {
|
|
1463
|
+
group.clear();
|
|
1464
|
+
configs.clear();
|
|
1465
|
+
sizes.clear();
|
|
1466
|
+
hidden.clear();
|
|
1467
|
+
order.length = 0;
|
|
1468
|
+
});
|
|
1469
|
+
function position(id) {
|
|
1470
|
+
if (group.selectedIds.has(id)) return "left";
|
|
1471
|
+
if (group.mixed(id)) return "right";
|
|
1472
|
+
return false;
|
|
1473
|
+
}
|
|
1474
|
+
function resolve(includeHidden) {
|
|
1475
|
+
const resolved = [];
|
|
1476
|
+
let index = 0;
|
|
1477
|
+
for (const ticket of /* @__PURE__ */ extractLeaves(cols.values())) {
|
|
1478
|
+
const id = String(ticket.id);
|
|
1479
|
+
const visible = !hidden.has(id);
|
|
1480
|
+
if (!includeHidden && !visible) continue;
|
|
1481
|
+
const meta = configs.get(id) ?? DEFAULT_EXTRAS;
|
|
1482
|
+
resolved.push({
|
|
1483
|
+
id,
|
|
1484
|
+
index: index++,
|
|
1485
|
+
size: sizes.get(id) ?? 0,
|
|
1486
|
+
offset: 0,
|
|
1487
|
+
pinned: position(id),
|
|
1488
|
+
resizable: meta.resizable,
|
|
1489
|
+
reorderable: meta.reorderable,
|
|
1490
|
+
minSize: meta.minSize,
|
|
1491
|
+
maxSize: meta.maxSize,
|
|
1492
|
+
visible
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
return resolved;
|
|
1496
|
+
}
|
|
1497
|
+
function split(resolved) {
|
|
1498
|
+
const left = [];
|
|
1499
|
+
const scrollable = [];
|
|
1500
|
+
const right = [];
|
|
1501
|
+
for (const col of resolved) if (col.pinned === "left") left.push(col);
|
|
1502
|
+
else if (col.pinned === "right") right.push(col);
|
|
1503
|
+
else scrollable.push(col);
|
|
1504
|
+
offsets(left);
|
|
1505
|
+
offsets(scrollable);
|
|
1506
|
+
offsets(right, true);
|
|
1507
|
+
return {
|
|
1508
|
+
left,
|
|
1509
|
+
scrollable,
|
|
1510
|
+
right
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
const pinned = computed(() => split(resolve(false)));
|
|
1514
|
+
const columns = computed(() => {
|
|
1515
|
+
const { left, scrollable, right } = pinned.value;
|
|
1516
|
+
return [
|
|
1517
|
+
...left,
|
|
1518
|
+
...scrollable,
|
|
1519
|
+
...right
|
|
1520
|
+
];
|
|
1521
|
+
});
|
|
1522
|
+
const allColumns = computed(() => resolve(true));
|
|
1523
|
+
function pin(id, pos) {
|
|
1524
|
+
if (!group.has(id)) return;
|
|
1525
|
+
group.unselect(id);
|
|
1526
|
+
group.unmix(id);
|
|
1527
|
+
if (pos === "left") group.select(id);
|
|
1528
|
+
else if (pos === "right") group.mix(id);
|
|
1529
|
+
}
|
|
1530
|
+
function resize(id, delta) {
|
|
1531
|
+
const { left, scrollable, right } = pinned.value;
|
|
1532
|
+
const all = [
|
|
1533
|
+
...left,
|
|
1534
|
+
...scrollable,
|
|
1535
|
+
...right
|
|
1536
|
+
];
|
|
1537
|
+
const target = all.find((c) => c.id === id);
|
|
1538
|
+
if (!target || !target.resizable) return;
|
|
1539
|
+
const allIndex = all.findIndex((c) => c.id === id);
|
|
1540
|
+
if (allIndex === -1 || allIndex === all.length - 1) return;
|
|
1541
|
+
const neighbor = all[allIndex + 1];
|
|
1542
|
+
const total = target.size + neighbor.size;
|
|
1543
|
+
const lower = Math.max(target.minSize, total - neighbor.maxSize);
|
|
1544
|
+
const upper = Math.min(target.maxSize, total - neighbor.minSize);
|
|
1545
|
+
const clamped = /* @__PURE__ */ clamp(target.size + delta, lower, upper);
|
|
1546
|
+
sizes.set(id, clamped);
|
|
1547
|
+
sizes.set(neighbor.id, total - clamped);
|
|
1548
|
+
}
|
|
1549
|
+
function reorder(from, to) {
|
|
1550
|
+
if (from === to) return;
|
|
1551
|
+
const tops = [...cols.values()];
|
|
1552
|
+
const ids = [];
|
|
1553
|
+
const leafToTop = [];
|
|
1554
|
+
for (const [top, ticket] of tops.entries()) for (const leaf of /* @__PURE__ */ extractLeaves([ticket])) {
|
|
1555
|
+
ids.push(String(leaf.id));
|
|
1556
|
+
leafToTop.push(top);
|
|
1557
|
+
}
|
|
1558
|
+
const visible = ids.filter((id) => !hidden.has(id));
|
|
1559
|
+
const fromId = visible[from];
|
|
1560
|
+
const toId = visible[to];
|
|
1561
|
+
if (/* @__PURE__ */ isUndefined(fromId) || /* @__PURE__ */ isUndefined(toId)) return;
|
|
1562
|
+
const fromTop = leafToTop[ids.indexOf(fromId)];
|
|
1563
|
+
const toTop = leafToTop[ids.indexOf(toId)];
|
|
1564
|
+
if (/* @__PURE__ */ isUndefined(fromTop) || /* @__PURE__ */ isUndefined(toTop) || fromTop === toTop) return;
|
|
1565
|
+
if (tops[fromTop]?.reorderable === false) return;
|
|
1566
|
+
const id = cols.lookup(fromTop);
|
|
1567
|
+
if (/* @__PURE__ */ isUndefined(id)) return;
|
|
1568
|
+
cols.move(id, toTop);
|
|
1569
|
+
}
|
|
1570
|
+
function distribute(incoming) {
|
|
1571
|
+
const tickets = /* @__PURE__ */ extractLeaves(cols.values());
|
|
1572
|
+
if (incoming.length !== tickets.length) {
|
|
1573
|
+
logger.warn(`createDataGrid: distribute() expected ${tickets.length} sizes but received ${incoming.length}; ignoring.`);
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
const sum = incoming.reduce((acc, value) => acc + value, 0);
|
|
1577
|
+
if (sum <= 0) {
|
|
1578
|
+
logger.warn(`createDataGrid: distribute() received sizes summing to ${sum}; expected a positive total.`);
|
|
1579
|
+
return;
|
|
1580
|
+
}
|
|
1581
|
+
const metas = tickets.map((ticket) => configs.get(String(ticket.id)) ?? DEFAULT_EXTRAS);
|
|
1582
|
+
let current = incoming.map((value, i) => /* @__PURE__ */ clamp(value / sum * 100, metas[i].minSize, metas[i].maxSize));
|
|
1583
|
+
let residual = 100 - current.reduce((acc, value) => acc + value, 0);
|
|
1584
|
+
const max = tickets.length * 2;
|
|
1585
|
+
for (let pass = 0; pass < max && Math.abs(residual) >= 1e-6; pass++) {
|
|
1586
|
+
const room = current.map((value, i) => residual > 0 ? metas[i].maxSize - value : value - metas[i].minSize);
|
|
1587
|
+
const available = room.reduce((acc, value) => acc + Math.max(0, value), 0);
|
|
1588
|
+
if (available <= 0) break;
|
|
1589
|
+
current = current.map((value, i) => room[i] <= 0 ? value : value + residual * (room[i] / available));
|
|
1590
|
+
residual = 100 - current.reduce((acc, value) => acc + value, 0);
|
|
1591
|
+
}
|
|
1592
|
+
if (Math.abs(residual) >= 1e-6) logger.warn(`createDataGrid: distribute() could not reach 100 within min/max constraints (off by ${residual.toFixed(2)})`);
|
|
1593
|
+
for (const [i, ticket] of tickets.entries()) sizes.set(String(ticket.id), current[i]);
|
|
1594
|
+
}
|
|
1595
|
+
function show(id) {
|
|
1596
|
+
hidden.delete(id);
|
|
1597
|
+
}
|
|
1598
|
+
function hide(id) {
|
|
1599
|
+
hidden.add(id);
|
|
1600
|
+
}
|
|
1601
|
+
function toggle(id) {
|
|
1602
|
+
if (hidden.has(id)) hidden.delete(id);
|
|
1603
|
+
else hidden.add(id);
|
|
1604
|
+
}
|
|
1605
|
+
function reset() {
|
|
1606
|
+
for (const ticket of /* @__PURE__ */ extractLeaves(cols.values())) {
|
|
1607
|
+
const id = String(ticket.id);
|
|
1608
|
+
sizes.set(id, share(id));
|
|
1609
|
+
}
|
|
1610
|
+
cols.reorder([...order]);
|
|
1611
|
+
hidden.clear();
|
|
1612
|
+
for (const ticket of /* @__PURE__ */ extractLeaves(cols.values())) {
|
|
1613
|
+
const id = String(ticket.id);
|
|
1614
|
+
group.unselect(id);
|
|
1615
|
+
group.unmix(id);
|
|
1616
|
+
}
|
|
1617
|
+
for (const [id, config] of configs) if (config.pinned === "left") group.select(id);
|
|
1618
|
+
else if (config.pinned === "right") group.mix(id);
|
|
1619
|
+
}
|
|
1620
|
+
return {
|
|
1621
|
+
pinned,
|
|
1622
|
+
columns,
|
|
1623
|
+
all: allColumns,
|
|
1624
|
+
pin,
|
|
1625
|
+
resize,
|
|
1626
|
+
reorder,
|
|
1627
|
+
distribute,
|
|
1628
|
+
show,
|
|
1629
|
+
hide,
|
|
1630
|
+
toggle,
|
|
1631
|
+
reset
|
|
1632
|
+
};
|
|
1633
|
+
}
|
|
1634
|
+
//#endregion
|
|
1635
|
+
//#region src/composables/createDataGrid/spanning.ts
|
|
1636
|
+
/**
|
|
1637
|
+
* @module createDataGrid/spanning
|
|
1638
|
+
*
|
|
1639
|
+
* @remarks
|
|
1640
|
+
* Computes a row span map from visible items. For each cell, determines
|
|
1641
|
+
* rowSpan and whether it's hidden (covered by a span from a previous row).
|
|
1642
|
+
* Spans do not cross page boundaries. Each row is addressed by a key: either a
|
|
1643
|
+
* `key(item, index)` projection (the grid passes the registry ticket id) or,
|
|
1644
|
+
* absent that, the row's `itemKey` value. Rows whose resolved key is
|
|
1645
|
+
* `undefined` cannot be addressed in the resulting map and are skipped.
|
|
1646
|
+
*
|
|
1647
|
+
* @internal Consumed only by `createDataGrid`. Fully decoupled (items ref +
|
|
1648
|
+
* columns + key + span fn — no grid/table types), so it is a latent composable.
|
|
1649
|
+
* Promote it to a standalone `createRowSpanning/` composable on a second
|
|
1650
|
+
* consumer. See `.claude/rules/composables.md` §"Sub-modules: inline, private
|
|
1651
|
+
* sibling, or promote".
|
|
1652
|
+
*/
|
|
1653
|
+
/**
|
|
1654
|
+
* Computes a row span map from visible items.
|
|
1655
|
+
*
|
|
1656
|
+
* @param options Row spanning configuration
|
|
1657
|
+
* @returns A computed map of item ID to column to SpanEntry
|
|
1658
|
+
*
|
|
1659
|
+
* @example
|
|
1660
|
+
* ```ts
|
|
1661
|
+
* const spans = createRowSpanning({
|
|
1662
|
+
* items: computed(() => grid.items.value),
|
|
1663
|
+
* columns: ['group', 'ticker', 'value'],
|
|
1664
|
+
* span: (item, column) => column === 'group' ? 3 : 1,
|
|
1665
|
+
* })
|
|
1666
|
+
*
|
|
1667
|
+
* spans.value.get(id)?.get('group') // { rowSpan: 3, hidden: false }
|
|
1668
|
+
* ```
|
|
1669
|
+
*/
|
|
1670
|
+
function createRowSpanning(options) {
|
|
1671
|
+
const { items, columns, key, itemKey = "id", span } = options;
|
|
1672
|
+
return computed(() => {
|
|
1673
|
+
const result = /* @__PURE__ */ new Map();
|
|
1674
|
+
if (!span) return result;
|
|
1675
|
+
const list = items.value;
|
|
1676
|
+
const resolved = toValue(columns);
|
|
1677
|
+
const covered = Array.from({ length: resolved.length }).fill(0);
|
|
1678
|
+
for (let row = 0; row < list.length; row++) {
|
|
1679
|
+
const item = list[row];
|
|
1680
|
+
const id = key ? key(item, row) : item[itemKey];
|
|
1681
|
+
if (/* @__PURE__ */ isUndefined(id)) continue;
|
|
1682
|
+
const cells = /* @__PURE__ */ new Map();
|
|
1683
|
+
for (const [col, column] of resolved.entries()) if (covered[col] > 0) {
|
|
1684
|
+
cells.set(column, {
|
|
1685
|
+
rowSpan: 1,
|
|
1686
|
+
hidden: true
|
|
1687
|
+
});
|
|
1688
|
+
covered[col]--;
|
|
1689
|
+
} else {
|
|
1690
|
+
const raw = span(item, column);
|
|
1691
|
+
const rowSpan = Math.min(Math.max(1, /* @__PURE__ */ isNaN(raw) ? 1 : raw), list.length - row);
|
|
1692
|
+
cells.set(column, {
|
|
1693
|
+
rowSpan,
|
|
1694
|
+
hidden: false
|
|
1695
|
+
});
|
|
1696
|
+
if (rowSpan > 1) covered[col] = rowSpan - 1;
|
|
1697
|
+
}
|
|
1698
|
+
result.set(id, cells);
|
|
1699
|
+
}
|
|
1700
|
+
return result;
|
|
1701
|
+
});
|
|
1702
|
+
}
|
|
1703
|
+
//#endregion
|
|
1704
|
+
//#region src/composables/createDataGrid/index.ts
|
|
1705
|
+
/**
|
|
1706
|
+
* @module createDataGrid
|
|
1707
|
+
*
|
|
1708
|
+
* @see https://0.vuetifyjs.com/composables/data/create-data-grid
|
|
1709
|
+
*
|
|
1710
|
+
* @remarks
|
|
1711
|
+
* Main factory that wires together column layout, cell editing, row ordering,
|
|
1712
|
+
* and row spanning on top of a createDataTable pipeline. Row ordering is
|
|
1713
|
+
* provided by a createSortable instance synced to the table's row registry
|
|
1714
|
+
* via register / unregister events; the ordered ids are applied post-sort,
|
|
1715
|
+
* pre-pagination by overriding the `items` projection.
|
|
1716
|
+
*
|
|
1717
|
+
* Rows are registered through the inherited registry surface (`register`,
|
|
1718
|
+
* `onboard`, `unregister`, `clear`) — they are not passed as an `items`
|
|
1719
|
+
* option to the factory.
|
|
1720
|
+
*
|
|
1721
|
+
* Follows the trinity pattern for dependency injection.
|
|
1722
|
+
*
|
|
1723
|
+
* @example
|
|
1724
|
+
* ```ts
|
|
1725
|
+
* const grid = createDataGrid()
|
|
1726
|
+
*
|
|
1727
|
+
* grid.columns.onboard([
|
|
1728
|
+
* { id: 'name', sortable: true },
|
|
1729
|
+
* { id: 'progress', editable: true },
|
|
1730
|
+
* ])
|
|
1731
|
+
*
|
|
1732
|
+
* grid.onboard(rows.map(value => ({ id: value.id, value })))
|
|
1733
|
+
* grid.layout.pin('name', 'left')
|
|
1734
|
+
* grid.editing.edit(row.id, 'progress')
|
|
1735
|
+
* ```
|
|
1736
|
+
*/
|
|
1737
|
+
/**
|
|
1738
|
+
* Creates a data grid instance with layout, editing, row ordering, and spanning
|
|
1739
|
+
* layered on top of the createDataTable pipeline.
|
|
1740
|
+
*
|
|
1741
|
+
* Rows live on the inherited registry surface — register them with
|
|
1742
|
+
* `grid.onboard(rows.map(value => ({ id: value.id, value })))` or
|
|
1743
|
+
* `grid.register({ id, value })`.
|
|
1744
|
+
*
|
|
1745
|
+
* @param options Data grid options
|
|
1746
|
+
* @returns Data grid context
|
|
1747
|
+
*
|
|
1748
|
+
* @example
|
|
1749
|
+
* ```ts
|
|
1750
|
+
* const grid = createDataGrid({ pagination: { initial: 1 } })
|
|
1751
|
+
*
|
|
1752
|
+
* grid.columns.onboard([
|
|
1753
|
+
* { id: 'name', sortable: true },
|
|
1754
|
+
* { id: 'progress', editable: true, validate: v => Number(v) >= 0 || 'must be positive' },
|
|
1755
|
+
* ])
|
|
1756
|
+
*
|
|
1757
|
+
* grid.onboard(rows.map(value => ({ id: value.id, value })))
|
|
1758
|
+
* grid.layout.pin('name', 'left')
|
|
1759
|
+
* grid.rows.move(rowId, 0)
|
|
1760
|
+
* grid.editing.edit(row.id, 'progress')
|
|
1761
|
+
* ```
|
|
1762
|
+
*/
|
|
1763
|
+
function createDataGrid(_options = {}) {
|
|
1764
|
+
const { editing: editingOptions, preserveRowOrder = false, rowSpanning, ...options } = _options;
|
|
1765
|
+
const table = createDataTable(options);
|
|
1766
|
+
const sortable = createSortable({ reactive: true });
|
|
1767
|
+
const dirty = shallowRef(false);
|
|
1768
|
+
table.on("register:ticket", (ticket) => {
|
|
1769
|
+
sortable.register({
|
|
1770
|
+
id: ticket.id,
|
|
1771
|
+
value: ticket.id
|
|
1772
|
+
});
|
|
1773
|
+
});
|
|
1774
|
+
table.on("unregister:ticket", (ticket) => {
|
|
1775
|
+
sortable.unregister(ticket.id);
|
|
1776
|
+
});
|
|
1777
|
+
table.on("clear:registry", () => {
|
|
1778
|
+
sortable.clear();
|
|
1779
|
+
dirty.value = false;
|
|
1780
|
+
});
|
|
1781
|
+
const order = toRef(() => sortable.keys());
|
|
1782
|
+
const sortedIds = computed(() => {
|
|
1783
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
1784
|
+
for (const ticket of table.values()) {
|
|
1785
|
+
const value = ticket.value;
|
|
1786
|
+
const queue = buckets.get(value);
|
|
1787
|
+
if (queue) queue.push(ticket.id);
|
|
1788
|
+
else buckets.set(value, [ticket.id]);
|
|
1789
|
+
}
|
|
1790
|
+
const ids = [];
|
|
1791
|
+
for (const value of table.sortedItems.value) {
|
|
1792
|
+
const id = buckets.get(value)?.shift();
|
|
1793
|
+
if (!/* @__PURE__ */ isUndefined(id)) ids.push(id);
|
|
1794
|
+
}
|
|
1795
|
+
return ids;
|
|
1796
|
+
});
|
|
1797
|
+
function move(id, toIndex) {
|
|
1798
|
+
if (!dirty.value) {
|
|
1799
|
+
const sorted = sortedIds.value;
|
|
1800
|
+
const seen = new Set(sorted);
|
|
1801
|
+
const rest = sortable.keys().filter((key) => !seen.has(key));
|
|
1802
|
+
sortable.reorder([...sorted, ...rest]);
|
|
1803
|
+
}
|
|
1804
|
+
sortable.move(id, toIndex);
|
|
1805
|
+
dirty.value = true;
|
|
1806
|
+
}
|
|
1807
|
+
function reset() {
|
|
1808
|
+
if (sortable.size > 0) sortable.reorder(table.keys());
|
|
1809
|
+
dirty.value = false;
|
|
1810
|
+
}
|
|
1811
|
+
useToggleScope(() => !toValue(preserveRowOrder), () => {
|
|
1812
|
+
watch(table.sort.columns, reset, { flush: "sync" });
|
|
1813
|
+
});
|
|
1814
|
+
const page = computed(() => {
|
|
1815
|
+
if (!dirty.value) {
|
|
1816
|
+
const start = table.pagination.pageStart.value;
|
|
1817
|
+
return {
|
|
1818
|
+
items: table.items.value,
|
|
1819
|
+
ids: table.total.value > table.sortedItems.value.length ? sortedIds.value : sortedIds.value.slice(start, start + table.items.value.length)
|
|
1820
|
+
};
|
|
1821
|
+
}
|
|
1822
|
+
const sorted = table.sortedItems.value;
|
|
1823
|
+
const ids = sortedIds.value;
|
|
1824
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1825
|
+
for (let i = 0; i < sorted.length; i++) byId.set(ids[i], sorted[i]);
|
|
1826
|
+
const seen = new Set(order.value);
|
|
1827
|
+
const items = [];
|
|
1828
|
+
const ordered = [];
|
|
1829
|
+
for (const id of order.value) {
|
|
1830
|
+
const value = byId.get(id);
|
|
1831
|
+
if (!/* @__PURE__ */ isUndefined(value)) {
|
|
1832
|
+
items.push(value);
|
|
1833
|
+
ordered.push(id);
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
for (const [i, element] of sorted.entries()) {
|
|
1837
|
+
const id = ids[i];
|
|
1838
|
+
if (!seen.has(id)) {
|
|
1839
|
+
items.push(element);
|
|
1840
|
+
ordered.push(id);
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
if (table.total.value > sorted.length) return {
|
|
1844
|
+
items,
|
|
1845
|
+
ids: ordered
|
|
1846
|
+
};
|
|
1847
|
+
const start = table.pagination.pageStart.value;
|
|
1848
|
+
const stop = table.pagination.pageStop.value;
|
|
1849
|
+
return {
|
|
1850
|
+
items: items.slice(start, stop),
|
|
1851
|
+
ids: ordered.slice(start, stop)
|
|
1852
|
+
};
|
|
1853
|
+
});
|
|
1854
|
+
const pageOrderedItems = toRef(() => page.value.items);
|
|
1855
|
+
const layout = createColumnLayout(table.columns);
|
|
1856
|
+
function lookup(row) {
|
|
1857
|
+
return table.get(row)?.value;
|
|
1858
|
+
}
|
|
1859
|
+
const editing = createCellEditing({
|
|
1860
|
+
columns: () => table.leaves.value.filter((col) => col.editable === true || /* @__PURE__ */ isFunction(col.editable)).map((col) => ({
|
|
1861
|
+
id: String(col.id),
|
|
1862
|
+
editable: col.editable,
|
|
1863
|
+
validate: col.validate
|
|
1864
|
+
})),
|
|
1865
|
+
lookup,
|
|
1866
|
+
registry: table,
|
|
1867
|
+
onEdit: editingOptions?.onEdit ? (row, column, value) => {
|
|
1868
|
+
const item = lookup(row);
|
|
1869
|
+
if (!/* @__PURE__ */ isUndefined(item)) editingOptions.onEdit(row, column, value, item);
|
|
1870
|
+
} : void 0
|
|
1871
|
+
});
|
|
1872
|
+
const spans = createRowSpanning({
|
|
1873
|
+
items: pageOrderedItems,
|
|
1874
|
+
columns: () => table.leaves.value.map((col) => col.id),
|
|
1875
|
+
key: (_item, index) => page.value.ids[index],
|
|
1876
|
+
span(item, column) {
|
|
1877
|
+
const col = table.leaves.value.find((leaf) => String(leaf.id) === column);
|
|
1878
|
+
if (col && /* @__PURE__ */ isFunction(col.span)) return col.span(item);
|
|
1879
|
+
return rowSpanning?.(item, column) ?? 1;
|
|
1880
|
+
}
|
|
1881
|
+
});
|
|
1882
|
+
return {
|
|
1883
|
+
...table,
|
|
1884
|
+
items: pageOrderedItems,
|
|
1885
|
+
layout,
|
|
1886
|
+
rows: {
|
|
1887
|
+
order,
|
|
1888
|
+
move,
|
|
1889
|
+
reset
|
|
1890
|
+
},
|
|
1891
|
+
editing,
|
|
1892
|
+
spans,
|
|
1893
|
+
get size() {
|
|
1894
|
+
return table.size;
|
|
1895
|
+
}
|
|
1896
|
+
};
|
|
1897
|
+
}
|
|
1898
|
+
/**
|
|
1899
|
+
* Creates a data grid context with dependency injection support.
|
|
1900
|
+
*
|
|
1901
|
+
* @param options Data grid context options including namespace
|
|
1902
|
+
* @returns A trinity tuple: [useDataGridContext, provideDataGridContext, defaultContext]
|
|
1903
|
+
*
|
|
1904
|
+
* @example
|
|
1905
|
+
* ```ts
|
|
1906
|
+
* const [useDataGrid, provideDataGrid, grid] = createDataGridContext({
|
|
1907
|
+
* namespace: 'v0:projects',
|
|
1908
|
+
* })
|
|
1909
|
+
*
|
|
1910
|
+
* grid.columns.onboard(columns)
|
|
1911
|
+
* grid.onboard(rows.map(value => ({ id: value.id, value })))
|
|
1912
|
+
* provideDataGrid()
|
|
1913
|
+
* const ctx = useDataGrid()
|
|
1914
|
+
* ```
|
|
1915
|
+
*/
|
|
1916
|
+
function createDataGridContext(_options = {}) {
|
|
1917
|
+
const { namespace = "v0:data-grid", ...options } = _options;
|
|
1918
|
+
const [useDataGridContext, _provideDataGridContext] = createContext(namespace);
|
|
1919
|
+
const context = createDataGrid(options);
|
|
1920
|
+
function provideDataGridContext(_context = context, app) {
|
|
1921
|
+
return _provideDataGridContext(_context, app);
|
|
1922
|
+
}
|
|
1923
|
+
return createTrinity(useDataGridContext, provideDataGridContext, context);
|
|
1924
|
+
}
|
|
1925
|
+
/**
|
|
1926
|
+
* Returns the current data grid context from dependency injection.
|
|
1927
|
+
*
|
|
1928
|
+
* @param namespace The namespace for the data grid context. @default 'v0:data-grid'
|
|
1929
|
+
* @returns The current data grid context
|
|
1930
|
+
*
|
|
1931
|
+
* @example
|
|
1932
|
+
* ```ts
|
|
1933
|
+
* const grid = useDataGrid<Project>()
|
|
1934
|
+
*
|
|
1935
|
+
* grid.layout.pin('id', 'left')
|
|
1936
|
+
* grid.rows.reset()
|
|
1937
|
+
* ```
|
|
1938
|
+
*/
|
|
1939
|
+
function useDataGrid(namespace = "v0:data-grid") {
|
|
1940
|
+
return useContext(namespace);
|
|
1941
|
+
}
|
|
1942
|
+
//#endregion
|
|
1943
|
+
//#region src/composables/createForm/index.ts
|
|
1944
|
+
/**
|
|
1945
|
+
* @module createForm
|
|
1946
|
+
*
|
|
1947
|
+
* @see https://0.vuetifyjs.com/composables/forms/create-form
|
|
1948
|
+
*
|
|
1949
|
+
* @remarks
|
|
1950
|
+
* Form composable that coordinates validation across multiple fields.
|
|
1951
|
+
*
|
|
1952
|
+
* Key features:
|
|
1953
|
+
* - Pure registry of ValidationContext instances
|
|
1954
|
+
* - Validations auto-register via useForm() injection
|
|
1955
|
+
* - Form-level submit, reset, and aggregate state
|
|
1956
|
+
* - Exposes disabled/readonly refs for component consumption
|
|
1957
|
+
* - Trinity pattern for DI
|
|
1958
|
+
*
|
|
1959
|
+
* Per-field validation logic lives in createValidation.
|
|
1960
|
+
* createForm is the mothership — it coordinates, not creates.
|
|
1961
|
+
*
|
|
1962
|
+
* @example
|
|
1963
|
+
* ```ts
|
|
1964
|
+
* import { createForm } from '@vuetify/v0'
|
|
1965
|
+
*
|
|
1966
|
+
* const form = createForm()
|
|
1967
|
+
* const isValid = await form.validate()
|
|
1968
|
+
* if (isValid) form.submit()
|
|
1969
|
+
* ```
|
|
1970
|
+
*/
|
|
1971
|
+
/**
|
|
1972
|
+
* Creates a new form instance.
|
|
1973
|
+
*
|
|
1974
|
+
* A form is a pure registry of validation contexts. Validations register
|
|
1975
|
+
* themselves via `useForm()` injection. The form coordinates submit, reset,
|
|
1976
|
+
* and aggregate state across all registered validations.
|
|
1977
|
+
*
|
|
1978
|
+
* @param options The options for the form instance.
|
|
1979
|
+
* @returns A new form context.
|
|
1980
|
+
*
|
|
1981
|
+
* @see https://0.vuetifyjs.com/composables/forms/create-form
|
|
1982
|
+
*
|
|
1983
|
+
* @example
|
|
1984
|
+
* ```ts
|
|
1985
|
+
* import { createForm, createValidation } from '@vuetify/v0'
|
|
1986
|
+
* import { shallowRef } from 'vue'
|
|
1987
|
+
*
|
|
1988
|
+
* const form = createForm()
|
|
1989
|
+
*
|
|
1990
|
+
* // Each validation is one input — form is just the coordinator
|
|
1991
|
+
* const email = shallowRef('')
|
|
1992
|
+
* const validation = createValidation({
|
|
1993
|
+
* value: email,
|
|
1994
|
+
* rules: ['required', 'email'],
|
|
1995
|
+
* })
|
|
1996
|
+
*
|
|
1997
|
+
* form.register({ value: validation })
|
|
1998
|
+
*
|
|
1999
|
+
* await form.submit()
|
|
2000
|
+
* form.reset()
|
|
2001
|
+
* ```
|
|
2002
|
+
*/
|
|
2003
|
+
function createForm(options = {}) {
|
|
2004
|
+
const { disabled = false, readonly = false, ..._options } = options;
|
|
2005
|
+
const registry = createRegistry({
|
|
2006
|
+
..._options,
|
|
2007
|
+
reactive: true
|
|
2008
|
+
});
|
|
2009
|
+
const isValidating = computed(() => {
|
|
2010
|
+
for (const ticket of registry.values()) if (ticket.value.isValidating.value) return true;
|
|
2011
|
+
return false;
|
|
2012
|
+
});
|
|
2013
|
+
const isValid = computed(() => {
|
|
2014
|
+
let hasNull = false;
|
|
2015
|
+
let hasFields = false;
|
|
2016
|
+
for (const ticket of registry.values()) {
|
|
2017
|
+
hasFields = true;
|
|
2018
|
+
if (ticket.value.isValid.value === false) return false;
|
|
2019
|
+
if (/* @__PURE__ */ isNull(ticket.value.isValid.value)) hasNull = true;
|
|
2020
|
+
}
|
|
2021
|
+
if (!hasFields) return null;
|
|
2022
|
+
return hasNull ? null : true;
|
|
2023
|
+
});
|
|
2024
|
+
function register(registration) {
|
|
2025
|
+
return registry.register(registration);
|
|
2026
|
+
}
|
|
2027
|
+
function reset() {
|
|
2028
|
+
for (const ticket of registry.values()) ticket.value.reset();
|
|
2029
|
+
}
|
|
2030
|
+
async function submit(id) {
|
|
2031
|
+
const ids = /* @__PURE__ */ isUndefined(id) ? [...registry.keys()] : /* @__PURE__ */ toArray(id);
|
|
2032
|
+
return (await Promise.all(ids.map(async (key) => {
|
|
2033
|
+
const ticket = registry.get(key);
|
|
2034
|
+
if (!ticket) return true;
|
|
2035
|
+
return ticket.value.validate();
|
|
2036
|
+
}))).every(Boolean);
|
|
2037
|
+
}
|
|
2038
|
+
return {
|
|
2039
|
+
...registry,
|
|
2040
|
+
register,
|
|
2041
|
+
submit,
|
|
2042
|
+
reset,
|
|
2043
|
+
disabled,
|
|
2044
|
+
readonly,
|
|
2045
|
+
isValid,
|
|
2046
|
+
isValidating,
|
|
2047
|
+
get size() {
|
|
2048
|
+
return registry.size;
|
|
2049
|
+
}
|
|
2050
|
+
};
|
|
2051
|
+
}
|
|
2052
|
+
/**
|
|
2053
|
+
* Creates a new form context using the Trinity pattern.
|
|
2054
|
+
*
|
|
2055
|
+
* @param options The options for the form context.
|
|
2056
|
+
* @returns A Trinity tuple: [useFormContext, provideFormContext, formContext]
|
|
2057
|
+
*
|
|
2058
|
+
* @see https://0.vuetifyjs.com/composables/forms/create-form
|
|
2059
|
+
*
|
|
2060
|
+
* @example
|
|
2061
|
+
* ```ts
|
|
2062
|
+
* import { createFormContext } from '@vuetify/v0'
|
|
2063
|
+
*
|
|
2064
|
+
* export const [useMyForm, provideMyForm, myForm] = createFormContext()
|
|
2065
|
+
* ```
|
|
2066
|
+
*/
|
|
2067
|
+
function createFormContext(_options = {}) {
|
|
2068
|
+
const { namespace = "v0:form", ...options } = _options;
|
|
2069
|
+
return createTrinity(namespace, createForm(options));
|
|
2070
|
+
}
|
|
2071
|
+
/**
|
|
2072
|
+
* Returns the current form instance.
|
|
959
2073
|
*
|
|
960
2074
|
* @param namespace The namespace for the form context. Defaults to `'v0:form'`.
|
|
961
2075
|
* @returns The current form instance, or undefined if not provided.
|
|
@@ -1063,14 +2177,18 @@ function createValidation(_options = {}) {
|
|
|
1063
2177
|
return group.batch(() => rules.map((r) => register(r)));
|
|
1064
2178
|
}
|
|
1065
2179
|
async function validate(_value = UNSET, silent = false) {
|
|
2180
|
+
const gen = ++generation;
|
|
1066
2181
|
const val = _value === UNSET ? toValue(valueSource) : _value;
|
|
1067
2182
|
const activeRules = [];
|
|
1068
2183
|
for (const ticket of group.selectedItems.value) activeRules.push(ticket.value);
|
|
1069
2184
|
if (activeRules.length === 0) {
|
|
1070
|
-
if (!silent)
|
|
2185
|
+
if (!silent) {
|
|
2186
|
+
errors.value = [];
|
|
2187
|
+
isValid.value = true;
|
|
2188
|
+
isValidating.value = false;
|
|
2189
|
+
}
|
|
1071
2190
|
return true;
|
|
1072
2191
|
}
|
|
1073
|
-
const gen = ++generation;
|
|
1074
2192
|
if (!silent) isValidating.value = true;
|
|
1075
2193
|
try {
|
|
1076
2194
|
const results = await Promise.all(activeRules.map((rule) => rule(val)));
|
|
@@ -1193,171 +2311,30 @@ function createInput(options) {
|
|
|
1193
2311
|
validation.reset();
|
|
1194
2312
|
}
|
|
1195
2313
|
return {
|
|
1196
|
-
id,
|
|
1197
|
-
label,
|
|
1198
|
-
name,
|
|
1199
|
-
form,
|
|
1200
|
-
required,
|
|
1201
|
-
descriptionId,
|
|
1202
|
-
errorId,
|
|
1203
|
-
descriptions,
|
|
1204
|
-
fieldErrors,
|
|
1205
|
-
hasDescription,
|
|
1206
|
-
hasError,
|
|
1207
|
-
value,
|
|
1208
|
-
isDirty,
|
|
1209
|
-
isFocused,
|
|
1210
|
-
isDisabled,
|
|
1211
|
-
isReadonly,
|
|
1212
|
-
isPristine,
|
|
1213
|
-
isTouched,
|
|
1214
|
-
errors,
|
|
1215
|
-
isValid,
|
|
1216
|
-
isValidating: validation.isValidating,
|
|
1217
|
-
validate,
|
|
1218
|
-
reset,
|
|
1219
|
-
state
|
|
1220
|
-
};
|
|
1221
|
-
}
|
|
1222
|
-
//#endregion
|
|
1223
|
-
//#region src/composables/createSortable/index.ts
|
|
1224
|
-
/**
|
|
1225
|
-
* @module createSortable
|
|
1226
|
-
*
|
|
1227
|
-
* @see https://0.vuetifyjs.com/composables/data/create-sortable
|
|
1228
|
-
*
|
|
1229
|
-
* @remarks
|
|
1230
|
-
* Headless ordered-list primitive. Extends `createModel` with mutation primitives
|
|
1231
|
-
* (`swap`, `reorder`) and an enriched `move:ticket` event on the registry event
|
|
1232
|
-
* bus. Honors `disabled` as a real mutation gate at both root and per-ticket scope.
|
|
1233
|
-
*
|
|
1234
|
-
* Pure logical composable — no DnD, no DOM, no keyboard. Drag-and-drop integration
|
|
1235
|
-
* composes with `useDragDrop`; keyboard reorder composes with `useVirtualFocus`.
|
|
1236
|
-
*
|
|
1237
|
-
* @example
|
|
1238
|
-
* ```ts
|
|
1239
|
-
* import { createSortable } from '@vuetify/v0'
|
|
1240
|
-
* import type { SortableTicketInput } from '@vuetify/v0'
|
|
1241
|
-
*
|
|
1242
|
-
* interface TaskTicket extends SortableTicketInput {
|
|
1243
|
-
* value: { id: number; label: string }
|
|
1244
|
-
* }
|
|
1245
|
-
*
|
|
1246
|
-
* const sortable = createSortable<TaskTicket>()
|
|
1247
|
-
* const a = sortable.register({ value: { id: 1, label: 'A' } })
|
|
1248
|
-
* const b = sortable.register({ value: { id: 2, label: 'B' } })
|
|
1249
|
-
*
|
|
1250
|
-
* sortable.move(a.id, 1)
|
|
1251
|
-
* sortable.on('move:ticket', ({ ticket, from, to }) => {
|
|
1252
|
-
* console.log(`${ticket.value.label} moved ${from} → ${to}`)
|
|
1253
|
-
* })
|
|
1254
|
-
* ```
|
|
1255
|
-
*/
|
|
1256
|
-
/**
|
|
1257
|
-
* Create an ordered-list state primitive.
|
|
1258
|
-
*
|
|
1259
|
-
* @param options Sortable options.
|
|
1260
|
-
* @returns A {@link SortableContext} that extends {@link ModelContext}.
|
|
1261
|
-
*
|
|
1262
|
-
* @example
|
|
1263
|
-
* ```ts
|
|
1264
|
-
* const sortable = createSortable<Todo>()
|
|
1265
|
-
* sortable.onboard(todos.value.map(value => ({ value })))
|
|
1266
|
-
* ```
|
|
1267
|
-
*/
|
|
1268
|
-
function createSortable(_options = {}) {
|
|
1269
|
-
const { disabled, ...options } = _options;
|
|
1270
|
-
const model = createModel({
|
|
1271
|
-
...options,
|
|
1272
|
-
events: true,
|
|
1273
|
-
disabled
|
|
1274
|
-
});
|
|
1275
|
-
const logger = useLogger();
|
|
1276
|
-
function move(id, toIndex) {
|
|
1277
|
-
if (toValue(disabled)) return void 0;
|
|
1278
|
-
const ticket = model.get(id);
|
|
1279
|
-
if (!ticket) return void 0;
|
|
1280
|
-
if (toValue(ticket.disabled)) return void 0;
|
|
1281
|
-
const from = ticket.index;
|
|
1282
|
-
const result = model.move(id, toIndex);
|
|
1283
|
-
if (result && result.index !== from) model.emit("move:ticket", {
|
|
1284
|
-
ticket: result,
|
|
1285
|
-
from,
|
|
1286
|
-
to: result.index
|
|
1287
|
-
});
|
|
1288
|
-
return result;
|
|
1289
|
-
}
|
|
1290
|
-
function swap(a, b) {
|
|
1291
|
-
if (toValue(disabled)) return;
|
|
1292
|
-
if (a === b) return;
|
|
1293
|
-
const ta = model.get(a);
|
|
1294
|
-
const tb = model.get(b);
|
|
1295
|
-
if (!ta || !tb) return;
|
|
1296
|
-
if (toValue(ta.disabled) || toValue(tb.disabled)) return;
|
|
1297
|
-
const indexA = ta.index;
|
|
1298
|
-
const indexB = tb.index;
|
|
1299
|
-
model.batch(() => {
|
|
1300
|
-
const ra = model.move(a, indexB);
|
|
1301
|
-
if (ra && ra.index !== indexA) model.emit("move:ticket", {
|
|
1302
|
-
ticket: ra,
|
|
1303
|
-
from: indexA,
|
|
1304
|
-
to: ra.index
|
|
1305
|
-
});
|
|
1306
|
-
const rb = model.move(b, indexA);
|
|
1307
|
-
if (rb && rb.index !== indexB) model.emit("move:ticket", {
|
|
1308
|
-
ticket: rb,
|
|
1309
|
-
from: indexB,
|
|
1310
|
-
to: rb.index
|
|
1311
|
-
});
|
|
1312
|
-
});
|
|
1313
|
-
}
|
|
1314
|
-
function reorder(ids) {
|
|
1315
|
-
if (toValue(disabled)) return;
|
|
1316
|
-
const currentSize = model.size;
|
|
1317
|
-
if (ids.length !== currentSize) {
|
|
1318
|
-
logger.warn(`Reorder expected ${currentSize} ids, got ${ids.length}.`);
|
|
1319
|
-
return;
|
|
1320
|
-
}
|
|
1321
|
-
const known = new Set(model.keys());
|
|
1322
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1323
|
-
for (const id of ids) {
|
|
1324
|
-
if (!known.has(id)) {
|
|
1325
|
-
logger.warn(`Reorder contains unknown id "${String(id)}".`);
|
|
1326
|
-
return;
|
|
1327
|
-
}
|
|
1328
|
-
if (seen.has(id)) {
|
|
1329
|
-
logger.warn(`Reorder contains duplicate id "${String(id)}".`);
|
|
1330
|
-
return;
|
|
1331
|
-
}
|
|
1332
|
-
seen.add(id);
|
|
1333
|
-
}
|
|
1334
|
-
const froms = /* @__PURE__ */ new Map();
|
|
1335
|
-
for (const id of ids) {
|
|
1336
|
-
const ticket = model.get(id);
|
|
1337
|
-
if (ticket) froms.set(id, ticket.index);
|
|
1338
|
-
}
|
|
1339
|
-
model.batch(() => {
|
|
1340
|
-
model.reorder(ids);
|
|
1341
|
-
for (const [index, id] of ids.entries()) {
|
|
1342
|
-
const from = froms.get(id);
|
|
1343
|
-
if (from === index) continue;
|
|
1344
|
-
const ticket = model.get(id);
|
|
1345
|
-
if (ticket) model.emit("move:ticket", {
|
|
1346
|
-
ticket,
|
|
1347
|
-
from,
|
|
1348
|
-
to: index
|
|
1349
|
-
});
|
|
1350
|
-
}
|
|
1351
|
-
});
|
|
1352
|
-
}
|
|
1353
|
-
return {
|
|
1354
|
-
...model,
|
|
1355
|
-
move,
|
|
1356
|
-
swap,
|
|
1357
|
-
reorder,
|
|
1358
|
-
get size() {
|
|
1359
|
-
return model.size;
|
|
1360
|
-
}
|
|
2314
|
+
id,
|
|
2315
|
+
label,
|
|
2316
|
+
name,
|
|
2317
|
+
form,
|
|
2318
|
+
required,
|
|
2319
|
+
descriptionId,
|
|
2320
|
+
errorId,
|
|
2321
|
+
descriptions,
|
|
2322
|
+
fieldErrors,
|
|
2323
|
+
hasDescription,
|
|
2324
|
+
hasError,
|
|
2325
|
+
value,
|
|
2326
|
+
isDirty,
|
|
2327
|
+
isFocused,
|
|
2328
|
+
isDisabled,
|
|
2329
|
+
isReadonly,
|
|
2330
|
+
isPristine,
|
|
2331
|
+
isTouched,
|
|
2332
|
+
errors,
|
|
2333
|
+
isValid,
|
|
2334
|
+
isValidating: validation.isValidating,
|
|
2335
|
+
validate,
|
|
2336
|
+
reset,
|
|
2337
|
+
state
|
|
1361
2338
|
};
|
|
1362
2339
|
}
|
|
1363
2340
|
//#endregion
|
|
@@ -1845,7 +2822,11 @@ function createNested(_options = {}) {
|
|
|
1845
2822
|
while (!/* @__PURE__ */ isUndefined(parentId)) {
|
|
1846
2823
|
const childIds = children.get(parentId);
|
|
1847
2824
|
if (childIds && childIds.length > 0) {
|
|
1848
|
-
const
|
|
2825
|
+
const enabledIds = childIds.filter((cid) => {
|
|
2826
|
+
const child = group.get(cid);
|
|
2827
|
+
return !child || !toValue(child.disabled);
|
|
2828
|
+
});
|
|
2829
|
+
const allSelected = enabledIds.length > 0 && enabledIds.every((cid) => selectedIds.has(cid));
|
|
1849
2830
|
const someSelected = !allSelected && childIds.some((cid) => selectedIds.has(cid) || mixedIds.has(cid));
|
|
1850
2831
|
if (allSelected) {
|
|
1851
2832
|
mixedIds.delete(parentId);
|
|
@@ -3023,6 +4004,20 @@ function useElementSize(target) {
|
|
|
3023
4004
|
* Use variable mode (default) for items with different widths like Breadcrumbs.
|
|
3024
4005
|
* Use uniform mode (itemWidth option) for same-width items like Pagination buttons.
|
|
3025
4006
|
*
|
|
4007
|
+
* ## Item registration contract
|
|
4008
|
+
*
|
|
4009
|
+
* Every child item must call `measure(index, el)` on mount and
|
|
4010
|
+
* `measure(index, undefined)` on unmount, regardless of mode. The width arg
|
|
4011
|
+
* is recorded in both modes but **only consumed by uniform mode for
|
|
4012
|
+
* isOverflowing's count** — capacity in uniform mode is derived purely
|
|
4013
|
+
* from `itemWidth`. Variable mode uses the per-item widths for both
|
|
4014
|
+
* capacity and isOverflowing.
|
|
4015
|
+
*
|
|
4016
|
+
* Consumers that compute their own capacity outside this composable (e.g.,
|
|
4017
|
+
* with a separate registry) still need the measure() calls if they want
|
|
4018
|
+
* `isOverflowing` to reflect reality — without them the count source is
|
|
4019
|
+
* empty and the signal is permanently false.
|
|
4020
|
+
*
|
|
3026
4021
|
* @example
|
|
3027
4022
|
* ```ts
|
|
3028
4023
|
* import { useTemplateRef } from 'vue'
|
|
@@ -3093,8 +4088,8 @@ function createOverflow(options = {}) {
|
|
|
3093
4088
|
/* v8 ignore start -- browser-only measurement code */
|
|
3094
4089
|
if (!IN_BROWSER) return;
|
|
3095
4090
|
const style = getComputedStyle(el);
|
|
3096
|
-
const marginX = Number.parseFloat(style.marginLeft) + Number.parseFloat(style.marginRight);
|
|
3097
|
-
const w = el.offsetWidth + marginX;
|
|
4091
|
+
const marginX = (Number.parseFloat(style.marginLeft) || 0) + (Number.parseFloat(style.marginRight) || 0);
|
|
4092
|
+
const w = (el.offsetWidth || 0) + marginX;
|
|
3098
4093
|
if (widths.value.get(index) !== w) widths.value = new Map(widths.value).set(index, w);
|
|
3099
4094
|
/* v8 ignore stop */
|
|
3100
4095
|
}
|
|
@@ -3111,36 +4106,42 @@ function createOverflow(options = {}) {
|
|
|
3111
4106
|
}
|
|
3112
4107
|
return sum;
|
|
3113
4108
|
});
|
|
4109
|
+
const capacity = computed(() => {
|
|
4110
|
+
const available = width.value - toValue(reserved);
|
|
4111
|
+
if (width.value === 0) return Infinity;
|
|
4112
|
+
if (available <= 0) return 0;
|
|
4113
|
+
const g = toValue(gap);
|
|
4114
|
+
const uniformWidth = toValue(itemWidth) ?? 0;
|
|
4115
|
+
if (!/* @__PURE__ */ isUndefined(itemWidth) && uniformWidth <= 0) return Infinity;
|
|
4116
|
+
if (uniformWidth > 0) {
|
|
4117
|
+
const subsequent = uniformWidth + g;
|
|
4118
|
+
if (available < uniformWidth) return 0;
|
|
4119
|
+
return Math.max(1, Math.floor((available - uniformWidth) / subsequent) + 1);
|
|
4120
|
+
}
|
|
4121
|
+
const entries = [...widths.value.entries()].toSorted((a, b) => a[0] - b[0]);
|
|
4122
|
+
if (toValue(reverse)) entries.reverse();
|
|
4123
|
+
let sum = 0;
|
|
4124
|
+
let count = 0;
|
|
4125
|
+
for (const [, w] of entries) {
|
|
4126
|
+
const next = sum + w + (count > 0 ? g : 0);
|
|
4127
|
+
if (next > available) break;
|
|
4128
|
+
sum = next;
|
|
4129
|
+
count++;
|
|
4130
|
+
}
|
|
4131
|
+
return count;
|
|
4132
|
+
});
|
|
3114
4133
|
return {
|
|
3115
4134
|
container,
|
|
3116
4135
|
width,
|
|
3117
|
-
capacity
|
|
3118
|
-
const available = width.value - toValue(reserved);
|
|
3119
|
-
if (width.value === 0) return Infinity;
|
|
3120
|
-
if (available <= 0) return 0;
|
|
3121
|
-
const g = toValue(gap);
|
|
3122
|
-
const uniformWidth = toValue(itemWidth) ?? 0;
|
|
3123
|
-
if (!/* @__PURE__ */ isUndefined(itemWidth) && uniformWidth <= 0) return Infinity;
|
|
3124
|
-
if (uniformWidth > 0) {
|
|
3125
|
-
const subsequent = uniformWidth + g;
|
|
3126
|
-
if (available < uniformWidth) return 0;
|
|
3127
|
-
return Math.max(1, Math.floor((available - uniformWidth) / subsequent) + 1);
|
|
3128
|
-
}
|
|
3129
|
-
const entries = [...widths.value.entries()].toSorted((a, b) => a[0] - b[0]);
|
|
3130
|
-
if (toValue(reverse)) entries.reverse();
|
|
3131
|
-
let sum = 0;
|
|
3132
|
-
let count = 0;
|
|
3133
|
-
for (const [, w] of entries) {
|
|
3134
|
-
const next = sum + w + (count > 0 ? g : 0);
|
|
3135
|
-
if (next > available) break;
|
|
3136
|
-
sum = next;
|
|
3137
|
-
count++;
|
|
3138
|
-
}
|
|
3139
|
-
return count;
|
|
3140
|
-
}),
|
|
4136
|
+
capacity,
|
|
3141
4137
|
total,
|
|
3142
4138
|
isOverflowing: toRef(() => {
|
|
3143
4139
|
if (width.value === 0) return false;
|
|
4140
|
+
if (!/* @__PURE__ */ isUndefined(itemWidth)) {
|
|
4141
|
+
const cap = capacity.value;
|
|
4142
|
+
if (cap === Infinity) return false;
|
|
4143
|
+
return widths.value.size > cap;
|
|
4144
|
+
}
|
|
3144
4145
|
return total.value > width.value - toValue(reserved);
|
|
3145
4146
|
}),
|
|
3146
4147
|
measure,
|
|
@@ -3360,6 +4361,7 @@ function useProgress(namespace = "v0:progress") {
|
|
|
3360
4361
|
* console.log(rating.value.value) // 4
|
|
3361
4362
|
* ```
|
|
3362
4363
|
*/
|
|
4364
|
+
const MAX_SIZE = 1e3;
|
|
3363
4365
|
/**
|
|
3364
4366
|
* Creates a rating instance.
|
|
3365
4367
|
*
|
|
@@ -3416,7 +4418,7 @@ function createRating(_options = {}) {
|
|
|
3416
4418
|
value,
|
|
3417
4419
|
items: computed(() => {
|
|
3418
4420
|
const current = value.value;
|
|
3419
|
-
return (/* @__PURE__ */ range(toValue(_size), 1)).map((i) => ({
|
|
4421
|
+
return (/* @__PURE__ */ range(/* @__PURE__ */ clamp(Math.floor(toValue(_size)), 0, MAX_SIZE), 1)).map((i) => ({
|
|
3420
4422
|
value: i,
|
|
3421
4423
|
state: getState(i, current)
|
|
3422
4424
|
}));
|
|
@@ -3607,18 +4609,16 @@ function createSlider(options = {}) {
|
|
|
3607
4609
|
pending = snapped;
|
|
3608
4610
|
return;
|
|
3609
4611
|
}
|
|
3610
|
-
|
|
4612
|
+
const gap = minStepsBetweenThumbs * step;
|
|
4613
|
+
let written;
|
|
4614
|
+
for (const [index, element] of snapped.entries()) {
|
|
3611
4615
|
const ticket = thumbs.value[index];
|
|
3612
4616
|
if (!ticket || !isRef(ticket.value)) continue;
|
|
3613
|
-
let constrained =
|
|
3614
|
-
if (!crossover)
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
if (!/* @__PURE__ */ isUndefined(prev)) constrained = Math.max(constrained, prev + gap);
|
|
3619
|
-
if (!/* @__PURE__ */ isUndefined(following)) constrained = Math.min(constrained, following - gap);
|
|
3620
|
-
}
|
|
3621
|
-
ticket.value.value = /* @__PURE__ */ clamp(constrained, min, max);
|
|
4617
|
+
let constrained = element;
|
|
4618
|
+
if (!crossover && !/* @__PURE__ */ isUndefined(written)) constrained = Math.max(constrained, written + gap);
|
|
4619
|
+
constrained = /* @__PURE__ */ clamp(constrained, min, max);
|
|
4620
|
+
ticket.value.value = constrained;
|
|
4621
|
+
written = constrained;
|
|
3622
4622
|
}
|
|
3623
4623
|
}
|
|
3624
4624
|
function set(index, value) {
|
|
@@ -5134,127 +6134,13 @@ function createDatePlugin(options) {
|
|
|
5134
6134
|
* ```
|
|
5135
6135
|
*/
|
|
5136
6136
|
function useDate(namespace = "v0:date") {
|
|
5137
|
-
if (!/* @__PURE__ */ instanceExists()) throw new
|
|
6137
|
+
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() }))", {
|
|
6138
|
+
code: "V0_PLUGIN_MISSING",
|
|
6139
|
+
plugin: "createDatePlugin"
|
|
6140
|
+
});
|
|
5138
6141
|
return useContext(namespace);
|
|
5139
6142
|
}
|
|
5140
6143
|
//#endregion
|
|
5141
|
-
//#region src/composables/useDelay/index.ts
|
|
5142
|
-
/**
|
|
5143
|
-
* @module useDelay
|
|
5144
|
-
*
|
|
5145
|
-
* @see https://0.vuetifyjs.com/composables/system/use-delay
|
|
5146
|
-
*
|
|
5147
|
-
* @remarks
|
|
5148
|
-
* Schedule open and close transitions with configurable delays.
|
|
5149
|
-
*
|
|
5150
|
-
* Composes `useTimer` for the underlying lifecycle (start / stop / pause / resume,
|
|
5151
|
-
* plus reactive `isActive`, `isPaused`, `remaining`) and adds direction tracking
|
|
5152
|
-
* (`isOpening`) and promise-based resolution.
|
|
5153
|
-
*
|
|
5154
|
-
* Key features:
|
|
5155
|
-
* - Reactive `openDelay` and `closeDelay` via `MaybeRefOrGetter`
|
|
5156
|
-
* - `start(isOpening)` returns a `Promise<boolean>` that resolves once the delay elapses
|
|
5157
|
-
* - Per-call `minDelay` floor enforces a minimum delay
|
|
5158
|
-
* - Automatic cleanup on scope disposal (timer cleared, pending promise settled with current direction)
|
|
5159
|
-
* - SSR-safe
|
|
5160
|
-
*
|
|
5161
|
-
* @example
|
|
5162
|
-
* ```ts
|
|
5163
|
-
* import { useDelay } from '@vuetify/v0'
|
|
5164
|
-
*
|
|
5165
|
-
* const delay = useDelay(isOpening => isVisible.value = isOpening, {
|
|
5166
|
-
* openDelay: 300,
|
|
5167
|
-
* closeDelay: 200,
|
|
5168
|
-
* })
|
|
5169
|
-
*
|
|
5170
|
-
* delay.start(true)
|
|
5171
|
-
* delay.start(false, { minDelay: 500 })
|
|
5172
|
-
* ```
|
|
5173
|
-
*/
|
|
5174
|
-
/**
|
|
5175
|
-
* Schedule open and close transitions with configurable delays.
|
|
5176
|
-
*
|
|
5177
|
-
* Composes `useTimer` for the lifecycle (`start` / `stop` / `pause` / `resume` plus
|
|
5178
|
-
* reactive `isActive`, `isPaused`, `remaining`) and adds direction tracking
|
|
5179
|
-
* (`isOpening`) and promise-based resolution.
|
|
5180
|
-
*
|
|
5181
|
-
* @param callback Optional callback invoked once a started delay elapses; receives the resolved direction.
|
|
5182
|
-
* @param options Reactive delay settings.
|
|
5183
|
-
* @returns Lifecycle controls and reactive state.
|
|
5184
|
-
*
|
|
5185
|
-
* @see https://0.vuetifyjs.com/composables/system/use-delay
|
|
5186
|
-
*
|
|
5187
|
-
* @example
|
|
5188
|
-
* ```ts
|
|
5189
|
-
* const delay = useDelay(isOpening => isVisible.value = isOpening, {
|
|
5190
|
-
* openDelay: 300,
|
|
5191
|
-
* closeDelay: 200,
|
|
5192
|
-
* })
|
|
5193
|
-
*
|
|
5194
|
-
* delay.start(true)
|
|
5195
|
-
* delay.start(false, { minDelay: 500 })
|
|
5196
|
-
* delay.pause()
|
|
5197
|
-
* delay.resume()
|
|
5198
|
-
* delay.stop()
|
|
5199
|
-
* ```
|
|
5200
|
-
*/
|
|
5201
|
-
function useDelay(callback, options = {}) {
|
|
5202
|
-
const { openDelay, closeDelay } = options;
|
|
5203
|
-
const isOpening = shallowRef(false);
|
|
5204
|
-
let minDelay = 0;
|
|
5205
|
-
let resolver;
|
|
5206
|
-
function fire() {
|
|
5207
|
-
const direction = isOpening.value;
|
|
5208
|
-
const resolve = resolver;
|
|
5209
|
-
resolver = void 0;
|
|
5210
|
-
resolve?.(direction);
|
|
5211
|
-
callback?.(direction);
|
|
5212
|
-
}
|
|
5213
|
-
function duration() {
|
|
5214
|
-
const raw = isOpening.value ? toValue(openDelay) : toValue(closeDelay);
|
|
5215
|
-
return Math.max(minDelay, raw ?? 0);
|
|
5216
|
-
}
|
|
5217
|
-
const timer = useTimer(fire, { duration });
|
|
5218
|
-
function start(direction, options = {}) {
|
|
5219
|
-
const previous = resolver;
|
|
5220
|
-
resolver = void 0;
|
|
5221
|
-
isOpening.value = direction;
|
|
5222
|
-
minDelay = options.minDelay ?? 0;
|
|
5223
|
-
previous?.(direction);
|
|
5224
|
-
const ms = duration();
|
|
5225
|
-
return new Promise((resolve) => {
|
|
5226
|
-
resolver = resolve;
|
|
5227
|
-
if (ms <= 0) {
|
|
5228
|
-
timer.stop();
|
|
5229
|
-
fire();
|
|
5230
|
-
return;
|
|
5231
|
-
}
|
|
5232
|
-
timer.start();
|
|
5233
|
-
});
|
|
5234
|
-
}
|
|
5235
|
-
function stop() {
|
|
5236
|
-
const resolve = resolver;
|
|
5237
|
-
resolver = void 0;
|
|
5238
|
-
timer.stop();
|
|
5239
|
-
resolve?.(isOpening.value);
|
|
5240
|
-
}
|
|
5241
|
-
onScopeDispose(() => {
|
|
5242
|
-
const resolve = resolver;
|
|
5243
|
-
resolver = void 0;
|
|
5244
|
-
resolve?.(isOpening.value);
|
|
5245
|
-
}, true);
|
|
5246
|
-
return {
|
|
5247
|
-
start,
|
|
5248
|
-
stop,
|
|
5249
|
-
pause: timer.pause,
|
|
5250
|
-
resume: timer.resume,
|
|
5251
|
-
isActive: shallowReadonly(timer.isActive),
|
|
5252
|
-
isPaused: shallowReadonly(timer.isPaused),
|
|
5253
|
-
remaining: shallowReadonly(timer.remaining),
|
|
5254
|
-
isOpening: shallowReadonly(isOpening)
|
|
5255
|
-
};
|
|
5256
|
-
}
|
|
5257
|
-
//#endregion
|
|
5258
6144
|
//#region src/composables/useMutationObserver/index.ts
|
|
5259
6145
|
/**
|
|
5260
6146
|
* @module useMutationObserver
|
|
@@ -5787,6 +6673,7 @@ function useDragDrop(options = {}) {
|
|
|
5787
6673
|
..._zones,
|
|
5788
6674
|
register(registration) {
|
|
5789
6675
|
const id = registration.id ?? /* @__PURE__ */ useId$1();
|
|
6676
|
+
if (_zones.has(id)) return _zones.register(registration);
|
|
5790
6677
|
const el = toRef(() => toValue(registration.el));
|
|
5791
6678
|
const isOver = toRef(() => active.value?.over === id);
|
|
5792
6679
|
const willAccept = toRef(() => safeAccept(registration.accept, active.value));
|
|
@@ -7778,114 +8665,77 @@ function createStorageFallback() {
|
|
|
7778
8665
|
}
|
|
7779
8666
|
const [createStorageContext, createStoragePlugin, useStorage] = createPluginContext("v0:storage", (options) => createStorage(options), { fallback: () => createStorageFallback() });
|
|
7780
8667
|
//#endregion
|
|
7781
|
-
//#region src/composables/
|
|
8668
|
+
//#region src/composables/useTooltip/index.ts
|
|
7782
8669
|
/**
|
|
7783
|
-
* @module
|
|
8670
|
+
* @module useTooltip
|
|
7784
8671
|
*
|
|
7785
|
-
* @see https://0.vuetifyjs.com/composables/
|
|
8672
|
+
* @see https://0.vuetifyjs.com/composables/plugins/use-tooltip
|
|
7786
8673
|
*
|
|
7787
8674
|
* @remarks
|
|
7788
|
-
*
|
|
7789
|
-
*
|
|
7790
|
-
*
|
|
7791
|
-
*
|
|
7792
|
-
* Key features:
|
|
7793
|
-
* - Uses Vue's effectScope for efficient reactive effect lifecycle management
|
|
7794
|
-
* - Automatic cleanup when condition becomes false
|
|
7795
|
-
* - Supports optional reset callback for scope restart capability
|
|
7796
|
-
* - Handles rapid toggling and parent scope disposal safely
|
|
7797
|
-
* - SSR-safe (effectScope is part of Vue core)
|
|
8675
|
+
* Region-scoped tooltip coordination plugin. Holds shared delay defaults and
|
|
8676
|
+
* a small registry of currently-open tooltips so individual tooltip instances
|
|
8677
|
+
* can skip their open delay when another tooltip is already visible (the
|
|
8678
|
+
* "warmup" pattern from React Aria / Radix).
|
|
7798
8679
|
*
|
|
7799
|
-
*
|
|
7800
|
-
*
|
|
8680
|
+
* Use the plugin form (`app.use(createTooltipPlugin())`) for app-wide
|
|
8681
|
+
* defaults.
|
|
7801
8682
|
*
|
|
7802
8683
|
* @example
|
|
7803
8684
|
* ```ts
|
|
7804
|
-
* import {
|
|
7805
|
-
* import { useToggleScope } from '@vuetify/v0'
|
|
8685
|
+
* import { createTooltipPlugin } from '@vuetify/v0'
|
|
7806
8686
|
*
|
|
7807
|
-
*
|
|
7808
|
-
* const { isActive } = useToggleScope(isEnabled, () => {
|
|
7809
|
-
* // effects run while isEnabled is true
|
|
7810
|
-
* })
|
|
8687
|
+
* app.use(createTooltipPlugin({ openDelay: 500 }))
|
|
7811
8688
|
* ```
|
|
7812
8689
|
*/
|
|
8690
|
+
function createTooltip(options = {}) {
|
|
8691
|
+
const openDelay = toRef(() => toValue(options.openDelay) ?? 700);
|
|
8692
|
+
const closeDelay = toRef(() => toValue(options.closeDelay) ?? 150);
|
|
8693
|
+
const skipDelay = toRef(() => toValue(options.skipDelay) ?? 300);
|
|
8694
|
+
const disabled = toRef(() => toValue(options.disabled) ?? false);
|
|
8695
|
+
const registry = createRegistry({
|
|
8696
|
+
reactive: true,
|
|
8697
|
+
events: true
|
|
8698
|
+
});
|
|
8699
|
+
let lastClosedAt = null;
|
|
8700
|
+
registry.on("unregister:ticket", () => {
|
|
8701
|
+
lastClosedAt = Date.now();
|
|
8702
|
+
});
|
|
8703
|
+
const isAnyOpen = toRef(() => registry.size > 0);
|
|
8704
|
+
function shouldSkipOpenDelay() {
|
|
8705
|
+
if (isAnyOpen.value) return true;
|
|
8706
|
+
if (/* @__PURE__ */ isNull(lastClosedAt)) return false;
|
|
8707
|
+
return Date.now() - lastClosedAt < skipDelay.value;
|
|
8708
|
+
}
|
|
8709
|
+
onScopeDispose(() => registry.dispose(), true);
|
|
8710
|
+
return {
|
|
8711
|
+
openDelay,
|
|
8712
|
+
closeDelay,
|
|
8713
|
+
skipDelay,
|
|
8714
|
+
disabled,
|
|
8715
|
+
isAnyOpen,
|
|
8716
|
+
shouldSkipOpenDelay,
|
|
8717
|
+
register: registry.register,
|
|
8718
|
+
unregister: registry.unregister
|
|
8719
|
+
};
|
|
8720
|
+
}
|
|
7813
8721
|
/**
|
|
7814
|
-
*
|
|
7815
|
-
*
|
|
7816
|
-
*
|
|
7817
|
-
*
|
|
7818
|
-
* @returns Controls object with isActive state and start/stop/reset methods
|
|
7819
|
-
*
|
|
7820
|
-
* @see https://vuejs.org/api/reactivity-advanced.html#effectscope
|
|
7821
|
-
* @see https://0.vuetifyjs.com/composables/system/use-toggle-scope
|
|
7822
|
-
*
|
|
7823
|
-
* @example
|
|
7824
|
-
* ```ts
|
|
7825
|
-
* import { ref } from 'vue'
|
|
7826
|
-
* import { useToggleScope } from '@vuetify/v0'
|
|
7827
|
-
*
|
|
7828
|
-
* const isEnabled = ref(false)
|
|
7829
|
-
*
|
|
7830
|
-
* const { isActive } = useToggleScope(isEnabled, () => {
|
|
7831
|
-
* // This code runs when isEnabled becomes true
|
|
7832
|
-
* const unwatch = watch(someRef, () => {
|
|
7833
|
-
* console.log('Watching...')
|
|
7834
|
-
* })
|
|
7835
|
-
*
|
|
7836
|
-
* // Cleanup happens automatically when isEnabled becomes false
|
|
7837
|
-
* })
|
|
7838
|
-
*
|
|
7839
|
-
* // Toggle the scope on/off
|
|
7840
|
-
* isEnabled.value = true // Starts the scope
|
|
7841
|
-
* console.log(isActive.value) // true
|
|
7842
|
-
* isEnabled.value = false // Stops and cleans up
|
|
7843
|
-
* console.log(isActive.value) // false
|
|
7844
|
-
* ```
|
|
8722
|
+
* Synthesized fallback used when `useTooltip()` is called without
|
|
8723
|
+
* `app.use(createTooltipPlugin())`. Returns a fresh context with the
|
|
8724
|
+
* documented defaults — `<Tooltip.Root>` keeps working but warmup
|
|
8725
|
+
* coordination is per-instance instead of region-wide.
|
|
7845
8726
|
*
|
|
7846
8727
|
* @example
|
|
7847
|
-
* With manual controls:
|
|
7848
8728
|
* ```ts
|
|
7849
|
-
*
|
|
7850
|
-
*
|
|
7851
|
-
* useToggleScope(isEnabled, (controls) => {
|
|
7852
|
-
* // Access controls inside the scope
|
|
7853
|
-
* console.log('Active:', controls.isActive.value)
|
|
8729
|
+
* import { createTooltipFallback } from '@vuetify/v0'
|
|
7854
8730
|
*
|
|
7855
|
-
*
|
|
7856
|
-
*
|
|
7857
|
-
* })
|
|
8731
|
+
* const tooltip = createTooltipFallback()
|
|
8732
|
+
* console.log(tooltip.openDelay.value) // 700
|
|
7858
8733
|
* ```
|
|
7859
8734
|
*/
|
|
7860
|
-
function
|
|
7861
|
-
|
|
7862
|
-
const isActive = toRef(() => !!scope.value);
|
|
7863
|
-
function start() {
|
|
7864
|
-
if (scope.value) return;
|
|
7865
|
-
scope.value = effectScope();
|
|
7866
|
-
scope.value.run(() => fn.length > 0 ? fn(controls) : fn());
|
|
7867
|
-
}
|
|
7868
|
-
function stop() {
|
|
7869
|
-
scope.value?.stop();
|
|
7870
|
-
scope.value = void 0;
|
|
7871
|
-
}
|
|
7872
|
-
function reset() {
|
|
7873
|
-
stop();
|
|
7874
|
-
start();
|
|
7875
|
-
}
|
|
7876
|
-
const controls = {
|
|
7877
|
-
isActive: shallowReadonly(isActive),
|
|
7878
|
-
start,
|
|
7879
|
-
stop,
|
|
7880
|
-
reset
|
|
7881
|
-
};
|
|
7882
|
-
watch(source, (active) => {
|
|
7883
|
-
if (active && !scope.value) start();
|
|
7884
|
-
else if (!active) stop();
|
|
7885
|
-
}, { immediate: true });
|
|
7886
|
-
onScopeDispose(stop, true);
|
|
7887
|
-
return controls;
|
|
8735
|
+
function createTooltipFallback() {
|
|
8736
|
+
return createTooltip();
|
|
7888
8737
|
}
|
|
8738
|
+
const [createTooltipContext, createTooltipPlugin, useTooltip] = createPluginContext("v0:tooltip", createTooltip, { fallback: () => createTooltipFallback() });
|
|
7889
8739
|
//#endregion
|
|
7890
8740
|
//#region src/composables/useEventListener/index.ts
|
|
7891
8741
|
/**
|
|
@@ -8262,4 +9112,4 @@ function useClickOutside(target, handler, options = {}) {
|
|
|
8262
9112
|
};
|
|
8263
9113
|
}
|
|
8264
9114
|
//#endregion
|
|
8265
|
-
export {
|
|
9115
|
+
export { useVirtual as $, usePrefersReducedMotion as A, createKanban as At, useMutationObserver as B, createSortable as Bt, useRovingFocus as C, useHydration as Ct, useMediaQuery as D, createNested as Dt, usePresence as E, createNumeric as Et, useHotkey as F, useForm as Ft, createBreakpoints as G, ClientComboboxAdapter as Gt, createDateContext as H, createComboboxContext as Ht, useDragDrop as I, createDataGrid as It, useBreakpoints as J, usePopover as Jt, createBreakpointsContext as K, ComboboxAdapter as Kt, KeyboardAdapter as L, createDataGridContext as Lt, useElementIntersection as M, createValidation as Mt, useIntersectionObserver as N, createForm as Nt, usePrefersContrast as O, createNestedContext as Ot, useImage as P, createFormContext as Pt, createVirtualContext as Q, useBreadcrumbs as Qt, PointerAdapter as R, useDataGrid as Rt, V0RtlAdapter as S, createHydrationPlugin as St, useProxyModel as T, createNumberField as Tt, createDatePlugin as U, useCombobox as Ut, createDate as V, createCombobox as Vt, useDate as W, ServerComboboxAdapter as Wt, toHighlight as X, createBreadcrumbs as Xt, toReactive as Y, useDelay as Yt, createVirtual as Z, createBreadcrumbsContext as Zt, createRtl as _, useResizeObserver as _t, createTooltipContext as a, useStep as at, createRtlPlugin as b, createHydration as bt, useTooltip as c, createRatingContext as ct, createStoragePlugin as d, createProgressContext as dt, createTimeline as et, useStorage as f, useProgress as ft, useStack as g, useElementSize as gt, createStackPlugin as h, useOverflow as ht, useWindowEventListener as i, createStepContext as it, useLazy as j, createInput as jt, usePrefersDark as k, useNested as kt, createStorage as l, useRating as lt, createStackContext as m, createOverflowContext as mt, useDocumentEventListener as n, useTimeline as nt, createTooltipFallback as o, createSlider as ot, createStack as p, createOverflow as pt, createBreakpointsPlugin as q, useVirtualFocus as qt, useEventListener as r, createStep as rt, createTooltipPlugin as s, createRating as st, useClickOutside as t, createTimelineContext as tt, createStorageContext as u, createProgress as ut, createRtlContext as v, toElement as vt, useRaf as w, createOtp as wt, useRtl as x, createHydrationContext as xt, createRtlFallback as y, createFallbackHydration as yt, DragDropAdapter as z, useToggleScope as zt };
|