base-ui-vue 0.3.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 (218) 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 +19 -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 +3 -3
  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 +868 -0
  48. package/dist/decrement/NumberFieldDecrement.cjs.map +1 -0
  49. package/dist/decrement/NumberFieldDecrement.js +701 -0
  50. package/dist/decrement/NumberFieldDecrement.js.map +1 -0
  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 +4 -48
  58. package/dist/fallback/AvatarFallback.cjs.map +1 -1
  59. package/dist/fallback/AvatarFallback.js +4 -42
  60. package/dist/fallback/AvatarFallback.js.map +1 -1
  61. package/dist/floating-ui-vue/index.js +638 -0
  62. package/dist/floating-ui-vue/index.js.map +1 -0
  63. package/dist/form/Form.cjs +3 -3
  64. package/dist/form/Form.cjs.map +1 -1
  65. package/dist/form/Form.js +1 -1
  66. package/dist/group/NumberFieldGroup.cjs +72 -0
  67. package/dist/group/NumberFieldGroup.cjs.map +1 -0
  68. package/dist/group/NumberFieldGroup.js +67 -0
  69. package/dist/group/NumberFieldGroup.js.map +1 -0
  70. package/dist/group/ToolbarGroup.cjs +2 -2
  71. package/dist/group/ToolbarGroup.cjs.map +1 -1
  72. package/dist/group/ToolbarGroup.js +1 -1
  73. package/dist/header/AccordionHeader.cjs +2 -2
  74. package/dist/header/AccordionHeader.cjs.map +1 -1
  75. package/dist/header/AccordionHeader.js +1 -1
  76. package/dist/hooks/useDismiss.cjs +502 -0
  77. package/dist/hooks/useDismiss.cjs.map +1 -0
  78. package/dist/hooks/useHoverFloatingInteraction.cjs +233 -0
  79. package/dist/hooks/useHoverFloatingInteraction.cjs.map +1 -0
  80. package/dist/image/AvatarImage.cjs +4 -4
  81. package/dist/image/AvatarImage.cjs.map +1 -1
  82. package/dist/image/AvatarImage.js +1 -1
  83. package/dist/increment/NumberFieldIncrement.cjs +113 -0
  84. package/dist/increment/NumberFieldIncrement.cjs.map +1 -0
  85. package/dist/increment/NumberFieldIncrement.js +108 -0
  86. package/dist/increment/NumberFieldIncrement.js.map +1 -0
  87. package/dist/index.cjs +80 -8
  88. package/dist/index.d.cts +4328 -1600
  89. package/dist/index.d.cts.map +1 -1
  90. package/dist/index.d.ts +4328 -1600
  91. package/dist/index.d.ts.map +1 -1
  92. package/dist/index.js +8 -4
  93. package/dist/index2.cjs +9435 -3236
  94. package/dist/index2.cjs.map +1 -1
  95. package/dist/index2.js +8905 -3049
  96. package/dist/index2.js.map +1 -1
  97. package/dist/rolldown-runtime.js +36 -0
  98. package/package.json +1 -1
  99. package/src/floating-ui-vue/components/FloatingPortal.ts +171 -0
  100. package/src/floating-ui-vue/components/FloatingPortal.vue +259 -0
  101. package/src/floating-ui-vue/components/FloatingRootStore.ts +83 -0
  102. package/src/floating-ui-vue/components/FloatingTree.ts +64 -0
  103. package/src/floating-ui-vue/components/FloatingTreeStore.ts +21 -0
  104. package/src/floating-ui-vue/hooks/useDismiss.ts +689 -0
  105. package/src/floating-ui-vue/hooks/useHoverFloatingInteraction.ts +244 -0
  106. package/src/floating-ui-vue/hooks/useHoverInteractionSharedState.ts +125 -0
  107. package/src/floating-ui-vue/hooks/useHoverShared.ts +53 -0
  108. package/src/floating-ui-vue/index.ts +48 -0
  109. package/src/floating-ui-vue/middleware/arrow.ts +114 -0
  110. package/src/floating-ui-vue/types.ts +117 -0
  111. package/src/floating-ui-vue/utils/composite.ts +20 -0
  112. package/src/floating-ui-vue/utils/constants.ts +2 -0
  113. package/src/floating-ui-vue/utils/createAttribute.ts +3 -0
  114. package/src/floating-ui-vue/utils/createEventEmitter.ts +20 -0
  115. package/src/floating-ui-vue/utils/element.ts +59 -0
  116. package/src/floating-ui-vue/utils/event.ts +15 -0
  117. package/src/floating-ui-vue/utils/nodes.ts +14 -0
  118. package/src/floating-ui-vue/utils/tabbable.ts +309 -0
  119. package/src/index.ts +7 -1
  120. package/src/number-field/decrement/NumberFieldDecrement.vue +109 -0
  121. package/src/number-field/group/NumberFieldGroup.vue +47 -0
  122. package/src/number-field/increment/NumberFieldIncrement.vue +109 -0
  123. package/src/number-field/index.ts +42 -0
  124. package/src/number-field/input/NumberFieldInput.vue +455 -0
  125. package/src/number-field/root/NumberFieldRoot.vue +626 -0
  126. package/src/number-field/root/NumberFieldRootContext.ts +94 -0
  127. package/src/number-field/root/useNumberFieldButton.ts +171 -0
  128. package/src/number-field/scrub-area/NumberFieldScrubArea.vue +359 -0
  129. package/src/number-field/scrub-area/NumberFieldScrubAreaContext.ts +26 -0
  130. package/src/number-field/scrub-area-cursor/NumberFieldScrubAreaCursor.vue +75 -0
  131. package/src/number-field/utils/constants.ts +4 -0
  132. package/src/number-field/utils/getViewportRect.ts +34 -0
  133. package/src/number-field/utils/parse.ts +248 -0
  134. package/src/number-field/utils/stateAttributesMapping.ts +9 -0
  135. package/src/number-field/utils/subscribeToVisualViewportResize.ts +27 -0
  136. package/src/number-field/utils/types.ts +24 -0
  137. package/src/number-field/utils/validate.ts +120 -0
  138. package/src/otp-field/index.ts +22 -0
  139. package/src/otp-field/input/OtpFieldInput.vue +336 -0
  140. package/src/otp-field/root/OtpFieldRoot.vue +583 -0
  141. package/src/otp-field/root/OtpFieldRootContext.ts +81 -0
  142. package/src/otp-field/utils/otp.ts +135 -0
  143. package/src/otp-field/utils/stateAttributesMapping.ts +16 -0
  144. package/src/radio/index.ts +28 -0
  145. package/src/radio/indicator/RadioIndicator.vue +90 -0
  146. package/src/radio/indicator/RadioIndicatorDataAttributes.ts +54 -0
  147. package/src/radio/root/RadioRoot.vue +544 -0
  148. package/src/radio/root/RadioRootContext.ts +18 -0
  149. package/src/radio/root/RadioRootDataAttributes.ts +46 -0
  150. package/src/radio/utils/stateAttributesMapping.ts +29 -0
  151. package/src/radio-group/RadioGroup.vue +381 -0
  152. package/src/radio-group/RadioGroupContext.ts +42 -0
  153. package/src/radio-group/RadioGroupDataAttributes.ts +6 -0
  154. package/src/radio-group/index.ts +10 -0
  155. package/src/tabs/index.ts +38 -0
  156. package/src/tabs/indicator/TabsIndicator.vue +201 -0
  157. package/src/tabs/indicator/TabsIndicatorCssVars.ts +26 -0
  158. package/src/tabs/indicator/TabsIndicatorDataAttributes.ts +12 -0
  159. package/src/tabs/indicator/prehydrationScript.min.ts +5 -0
  160. package/src/tabs/indicator/prehydrationScript.template.js +87 -0
  161. package/src/tabs/list/TabsList.vue +201 -0
  162. package/src/tabs/list/TabsListContext.ts +29 -0
  163. package/src/tabs/list/TabsListDataAttributes.ts +12 -0
  164. package/src/tabs/panel/TabsPanel.vue +167 -0
  165. package/src/tabs/panel/TabsPanelDataAttributes.ts +28 -0
  166. package/src/tabs/root/TabsRoot.vue +468 -0
  167. package/src/tabs/root/TabsRootContext.ts +56 -0
  168. package/src/tabs/root/TabsRootDataAttributes.ts +12 -0
  169. package/src/tabs/root/stateAttributesMapping.ts +9 -0
  170. package/src/tabs/tab/TabsTab.vue +274 -0
  171. package/src/tabs/tab/TabsTabDataAttributes.ts +20 -0
  172. package/src/tabs/utils/areTabValuesEqual.ts +6 -0
  173. package/src/tooltip/arrow/TooltipArrow.vue +116 -0
  174. package/src/tooltip/arrow/TooltipArrowDataAttributes.ts +31 -0
  175. package/src/tooltip/index.ts +54 -0
  176. package/src/tooltip/popup/TooltipPopup.vue +142 -0
  177. package/src/tooltip/popup/TooltipPopupDataAttributes.ts +35 -0
  178. package/src/tooltip/portal/TooltipPortal.vue +83 -0
  179. package/src/tooltip/portal/TooltipPortalContext.ts +19 -0
  180. package/src/tooltip/positioner/TooltipPositioner.vue +221 -0
  181. package/src/tooltip/positioner/TooltipPositionerContext.ts +23 -0
  182. package/src/tooltip/positioner/TooltipPositionerCssVars.ts +27 -0
  183. package/src/tooltip/positioner/TooltipPositionerDataAttributes.ts +26 -0
  184. package/src/tooltip/provider/TooltipProvider.vue +78 -0
  185. package/src/tooltip/provider/TooltipProviderContext.ts +19 -0
  186. package/src/tooltip/root/TooltipRoot.vue +401 -0
  187. package/src/tooltip/root/TooltipRootContext.ts +71 -0
  188. package/src/tooltip/store/TooltipHandle.ts +215 -0
  189. package/src/tooltip/trigger/TooltipTrigger.vue +343 -0
  190. package/src/tooltip/trigger/TooltipTriggerDataAttributes.ts +12 -0
  191. package/src/tooltip/utils/constants.ts +2 -0
  192. package/src/tooltip/viewport/TooltipViewport.vue +149 -0
  193. package/src/tooltip/viewport/TooltipViewportCssVars.ts +14 -0
  194. package/src/tooltip/viewport/TooltipViewportDataAttributes.ts +26 -0
  195. package/src/utils/FloatingPortalLite.vue +66 -0
  196. package/src/utils/FocusGuard.vue +36 -0
  197. package/src/utils/adaptiveOriginMiddleware.ts +79 -0
  198. package/src/utils/addEventListener.ts +16 -0
  199. package/src/utils/constants.ts +11 -0
  200. package/src/utils/createBaseUIEventDetails.ts +2 -0
  201. package/src/utils/detectBrowser.ts +15 -0
  202. package/src/utils/formatNumber.ts +35 -2
  203. package/src/utils/getCssDimensions.ts +19 -0
  204. package/src/utils/getDisabledMountTransitionStyles.ts +9 -0
  205. package/src/utils/hideMiddleware.ts +19 -0
  206. package/src/utils/mergeCleanups.ts +7 -0
  207. package/src/utils/platform.ts +18 -0
  208. package/src/utils/popupStateMapping.ts +103 -0
  209. package/src/utils/reasons.ts +4 -0
  210. package/src/utils/serializeValue.ts +17 -0
  211. package/src/utils/useAnchorPositioning.ts +644 -0
  212. package/src/utils/useInterval.ts +45 -0
  213. package/src/utils/usePopupAutoResize.ts +254 -0
  214. package/src/utils/usePopupViewport.ts +398 -0
  215. package/src/utils/usePositioner.ts +61 -0
  216. package/src/utils/usePressAndHold.ts +260 -0
  217. package/src/utils/useRenderElement.ts +3 -0
  218. package/src/utils/useValueChanged.ts +21 -0
