reka-ui 2.9.6 → 2.9.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/Dialog/DialogOverlayImpl.cjs +2 -1
  2. package/dist/Dialog/DialogOverlayImpl.cjs.map +1 -1
  3. package/dist/Dialog/DialogOverlayImpl.js +3 -2
  4. package/dist/Dialog/DialogOverlayImpl.js.map +1 -1
  5. package/dist/FocusScope/FocusScope.cjs +2 -0
  6. package/dist/FocusScope/FocusScope.cjs.map +1 -1
  7. package/dist/FocusScope/FocusScope.js +2 -0
  8. package/dist/FocusScope/FocusScope.js.map +1 -1
  9. package/dist/Listbox/ListboxItem.cjs +7 -2
  10. package/dist/Listbox/ListboxItem.cjs.map +1 -1
  11. package/dist/Listbox/ListboxItem.js +7 -2
  12. package/dist/Listbox/ListboxItem.js.map +1 -1
  13. package/dist/Listbox/ListboxRoot.cjs +2 -0
  14. package/dist/Listbox/ListboxRoot.cjs.map +1 -1
  15. package/dist/Listbox/ListboxRoot.js +2 -0
  16. package/dist/Listbox/ListboxRoot.js.map +1 -1
  17. package/dist/Menu/MenuContentImpl.cjs +2 -1
  18. package/dist/Menu/MenuContentImpl.cjs.map +1 -1
  19. package/dist/Menu/MenuContentImpl.js +2 -1
  20. package/dist/Menu/MenuContentImpl.js.map +1 -1
  21. package/dist/Menu/MenuItemImpl.cjs +4 -2
  22. package/dist/Menu/MenuItemImpl.cjs.map +1 -1
  23. package/dist/Menu/MenuItemImpl.js +4 -2
  24. package/dist/Menu/MenuItemImpl.js.map +1 -1
  25. package/dist/NavigationMenu/NavigationMenuContentImpl.cjs +1 -0
  26. package/dist/NavigationMenu/NavigationMenuContentImpl.cjs.map +1 -1
  27. package/dist/NavigationMenu/NavigationMenuContentImpl.js +1 -0
  28. package/dist/NavigationMenu/NavigationMenuContentImpl.js.map +1 -1
  29. package/dist/Select/SelectContent.cjs +15 -2
  30. package/dist/Select/SelectContent.cjs.map +1 -1
  31. package/dist/Select/SelectContent.js +16 -3
  32. package/dist/Select/SelectContent.js.map +1 -1
  33. package/dist/Tabs/TabsIndicator.cjs +2 -1
  34. package/dist/Tabs/TabsIndicator.cjs.map +1 -1
  35. package/dist/Tabs/TabsIndicator.js +3 -2
  36. package/dist/Tabs/TabsIndicator.js.map +1 -1
  37. package/dist/Toast/ToastAnnounce.cjs +13 -4
  38. package/dist/Toast/ToastAnnounce.cjs.map +1 -1
  39. package/dist/Toast/ToastAnnounce.js +15 -6
  40. package/dist/Toast/ToastAnnounce.js.map +1 -1
  41. package/dist/Toast/ToastRootImpl.cjs +3 -1
  42. package/dist/Toast/ToastRootImpl.cjs.map +1 -1
  43. package/dist/Toast/ToastRootImpl.js +4 -2
  44. package/dist/Toast/ToastRootImpl.js.map +1 -1
  45. package/dist/constant.d.cts.map +1 -1
  46. package/dist/index.d.cts +2 -2
  47. package/dist/index.d.ts +2 -2
  48. package/dist/index2.d.ts.map +1 -1
  49. package/dist/index3.d.ts +15 -15
  50. package/dist/index4.d.cts +660 -660
  51. package/dist/index4.d.cts.map +1 -1
  52. package/dist/index4.d.ts +647 -647
  53. package/dist/index4.d.ts.map +1 -1
  54. package/dist/internal.d.cts +2 -2
  55. package/dist/internal.d.cts.map +1 -1
  56. package/dist/internal.d.ts +2 -2
  57. package/package.json +4 -4
  58. package/src/Calendar/CalendarRoot.vue +1 -1
  59. package/src/DateRangeField/DateRangeFieldRoot.vue +1 -1
  60. package/src/Dialog/DialogOverlayImpl.vue +1 -0
  61. package/src/FocusScope/FocusScope.vue +5 -0
  62. package/src/Listbox/ListboxItem.vue +2 -2
  63. package/src/Listbox/ListboxRoot.vue +7 -0
  64. package/src/Menu/MenuContentImpl.vue +3 -2
  65. package/src/Menu/MenuItemImpl.vue +10 -2
  66. package/src/MonthPicker/MonthPickerRoot.vue +1 -1
  67. package/src/NavigationMenu/NavigationMenuContentImpl.vue +3 -0
  68. package/src/NavigationMenu/__test__/NavigationMenuUnmountOnHideFalse.vue +45 -0
  69. package/src/Select/SelectContent.vue +19 -3
  70. package/src/Select/__test__/SelectUnmountCleanup.vue +43 -0
  71. package/src/Tabs/TabsIndicator.vue +4 -2
  72. package/src/Toast/ToastAnnounce.vue +17 -6
  73. package/src/Toast/ToastRootImpl.vue +14 -1
  74. package/src/YearPicker/YearPickerRoot.vue +1 -1
  75. package/src/index.ts +7 -0
