reka-ui 2.9.9 → 2.9.10
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/dist/Combobox/ComboboxContentImpl.cjs +8 -2
- package/dist/Combobox/ComboboxContentImpl.cjs.map +1 -1
- package/dist/Combobox/ComboboxContentImpl.js +8 -2
- package/dist/Combobox/ComboboxContentImpl.js.map +1 -1
- package/dist/Dialog/DialogContent.cjs +2 -1
- package/dist/Dialog/DialogContent.cjs.map +1 -1
- package/dist/Dialog/DialogContent.js +2 -1
- package/dist/Dialog/DialogContent.js.map +1 -1
- package/dist/Dialog/DialogContentModal.cjs +4 -3
- package/dist/Dialog/DialogContentModal.cjs.map +1 -1
- package/dist/Dialog/DialogContentModal.js +4 -3
- package/dist/Dialog/DialogContentModal.js.map +1 -1
- package/dist/DismissableLayer/DismissableLayer.cjs +10 -9
- package/dist/DismissableLayer/DismissableLayer.cjs.map +1 -1
- package/dist/DismissableLayer/DismissableLayer.js +11 -10
- package/dist/DismissableLayer/DismissableLayer.js.map +1 -1
- package/dist/Listbox/ListboxRoot.cjs +4 -1
- package/dist/Listbox/ListboxRoot.cjs.map +1 -1
- package/dist/Listbox/ListboxRoot.js +4 -1
- package/dist/Listbox/ListboxRoot.js.map +1 -1
- package/dist/Listbox/ListboxVirtualizer.cjs +7 -3
- package/dist/Listbox/ListboxVirtualizer.cjs.map +1 -1
- package/dist/Listbox/ListboxVirtualizer.js +7 -3
- package/dist/Listbox/ListboxVirtualizer.js.map +1 -1
- package/dist/index3.d.cts +19 -19
- package/dist/index3.d.ts +3 -3
- package/dist/index4.d.cts +631 -628
- package/dist/index4.d.cts.map +1 -1
- package/dist/index4.d.ts +675 -672
- package/dist/index4.d.ts.map +1 -1
- package/dist/internal.d.ts +2 -2
- package/dist/internal.d.ts.map +1 -1
- package/dist/shared/useId.cjs +7 -8
- package/dist/shared/useId.cjs.map +1 -1
- package/dist/shared/useId.js +7 -8
- package/dist/shared/useId.js.map +1 -1
- package/package.json +6 -5
- package/src/Combobox/ComboboxContentImpl.vue +17 -4
- package/src/Dialog/DialogContent.vue +7 -1
- package/src/Dialog/DialogContentModal.vue +4 -2
- package/src/DismissableLayer/DismissableLayer.vue +39 -21
- package/src/Listbox/ListboxRoot.vue +7 -4
- package/src/Listbox/ListboxVirtualizer.vue +19 -3
- package/src/shared/useId.ts +14 -6
package/dist/internal.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "./index2.js";
|
|
2
2
|
import "./index3.js";
|
|
3
3
|
import { MenuArrowProps, MenuCheckboxItemEmits, MenuCheckboxItemProps, MenuContentEmits, MenuContentProps, MenuEmits, MenuGroupProps, MenuItemEmits, MenuItemIndicatorProps, MenuItemProps, MenuLabelProps, MenuPortalProps, MenuProps, MenuRadioGroupEmits, MenuRadioGroupProps, MenuRadioItemEmits, MenuRadioItemProps, MenuSeparatorProps, MenuSubContentEmits, MenuSubContentProps, MenuSubEmits, MenuSubProps, MenuSubTriggerProps, PopperAnchorProps, _default$277 as _default$13, _default$278 as _default$8, _default$279 as _default, _default$280 as _default$6, _default$281 as _default$10, _default$282 as _default$3, _default$283 as _default$14, _default$284 as _default$12, _default$285 as _default$2, _default$286 as _default$1, _default$287 as _default$11, _default$288 as _default$7, _default$290 as _default$9, _default$291 as _default$4, _default$292 as _default$5, injectMenuContext, injectMenuRootContext } from "./index4.js";
|
|
4
|
-
import * as
|
|
4
|
+
import * as vue1641 from "vue";
|
|
5
5
|
|
|
6
6
|
//#region src/Menu/MenuAnchor.vue.d.ts
|
|
7
7
|
interface MenuAnchorProps extends PopperAnchorProps {}
|
|
@@ -9,7 +9,7 @@ declare var __VLS_8: {};
|
|
|
9
9
|
type __VLS_Slots = {} & {
|
|
10
10
|
default?: (props: typeof __VLS_8) => any;
|
|
11
11
|
};
|
|
12
|
-
declare const __VLS_base:
|
|
12
|
+
declare const __VLS_base: vue1641.DefineComponent<MenuAnchorProps, {}, {}, {}, {}, vue1641.ComponentOptionsMixin, vue1641.ComponentOptionsMixin, {}, string, vue1641.PublicProps, Readonly<MenuAnchorProps> & Readonly<{}>, {}, {}, {}, {}, string, vue1641.ComponentProvideOptions, false, {}, any>;
|
|
13
13
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
14
14
|
declare const _default$15: typeof __VLS_export;
|
|
15
15
|
type __VLS_WithSlots<T, S> = T & {
|
package/dist/internal.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"internal.d.ts","names":[],"sources":["../src/Menu/MenuAnchor.vue"],"sourcesContent":[],"mappings":";;;;;;UAoBU,eAAA,SAAwB;YAsC9B;KACC,WAAA;2BACwB;AA3CoB,CAAA;AAGE,cA2C7C,UALgB,EAKN,
|
|
1
|
+
{"version":3,"file":"internal.d.ts","names":[],"sources":["../src/Menu/MenuAnchor.vue"],"sourcesContent":[],"mappings":";;;;;;UAoBU,eAAA,SAAwB;YAsC9B;KACC,WAAA;2BACwB;AA3CoB,CAAA;AAGE,cA2C7C,UALgB,EAKN,OAAA,CAAA,eALM,CAKN,eALM,EAAA,CAAA,CAAA,EAAA,CAAA,CAAA,EAAA,CAAA,CAAA,EAAA,CAAA,CAAA,EAKN,OAAA,CAAA,qBAAA,EAAA,OAAA,CAAA,qBAAA,EALM,CAAA,CAAA,EAAA,MAAA,EAKN,OAAA,CAAA,WAAA,EAAA,QALM,CAKN,eALM,CAAA,GAKN,QALM,CAAA,CAAA,CAAA,CAAA,EAAA,CAAA,CAAA,EAAA,CAAA,CAAA,EAAA,CAAA,CAAA,EAAA,CAAA,CAAA,EAAA,MAAA,EAKN,OAAA,CAAA,uBAAA,EALM,KAAA,EAAA,CAAA,CAAA,EAAA,GAAA,CAAA;AAAA,cAQhB,YAPU,EAOW,eANS,CAAA,OAMc,UANd,EAM0B,WAN1B,CAAA;AAAA,cAMM,WADxC,EAAA,OAE0B,YAF1B;KAGG,eALW,CAAA,CAAA,EAAA,CAAA,CAAA,GAKa,CALb,GAAA;EAAA,MAAA,EAAA;IAAA,MAAA,EAON,CAPM;EAAA,CAAA;CAAA"}
|
package/dist/shared/useId.cjs
CHANGED
|
@@ -5,21 +5,20 @@ const vue = require_rolldown_runtime.__toESM(require("vue"));
|
|
|
5
5
|
//#region src/shared/useId.ts
|
|
6
6
|
let count = 0;
|
|
7
7
|
/**
|
|
8
|
-
* The `useId` function generates a unique identifier using a provided deterministic ID
|
|
9
|
-
*
|
|
8
|
+
* The `useId` function generates a unique identifier using a provided deterministic ID,
|
|
9
|
+
* a configured `<ConfigProvider>` ID source, Vue's native `useId`, or a fallback counter.
|
|
10
10
|
* @param {string | null | undefined} [deterministicId] - The `useId` function you provided takes an
|
|
11
11
|
* optional parameter `deterministicId`, which can be a string, null, or undefined. If
|
|
12
12
|
* `deterministicId` is provided, the function will return it. Otherwise, it will generate an id using
|
|
13
|
-
* the
|
|
13
|
+
* the configured ID source.
|
|
14
14
|
*/
|
|
15
15
|
function useId(deterministicId, prefix = "reka") {
|
|
16
16
|
if (deterministicId) return deterministicId;
|
|
17
17
|
let id;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
18
|
+
const configProviderContext = require_ConfigProvider_ConfigProvider.injectConfigProviderContext({ useId: void 0 });
|
|
19
|
+
if (configProviderContext.useId) id = configProviderContext.useId();
|
|
20
|
+
else if ("useId" in vue) id = vue.useId?.();
|
|
21
|
+
else id = `${++count}`;
|
|
23
22
|
return prefix ? `${prefix}-${id}` : id;
|
|
24
23
|
}
|
|
25
24
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useId.cjs","names":["deterministicId?: string | null | undefined","id: string"],"sources":["../../src/shared/useId.ts"],"sourcesContent":[],"mappings":";;;;;AAKA,IAAI,QAAQ;;;;;;;;;AASZ,SAAgB,MAAMA,iBAA6C,SAAS,QAAQ;AAClF,KAAI,gBACF,QAAO;CAET,IAAIC;
|
|
1
|
+
{"version":3,"file":"useId.cjs","names":["deterministicId?: string | null | undefined","id: string"],"sources":["../../src/shared/useId.ts"],"sourcesContent":[],"mappings":";;;;;AAKA,IAAI,QAAQ;;;;;;;;;AASZ,SAAgB,MAAMA,iBAA6C,SAAS,QAAQ;AAClF,KAAI,gBACF,QAAO;CAET,IAAIC;CACJ,MAAM,wBAAwB,kEAA4B,EAAE,cAAkB,EAAC;AAM/E,KAAI,sBAAsB,MACxB,MAAK,sBAAsB,OAAO;UAE3B,WAAW,IAClB,MAAK,IAAI,SAAS;KAGlB,MAAK,GAAG,EAAE,OAAO;AAGnB,QAAO,SAAS,GAAG,OAAO,CAAC,EAAE,IAAI,GAAG;AACrC"}
|
package/dist/shared/useId.js
CHANGED
|
@@ -4,21 +4,20 @@ import * as vue from "vue";
|
|
|
4
4
|
//#region src/shared/useId.ts
|
|
5
5
|
let count = 0;
|
|
6
6
|
/**
|
|
7
|
-
* The `useId` function generates a unique identifier using a provided deterministic ID
|
|
8
|
-
*
|
|
7
|
+
* The `useId` function generates a unique identifier using a provided deterministic ID,
|
|
8
|
+
* a configured `<ConfigProvider>` ID source, Vue's native `useId`, or a fallback counter.
|
|
9
9
|
* @param {string | null | undefined} [deterministicId] - The `useId` function you provided takes an
|
|
10
10
|
* optional parameter `deterministicId`, which can be a string, null, or undefined. If
|
|
11
11
|
* `deterministicId` is provided, the function will return it. Otherwise, it will generate an id using
|
|
12
|
-
* the
|
|
12
|
+
* the configured ID source.
|
|
13
13
|
*/
|
|
14
14
|
function useId(deterministicId, prefix = "reka") {
|
|
15
15
|
if (deterministicId) return deterministicId;
|
|
16
16
|
let id;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
17
|
+
const configProviderContext = injectConfigProviderContext({ useId: void 0 });
|
|
18
|
+
if (configProviderContext.useId) id = configProviderContext.useId();
|
|
19
|
+
else if ("useId" in vue) id = vue.useId?.();
|
|
20
|
+
else id = `${++count}`;
|
|
22
21
|
return prefix ? `${prefix}-${id}` : id;
|
|
23
22
|
}
|
|
24
23
|
|
package/dist/shared/useId.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useId.js","names":["deterministicId?: string | null | undefined","id: string"],"sources":["../../src/shared/useId.ts"],"sourcesContent":[],"mappings":";;;;AAKA,IAAI,QAAQ;;;;;;;;;AASZ,SAAgB,MAAMA,iBAA6C,SAAS,QAAQ;AAClF,KAAI,gBACF,QAAO;CAET,IAAIC;
|
|
1
|
+
{"version":3,"file":"useId.js","names":["deterministicId?: string | null | undefined","id: string"],"sources":["../../src/shared/useId.ts"],"sourcesContent":[],"mappings":";;;;AAKA,IAAI,QAAQ;;;;;;;;;AASZ,SAAgB,MAAMA,iBAA6C,SAAS,QAAQ;AAClF,KAAI,gBACF,QAAO;CAET,IAAIC;CACJ,MAAM,wBAAwB,4BAA4B,EAAE,cAAkB,EAAC;AAM/E,KAAI,sBAAsB,MACxB,MAAK,sBAAsB,OAAO;UAE3B,WAAW,IAClB,MAAK,IAAI,SAAS;KAGlB,MAAK,GAAG,EAAE,OAAO;AAGnB,QAAO,SAAS,GAAG,OAAO,CAAC,EAAE,IAAI,GAAG;AACrC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reka-ui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.9.
|
|
4
|
+
"version": "2.9.10",
|
|
5
5
|
"description": "Vue port for Radix UI Primitives.",
|
|
6
6
|
"author": "UnoVue Contributors (https://github.com/unovue)",
|
|
7
7
|
"license": "MIT",
|
|
@@ -112,14 +112,15 @@
|
|
|
112
112
|
"@types/jsdom": "^28.0.0",
|
|
113
113
|
"@types/node": "^24.0.13",
|
|
114
114
|
"@vitejs/plugin-vue": "^6.0.7",
|
|
115
|
-
"@vitest/coverage-istanbul": "^3.2.
|
|
116
|
-
"@vue/
|
|
115
|
+
"@vitest/coverage-istanbul": "^3.2.6",
|
|
116
|
+
"@vue/server-renderer": "^3.5.17",
|
|
117
|
+
"@vue/test-utils": "^2.4.11",
|
|
117
118
|
"@vue/tsconfig": "^0.7.0",
|
|
118
119
|
"jsdom": "^26.1.0",
|
|
119
120
|
"size-limit": "^12.0.1",
|
|
120
121
|
"tsdown": "^0.12.9",
|
|
121
|
-
"vite": "^8.0.
|
|
122
|
-
"vitest": "^4.1.
|
|
122
|
+
"vite": "^8.0.16",
|
|
123
|
+
"vitest": "^4.1.8",
|
|
123
124
|
"vitest-axe": "0.1.0",
|
|
124
125
|
"vitest-canvas-mock": "^0.3.3",
|
|
125
126
|
"vue": "3.5.17",
|
|
@@ -97,6 +97,19 @@ onUnmounted(() => {
|
|
|
97
97
|
rootContext.triggerElement.value?.focus()
|
|
98
98
|
}
|
|
99
99
|
})
|
|
100
|
+
|
|
101
|
+
function isEventTargetWithinCombobox(target: EventTarget | null) {
|
|
102
|
+
if (rootContext.parentElement.value?.contains(target as Node))
|
|
103
|
+
return true
|
|
104
|
+
|
|
105
|
+
// A `<label>` associated (via `for`) with an element inside the combobox forwards its
|
|
106
|
+
// click/focus to that control, so interacting with it should not dismiss the content.
|
|
107
|
+
// Without this, clicking such a label while open dismisses on `pointerdown` and the
|
|
108
|
+
// forwarded click/focus immediately re-opens it.
|
|
109
|
+
const label = target instanceof Element ? target.closest('label') : null
|
|
110
|
+
const control = label?.control
|
|
111
|
+
return !!control && !!rootContext.parentElement.value?.contains(control)
|
|
112
|
+
}
|
|
100
113
|
</script>
|
|
101
114
|
|
|
102
115
|
<template>
|
|
@@ -111,15 +124,15 @@ onUnmounted(() => {
|
|
|
111
124
|
:disable-outside-pointer-events="disableOutsidePointerEvents"
|
|
112
125
|
@dismiss="rootContext.onOpenChange(false)"
|
|
113
126
|
@focus-outside="(ev) => {
|
|
114
|
-
// if
|
|
115
|
-
if (
|
|
127
|
+
// if focusing inside the combobox (or a label tied to it), prevent dismiss
|
|
128
|
+
if (isEventTargetWithinCombobox(ev.target)) ev.preventDefault()
|
|
116
129
|
emits('focusOutside', ev)
|
|
117
130
|
}"
|
|
118
131
|
@interact-outside="emits('interactOutside', $event)"
|
|
119
132
|
@escape-key-down="emits('escapeKeyDown', $event)"
|
|
120
133
|
@pointer-down-outside="(ev) => {
|
|
121
|
-
// if clicking inside the combobox, prevent dismiss
|
|
122
|
-
if (
|
|
134
|
+
// if clicking inside the combobox (or a label tied to it), prevent dismiss
|
|
135
|
+
if (isEventTargetWithinCombobox(ev.target)) ev.preventDefault()
|
|
123
136
|
emits('pointerDownOutside', ev)
|
|
124
137
|
}"
|
|
125
138
|
>
|
|
@@ -22,7 +22,13 @@ import DialogContentModal from './DialogContentModal.vue'
|
|
|
22
22
|
import DialogContentNonModal from './DialogContentNonModal.vue'
|
|
23
23
|
import { injectDialogRootContext } from './DialogRoot.vue'
|
|
24
24
|
|
|
25
|
-
const props = defineProps<DialogContentProps>()
|
|
25
|
+
const props = withDefaults(defineProps<DialogContentProps>(), {
|
|
26
|
+
// Keep `undefined` (instead of Vue's boolean coercion to `false`) so the
|
|
27
|
+
// modal/non-modal child can apply its own default. This lets a modal
|
|
28
|
+
// `DialogContent` stay locked by default while still honoring an explicit
|
|
29
|
+
// `:disable-outside-pointer-events="false"` (#2677).
|
|
30
|
+
disableOutsidePointerEvents: undefined,
|
|
31
|
+
})
|
|
26
32
|
const emits = defineEmits<DialogContentEmits>()
|
|
27
33
|
|
|
28
34
|
const rootContext = injectDialogRootContext()
|
|
@@ -4,7 +4,9 @@ import { useEmitAsProps, useForwardExpose, useHideOthers } from '@/shared'
|
|
|
4
4
|
import DialogContentImpl from './DialogContentImpl.vue'
|
|
5
5
|
import { injectDialogRootContext } from './DialogRoot.vue'
|
|
6
6
|
|
|
7
|
-
const props = defineProps<DialogContentImplProps>()
|
|
7
|
+
const props = withDefaults(defineProps<DialogContentImplProps>(), {
|
|
8
|
+
disableOutsidePointerEvents: true,
|
|
9
|
+
})
|
|
8
10
|
const emits = defineEmits<DialogContentImplEmits>()
|
|
9
11
|
|
|
10
12
|
const rootContext = injectDialogRootContext()
|
|
@@ -20,7 +22,7 @@ useHideOthers(currentElement)
|
|
|
20
22
|
v-bind="{ ...props, ...emitsAsProps }"
|
|
21
23
|
:ref="forwardRef"
|
|
22
24
|
:trap-focus="rootContext.open.value"
|
|
23
|
-
:disable-outside-pointer-events="
|
|
25
|
+
:disable-outside-pointer-events="props.disableOutsidePointerEvents"
|
|
24
26
|
@close-auto-focus="
|
|
25
27
|
(event) => {
|
|
26
28
|
if (!event.defaultPrevented) {
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
computed,
|
|
10
10
|
nextTick,
|
|
11
11
|
reactive,
|
|
12
|
+
watch,
|
|
12
13
|
watchEffect,
|
|
13
14
|
} from 'vue'
|
|
14
15
|
import { isNullish, useForwardExpose } from '@/shared'
|
|
@@ -138,28 +139,45 @@ onKeyStroke('Escape', (event) => {
|
|
|
138
139
|
emits('dismiss')
|
|
139
140
|
})
|
|
140
141
|
|
|
141
|
-
watchEffect
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
142
|
+
// Use `watch` with explicit sources (instead of `watchEffect`) so this effect
|
|
143
|
+
// only re-runs when `layerElement` or `disableOutsidePointerEvents` change.
|
|
144
|
+
// Reading `context.layersWithOutsidePointerEventsDisabled.size` inside the
|
|
145
|
+
// callback must NOT make it reactive: otherwise adding/removing any other
|
|
146
|
+
// layer would re-run this effect and its cleanup could prematurely restore the
|
|
147
|
+
// body's `pointer-events` while an ancestor layer is still open (#2674).
|
|
148
|
+
watch(
|
|
149
|
+
[layerElement, () => props.disableOutsidePointerEvents],
|
|
150
|
+
([element, disableOutsidePointerEvents], _, onCleanup) => {
|
|
151
|
+
if (!element)
|
|
152
|
+
return
|
|
153
|
+
if (disableOutsidePointerEvents) {
|
|
154
|
+
if (context.layersWithOutsidePointerEventsDisabled.size === 0) {
|
|
155
|
+
context.originalBodyPointerEvents = ownerDocument.value.body.style.pointerEvents
|
|
156
|
+
ownerDocument.value.body.style.pointerEvents = 'none'
|
|
157
|
+
}
|
|
158
|
+
context.layersWithOutsidePointerEventsDisabled.add(element)
|
|
159
|
+
|
|
160
|
+
// Remove this layer from the set on cleanup (re-run via prop toggle, or
|
|
161
|
+
// unmount) and restore the body's `pointer-events` only once the last
|
|
162
|
+
// disabling layer is gone. Removing here — rather than relying solely on
|
|
163
|
+
// the unmount-only effect below — keeps the set accurate when
|
|
164
|
+
// `disableOutsidePointerEvents` toggles `true -> false` while still
|
|
165
|
+
// mounted (e.g. a modal Menu closing). Checking `size === 0` *after*
|
|
166
|
+
// deletion makes the restore independent of cleanup ordering (#2674).
|
|
167
|
+
onCleanup(() => {
|
|
168
|
+
context.layersWithOutsidePointerEventsDisabled.delete(element)
|
|
169
|
+
if (
|
|
170
|
+
context.layersWithOutsidePointerEventsDisabled.size === 0
|
|
171
|
+
&& !isNullish(context.originalBodyPointerEvents)
|
|
172
|
+
) {
|
|
173
|
+
ownerDocument.value.body.style.pointerEvents = context.originalBodyPointerEvents
|
|
174
|
+
}
|
|
175
|
+
})
|
|
160
176
|
}
|
|
161
|
-
|
|
162
|
-
}
|
|
177
|
+
layers.value.add(element)
|
|
178
|
+
},
|
|
179
|
+
{ immediate: true },
|
|
180
|
+
)
|
|
163
181
|
|
|
164
182
|
watchEffect((cleanupFn) => {
|
|
165
183
|
cleanupFn(() => {
|
|
@@ -16,7 +16,7 @@ type ListboxRootContext<T> = {
|
|
|
16
16
|
highlightOnHover: Ref<boolean>
|
|
17
17
|
highlightedElement: Ref<HTMLElement | null>
|
|
18
18
|
isVirtual: Ref<boolean>
|
|
19
|
-
virtualFocusHook: EventHook<Event
|
|
19
|
+
virtualFocusHook: EventHook<{ event?: Event, scroll: boolean }>
|
|
20
20
|
virtualKeydownHook: EventHook<KeyboardEvent>
|
|
21
21
|
virtualHighlightHook: EventHook<any>
|
|
22
22
|
by?: string | ((a: T, b: T) => boolean)
|
|
@@ -150,7 +150,7 @@ const highlightedElement = ref<HTMLElement | null>(null)
|
|
|
150
150
|
const previousElement = ref<HTMLElement | null>(null)
|
|
151
151
|
const isVirtual = ref(false)
|
|
152
152
|
const isComposing = ref(false)
|
|
153
|
-
const virtualFocusHook = createEventHook<Event
|
|
153
|
+
const virtualFocusHook = createEventHook<{ event?: Event, scroll: boolean }>()
|
|
154
154
|
const virtualKeydownHook = createEventHook<KeyboardEvent>()
|
|
155
155
|
const virtualHighlightHook = createEventHook<T>()
|
|
156
156
|
|
|
@@ -333,8 +333,11 @@ async function highlightSelected(event?: Event, scroll = true) {
|
|
|
333
333
|
return
|
|
334
334
|
await nextTick()
|
|
335
335
|
if (isVirtual.value) {
|
|
336
|
-
// Trigger on nextTick for Virtualizer to be mounted
|
|
337
|
-
|
|
336
|
+
// Trigger on nextTick for Virtualizer to be mounted.
|
|
337
|
+
// `scroll` is `false` on the initial mount highlight, so the virtualizer sets
|
|
338
|
+
// its roving-tabindex target without focusing/scrolling — otherwise a
|
|
339
|
+
// virtualized Listbox below the fold would pull the page to it on load.
|
|
340
|
+
virtualFocusHook.trigger({ event, scroll })
|
|
338
341
|
}
|
|
339
342
|
else {
|
|
340
343
|
const collection = getCollectionItem()
|
|
@@ -104,7 +104,11 @@ const virtualizedItems = computed(() => virtualizer.value.getVirtualItems().map(
|
|
|
104
104
|
}
|
|
105
105
|
}))
|
|
106
106
|
|
|
107
|
-
rootContext.virtualFocusHook.on((event) => {
|
|
107
|
+
rootContext.virtualFocusHook.on(({ event, scroll }) => {
|
|
108
|
+
// `scroll` is `false` only for the initial mount highlight. There we set the
|
|
109
|
+
// roving-tabindex target without focusing or scrolling, so a virtualized
|
|
110
|
+
// Listbox below the fold doesn't pull the page to it on load. User-driven
|
|
111
|
+
// highlights (keyboard, typeahead, select) keep scrolling as before.
|
|
108
112
|
const index = props.options.findIndex((option) => {
|
|
109
113
|
if (Array.isArray(rootContext.modelValue.value))
|
|
110
114
|
return compare(option, rootContext.modelValue.value[0], rootContext.by)
|
|
@@ -114,19 +118,31 @@ rootContext.virtualFocusHook.on((event) => {
|
|
|
114
118
|
if (index !== -1) {
|
|
115
119
|
event?.preventDefault()
|
|
116
120
|
|
|
121
|
+
// Bringing the checked item into the (internal) scroll viewport is safe — it
|
|
122
|
+
// only scrolls the listbox container, never the page.
|
|
117
123
|
virtualizer.value.scrollToIndex(index, { align: 'start' })
|
|
118
124
|
requestAnimationFrame(() => {
|
|
119
125
|
const item = queryCheckedElement(parentEl.value)
|
|
120
126
|
if (item) {
|
|
121
|
-
rootContext.changeHighlight(item)
|
|
127
|
+
rootContext.changeHighlight(item, scroll, scroll ? undefined : false)
|
|
122
128
|
if (event)
|
|
123
129
|
item?.focus()
|
|
124
130
|
}
|
|
125
131
|
})
|
|
126
132
|
}
|
|
127
|
-
else {
|
|
133
|
+
else if (scroll) {
|
|
128
134
|
rootContext.highlightFirstItem()
|
|
129
135
|
}
|
|
136
|
+
else {
|
|
137
|
+
// Mount highlight with no checked item: highlight the first enabled item only,
|
|
138
|
+
// mirroring the non-virtual path. `highlightFirstItem` is reserved for
|
|
139
|
+
// user-driven PageUp/Home navigation, which focuses and scrolls.
|
|
140
|
+
requestAnimationFrame(() => {
|
|
141
|
+
const item = getItems().find(i => i.ref.dataset.disabled !== '')?.ref
|
|
142
|
+
if (item)
|
|
143
|
+
rootContext.changeHighlight(item, false, false)
|
|
144
|
+
})
|
|
145
|
+
}
|
|
130
146
|
})
|
|
131
147
|
|
|
132
148
|
rootContext.virtualHighlightHook.on((value) => {
|
package/src/shared/useId.ts
CHANGED
|
@@ -5,24 +5,32 @@ import { injectConfigProviderContext } from '@/ConfigProvider/ConfigProvider.vue
|
|
|
5
5
|
|
|
6
6
|
let count = 0
|
|
7
7
|
/**
|
|
8
|
-
* The `useId` function generates a unique identifier using a provided deterministic ID
|
|
9
|
-
*
|
|
8
|
+
* The `useId` function generates a unique identifier using a provided deterministic ID,
|
|
9
|
+
* a configured `<ConfigProvider>` ID source, Vue's native `useId`, or a fallback counter.
|
|
10
10
|
* @param {string | null | undefined} [deterministicId] - The `useId` function you provided takes an
|
|
11
11
|
* optional parameter `deterministicId`, which can be a string, null, or undefined. If
|
|
12
12
|
* `deterministicId` is provided, the function will return it. Otherwise, it will generate an id using
|
|
13
|
-
* the
|
|
13
|
+
* the configured ID source.
|
|
14
14
|
*/
|
|
15
15
|
export function useId(deterministicId?: string | null | undefined, prefix = 'reka') {
|
|
16
16
|
if (deterministicId)
|
|
17
17
|
return deterministicId
|
|
18
18
|
|
|
19
19
|
let id: string
|
|
20
|
-
|
|
20
|
+
const configProviderContext = injectConfigProviderContext({ useId: undefined })
|
|
21
|
+
|
|
22
|
+
// Keep the app-provided ID source authoritative. Frameworks such as Nuxt can
|
|
23
|
+
// prerender with a different Vue app ID prefix than the hydrating client, so
|
|
24
|
+
// falling through to Vue's native useId would bypass the stable source that
|
|
25
|
+
// ConfigProvider was explicitly given.
|
|
26
|
+
if (configProviderContext.useId) {
|
|
27
|
+
id = configProviderContext.useId()
|
|
28
|
+
}
|
|
29
|
+
else if ('useId' in vue) {
|
|
21
30
|
id = vue.useId?.()
|
|
22
31
|
}
|
|
23
32
|
else {
|
|
24
|
-
|
|
25
|
-
id = configProviderContext.useId?.() ?? `${++count}`
|
|
33
|
+
id = `${++count}`
|
|
26
34
|
}
|
|
27
35
|
|
|
28
36
|
return prefix ? `${prefix}-${id}` : id
|