base-ui-vue 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/dist/arrow/TooltipArrow.cjs +609 -0
  2. package/dist/arrow/TooltipArrow.cjs.map +1 -0
  3. package/dist/arrow/TooltipArrow.js +478 -0
  4. package/dist/arrow/TooltipArrow.js.map +1 -0
  5. package/dist/button/Button.cjs +5 -317
  6. package/dist/button/Button.cjs.map +1 -1
  7. package/dist/button/Button.js +3 -249
  8. package/dist/button/Button.js.map +1 -1
  9. package/dist/button/ToolbarButton.cjs +13 -6
  10. package/dist/button/ToolbarButton.cjs.map +1 -1
  11. package/dist/button/ToolbarButton.js +3 -2
  12. package/dist/button/ToolbarButton.js.map +1 -1
  13. package/dist/checkbox/index.cjs +20 -9
  14. package/dist/checkbox/index.cjs.map +1 -1
  15. package/dist/checkbox/index.js +7 -2
  16. package/dist/checkbox/index.js.map +1 -1
  17. package/dist/checkbox-group/CheckboxGroup.cjs +14 -13
  18. package/dist/checkbox-group/CheckboxGroup.cjs.map +1 -1
  19. package/dist/checkbox-group/CheckboxGroup.js +2 -1
  20. package/dist/checkbox-group/CheckboxGroup.js.map +1 -1
  21. package/dist/components/FloatingPortal.cjs +66 -0
  22. package/dist/components/FloatingPortal.cjs.map +1 -0
  23. package/dist/components/FloatingPortal.js +55 -0
  24. package/dist/components/FloatingPortal.js.map +1 -0
  25. package/dist/components/FloatingRootStore.cjs +101 -0
  26. package/dist/components/FloatingRootStore.cjs.map +1 -0
  27. package/dist/components/FloatingRootStore.js +84 -0
  28. package/dist/components/FloatingRootStore.js.map +1 -0
  29. package/dist/components/FloatingTree.cjs +33 -0
  30. package/dist/components/FloatingTree.cjs.map +1 -0
  31. package/dist/components/FloatingTree.js +22 -0
  32. package/dist/components/FloatingTree.js.map +1 -0
  33. package/dist/composite/composite.cjs +6 -0
  34. package/dist/composite/composite.js +1 -1
  35. package/dist/content/ScrollAreaContent.cjs +3 -3
  36. package/dist/content/ScrollAreaContent.cjs.map +1 -1
  37. package/dist/content/ScrollAreaContent.js +1 -1
  38. package/dist/control/FieldControl.cjs +3 -3
  39. package/dist/control/FieldControl.cjs.map +1 -1
  40. package/dist/control/FieldControl.js +1 -1
  41. package/dist/control/SliderControl.cjs +3 -3
  42. package/dist/control/SliderControl.cjs.map +1 -1
  43. package/dist/control/SliderControl.js +1 -1
  44. package/dist/corner/ScrollAreaCorner.cjs +3 -3
  45. package/dist/corner/ScrollAreaCorner.cjs.map +1 -1
  46. package/dist/corner/ScrollAreaCorner.js +1 -1
  47. package/dist/decrement/NumberFieldDecrement.cjs +10 -3
  48. package/dist/decrement/NumberFieldDecrement.cjs.map +1 -1
  49. package/dist/decrement/NumberFieldDecrement.js +3 -2
  50. package/dist/decrement/NumberFieldDecrement.js.map +1 -1
  51. package/dist/description/FieldDescription.cjs +2 -2
  52. package/dist/description/FieldDescription.cjs.map +1 -1
  53. package/dist/description/FieldDescription.js +1 -1
  54. package/dist/error/FieldError.cjs +2 -2
  55. package/dist/error/FieldError.cjs.map +1 -1
  56. package/dist/error/FieldError.js +1 -1
  57. package/dist/fallback/AvatarFallback.cjs +2 -2
  58. package/dist/fallback/AvatarFallback.cjs.map +1 -1
  59. package/dist/fallback/AvatarFallback.js +1 -1
  60. package/dist/floating-ui-vue/index.js +638 -0
  61. package/dist/floating-ui-vue/index.js.map +1 -0
  62. package/dist/form/Form.cjs +3 -3
  63. package/dist/form/Form.cjs.map +1 -1
  64. package/dist/form/Form.js +1 -1
  65. package/dist/group/NumberFieldGroup.cjs +3 -3
  66. package/dist/group/NumberFieldGroup.cjs.map +1 -1
  67. package/dist/group/NumberFieldGroup.js +1 -1
  68. package/dist/group/ToolbarGroup.cjs +2 -2
  69. package/dist/group/ToolbarGroup.cjs.map +1 -1
  70. package/dist/group/ToolbarGroup.js +1 -1
  71. package/dist/header/AccordionHeader.cjs +2 -2
  72. package/dist/header/AccordionHeader.cjs.map +1 -1
  73. package/dist/header/AccordionHeader.js +1 -1
  74. package/dist/hooks/useDismiss.cjs +502 -0
  75. package/dist/hooks/useDismiss.cjs.map +1 -0
  76. package/dist/hooks/useHoverFloatingInteraction.cjs +233 -0
  77. package/dist/hooks/useHoverFloatingInteraction.cjs.map +1 -0
  78. package/dist/image/AvatarImage.cjs +4 -4
  79. package/dist/image/AvatarImage.cjs.map +1 -1
  80. package/dist/image/AvatarImage.js +1 -1
  81. package/dist/increment/NumberFieldIncrement.cjs +3 -2
  82. package/dist/increment/NumberFieldIncrement.cjs.map +1 -1
  83. package/dist/increment/NumberFieldIncrement.js +2 -1
  84. package/dist/increment/NumberFieldIncrement.js.map +1 -1
  85. package/dist/index.cjs +60 -7
  86. package/dist/index.d.cts +3838 -1726
  87. package/dist/index.d.cts.map +1 -1
  88. package/dist/index.d.ts +3838 -1726
  89. package/dist/index.d.ts.map +1 -1
  90. package/dist/index.js +4 -3
  91. package/dist/index2.cjs +7475 -3089
  92. package/dist/index2.cjs.map +1 -1
  93. package/dist/index2.js +7215 -3106
  94. package/dist/index2.js.map +1 -1
  95. package/dist/rolldown-runtime.js +36 -0
  96. package/package.json +1 -1
  97. package/src/floating-ui-vue/components/FloatingPortal.ts +171 -0
  98. package/src/floating-ui-vue/components/FloatingPortal.vue +259 -0
  99. package/src/floating-ui-vue/components/FloatingRootStore.ts +83 -0
  100. package/src/floating-ui-vue/components/FloatingTree.ts +64 -0
  101. package/src/floating-ui-vue/components/FloatingTreeStore.ts +21 -0
  102. package/src/floating-ui-vue/hooks/useDismiss.ts +689 -0
  103. package/src/floating-ui-vue/hooks/useHoverFloatingInteraction.ts +244 -0
  104. package/src/floating-ui-vue/hooks/useHoverInteractionSharedState.ts +125 -0
  105. package/src/floating-ui-vue/hooks/useHoverShared.ts +53 -0
  106. package/src/floating-ui-vue/index.ts +48 -0
  107. package/src/floating-ui-vue/middleware/arrow.ts +114 -0
  108. package/src/floating-ui-vue/types.ts +117 -0
  109. package/src/floating-ui-vue/utils/composite.ts +20 -0
  110. package/src/floating-ui-vue/utils/constants.ts +2 -0
  111. package/src/floating-ui-vue/utils/createAttribute.ts +3 -0
  112. package/src/floating-ui-vue/utils/createEventEmitter.ts +20 -0
  113. package/src/floating-ui-vue/utils/element.ts +59 -0
  114. package/src/floating-ui-vue/utils/event.ts +15 -0
  115. package/src/floating-ui-vue/utils/nodes.ts +14 -0
  116. package/src/floating-ui-vue/utils/tabbable.ts +309 -0
  117. package/src/index.ts +4 -0
  118. package/src/radio/index.ts +28 -0
  119. package/src/radio/indicator/RadioIndicator.vue +90 -0
  120. package/src/radio/indicator/RadioIndicatorDataAttributes.ts +54 -0
  121. package/src/radio/root/RadioRoot.vue +544 -0
  122. package/src/radio/root/RadioRootContext.ts +18 -0
  123. package/src/radio/root/RadioRootDataAttributes.ts +46 -0
  124. package/src/radio/utils/stateAttributesMapping.ts +29 -0
  125. package/src/radio-group/RadioGroup.vue +381 -0
  126. package/src/radio-group/RadioGroupContext.ts +42 -0
  127. package/src/radio-group/RadioGroupDataAttributes.ts +6 -0
  128. package/src/radio-group/index.ts +10 -0
  129. package/src/tabs/index.ts +38 -0
  130. package/src/tabs/indicator/TabsIndicator.vue +201 -0
  131. package/src/tabs/indicator/TabsIndicatorCssVars.ts +26 -0
  132. package/src/tabs/indicator/TabsIndicatorDataAttributes.ts +12 -0
  133. package/src/tabs/indicator/prehydrationScript.min.ts +5 -0
  134. package/src/tabs/indicator/prehydrationScript.template.js +87 -0
  135. package/src/tabs/list/TabsList.vue +201 -0
  136. package/src/tabs/list/TabsListContext.ts +29 -0
  137. package/src/tabs/list/TabsListDataAttributes.ts +12 -0
  138. package/src/tabs/panel/TabsPanel.vue +167 -0
  139. package/src/tabs/panel/TabsPanelDataAttributes.ts +28 -0
  140. package/src/tabs/root/TabsRoot.vue +468 -0
  141. package/src/tabs/root/TabsRootContext.ts +56 -0
  142. package/src/tabs/root/TabsRootDataAttributes.ts +12 -0
  143. package/src/tabs/root/stateAttributesMapping.ts +9 -0
  144. package/src/tabs/tab/TabsTab.vue +274 -0
  145. package/src/tabs/tab/TabsTabDataAttributes.ts +20 -0
  146. package/src/tabs/utils/areTabValuesEqual.ts +6 -0
  147. package/src/tooltip/arrow/TooltipArrow.vue +116 -0
  148. package/src/tooltip/arrow/TooltipArrowDataAttributes.ts +31 -0
  149. package/src/tooltip/index.ts +54 -0
  150. package/src/tooltip/popup/TooltipPopup.vue +142 -0
  151. package/src/tooltip/popup/TooltipPopupDataAttributes.ts +35 -0
  152. package/src/tooltip/portal/TooltipPortal.vue +83 -0
  153. package/src/tooltip/portal/TooltipPortalContext.ts +19 -0
  154. package/src/tooltip/positioner/TooltipPositioner.vue +221 -0
  155. package/src/tooltip/positioner/TooltipPositionerContext.ts +23 -0
  156. package/src/tooltip/positioner/TooltipPositionerCssVars.ts +27 -0
  157. package/src/tooltip/positioner/TooltipPositionerDataAttributes.ts +26 -0
  158. package/src/tooltip/provider/TooltipProvider.vue +78 -0
  159. package/src/tooltip/provider/TooltipProviderContext.ts +19 -0
  160. package/src/tooltip/root/TooltipRoot.vue +401 -0
  161. package/src/tooltip/root/TooltipRootContext.ts +71 -0
  162. package/src/tooltip/store/TooltipHandle.ts +215 -0
  163. package/src/tooltip/trigger/TooltipTrigger.vue +343 -0
  164. package/src/tooltip/trigger/TooltipTriggerDataAttributes.ts +12 -0
  165. package/src/tooltip/utils/constants.ts +2 -0
  166. package/src/tooltip/viewport/TooltipViewport.vue +149 -0
  167. package/src/tooltip/viewport/TooltipViewportCssVars.ts +14 -0
  168. package/src/tooltip/viewport/TooltipViewportDataAttributes.ts +26 -0
  169. package/src/utils/FloatingPortalLite.vue +66 -0
  170. package/src/utils/FocusGuard.vue +36 -0
  171. package/src/utils/adaptiveOriginMiddleware.ts +79 -0
  172. package/src/utils/addEventListener.ts +16 -0
  173. package/src/utils/constants.ts +11 -0
  174. package/src/utils/createBaseUIEventDetails.ts +2 -0
  175. package/src/utils/getCssDimensions.ts +19 -0
  176. package/src/utils/getDisabledMountTransitionStyles.ts +9 -0
  177. package/src/utils/hideMiddleware.ts +19 -0
  178. package/src/utils/mergeCleanups.ts +7 -0
  179. package/src/utils/platform.ts +18 -0
  180. package/src/utils/popupStateMapping.ts +103 -0
  181. package/src/utils/reasons.ts +4 -0
  182. package/src/utils/serializeValue.ts +17 -0
  183. package/src/utils/useAnchorPositioning.ts +644 -0
  184. package/src/utils/usePopupAutoResize.ts +254 -0
  185. package/src/utils/usePopupViewport.ts +398 -0
  186. package/src/utils/usePositioner.ts +61 -0
  187. package/src/utils/useRenderElement.ts +3 -0