@@ -0,0 +1,401 @@
1
+ <script setup lang="ts" generic="Payload = unknown">
2
+ import type { BaseUIChangeEventDetails } from '../../utils/createBaseUIEventDetails'
3
+ import type { REASONS as TooltipReasons } from '../../utils/reasons'
4
+ import type { BaseUIComponentProps } from '../../utils/types'
5
+ import type { TooltipHandle } from '../store/TooltipHandle'
6
+ import type { TooltipRootContext } from './TooltipRootContext'
7
+ import { computed, getCurrentInstance, onScopeDispose, provide, shallowRef, toRef, watch } from 'vue'
8
+ import { useDismiss } from '../../floating-ui-vue'
9
+ import { createFloatingRootContext } from '../../floating-ui-vue/components/FloatingRootStore'
10
+ import { REASONS } from '../../utils/reasons'
11
+ import { useControllableState } from '../../utils/useControllableState'
12
+ import { useTimeout } from '../../utils/useTimeout'
13
+ import { useTransitionStatus } from '../../utils/useTransitionStatus'
14
+ import { useTooltipProviderContext } from '../provider/TooltipProviderContext'
15
+ import { createTooltipChangeEventDetails, TooltipStore } from '../store/TooltipHandle'
16
+ import { OPEN_DELAY } from '../utils/constants'
17
+ import { tooltipRootContextKey } from './TooltipRootContext'
18
+
19
+ /**
20
+ * Groups all parts of the tooltip.
21
+ * Doesn't render its own HTML element.
22
+ *
23
+ * Documentation: [Base UI Vue Tooltip](https://baseui-vue.com/docs/components/tooltip)
24
+ */
25
+ defineOptions({
26
+ name: 'TooltipRoot',
27
+ })
28
+
29
+ const props = withDefaults(defineProps<TooltipRootProps<Payload>>(), {
30
+ defaultOpen: false,
31
+ disableHoverablePopup: false,
32
+ trackCursorAxis: 'none',
33
+ disabled: false,
34
+ defaultTriggerId: null,
35
+ })
36
+
37
+ const emit = defineEmits<{
38
+ (e: 'openChange', open: boolean, details: TooltipRootChangeEventDetails): void
39
+ (e: 'openChangeComplete', open: boolean): void
40
+ }>()
41
+
42
+ const instance = getCurrentInstance()
43
+
44
+ const isOpenControlled = computed(() => {
45
+ const vnodeProps = instance?.vnode.props as Record<string, unknown> | null | undefined
46
+ return Boolean(vnodeProps && 'open' in vnodeProps)
47
+ })
48
+
49
+ const isTriggerIdControlled = computed(() => {
50
+ const vnodeProps = instance?.vnode.props as Record<string, unknown> | null | undefined
51
+ return Boolean(vnodeProps && ('triggerId' in vnodeProps || 'trigger-id' in vnodeProps))
52
+ })
53
+
54
+ const localStore = new TooltipStore<Payload>()
55
+ const store = props.handle?.store ?? localStore
56
+
57
+ const provider = useTooltipProviderContext()
58
+ const closeTimer = useTimeout()
59
+
60
+ const uncontrolledTriggerId = shallowRef<string | null>(props.defaultTriggerId ?? null)
61
+ const activeTriggerId = computed({
62
+ get() {
63
+ return isTriggerIdControlled.value ? props.triggerId ?? null : uncontrolledTriggerId.value
64
+ },
65
+ set(next: string | null) {
66
+ if (!isTriggerIdControlled.value) {
67
+ uncontrolledTriggerId.value = next
68
+ }
69
+ },
70
+ })
71
+
72
+ const { value: openState, setValue: setOpenState } = useControllableState<boolean>({
73
+ controlled: () => (isOpenControlled.value ? props.open : undefined),
74
+ default: () => props.defaultOpen ?? false,
75
+ name: 'TooltipRoot',
76
+ state: 'open',
77
+ })
78
+
79
+ const disabled = toRef(props, 'disabled')
80
+ const disableHoverablePopup = toRef(props, 'disableHoverablePopup')
81
+ const trackCursorAxis = toRef(props, 'trackCursorAxis')
82
+ const open = computed(() => !disabled.value && openState.value)
83
+ const hasViewport = shallowRef(false)
84
+
85
+ const { mounted, setMounted, transitionStatus } = useTransitionStatus(open, true, true)
86
+
87
+ const instantType = shallowRef<TooltipInstantType>(undefined)
88
+ const preventUnmountOnClose = shallowRef(false)
89
+
90
+ const popupId = shallowRef<string | undefined>(undefined)
91
+ const popupRef = shallowRef<HTMLElement | null>(null)
92
+ const positionerRef = shallowRef<HTMLElement | null>(null)
93
+ const popupWidth = shallowRef<number | undefined>(undefined)
94
+ const popupHeight = shallowRef<number | undefined>(undefined)
95
+
96
+ const activeTrigger = computed(() =>
97
+ store.getTrigger(activeTriggerId.value) as ReturnType<typeof store.getTrigger>,
98
+ )
99
+ const payload = computed(() => activeTrigger.value?.payload as Payload | undefined)
100
+
101
+ function getOpenDelay(triggerDelay: number | undefined) {
102
+ return provider?.getOpenDelay(triggerDelay) ?? {
103
+ delay: triggerDelay ?? OPEN_DELAY,
104
+ instant: false,
105
+ }
106
+ }
107
+
108
+ function getCloseDelay(triggerCloseDelay: number | undefined) {
109
+ return provider?.getCloseDelay(triggerCloseDelay) ?? triggerCloseDelay ?? 0
110
+ }
111
+
112
+ function requestOpenChange(
113
+ nextOpen: boolean,
114
+ details: TooltipRootChangeEventDetails,
115
+ triggerId?: string | null,
116
+ ) {
117
+ if (nextOpen && disabled.value) {
118
+ return
119
+ }
120
+
121
+ const nextTriggerId = triggerId === undefined ? activeTriggerId.value : triggerId
122
+ const triggerChanged = nextTriggerId !== activeTriggerId.value
123
+
124
+ if (nextOpen === openState.value && !triggerChanged) {
125
+ return
126
+ }
127
+
128
+ preventUnmountOnClose.value = false
129
+ details.preventUnmountOnClose = () => {
130
+ preventUnmountOnClose.value = true
131
+ }
132
+
133
+ emit('openChange', nextOpen, details)
134
+
135
+ if (details.isCanceled) {
136
+ return
137
+ }
138
+
139
+ floatingRootContext.dispatchOpenChange(nextOpen, details)
140
+
141
+ if (triggerId !== undefined) {
142
+ activeTriggerId.value = triggerId
143
+ }
144
+
145
+ if (nextOpen) {
146
+ provider?.notifyOpen()
147
+
148
+ if (!mounted.value) {
149
+ setMounted(true)
150
+ }
151
+
152
+ if (details.reason === REASONS.triggerFocus) {
153
+ instantType.value = 'focus'
154
+ }
155
+ else if (details.reason !== REASONS.triggerHover) {
156
+ instantType.value = undefined
157
+ }
158
+ }
159
+ else {
160
+ provider?.notifyClose()
161
+
162
+ if (details.reason === REASONS.triggerPress || details.reason === REASONS.escapeKey) {
163
+ instantType.value = 'dismiss'
164
+ }
165
+ else {
166
+ instantType.value = undefined
167
+ }
168
+ }
169
+
170
+ setOpenState(nextOpen)
171
+ }
172
+
173
+ function scheduleClose(
174
+ delay: number,
175
+ details: TooltipRootChangeEventDetails,
176
+ triggerId?: string | null,
177
+ ) {
178
+ closeTimer.clear()
179
+
180
+ if (delay <= 0) {
181
+ requestOpenChange(false, details, triggerId)
182
+ return
183
+ }
184
+
185
+ closeTimer.start(delay, () => {
186
+ requestOpenChange(false, details, triggerId)
187
+ })
188
+ }
189
+
190
+ function forceUnmount() {
191
+ closeTimer.clear()
192
+ setMounted(false)
193
+ preventUnmountOnClose.value = false
194
+ }
195
+
196
+ function completeOpenChange() {
197
+ emit('openChangeComplete', open.value)
198
+
199
+ if (!open.value && !preventUnmountOnClose.value) {
200
+ setMounted(false)
201
+ }
202
+
203
+ if (!open.value) {
204
+ preventUnmountOnClose.value = false
205
+ }
206
+ }
207
+
208
+ function close() {
209
+ requestOpenChange(
210
+ false,
211
+ createTooltipChangeEventDetails(REASONS.imperativeAction),
212
+ )
213
+ }
214
+
215
+ function maybeActivateTrigger(id: string) {
216
+ if (open.value && activeTriggerId.value == null) {
217
+ activeTriggerId.value = id
218
+ }
219
+ }
220
+
221
+ watch(disabled, (isDisabled) => {
222
+ if (isDisabled && openState.value) {
223
+ requestOpenChange(
224
+ false,
225
+ createTooltipChangeEventDetails(REASONS.disabled),
226
+ )
227
+ }
228
+ })
229
+
230
+ watch(open, (isOpen) => {
231
+ if (isOpen && mounted.value === false) {
232
+ setMounted(true)
233
+ }
234
+ })
235
+
236
+ const floatingRootContext = createFloatingRootContext({
237
+ open,
238
+ transitionStatus,
239
+ domReferenceElement: () => activeTrigger.value?.element ?? null,
240
+ referenceElement: () => activeTrigger.value?.element ?? null,
241
+ floatingElement: () => popupRef.value,
242
+ floatingId: () => popupId.value,
243
+ triggerElements: store.triggerElements,
244
+ clearCloseTimer: closeTimer.clear,
245
+ onOpenChange(nextOpen, details) {
246
+ requestOpenChange(
247
+ nextOpen,
248
+ details as TooltipRootChangeEventDetails,
249
+ )
250
+ },
251
+ })
252
+
253
+ const dismiss = useDismiss(floatingRootContext, {
254
+ enabled: () => !disabled.value,
255
+ referencePress: () => activeTrigger.value?.closeOnClick ?? true,
256
+ })
257
+
258
+ store.controller.value = {
259
+ open,
260
+ dismiss,
261
+ activeTriggerId,
262
+ disabled,
263
+ disableHoverablePopup,
264
+ trackCursorAxis,
265
+ instantType,
266
+ popupId,
267
+ requestOpenChange,
268
+ getOpenDelay,
269
+ getCloseDelay,
270
+ scheduleClose,
271
+ clearCloseTimer: closeTimer.clear,
272
+ forceUnmount,
273
+ maybeActivateTrigger,
274
+ }
275
+
276
+ onScopeDispose(() => {
277
+ if (store.controller.value?.requestOpenChange === requestOpenChange) {
278
+ store.controller.value = null
279
+ }
280
+ })
281
+
282
+ const context: TooltipRootContext<Payload> = {
283
+ store,
284
+ floatingRootContext,
285
+ dismiss,
286
+ open,
287
+ mounted,
288
+ transitionStatus,
289
+ disabled,
290
+ disableHoverablePopup,
291
+ trackCursorAxis,
292
+ hasViewport,
293
+ instantType,
294
+ activeTriggerId,
295
+ activeTrigger,
296
+ payload,
297
+ popupId,
298
+ popupRef,
299
+ positionerRef,
300
+ popupWidth,
301
+ popupHeight,
302
+ requestOpenChange,
303
+ getOpenDelay,
304
+ getCloseDelay,
305
+ scheduleClose,
306
+ clearCloseTimer: closeTimer.clear,
307
+ completeOpenChange,
308
+ setMounted,
309
+ setPopupId(id: string | undefined) {
310
+ popupId.value = id
311
+ },
312
+ setHasViewport(next: boolean) {
313
+ hasViewport.value = next
314
+ },
315
+ }
316
+
317
+ provide(tooltipRootContextKey, context)
318
+
319
+ defineExpose<TooltipRootActions>({
320
+ close,
321
+ unmount: forceUnmount,
322
+ })
323
+ </script>
324
+
325
+ <script lang="ts">
326
+ export type TooltipInstantType = 'delay' | 'dismiss' | 'focus' | undefined
327
+ export type TooltipTrackCursorAxis = 'none' | 'x' | 'y' | 'both'
328
+
329
+ export interface TooltipRootState {}
330
+
331
+ export interface TooltipRootProps<Payload = unknown>
332
+ extends BaseUIComponentProps<TooltipRootState> {
333
+ /**
334
+ * Whether the tooltip is initially open.
335
+ *
336
+ * To render a controlled tooltip, use the `open` prop instead.
337
+ * @default false
338
+ */
339
+ defaultOpen?: boolean
340
+ /**
341
+ * Whether the tooltip is currently open.
342
+ */
343
+ open?: boolean
344
+ /**
345
+ * Whether the tooltip contents can be hovered without closing the tooltip.
346
+ * @default false
347
+ */
348
+ disableHoverablePopup?: boolean
349
+ /**
350
+ * Determines which axis the tooltip should track the cursor on.
351
+ * @default 'none'
352
+ */
353
+ trackCursorAxis?: TooltipTrackCursorAxis
354
+ /**
355
+ * Whether the tooltip is disabled.
356
+ * @default false
357
+ */
358
+ disabled?: boolean
359
+ /**
360
+ * A handle to associate the tooltip with a trigger.
361
+ * If specified, allows external triggers to control the tooltip's open state.
362
+ * Can be created with the `createTooltipHandle()` function.
363
+ */
364
+ handle?: TooltipHandle<Payload>
365
+ /**
366
+ * ID of the trigger that the tooltip is associated with.
367
+ * This is useful in conjunction with the `open` prop to create a controlled tooltip.
368
+ * There's no need to specify this prop when the tooltip is uncontrolled (that is, when the `open` prop is not set).
369
+ */
370
+ triggerId?: string | null
371
+ /**
372
+ * ID of the trigger that the tooltip is associated with.
373
+ * This is useful in conjunction with the `defaultOpen` prop to create an initially open tooltip.
374
+ */
375
+ defaultTriggerId?: string | null
376
+ }
377
+
378
+ export interface TooltipRootActions {
379
+ unmount: () => void
380
+ close: () => void
381
+ }
382
+
383
+ export type TooltipRootChangeEventReason
384
+ = | typeof TooltipReasons.triggerHover
385
+ | typeof TooltipReasons.triggerFocus
386
+ | typeof TooltipReasons.triggerPress
387
+ | typeof TooltipReasons.outsidePress
388
+ | typeof TooltipReasons.escapeKey
389
+ | typeof TooltipReasons.disabled
390
+ | typeof TooltipReasons.imperativeAction
391
+ | typeof TooltipReasons.none
392
+
393
+ export type TooltipRootChangeEventDetails
394
+ = BaseUIChangeEventDetails<TooltipRootChangeEventReason, {
395
+ preventUnmountOnClose: () => void
396
+ }>
397
+ </script>
398
+
399
+ <template>
400
+ <slot :payload="payload" :state="{ open }" />
401
+ </template>
@@ -0,0 +1,71 @@
1
+ import type {
2
+ ComputedRef,
3
+ InjectionKey,
4
+ Ref,
5
+ ShallowRef,
6
+ } from 'vue'
7
+ import type { ElementProps, FloatingRootContext } from '../../floating-ui-vue/types'
8
+ import type { TransitionStatus } from '../../utils/useTransitionStatus'
9
+ import type { TooltipStore, TooltipTriggerRecord } from '../store/TooltipHandle'
10
+ import type {
11
+ TooltipInstantType,
12
+ TooltipRootChangeEventDetails,
13
+ TooltipTrackCursorAxis,
14
+ } from './TooltipRoot.vue'
15
+ import { inject } from 'vue'
16
+
17
+ export interface TooltipRootContext<Payload = unknown> {
18
+ store: TooltipStore<Payload>
19
+ floatingRootContext: FloatingRootContext
20
+ dismiss: ElementProps
21
+ open: Readonly<Ref<boolean>>
22
+ mounted: Ref<boolean>
23
+ transitionStatus: Ref<TransitionStatus>
24
+ disabled: Readonly<Ref<boolean>>
25
+ disableHoverablePopup: Readonly<Ref<boolean>>
26
+ trackCursorAxis: Readonly<Ref<TooltipTrackCursorAxis>>
27
+ hasViewport: Ref<boolean>
28
+ instantType: Ref<TooltipInstantType>
29
+ activeTriggerId: Ref<string | null>
30
+ activeTrigger: ComputedRef<TooltipTriggerRecord<Payload> | undefined>
31
+ payload: ComputedRef<Payload | undefined>
32
+ popupId: Ref<string | undefined>
33
+ popupRef: Ref<HTMLElement | null>
34
+ positionerRef: Ref<HTMLElement | null>
35
+ popupWidth: ShallowRef<number | undefined>
36
+ popupHeight: ShallowRef<number | undefined>
37
+ requestOpenChange: (
38
+ nextOpen: boolean,
39
+ details: TooltipRootChangeEventDetails,
40
+ triggerId?: string | null,
41
+ ) => void
42
+ getOpenDelay: (triggerDelay: number | undefined) => { delay: number, instant: boolean }
43
+ getCloseDelay: (triggerCloseDelay: number | undefined) => number
44
+ scheduleClose: (
45
+ delay: number,
46
+ details: TooltipRootChangeEventDetails,
47
+ triggerId?: string | null,
48
+ ) => void
49
+ clearCloseTimer: () => void
50
+ completeOpenChange: () => void
51
+ setMounted: (next: boolean) => void
52
+ setPopupId: (id: string | undefined) => void
53
+ setHasViewport: (next: boolean) => void
54
+ }
55
+
56
+ export const tooltipRootContextKey: InjectionKey<TooltipRootContext>
57
+ = Symbol('TooltipRootContext')
58
+
59
+ export function useTooltipRootContext(optional?: false): TooltipRootContext
60
+ export function useTooltipRootContext(optional: true): TooltipRootContext | undefined
61
+ export function useTooltipRootContext(optional = false): TooltipRootContext | undefined {
62
+ const context = inject(tooltipRootContextKey, undefined)
63
+
64
+ if (context === undefined && !optional) {
65
+ throw new Error(
66
+ 'Base UI Vue: TooltipRootContext is missing. Tooltip parts must be placed within <TooltipRoot> or receive a tooltip handle.',
67
+ )
68
+ }
69
+
70
+ return context
71
+ }
@@ -0,0 +1,215 @@
1
+ import type { Ref, ShallowRef } from 'vue'
2
+ import type { ElementProps, FloatingTriggerMap } from '../../floating-ui-vue/types'
3
+ import type {
4
+ TooltipInstantType,
5
+ TooltipRootChangeEventDetails,
6
+ TooltipRootChangeEventReason,
7
+ TooltipTrackCursorAxis,
8
+ } from '../root/TooltipRoot.vue'
9
+ import { shallowRef } from 'vue'
10
+ import { createChangeEventDetails } from '../../utils/createBaseUIEventDetails'
11
+ import { REASONS } from '../../utils/reasons'
12
+
13
+ export interface TooltipTriggerRecord<Payload = unknown> {
14
+ id: string
15
+ element: HTMLElement
16
+ payload: Payload | undefined
17
+ closeOnClick: boolean
18
+ closeDelay: number | undefined
19
+ }
20
+
21
+ export interface TooltipRootController {
22
+ open: Readonly<Ref<boolean>>
23
+ dismiss: ElementProps
24
+ activeTriggerId: Ref<string | null>
25
+ disabled: Readonly<Ref<boolean>>
26
+ disableHoverablePopup: Readonly<Ref<boolean>>
27
+ trackCursorAxis: Readonly<Ref<TooltipTrackCursorAxis>>
28
+ instantType: Ref<TooltipInstantType>
29
+ popupId: Ref<string | undefined>
30
+ requestOpenChange: (
31
+ nextOpen: boolean,
32
+ details: TooltipRootChangeEventDetails,
33
+ triggerId?: string | null,
34
+ ) => void
35
+ getOpenDelay: (triggerDelay: number | undefined) => { delay: number, instant: boolean }
36
+ getCloseDelay: (triggerCloseDelay: number | undefined) => number
37
+ scheduleClose: (
38
+ delay: number,
39
+ details: TooltipRootChangeEventDetails,
40
+ triggerId?: string | null,
41
+ ) => void
42
+ clearCloseTimer: () => void
43
+ forceUnmount: () => void
44
+ maybeActivateTrigger: (id: string) => void
45
+ }
46
+
47
+ export class TooltipStore<Payload = unknown> {
48
+ readonly controller: ShallowRef<TooltipRootController | null> = shallowRef(null)
49
+
50
+ private readonly triggers = new Map<string, TooltipTriggerRecord<Payload>>()
51
+ private readonly retainedTriggers = new Map<string, TooltipTriggerRecord<Payload>>()
52
+
53
+ readonly triggerElements: FloatingTriggerMap = {
54
+ hasElement: (element: Element) => {
55
+ return Array.from(this.triggers.values()).some(record => record.element === element)
56
+ },
57
+ hasMatchingElement: (predicate: (element: Element) => boolean) => {
58
+ return Array.from(this.triggers.values()).some(record => predicate(record.element))
59
+ },
60
+ getById: (id: string) => {
61
+ return this.triggers.get(id)?.element
62
+ },
63
+ entries: () => {
64
+ const entries = Array.from(this.triggers.entries())
65
+ .map(([id, record]) => [id, record.element] as [string, HTMLElement])
66
+
67
+ return entries[Symbol.iterator]()
68
+ },
69
+ elements: () => {
70
+ const elements = Array.from(this.triggers.values())
71
+ .map(record => record.element)
72
+
73
+ return elements[Symbol.iterator]()
74
+ },
75
+ get size() {
76
+ return Array.from(this.entries()).length
77
+ },
78
+ }
79
+
80
+ readonly version = shallowRef(0)
81
+
82
+ registerTrigger(record: TooltipTriggerRecord<Payload>) {
83
+ this.retainedTriggers.delete(record.id)
84
+ this.triggers.set(record.id, record)
85
+ this.version.value += 1
86
+ this.controller.value?.maybeActivateTrigger(record.id)
87
+ }
88
+
89
+ unregisterTrigger(id: string, element: HTMLElement) {
90
+ const existing = this.triggers.get(id)
91
+
92
+ if (existing?.element !== element) {
93
+ return
94
+ }
95
+
96
+ const isActiveTrigger = this.controller.value?.activeTriggerId.value === id
97
+
98
+ if (isActiveTrigger) {
99
+ this.retainedTriggers.set(id, existing)
100
+ }
101
+
102
+ this.triggers.delete(id)
103
+ this.version.value += 1
104
+
105
+ if (isActiveTrigger) {
106
+ this.requestCloseWhenActiveTriggerStaysUnmounted(id)
107
+ }
108
+ }
109
+
110
+ getTrigger(id: string | null | undefined): TooltipTriggerRecord<Payload> | undefined {
111
+ // Keep Map mutations visible to Vue computed refs that read trigger data.
112
+ void this.version.value
113
+
114
+ if (id == null) {
115
+ return undefined
116
+ }
117
+
118
+ return this.triggers.get(id) ?? this.retainedTriggers.get(id)
119
+ }
120
+
121
+ private requestCloseWhenActiveTriggerStaysUnmounted(id: string) {
122
+ queueMicrotask(() => {
123
+ const controller = this.controller.value
124
+
125
+ if (
126
+ !controller
127
+ || !controller.open.value
128
+ || controller.activeTriggerId.value !== id
129
+ ) {
130
+ this.retainedTriggers.delete(id)
131
+ return
132
+ }
133
+
134
+ if (this.triggers.has(id)) {
135
+ this.retainedTriggers.delete(id)
136
+ return
137
+ }
138
+
139
+ const details = createTooltipChangeEventDetails(REASONS.none)
140
+
141
+ controller.requestOpenChange(false, details)
142
+
143
+ if (!details.isCanceled) {
144
+ controller.activeTriggerId.value = null
145
+ this.retainedTriggers.delete(id)
146
+ }
147
+ })
148
+ }
149
+
150
+ open(triggerId: string) {
151
+ const trigger = this.getTrigger(triggerId)
152
+
153
+ if (!trigger) {
154
+ throw new Error(`Base UI Vue: TooltipHandle.open: No trigger found with id "${triggerId}".`)
155
+ }
156
+
157
+ this.controller.value?.requestOpenChange(
158
+ true,
159
+ createTooltipChangeEventDetails(REASONS.imperativeAction, undefined, trigger.element),
160
+ triggerId,
161
+ )
162
+ }
163
+
164
+ close() {
165
+ this.controller.value?.requestOpenChange(
166
+ false,
167
+ createTooltipChangeEventDetails(REASONS.imperativeAction),
168
+ )
169
+ }
170
+
171
+ get isOpen() {
172
+ return this.controller.value?.open.value ?? false
173
+ }
174
+ }
175
+
176
+ export function createTooltipHandle<Payload = unknown>(): TooltipHandle<Payload> {
177
+ return new TooltipHandle<Payload>()
178
+ }
179
+
180
+ /**
181
+ * A handle to control a tooltip imperatively and to associate detached triggers with it.
182
+ */
183
+ export class TooltipHandle<Payload = unknown> {
184
+ readonly store = new TooltipStore<Payload>()
185
+
186
+ open(triggerId: string) {
187
+ this.store.open(triggerId)
188
+ }
189
+
190
+ close() {
191
+ this.store.close()
192
+ }
193
+
194
+ get isOpen() {
195
+ return this.store.isOpen
196
+ }
197
+ }
198
+
199
+ export function createTooltipChangeEventDetails(
200
+ reason: TooltipRootChangeEventReason,
201
+ event?: Event,
202
+ trigger?: HTMLElement,
203
+ ): TooltipRootChangeEventDetails {
204
+ return createChangeEventDetails<
205
+ TooltipRootChangeEventReason,
206
+ { preventUnmountOnClose: () => void }
207
+ >(
208
+ reason,
209
+ event,
210
+ trigger,
211
+ {
212
+ preventUnmountOnClose() {},
213
+ },
214
+ )
215
+ }