@@ -1,7 +1,7 @@
1
1
  import "./index2.cjs";
2
2
  import "./index3.cjs";
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.cjs";
4
- import * as vue28 from "vue";
4
+ import * as vue136 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: vue28.DefineComponent<MenuAnchorProps, {}, {}, {}, {}, vue28.ComponentOptionsMixin, vue28.ComponentOptionsMixin, {}, string, vue28.PublicProps, Readonly<MenuAnchorProps> & Readonly<{}>, {}, {}, {}, {}, string, vue28.ComponentProvideOptions, false, {}, any>;
12
+ declare const __VLS_base: vue136.DefineComponent<MenuAnchorProps, {}, {}, {}, {}, vue136.ComponentOptionsMixin, vue136.ComponentOptionsMixin, {}, string, vue136.PublicProps, Readonly<MenuAnchorProps> & Readonly<{}>, {}, {}, {}, {}, string, vue136.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 & {
@@ -1 +1 @@
1
- {"version":3,"file":"internal.d.cts","names":[],"sources":["../src/Menu/MenuAnchor.vue"],"sourcesContent":[],"mappings":";;;;;;UAoBU,eAAA,SAAwB;YAsC9B;KACC,WAAA;2BACwB;AA3CoB,CAAA;AAGE,cA2C7C,UALgB,EAKN,KAAA,CAAA,eALM,CAKN,eALM,EAAA,CAAA,CAAA,EAAA,CAAA,CAAA,EAAA,CAAA,CAAA,EAAA,CAAA,CAAA,EAKN,KAAA,CAAA,qBAAA,EAAA,KAAA,CAAA,qBAAA,EALM,CAAA,CAAA,EAAA,MAAA,EAKN,KAAA,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,KAAA,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"}
1
+ {"version":3,"file":"internal.d.cts","names":[],"sources":["../src/Menu/MenuAnchor.vue"],"sourcesContent":[],"mappings":";;;;;;UAoBU,eAAA,SAAwB;YAsC9B;KACC,WAAA;2BACwB;AA3CoB,CAAA;AAGE,cA2C7C,UALgB,EAKN,MAAA,CAAA,eALM,CAKN,eALM,EAAA,CAAA,CAAA,EAAA,CAAA,CAAA,EAAA,CAAA,CAAA,EAAA,CAAA,CAAA,EAKN,MAAA,CAAA,qBAAA,EAAA,MAAA,CAAA,qBAAA,EALM,CAAA,CAAA,EAAA,MAAA,EAKN,MAAA,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,MAAA,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"}
@@ -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 vue51 from "vue";
4
+ import * as vue50 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: vue51.DefineComponent<MenuAnchorProps, {}, {}, {}, {}, vue51.ComponentOptionsMixin, vue51.ComponentOptionsMixin, {}, string, vue51.PublicProps, Readonly<MenuAnchorProps> & Readonly<{}>, {}, {}, {}, {}, string, vue51.ComponentProvideOptions, false, {}, any>;
12
+ declare const __VLS_base: vue50.DefineComponent<MenuAnchorProps, {}, {}, {}, {}, vue50.ComponentOptionsMixin, vue50.ComponentOptionsMixin, {}, string, vue50.PublicProps, Readonly<MenuAnchorProps> & Readonly<{}>, {}, {}, {}, {}, string, vue50.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "reka-ui",
3
3
  "type": "module",
4
- "version": "2.9.6",
4
+ "version": "2.9.8",
5
5
  "description": "Vue port for Radix UI Primitives.",
6
6
  "author": "UnoVue Contributors (https://github.com/unovue)",
7
7
  "license": "MIT",
@@ -111,14 +111,14 @@
111
111
  "@tsconfig/node24": "^24.0.0",
112
112
  "@types/jsdom": "^28.0.0",
113
113
  "@types/node": "^24.0.13",
114
- "@vitejs/plugin-vue": "^6.0.5",
114
+ "@vitejs/plugin-vue": "^6.0.6",
115
115
  "@vitest/coverage-istanbul": "^3.2.4",
116
- "@vue/test-utils": "^2.4.6",
116
+ "@vue/test-utils": "^2.4.10",
117
117
  "@vue/tsconfig": "^0.7.0",
118
118
  "jsdom": "^26.1.0",
119
119
  "size-limit": "^12.0.1",
120
120
  "tsdown": "^0.12.9",
121
- "vite": "^8.0.8",
121
+ "vite": "^8.0.12",
122
122
  "vitest": "^3.2.4",
123
123
  "vitest-axe": "0.1.0",
124
124
  "vitest-canvas-mock": "^0.3.3",
@@ -98,7 +98,7 @@ export interface CalendarRootProps extends PrimitiveProps {
98
98
  /** A function that returns the previous page of the calendar. It receives the current placeholder as an argument inside the component. */
99
99
  prevPage?: (placeholder: DateValue) => DateValue
100
100
  /** The controlled selected date value of the calendar. Can be bound as `v-model`. */
101
- modelValue?: DateValue | DateValue[] | undefined
101
+ modelValue?: DateValue | DateValue[] | null
102
102
  /** Whether multiple dates can be selected */
103
103
  multiple?: boolean
104
104
  /** Whether or not to disable days outside the current view. */
@@ -84,7 +84,7 @@ export interface DateRangeFieldRootProps extends PrimitiveProps, FormFieldProps
84
84
 
85
85
  export type DateRangeFieldRootEmits = {
86
86
  /** Event handler called whenever the model value changes */
87
- 'update:modelValue': [DateRange]
87
+ 'update:modelValue': [date: DateRange]
88
88
  /** Event handler called whenever the placeholder value changes */
89
89
  'update:placeholder': [date: DateValue]
90
90
  }
@@ -23,6 +23,7 @@ useForwardExpose()
23
23
  :as-child="asChild"
24
24
  :data-state="rootContext.open.value ? 'open' : 'closed'"
25
25
  style="pointer-events: auto"
26
+ @pointerdown.left.prevent
26
27
  >
27
28
  <slot />
28
29
  </Primitive>
@@ -185,6 +185,10 @@ watchEffect(async (cleanupFn) => {
185
185
  container.addEventListener(AUTOFOCUS_ON_UNMOUNT, unmountEventHandler)
186
186
  container.dispatchEvent(unmountEvent)
187
187
 
188
+ // Signal that blur events fired below are system-initiated (focus trap
189
+ // cleanup), not user-initiated. Consumers can use this to skip validation.
190
+ container.setAttribute('data-focus-scope-unmounting', '')
191
+
188
192
  setTimeout(() => {
189
193
  if (!unmountEvent.defaultPrevented)
190
194
  focus(previouslyFocusedElement ?? document.body, { select: true })
@@ -193,6 +197,7 @@ watchEffect(async (cleanupFn) => {
193
197
  container.removeEventListener(AUTOFOCUS_ON_UNMOUNT, unmountEventHandler)
194
198
 
195
199
  focusScopesStack.remove(focusScope)
200
+ container.removeAttribute('data-focus-scope-unmounting')
196
201
  }, 0)
197
202
  })
198
203
  })
@@ -44,7 +44,7 @@ const { CollectionItem } = useCollection()
44
44
  const { forwardRef, currentElement } = useForwardExpose()
45
45
  const rootContext = injectListboxRootContext()
46
46
 
47
- const isHighlighted = computed(() => currentElement.value === rootContext.highlightedElement.value)
47
+ const isHighlighted = computed(() => currentElement.value != null && currentElement.value === rootContext.highlightedElement.value)
48
48
  const isSelected = computed(() => valueComparator(rootContext.modelValue.value, props.value, rootContext.by))
49
49
 
50
50
  const disabled = computed(() => rootContext.disabled.value || props.disabled)
@@ -76,7 +76,7 @@ provideListboxItemContext({
76
76
  :id="id"
77
77
  v-bind="$attrs"
78
78
  :ref="forwardRef"
79
- v-memo="[isHighlighted, isSelected]"
79
+ v-memo="[isHighlighted, isSelected, disabled, rootContext.focusable.value]"
80
80
  role="option"
81
81
  :tabindex="rootContext.focusable.value ? isHighlighted ? '0' : '-1' : -1"
82
82
  :aria-selected="isSelected"
@@ -79,6 +79,7 @@ export type ListboxRootEmits<T = AcceptableValue> = {
79
79
  import type { EventHook } from '@vueuse/core'
80
80
  import type { Ref } from 'vue'
81
81
  import { createEventHook, useVModel } from '@vueuse/core'
82
+ import { isClient } from '@vueuse/shared'
82
83
  import { nextTick, ref, toRefs, watch } from 'vue'
83
84
  import { useCollection } from '@/Collection'
84
85
  import { VisuallyHiddenInput } from '@/VisuallyHidden'
@@ -324,6 +325,12 @@ function handleMultipleReplace(event: KeyboardEvent, targetEl: HTMLElement) {
324
325
  }
325
326
 
326
327
  async function highlightSelected(event?: Event) {
328
+ // highlightSelected is called inside a watch with immediate set to true.
329
+ // This results in code execution during SSR.
330
+ // Ensure this code only runs in a browser environment, since it performs
331
+ // DOM-only side effects (focus, scrollIntoView, synthetic KeyboardEvent).
332
+ if (!isClient)
333
+ return
327
334
  await nextTick()
328
335
  if (isVirtual.value) {
329
336
  // Trigger on nextTick for Virtualizer to be mounted
@@ -24,7 +24,7 @@ import { useBodyScrollLock } from '@/shared/useBodyScrollLock'
24
24
 
25
25
  export interface MenuContentContext {
26
26
  onItemEnter: (event: PointerEvent) => boolean
27
- onItemLeave: (event: PointerEvent) => void
27
+ onItemLeave: (event: PointerEvent) => boolean
28
28
  onTriggerLeave: (event: PointerEvent) => boolean
29
29
  searchRef: Ref<string>
30
30
  highlightedElement: Ref<HTMLElement | undefined>
@@ -303,13 +303,14 @@ provideMenuContentContext({
303
303
  },
304
304
  onItemLeave: (event) => {
305
305
  if (isPointerMovingToSubmenu(event))
306
- return
306
+ return true
307
307
 
308
308
  const isInputFocused = ['INPUT', 'TEXTAREA'].includes(getActiveElement()?.tagName || '')
309
309
  if (!isInputFocused)
310
310
  contentElement.value?.focus()
311
311
 
312
312
  currentItemId.value = null
313
+ return false
313
314
  },
314
315
  onTriggerLeave: (event) => {
315
316
  // event.preventDefault() we can't prevent pointerLeave event
@@ -33,7 +33,7 @@ const { forwardRef, currentElement } = useForwardExpose()
33
33
  const { CollectionItem } = useCollection()
34
34
 
35
35
  const isFocused = ref(false)
36
- const isHighlighted = computed(() => isFocused.value || (contentContext.highlightedElement.value === currentElement.value))
36
+ const isHighlighted = computed(() => isFocused.value || (currentElement.value != null && contentContext.highlightedElement.value === currentElement.value))
37
37
 
38
38
  async function handlePointerMove(event: PointerEvent) {
39
39
  if (event.defaultPrevented || !isMouseEvent(event))
@@ -60,7 +60,15 @@ async function handlePointerLeave(event: PointerEvent) {
60
60
  if (!isMouseEvent(event))
61
61
  return
62
62
 
63
- contentContext.onItemLeave(event)
63
+ // If the highlight was already claimed by another element (e.g. the pointer moved
64
+ // directly onto another item, whose synchronous `pointermove` ran before this
65
+ // `nextTick` resolved), this leave is stale and must not reset focus/roving state.
66
+ if (contentContext.highlightedElement.value !== currentElement.value)
67
+ return
68
+
69
+ const isMovingToSubmenu = contentContext.onItemLeave(event)
70
+ if (!isMovingToSubmenu && contentContext.highlightedElement.value === currentElement.value)
71
+ contentContext.highlightedElement.value = undefined
64
72
  }
65
73
  </script>
66
74
 
@@ -74,7 +74,7 @@ export interface MonthPickerRootProps extends PrimitiveProps {
74
74
  /** A function that returns the previous page of the month picker. Receives the current placeholder as an argument. */
75
75
  prevPage?: (placeholder: DateValue) => DateValue
76
76
  /** The controlled selected month value of the month picker. Can be bound as `v-model`. */
77
- modelValue?: DateValue | DateValue[] | undefined
77
+ modelValue?: DateValue | DateValue[] | null
78
78
  /** Whether multiple months can be selected */
79
79
  multiple?: boolean
80
80
  }
@@ -180,6 +180,9 @@ function handleKeydown(ev: KeyboardEvent) {
180
180
  }
181
181
 
182
182
  function handleDismiss() {
183
+ if (menuContext.modelValue.value !== itemContext.value)
184
+ return
185
+
183
186
  const rootContentDismissEvent = new Event(EVENT_ROOT_CONTENT_DISMISS, {
184
187
  bubbles: true,
185
188
  cancelable: true,
@@ -0,0 +1,45 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue'
3
+ import {
4
+ NavigationMenuContent,
5
+ NavigationMenuItem,
6
+ NavigationMenuList,
7
+ NavigationMenuRoot,
8
+ NavigationMenuTrigger,
9
+ } from '..'
10
+
11
+ const modelValue = ref('')
12
+ </script>
13
+
14
+ <template>
15
+ <NavigationMenuRoot
16
+ v-model="modelValue"
17
+ :unmount-on-hide="false"
18
+ :disable-hover-trigger="true"
19
+ >
20
+ <span data-testid="model-value">{{ modelValue }}</span>
21
+ <NavigationMenuList>
22
+ <NavigationMenuItem value="one">
23
+ <NavigationMenuTrigger data-testid="trigger-one">
24
+ One
25
+ </NavigationMenuTrigger>
26
+ <NavigationMenuContent data-testid="content-one">
27
+ <button data-testid="inside-one">
28
+ Inside one
29
+ </button>
30
+ </NavigationMenuContent>
31
+ </NavigationMenuItem>
32
+
33
+ <NavigationMenuItem value="two">
34
+ <NavigationMenuTrigger data-testid="trigger-two">
35
+ Two
36
+ </NavigationMenuTrigger>
37
+ <NavigationMenuContent data-testid="content-two">
38
+ <button data-testid="inside-two">
39
+ Inside two
40
+ </button>
41
+ </NavigationMenuContent>
42
+ </NavigationMenuItem>
43
+ </NavigationMenuList>
44
+ </NavigationMenuRoot>
45
+ </template>
@@ -3,7 +3,7 @@ import type {
3
3
  SelectContentImplEmits,
4
4
  SelectContentImplProps,
5
5
  } from './SelectContentImpl.vue'
6
- import { computed, onMounted, ref, watch } from 'vue'
6
+ import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
7
7
 
8
8
  export type SelectContentEmits = SelectContentImplEmits
9
9
 
@@ -44,15 +44,31 @@ const presenceRef = ref<InstanceType<typeof Presence>>()
44
44
  const present = computed(() => props.forceMount || rootContext.open.value)
45
45
  const renderPresence = ref(present.value)
46
46
 
47
- watch(present, () => {
47
+ let renderPresenceTimeout: ReturnType<typeof setTimeout> | undefined
48
+
49
+ function clearRenderPresenceTimeout() {
50
+ if (renderPresenceTimeout) {
51
+ clearTimeout(renderPresenceTimeout)
52
+ renderPresenceTimeout = undefined
53
+ }
54
+ }
55
+
56
+ watch(present, (_value, _oldValue, onCleanup) => {
48
57
  // Toggle render presence after a delay (nextTick is not enough)
49
58
  // to allow children to re-render with the latest state.
50
59
  // Otherwise, they would remain in the old state during the transition,
51
60
  // which would prevent the animation that depend on state (e.g., data-[state=closed])
52
61
  // from being applied accurately.
53
62
  // @see https://github.com/unovue/reka-ui/issues/1865
54
- setTimeout(() => renderPresence.value = present.value)
63
+ clearRenderPresenceTimeout()
64
+ renderPresenceTimeout = setTimeout(() => {
65
+ renderPresence.value = present.value
66
+ renderPresenceTimeout = undefined
67
+ })
68
+ onCleanup(clearRenderPresenceTimeout)
55
69
  })
70
+
71
+ onUnmounted(clearRenderPresenceTimeout)
56
72
  </script>
57
73
 
58
74
  <template>
@@ -0,0 +1,43 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue'
3
+ import {
4
+ SelectContent,
5
+ SelectItem,
6
+ SelectItemText,
7
+ SelectPortal,
8
+ SelectRoot,
9
+ SelectTrigger,
10
+ SelectValue,
11
+ SelectViewport,
12
+ } from '..'
13
+
14
+ const open = ref(true)
15
+ const mounted = ref(true)
16
+ </script>
17
+
18
+ <template>
19
+ <button @click="open = false">
20
+ Close
21
+ </button>
22
+ <button @click="mounted = false">
23
+ Unmount
24
+ </button>
25
+
26
+ <SelectRoot
27
+ v-if="mounted"
28
+ v-model:open="open"
29
+ >
30
+ <SelectTrigger aria-label="Fruit">
31
+ <SelectValue placeholder="Please select a fruit" />
32
+ </SelectTrigger>
33
+ <SelectPortal>
34
+ <SelectContent position="popper">
35
+ <SelectViewport>
36
+ <SelectItem value="apple">
37
+ <SelectItemText>Apple</SelectItemText>
38
+ </SelectItem>
39
+ </SelectViewport>
40
+ </SelectContent>
41
+ </SelectPortal>
42
+ </SelectRoot>
43
+ </template>
@@ -8,7 +8,7 @@ export interface TabsIndicatorProps extends PrimitiveProps {}
8
8
  </script>
9
9
 
10
10
  <script setup lang="ts">
11
- import { useResizeObserver } from '@vueuse/core'
11
+ import { useMounted, useResizeObserver } from '@vueuse/core'
12
12
  import { Primitive } from '@/Primitive'
13
13
 
14
14
  const props = defineProps<TabsIndicatorProps>()
@@ -18,6 +18,8 @@ defineExpose({
18
18
  })
19
19
  useForwardExpose()
20
20
 
21
+ const isMounted = useMounted()
22
+
21
23
  interface IndicatorStyle {
22
24
  size: number | null
23
25
  position: number | null
@@ -61,7 +63,7 @@ function updateIndicatorStyle() {
61
63
 
62
64
  <template>
63
65
  <Primitive
64
- v-if="typeof indicatorStyle.size === 'number'"
66
+ v-if="isMounted && typeof indicatorStyle.size === 'number'"
65
67
  v-bind="props"
66
68
  :style="{
67
69
  '--reka-tabs-indicator-size': `${indicatorStyle.size}px`,
@@ -1,7 +1,6 @@
1
1
  <script setup lang="ts">
2
- import { useRafFn } from '@vueuse/core'
3
- import { useTimeout } from '@vueuse/shared'
4
- import { ref } from 'vue'
2
+ import { isClient, useTimeout } from '@vueuse/shared'
3
+ import { onScopeDispose, ref } from 'vue'
5
4
  import { VisuallyHidden } from '@/VisuallyHidden'
6
5
  import { injectToastProviderContext } from './ToastProvider.vue'
7
6
 
@@ -10,9 +9,21 @@ const providerContext = injectToastProviderContext()
10
9
  const isAnnounced = useTimeout(1000)
11
10
  const renderAnnounceText = ref(false)
12
11
 
13
- useRafFn(() => {
14
- renderAnnounceText.value = true
15
- })
12
+ // Render text content in the next frame to ensure toast is announced in NVDA.
13
+ // Double rAF mirrors Radix UI's `useNextFrame` behavior.
14
+ let raf1 = 0
15
+ let raf2 = 0
16
+ if (isClient) {
17
+ raf1 = requestAnimationFrame(() => {
18
+ raf2 = requestAnimationFrame(() => {
19
+ renderAnnounceText.value = true
20
+ })
21
+ })
22
+ onScopeDispose(() => {
23
+ cancelAnimationFrame(raf1)
24
+ cancelAnimationFrame(raf2)
25
+ })
26
+ }
16
27
  </script>
17
28
 
18
29
  <template>
@@ -183,7 +183,20 @@ provideToastRootContext({ onClose: handleClose })
183
183
  role="alert"
184
184
  :aria-live="type === 'foreground' ? 'assertive' : 'polite'"
185
185
  >
186
- {{ announceTextContent }}
186
+ <!--
187
+ Render each chunk as its own text node so screen readers get the
188
+ natural pause break between nodes (see comment in utils.ts).
189
+ Interpolating the array directly with `{{ announceTextContent }}`
190
+ would route through Vue's `toDisplayString`, which JSON-stringifies
191
+ arrays — the live region would then announce literal `[`, quotes
192
+ and commas instead of the toast title and description.
193
+ -->
194
+ <template
195
+ v-for="(text, i) in announceTextContent"
196
+ :key="i"
197
+ >
198
+ {{ text }}
199
+ </template>
187
200
  </ToastAnnounce>
188
201
 
189
202
  <Teleport
@@ -75,7 +75,7 @@ export interface YearPickerRootProps extends PrimitiveProps {
75
75
  /** A function that returns the previous page of the year picker. Receives the current placeholder as an argument. */
76
76
  prevPage?: (placeholder: DateValue) => DateValue
77
77
  /** The controlled selected year value of the year picker. Can be bound as `v-model`. */
78
- modelValue?: DateValue | DateValue[] | undefined
78
+ modelValue?: DateValue | DateValue[] | null
79
79
  /** Whether multiple years can be selected */
80
80
  multiple?: boolean
81
81
  /** Number of years to display per page */
package/src/index.ts CHANGED
@@ -96,7 +96,14 @@ export {
96
96
  } from './shared/color'
97
97
  export {
98
98
  type AcceptableValue,
99
+ type DataOrientation,
100
+ type Direction,
101
+ type FormFieldProps,
99
102
  type GenericComponentInstance,
103
+ type ScrollBodyOption,
104
+ type SingleOrMultipleProps,
105
+ type SingleOrMultipleType,
106
+ type StringOrNumber,
100
107
  } from './shared/types'
101
108
  export * from './Slider'
102
109
  export * from './Splitter'