@@ -0,0 +1,36 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __exportAll = (all, no_symbols) => {
7
+ let target = {};
8
+ for (var name in all) {
9
+ __defProp(target, name, {
10
+ get: all[name],
11
+ enumerable: true
12
+ });
13
+ }
14
+ if (!no_symbols) {
15
+ __defProp(target, Symbol.toStringTag, { value: "Module" });
16
+ }
17
+ return target;
18
+ };
19
+ var __copyProps = (to, from, except, desc) => {
20
+ if (from && typeof from === "object" || typeof from === "function") {
21
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
22
+ key = keys[i];
23
+ if (!__hasOwnProp.call(to, key) && key !== except) {
24
+ __defProp(to, key, {
25
+ get: ((k) => from[k]).bind(null, key),
26
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
27
+ });
28
+ }
29
+ }
30
+ }
31
+ return to;
32
+ };
33
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
34
+
35
+ //#endregion
36
+ export { __exportAll, __reExport };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "base-ui-vue",
3
3
  "type": "module",
4
- "version": "0.4.0",
4
+ "version": "0.5.0",
5
5
  "description": "Vue port of Base UI",
6
6
  "author": "vuepont",
7
7
  "license": "MIT",
@@ -0,0 +1,171 @@
1
+ import type {
2
+ ComponentPublicInstance,
3
+ ComputedRef,
4
+ InjectionKey,
5
+ MaybeRefOrGetter,
6
+ Ref,
7
+ TeleportProps,
8
+ } from 'vue'
9
+ import type { BaseUIComponentProps } from '../../utils/types'
10
+ import type {
11
+ RenderRef,
12
+ UseRenderElementComponentProps,
13
+ UseRenderElementReturn,
14
+ } from '../../utils/useRenderElement'
15
+ import { computed, inject, isRef, shallowRef, toValue } from 'vue'
16
+ import { mergeProps } from '../../merge-props/mergeProps'
17
+ import { EMPTY_OBJECT } from '../../utils/constants'
18
+ import { useBaseUiId } from '../../utils/useBaseUiId'
19
+ import { useMergedRefs } from '../../utils/useMergedRefs'
20
+ import { useRenderElement } from '../../utils/useRenderElement'
21
+ import { createAttribute } from '../utils/createAttribute'
22
+
23
+ export interface FloatingPortalState {}
24
+
25
+ export type FloatingPortalTarget = TeleportProps['to']
26
+
27
+ export type FloatingPortalContainer = MaybeRefOrGetter<FloatingPortalTarget>
28
+
29
+ export interface FloatingPortalProps<
30
+ State extends Record<string, any> = FloatingPortalState,
31
+ > extends BaseUIComponentProps<State> {
32
+ /**
33
+ * A parent element to render the portal element into.
34
+ */
35
+ container?: FloatingPortalContainer
36
+ }
37
+
38
+ export interface FloatingPortal {}
39
+
40
+ // eslint-disable-next-line ts/no-namespace
41
+ export namespace FloatingPortal {
42
+ export type State = FloatingPortalState
43
+ export type Target = FloatingPortalTarget
44
+ export type Container = FloatingPortalContainer
45
+ export type Props<
46
+ State extends Record<string, any> = FloatingPortalState,
47
+ > = FloatingPortalProps<State>
48
+ }
49
+
50
+ export type FloatingPortalFocusManagerState = null | {
51
+ modal: boolean
52
+ open: boolean
53
+ onOpenChange: (
54
+ open: boolean,
55
+ data?: { reason?: string | undefined, event?: Event | undefined },
56
+ ) => void
57
+ domReference: Element | null
58
+ closeOnFocusOut: boolean
59
+ }
60
+
61
+ export interface FloatingPortalContext {
62
+ portalNode: Ref<HTMLElement | null>
63
+ setFocusManagerState: (
64
+ next:
65
+ | FloatingPortalFocusManagerState
66
+ | ((prev: FloatingPortalFocusManagerState) => FloatingPortalFocusManagerState),
67
+ ) => void
68
+ beforeInsideRef: Ref<HTMLSpanElement | null>
69
+ afterInsideRef: Ref<HTMLSpanElement | null>
70
+ beforeOutsideRef: Ref<HTMLSpanElement | null>
71
+ afterOutsideRef: Ref<HTMLSpanElement | null>
72
+ }
73
+
74
+ export const floatingPortalContextKey: InjectionKey<FloatingPortalContext>
75
+ = Symbol('FloatingPortalContext')
76
+
77
+ export function usePortalContext() {
78
+ return inject(floatingPortalContextKey, null)
79
+ }
80
+
81
+ function resolvePortalTarget(
82
+ target: MaybeRefOrGetter<FloatingPortalTarget> | undefined,
83
+ ) {
84
+ if (target === undefined) {
85
+ return undefined
86
+ }
87
+
88
+ const resolvedTarget = toValue(target)
89
+
90
+ return isRef(resolvedTarget) ? resolvedTarget.value : resolvedTarget
91
+ }
92
+
93
+ export interface UseFloatingPortalNodeProps<
94
+ State extends Record<string, any> = FloatingPortalState,
95
+ > {
96
+ ref?: RenderRef | undefined
97
+ container?: FloatingPortalContainer | undefined
98
+ to?: MaybeRefOrGetter<FloatingPortalTarget> | undefined
99
+ componentProps?: UseRenderElementComponentProps<State> | undefined
100
+ elementProps?: MaybeRefOrGetter<Record<string, any> | undefined>
101
+ state?: MaybeRefOrGetter<State | undefined>
102
+ }
103
+
104
+ export interface UseFloatingPortalNodeResult<
105
+ State extends Record<string, any> = FloatingPortalState,
106
+ > extends UseRenderElementReturn<State> {
107
+ portalNode: Ref<HTMLElement | null>
108
+ portalTarget: ComputedRef<FloatingPortalTarget>
109
+ shouldRender: ComputedRef<boolean>
110
+ }
111
+
112
+ export function useFloatingPortalNode<
113
+ State extends Record<string, any> = FloatingPortalState,
114
+ >(
115
+ props: UseFloatingPortalNodeProps<State> = {},
116
+ ): UseFloatingPortalNodeResult<State> {
117
+ const uniqueId = useBaseUiId()
118
+ const portalContext = usePortalContext()
119
+ const portalNode = shallowRef<HTMLElement | null>(null)
120
+ const portalAttribute = createAttribute('portal')
121
+
122
+ const state = computed(() =>
123
+ toValue(props.state) ?? (EMPTY_OBJECT as State),
124
+ )
125
+
126
+ const portalTarget = computed<FloatingPortalTarget>(() => {
127
+ const container = resolvePortalTarget(props.container)
128
+
129
+ if (container !== undefined) {
130
+ return container
131
+ }
132
+
133
+ const explicitTarget = resolvePortalTarget(props.to)
134
+
135
+ if (explicitTarget !== undefined) {
136
+ return explicitTarget
137
+ }
138
+
139
+ return portalContext?.portalNode.value ?? 'body'
140
+ })
141
+
142
+ const shouldRender = computed(() => uniqueId != null && portalTarget.value != null)
143
+
144
+ const portalProps = computed(() =>
145
+ mergeProps(
146
+ {
147
+ id: uniqueId,
148
+ [portalAttribute]: '',
149
+ },
150
+ toValue(props.elementProps),
151
+ ),
152
+ )
153
+
154
+ const renderElement = useRenderElement({
155
+ componentProps: props.componentProps ?? EMPTY_OBJECT,
156
+ state,
157
+ props: portalProps,
158
+ defaultTagName: 'div',
159
+ ref: useMergedRefs<HTMLElement | ComponentPublicInstance>(
160
+ portalNode,
161
+ props.ref,
162
+ ),
163
+ })
164
+
165
+ return {
166
+ ...renderElement,
167
+ portalNode,
168
+ portalTarget,
169
+ shouldRender,
170
+ }
171
+ }
@@ -0,0 +1,259 @@
1
+ <script lang="ts">
2
+ import type { ComponentPublicInstance } from 'vue'
3
+ import type {
4
+ FloatingPortalFocusManagerState,
5
+ FloatingPortal as FloatingPortalTypes,
6
+ } from './FloatingPortal'
7
+ </script>
8
+
9
+ <script setup lang="ts">
10
+ import {
11
+ computed,
12
+ provide,
13
+ shallowRef,
14
+ unref,
15
+ useAttrs,
16
+ watch,
17
+ } from 'vue'
18
+ import { addEventListener } from '../../utils/addEventListener'
19
+ import { ownerVisuallyHidden } from '../../utils/constants'
20
+ import { createChangeEventDetails } from '../../utils/createBaseUIEventDetails'
21
+ import FocusGuard from '../../utils/FocusGuard.vue'
22
+ import { mergeCleanups } from '../../utils/mergeCleanups'
23
+ import { REASONS } from '../../utils/reasons'
24
+ import {
25
+ disableFocusInside,
26
+ enableFocusInside,
27
+ getNextTabbable,
28
+ getPreviousTabbable,
29
+ isOutsideEvent,
30
+ } from '../utils/tabbable'
31
+ import {
32
+ floatingPortalContextKey,
33
+ useFloatingPortalNode,
34
+ } from './FloatingPortal'
35
+
36
+ export interface FloatingPortalState {}
37
+
38
+ export interface FloatingPortalProps
39
+ extends FloatingPortalTypes.Props<FloatingPortalState> {
40
+ /**
41
+ * Vue Teleport target. Prefer `container` for React API parity.
42
+ */
43
+ to?: FloatingPortalTypes.Target
44
+ /**
45
+ * Whether to render focus guards around the portal.
46
+ */
47
+ renderGuards?: boolean
48
+ /**
49
+ * Disables Vue Teleport while preserving portal mounting behavior.
50
+ * @default false
51
+ */
52
+ disabled?: boolean
53
+ }
54
+
55
+ type FocusGuardTemplateRef
56
+ = | Element
57
+ | ComponentPublicInstance
58
+ | { element?: HTMLSpanElement | null }
59
+ | null
60
+
61
+ defineOptions({
62
+ name: 'FloatingPortal',
63
+ inheritAttrs: false,
64
+ })
65
+
66
+ const props = withDefaults(defineProps<FloatingPortalProps>(), {
67
+ as: 'div',
68
+ disabled: false,
69
+ })
70
+
71
+ const attrs = useAttrs()
72
+
73
+ const state = computed<FloatingPortalState>(() => ({}))
74
+ const focusManagerState = shallowRef<FloatingPortalFocusManagerState>(null)
75
+ const focusInsideDisabled = shallowRef(false)
76
+
77
+ const beforeOutsideRef = shallowRef<HTMLSpanElement | null>(null)
78
+ const afterOutsideRef = shallowRef<HTMLSpanElement | null>(null)
79
+ const beforeInsideRef = shallowRef<HTMLSpanElement | null>(null)
80
+ const afterInsideRef = shallowRef<HTMLSpanElement | null>(null)
81
+
82
+ const {
83
+ portalNode,
84
+ portalTarget,
85
+ shouldRender,
86
+ tag,
87
+ mergedProps,
88
+ renderless,
89
+ ref: renderRef,
90
+ } = useFloatingPortalNode({
91
+ container: () => props.container,
92
+ to: () => props.to,
93
+ componentProps: props,
94
+ elementProps: computed(() => attrs),
95
+ state,
96
+ })
97
+
98
+ const shouldRenderGuards = computed(() => {
99
+ if (typeof props.renderGuards === 'boolean') {
100
+ return props.renderGuards
101
+ }
102
+
103
+ const manager = focusManagerState.value
104
+
105
+ return !!manager && !manager.modal && manager.open && !!portalNode.value
106
+ })
107
+
108
+ function setFocusManagerState(
109
+ next:
110
+ | FloatingPortalFocusManagerState
111
+ | ((prev: FloatingPortalFocusManagerState) => FloatingPortalFocusManagerState),
112
+ ) {
113
+ focusManagerState.value = typeof next === 'function'
114
+ ? next(focusManagerState.value)
115
+ : next
116
+ }
117
+
118
+ watch(
119
+ [portalNode, () => focusManagerState.value?.modal],
120
+ ([node, modal], _previous, onCleanup) => {
121
+ if (!node || modal) {
122
+ return
123
+ }
124
+
125
+ function onFocus(event: FocusEvent) {
126
+ if (!node || !event.relatedTarget || !isOutsideEvent(event)) {
127
+ return
128
+ }
129
+
130
+ if (event.type === 'focusin') {
131
+ if (focusInsideDisabled.value) {
132
+ enableFocusInside(node)
133
+ focusInsideDisabled.value = false
134
+ }
135
+ }
136
+ else {
137
+ disableFocusInside(node)
138
+ focusInsideDisabled.value = true
139
+ }
140
+ }
141
+
142
+ onCleanup(mergeCleanups(
143
+ addEventListener(node, 'focusin', onFocus, true),
144
+ addEventListener(node, 'focusout', onFocus, true),
145
+ ))
146
+ },
147
+ { immediate: true },
148
+ )
149
+
150
+ watch(
151
+ [portalNode, () => focusManagerState.value?.open],
152
+ ([node, open]) => {
153
+ if (!node || open !== true || !focusInsideDisabled.value) {
154
+ return
155
+ }
156
+
157
+ enableFocusInside(node)
158
+ focusInsideDisabled.value = false
159
+ },
160
+ { immediate: true, flush: 'post' },
161
+ )
162
+
163
+ provide(floatingPortalContextKey, {
164
+ beforeOutsideRef,
165
+ afterOutsideRef,
166
+ beforeInsideRef,
167
+ afterInsideRef,
168
+ portalNode,
169
+ setFocusManagerState,
170
+ })
171
+
172
+ function handleBeforeOutsideFocus(event: FocusEvent) {
173
+ if (portalNode.value && isOutsideEvent(event, portalNode.value)) {
174
+ beforeInsideRef.value?.focus()
175
+ return
176
+ }
177
+
178
+ const domReference = focusManagerState.value?.domReference ?? null
179
+ getPreviousTabbable(domReference)?.focus()
180
+ }
181
+
182
+ function handleAfterOutsideFocus(event: FocusEvent) {
183
+ if (portalNode.value && isOutsideEvent(event, portalNode.value)) {
184
+ afterInsideRef.value?.focus()
185
+ return
186
+ }
187
+
188
+ const manager = focusManagerState.value
189
+ const domReference = manager?.domReference ?? null
190
+
191
+ getNextTabbable(domReference)?.focus()
192
+
193
+ if (manager?.closeOnFocusOut) {
194
+ manager.onOpenChange(
195
+ false,
196
+ createChangeEventDetails(REASONS.focusOut, event),
197
+ )
198
+ }
199
+ }
200
+
201
+ function setBeforeOutsideRef(value: FocusGuardTemplateRef) {
202
+ beforeOutsideRef.value = resolveFocusGuardElement(value)
203
+ }
204
+
205
+ function setAfterOutsideRef(value: FocusGuardTemplateRef) {
206
+ afterOutsideRef.value = resolveFocusGuardElement(value)
207
+ }
208
+
209
+ function resolveFocusGuardElement(value: FocusGuardTemplateRef) {
210
+ if (!value) {
211
+ return null
212
+ }
213
+
214
+ if (value instanceof HTMLSpanElement) {
215
+ return value
216
+ }
217
+
218
+ if ('element' in value) {
219
+ const element = unref(value.element)
220
+ return element instanceof HTMLSpanElement ? element : null
221
+ }
222
+
223
+ if ('$el' in value && value.$el instanceof HTMLSpanElement) {
224
+ return value.$el
225
+ }
226
+
227
+ return null
228
+ }
229
+
230
+ defineExpose({
231
+ element: portalNode,
232
+ })
233
+ </script>
234
+
235
+ <template>
236
+ <FocusGuard
237
+ v-if="shouldRenderGuards && portalNode"
238
+ :ref="setBeforeOutsideRef"
239
+ data-type="outside"
240
+ @focus="handleBeforeOutsideFocus"
241
+ />
242
+ <span
243
+ v-if="shouldRenderGuards && portalNode"
244
+ :aria-owns="portalNode.id"
245
+ :style="ownerVisuallyHidden"
246
+ />
247
+ <Teleport v-if="shouldRender" :to="portalTarget" :disabled="disabled">
248
+ <slot v-if="renderless" :ref="renderRef" :props="mergedProps" :state="state" />
249
+ <component :is="tag" v-else :ref="renderRef" v-bind="mergedProps">
250
+ <slot :state="state" />
251
+ </component>
252
+ </Teleport>
253
+ <FocusGuard
254
+ v-if="shouldRenderGuards && portalNode"
255
+ :ref="setAfterOutsideRef"
256
+ data-type="outside"
257
+ @focus="handleAfterOutsideFocus"
258
+ />
259
+ </template>
@@ -0,0 +1,83 @@
1
+ import type { ComputedRef, MaybeRefOrGetter } from 'vue'
2
+ import type { BaseUIChangeEventDetails } from '../../utils/createBaseUIEventDetails'
3
+ import type { TransitionStatus } from '../../utils/useTransitionStatus'
4
+ import type {
5
+ ContextData,
6
+ FloatingRootContext,
7
+ FloatingRootState,
8
+ FloatingTriggerMap,
9
+ ReferenceType,
10
+ } from '../types'
11
+ import { computed, shallowRef, toValue } from 'vue'
12
+ import { createEventEmitter } from '../utils/createEventEmitter'
13
+ import { isClickLikeEvent } from '../utils/event'
14
+
15
+ export interface CreateFloatingRootContextOptions {
16
+ open: MaybeRefOrGetter<boolean>
17
+ transitionStatus: MaybeRefOrGetter<TransitionStatus>
18
+ domReferenceElement: MaybeRefOrGetter<Element | null>
19
+ referenceElement: MaybeRefOrGetter<ReferenceType | null>
20
+ floatingElement: MaybeRefOrGetter<HTMLElement | null>
21
+ floatingId: MaybeRefOrGetter<string | undefined>
22
+ triggerElements: FloatingTriggerMap
23
+ nested?: boolean
24
+ onOpenChange?: (open: boolean, eventDetails: BaseUIChangeEventDetails<string, any>) => void
25
+ clearCloseTimer?: () => void
26
+ }
27
+
28
+ export function createFloatingRootContext(
29
+ options: CreateFloatingRootContextOptions,
30
+ ): FloatingRootContext {
31
+ const state: { [K in keyof FloatingRootState]: ComputedRef<FloatingRootState[K]> } = {
32
+ open: computed(() => toValue(options.open)),
33
+ transitionStatus: computed(() => toValue(options.transitionStatus)),
34
+ domReferenceElement: computed(() => toValue(options.domReferenceElement)),
35
+ referenceElement: computed(() => toValue(options.referenceElement)),
36
+ floatingElement: computed(() => toValue(options.floatingElement)),
37
+ floatingId: computed(() => toValue(options.floatingId)),
38
+ }
39
+
40
+ const context = {
41
+ dataRef: shallowRef<ContextData>({}),
42
+ events: createEventEmitter(),
43
+ nested: options.nested ?? false,
44
+ triggerElements: options.triggerElements,
45
+ clearCloseTimer: options.clearCloseTimer,
46
+ }
47
+
48
+ const rootContext: FloatingRootContext = {
49
+ context,
50
+ useState(key) {
51
+ return state[key]
52
+ },
53
+ select<K extends keyof FloatingRootState>(key: K) {
54
+ return state[key].value
55
+ },
56
+ setOpen(open, eventDetails) {
57
+ options.onOpenChange?.(open, eventDetails)
58
+ },
59
+ dispatchOpenChange(open, eventDetails) {
60
+ syncOpenEvent(open, eventDetails.event)
61
+
62
+ context.events.emit('openchange', {
63
+ open,
64
+ reason: eventDetails.reason,
65
+ nativeEvent: eventDetails.event,
66
+ nested: context.nested,
67
+ triggerElement: eventDetails.trigger,
68
+ })
69
+ },
70
+ }
71
+
72
+ function syncOpenEvent(open: boolean, event: Event | undefined) {
73
+ if (
74
+ !open
75
+ || !state.open.value
76
+ || (event != null && isClickLikeEvent(event))
77
+ ) {
78
+ context.dataRef.value.openEvent = open ? event : undefined
79
+ }
80
+ }
81
+
82
+ return rootContext
83
+ }
@@ -0,0 +1,64 @@
1
+ import type { InjectionKey } from 'vue'
2
+ import type { FloatingNodeType, FloatingTreeType } from '../types'
3
+ import { inject, onScopeDispose, provide } from 'vue'
4
+ import { useBaseUiId } from '../../utils/useBaseUiId'
5
+ import { FloatingTreeStore } from './FloatingTreeStore'
6
+
7
+ const floatingNodeContextKey: InjectionKey<FloatingNodeType | null>
8
+ = Symbol('FloatingNodeContext')
9
+ const floatingTreeContextKey: InjectionKey<FloatingTreeType | null>
10
+ = Symbol('FloatingTreeContext')
11
+
12
+ /**
13
+ * Returns the parent node id for nested floating elements, if available.
14
+ */
15
+ export function useFloatingParentNodeId() {
16
+ return inject(floatingNodeContextKey, null)?.id ?? null
17
+ }
18
+
19
+ /**
20
+ * Returns the nearest floating tree context, if available.
21
+ */
22
+ export function useFloatingTree(externalTree?: FloatingTreeStore) {
23
+ const contextTree = inject(floatingTreeContextKey, null)
24
+ return externalTree ?? contextTree
25
+ }
26
+
27
+ /**
28
+ * Registers a node into the nearest floating tree, returning its id.
29
+ */
30
+ export function useFloatingNodeId(externalTree?: FloatingTreeStore) {
31
+ const id = useBaseUiId()
32
+ const tree = useFloatingTree(externalTree)
33
+ const parentId = useFloatingParentNodeId()
34
+
35
+ if (id) {
36
+ const node = { id, parentId }
37
+ tree?.addNode(node)
38
+ onScopeDispose(() => {
39
+ tree?.removeNode(node)
40
+ })
41
+ }
42
+
43
+ return id
44
+ }
45
+
46
+ /**
47
+ * Provides the current floating node id and parent id to nested floating elements.
48
+ */
49
+ export function provideFloatingNode(id: string | undefined) {
50
+ const parentId = useFloatingParentNodeId()
51
+ provide(floatingNodeContextKey, {
52
+ id,
53
+ parentId,
54
+ })
55
+ }
56
+
57
+ /**
58
+ * Provides a floating tree store to descendants, creating one when needed.
59
+ */
60
+ export function provideFloatingTree(externalTree?: FloatingTreeStore) {
61
+ const tree = externalTree ?? new FloatingTreeStore()
62
+ provide(floatingTreeContextKey, tree)
63
+ return tree
64
+ }
@@ -0,0 +1,21 @@
1
+ import type { Ref } from 'vue'
2
+ import type { FloatingEvents, FloatingNodeType } from '../types'
3
+ import { shallowRef } from 'vue'
4
+ import { createEventEmitter } from '../utils/createEventEmitter'
5
+
6
+ /**
7
+ * Stores and manages floating elements in a tree structure.
8
+ */
9
+ export class FloatingTreeStore {
10
+ readonly nodesRef: Ref<FloatingNodeType[]> = shallowRef([])
11
+
12
+ readonly events: FloatingEvents = createEventEmitter()
13
+
14
+ addNode(node: FloatingNodeType) {
15
+ this.nodesRef.value = [...this.nodesRef.value, node]
16
+ }
17
+
18
+ removeNode(node: FloatingNodeType) {
19
+ this.nodesRef.value = this.nodesRef.value.filter(item => item !== node)
20
+ }
21
+ }