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,468 @@
1
+ <script setup lang="ts">
2
+ import type { Ref } from 'vue'
3
+ import type { CompositeMetadata } from '../../composite/list/CompositeList.vue'
4
+ import type { BaseUIChangeEventDetails } from '../../utils/createBaseUIEventDetails'
5
+ import type { BaseUIComponentProps, Orientation } from '../../utils/types'
6
+ import type { TabsTabActivationDirection, TabsTabMetadata, TabsTabValue } from '../tab/TabsTab.vue'
7
+ import { computed, provide, ref, shallowRef, useAttrs, watch } from 'vue'
8
+ import CompositeList from '../../composite/list/CompositeList.vue'
9
+ import { createChangeEventDetails } from '../../utils/createBaseUIEventDetails'
10
+ import { REASONS } from '../../utils/reasons'
11
+ import { useControllableState } from '../../utils/useControllableState'
12
+ import { useRenderElement } from '../../utils/useRenderElement'
13
+ import { areTabValuesEqual } from '../utils/areTabValuesEqual'
14
+ import { tabsStateAttributesMapping } from './stateAttributesMapping'
15
+ import { tabsRootContextKey } from './TabsRootContext'
16
+
17
+ export type TabsRootOrientation = Orientation
18
+
19
+ export interface TabsRootState {
20
+ /**
21
+ * The component orientation.
22
+ */
23
+ orientation: TabsRootOrientation
24
+ /**
25
+ * The direction used for tab activation.
26
+ */
27
+ tabActivationDirection: TabsTabActivationDirection
28
+ }
29
+
30
+ export type TabsRootChangeEventReason
31
+ = | typeof REASONS.none
32
+ | typeof REASONS.disabled
33
+ | typeof REASONS.missing
34
+ | typeof REASONS.initial
35
+
36
+ export type TabsRootChangeEventDetails = BaseUIChangeEventDetails<
37
+ TabsRootChangeEventReason,
38
+ {
39
+ /**
40
+ * The position of the active tab relative to the previously active tab.
41
+ */
42
+ activationDirection: TabsTabActivationDirection
43
+ }
44
+ >
45
+
46
+ export interface TabsRootProps extends BaseUIComponentProps<TabsRootState> {
47
+ /**
48
+ * The default value. Use when the component is not controlled.
49
+ * When the value is `null`, no Tab will be active.
50
+ * @default 0
51
+ */
52
+ defaultValue?: TabsTabValue
53
+ /**
54
+ * The value of the currently active Tab. Use when the component is controlled.
55
+ * When the value is `null`, no Tab will be active.
56
+ */
57
+ value?: TabsTabValue
58
+ /**
59
+ * The component orientation.
60
+ * @default 'horizontal'
61
+ */
62
+ orientation?: TabsRootOrientation
63
+ }
64
+
65
+ /**
66
+ * Groups the tabs and the corresponding panels.
67
+ * Renders a `<div>` element.
68
+ *
69
+ * Documentation: [Base UI Vue Tabs](https://baseui-vue.com/docs/components/tabs)
70
+ */
71
+ defineOptions({
72
+ name: 'TabsRoot',
73
+ inheritAttrs: false,
74
+ })
75
+
76
+ const props = withDefaults(defineProps<TabsRootProps>(), {
77
+ as: 'div',
78
+ orientation: 'horizontal',
79
+ })
80
+
81
+ const emit = defineEmits<{
82
+ /**
83
+ * Event handler called when the active tab value changes.
84
+ */
85
+ valueChange: [value: TabsTabValue, eventDetails: TabsRootChangeEventDetails]
86
+ }>()
87
+
88
+ const attrs = useAttrs()
89
+
90
+ const isValueControlled = computed(() => props.value !== undefined)
91
+ const hasExplicitDefaultValueProp = props.defaultValue !== undefined
92
+
93
+ const initialDefaultValue = props.defaultValue === undefined ? 0 : props.defaultValue
94
+ const initialValue = props.value === undefined ? initialDefaultValue : props.value
95
+ const tabPanelRefs = ref<Array<HTMLElement | null>>([])
96
+ const tabPanelRefsHolder = { elementsRef: tabPanelRefs }
97
+ const tabMap = shallowRef<Map<Element, CompositeMetadata<TabsTabMetadata> | null>>(new Map())
98
+ const lastKnownTabElementRef = shallowRef<Element | undefined>(undefined)
99
+ const mountedTabPanels = shallowRef(new Map<TabsTabValue | number, string>())
100
+ const activationDirectionState = ref<{
101
+ previousValue: TabsTabValue
102
+ tabActivationDirection: TabsTabActivationDirection
103
+ }>({
104
+ previousValue: initialValue,
105
+ tabActivationDirection: 'none',
106
+ })
107
+
108
+ const { value, setValue } = useControllableState<TabsTabValue>({
109
+ controlled: () => (isValueControlled.value ? props.value : undefined),
110
+ default: () => props.defaultValue === undefined ? 0 : props.defaultValue,
111
+ name: 'TabsRoot',
112
+ })
113
+
114
+ function setTabValue(nextValue: TabsTabValue) {
115
+ setValue(() => nextValue)
116
+ }
117
+
118
+ function getTabElementBySelectedValue(selectedValue: TabsTabValue | undefined) {
119
+ if (selectedValue === undefined) {
120
+ return null
121
+ }
122
+
123
+ for (const [tabElement, tabMetadata] of tabMap.value.entries()) {
124
+ if (tabMetadata != null && areTabValuesEqual(selectedValue, getTabMetadataValue(tabMetadata))) {
125
+ return tabElement as HTMLElement
126
+ }
127
+ }
128
+
129
+ return null
130
+ }
131
+
132
+ function getTabIdByPanelValue(panelValue: TabsTabValue) {
133
+ for (const tabMetadata of tabMap.value.values()) {
134
+ if (tabMetadata != null && areTabValuesEqual(getTabMetadataValue(tabMetadata), panelValue)) {
135
+ return tabMetadata.id
136
+ }
137
+ }
138
+ return undefined
139
+ }
140
+
141
+ function getTabPanelIdByValue(tabValue: TabsTabValue) {
142
+ for (const [panelValue, panelId] of mountedTabPanels.value.entries()) {
143
+ if (areTabValuesEqual(panelValue, tabValue)) {
144
+ return panelId
145
+ }
146
+ }
147
+ return undefined
148
+ }
149
+
150
+ function getTabMetadataValue(tabMetadata: CompositeMetadata<TabsTabMetadata>): TabsTabValue | undefined {
151
+ return tabMetadata.value ?? tabMetadata.index ?? undefined
152
+ }
153
+
154
+ function registerMountedTabPanel(panelValue: TabsTabValue | number, panelId: string) {
155
+ for (const [mountedPanelValue, mountedPanelId] of mountedTabPanels.value.entries()) {
156
+ if (areTabValuesEqual(mountedPanelValue, panelValue) && mountedPanelId === panelId) {
157
+ return
158
+ }
159
+ }
160
+
161
+ const next = new Map(mountedTabPanels.value)
162
+ for (const mountedPanelValue of next.keys()) {
163
+ if (areTabValuesEqual(mountedPanelValue, panelValue)) {
164
+ next.delete(mountedPanelValue)
165
+ }
166
+ }
167
+ next.set(panelValue, panelId)
168
+ mountedTabPanels.value = next
169
+ }
170
+
171
+ function unregisterMountedTabPanel(panelValue: TabsTabValue | number, panelId: string) {
172
+ let keyToDelete: TabsTabValue | number | undefined
173
+
174
+ for (const [mountedPanelValue, mountedPanelId] of mountedTabPanels.value.entries()) {
175
+ if (areTabValuesEqual(mountedPanelValue, panelValue) && mountedPanelId === panelId) {
176
+ keyToDelete = mountedPanelValue
177
+ break
178
+ }
179
+ }
180
+
181
+ if (keyToDelete === undefined) {
182
+ return
183
+ }
184
+
185
+ const next = new Map(mountedTabPanels.value)
186
+ next.delete(keyToDelete)
187
+ mountedTabPanels.value = next
188
+ }
189
+
190
+ function setTabMap(map: Map<Element, CompositeMetadata<TabsTabMetadata> | null>) {
191
+ tabMap.value = map
192
+ }
193
+
194
+ function notifyAutomaticValueChange(nextValue: TabsTabValue, reason: TabsRootChangeEventReason) {
195
+ emit(
196
+ 'valueChange',
197
+ nextValue,
198
+ createChangeEventDetails(reason, undefined, undefined, {
199
+ activationDirection: 'none',
200
+ }),
201
+ )
202
+ }
203
+
204
+ function onValueChange(newValue: TabsTabValue, eventDetails: TabsRootChangeEventDetails) {
205
+ const activationDirection = computeActivationDirection(
206
+ value.value,
207
+ newValue,
208
+ props.orientation,
209
+ tabMap.value,
210
+ )
211
+
212
+ eventDetails.activationDirection = activationDirection
213
+ emit('valueChange', newValue, eventDetails)
214
+
215
+ if (eventDetails.isCanceled) {
216
+ return
217
+ }
218
+
219
+ setTabValue(newValue)
220
+ }
221
+
222
+ watch(
223
+ [value, tabMap, () => props.orientation],
224
+ () => {
225
+ const previousValue = activationDirectionState.value.previousValue
226
+ const nextValue = value.value
227
+
228
+ if (areTabValuesEqual(nextValue, previousValue)) {
229
+ return
230
+ }
231
+
232
+ const tabActivationDirection = computeActivationDirection(
233
+ previousValue,
234
+ nextValue,
235
+ props.orientation,
236
+ tabMap.value,
237
+ )
238
+ const directionComputationIncomplete
239
+ = previousValue != null
240
+ && nextValue != null
241
+ && getTabElementBySelectedValue(nextValue) == null
242
+
243
+ activationDirectionState.value = {
244
+ previousValue: directionComputationIncomplete ? previousValue : nextValue,
245
+ tabActivationDirection,
246
+ }
247
+ },
248
+ { flush: 'sync' },
249
+ )
250
+
251
+ const selectedTabMetadata = computed(() => {
252
+ for (const tabMetadata of tabMap.value.values()) {
253
+ if (tabMetadata != null && areTabValuesEqual(getTabMetadataValue(tabMetadata), value.value)) {
254
+ return tabMetadata
255
+ }
256
+ }
257
+ return undefined
258
+ })
259
+
260
+ const firstEnabledTabValue = computed(() => {
261
+ for (const tabMetadata of tabMap.value.values()) {
262
+ if (tabMetadata != null && !tabMetadata.disabled) {
263
+ return getTabMetadataValue(tabMetadata)
264
+ }
265
+ }
266
+ return undefined
267
+ })
268
+
269
+ const shouldNotifyInitialValueChange = ref(!hasExplicitDefaultValueProp)
270
+ const shouldHonorDisabledDefaultValue = ref(hasExplicitDefaultValueProp)
271
+ const didRegisterTabs = ref(false)
272
+
273
+ watch(
274
+ [tabMap, value, selectedTabMetadata, firstEnabledTabValue],
275
+ () => {
276
+ if (isValueControlled.value) {
277
+ return
278
+ }
279
+
280
+ function commitAutomaticValueChange(
281
+ fallbackValue: TabsTabValue,
282
+ fallbackReason: TabsRootChangeEventReason,
283
+ ) {
284
+ setTabValue(fallbackValue)
285
+ activationDirectionState.value = {
286
+ previousValue: fallbackValue,
287
+ tabActivationDirection: 'none',
288
+ }
289
+ notifyAutomaticValueChange(fallbackValue, fallbackReason)
290
+ shouldNotifyInitialValueChange.value = false
291
+ }
292
+
293
+ if (tabMap.value.size === 0) {
294
+ if (
295
+ didRegisterTabs.value
296
+ && value.value !== null
297
+ && !lastKnownTabElementRef.value?.isConnected
298
+ ) {
299
+ commitAutomaticValueChange(null, REASONS.missing)
300
+ }
301
+ return
302
+ }
303
+
304
+ didRegisterTabs.value = true
305
+ lastKnownTabElementRef.value = tabMap.value.keys().next().value
306
+
307
+ const selectionIsDisabled = selectedTabMetadata.value?.disabled
308
+ const selectionIsMissing = selectedTabMetadata.value == null && value.value !== null
309
+
310
+ if (!selectionIsDisabled && areTabValuesEqual(value.value, initialDefaultValue)) {
311
+ shouldHonorDisabledDefaultValue.value = false
312
+ }
313
+
314
+ if (
315
+ shouldHonorDisabledDefaultValue.value
316
+ && selectionIsDisabled
317
+ && areTabValuesEqual(value.value, initialDefaultValue)
318
+ ) {
319
+ return
320
+ }
321
+
322
+ if (selectionIsDisabled || selectionIsMissing) {
323
+ const fallbackValue = firstEnabledTabValue.value ?? null
324
+
325
+ if (areTabValuesEqual(value.value, fallbackValue)) {
326
+ shouldNotifyInitialValueChange.value = false
327
+ return
328
+ }
329
+
330
+ let fallbackReason: TabsRootChangeEventReason = REASONS.missing
331
+
332
+ if (shouldNotifyInitialValueChange.value) {
333
+ fallbackReason = REASONS.initial
334
+ }
335
+ else if (selectionIsDisabled) {
336
+ fallbackReason = REASONS.disabled
337
+ }
338
+
339
+ commitAutomaticValueChange(fallbackValue, fallbackReason)
340
+ return
341
+ }
342
+
343
+ if (shouldNotifyInitialValueChange.value && selectedTabMetadata.value != null) {
344
+ notifyAutomaticValueChange(value.value, REASONS.initial)
345
+ shouldNotifyInitialValueChange.value = false
346
+ }
347
+ },
348
+ { immediate: true, flush: 'post' },
349
+ )
350
+
351
+ const tabActivationDirection = computed(
352
+ () => activationDirectionState.value.tabActivationDirection,
353
+ )
354
+
355
+ const state = computed<TabsRootState>(() => ({
356
+ orientation: props.orientation,
357
+ tabActivationDirection: tabActivationDirection.value,
358
+ }))
359
+
360
+ provide(tabsRootContextKey, {
361
+ getTabElementBySelectedValue,
362
+ getTabIdByPanelValue,
363
+ getTabPanelIdByValue,
364
+ onValueChange,
365
+ orientation: computed(() => props.orientation),
366
+ registerMountedTabPanel,
367
+ setTabMap,
368
+ tabActivationDirection,
369
+ tabMap,
370
+ tabPanelRefs,
371
+ unregisterMountedTabPanel,
372
+ value: value as Readonly<Ref<TabsTabValue>>,
373
+ })
374
+
375
+ const {
376
+ tag,
377
+ mergedProps,
378
+ renderless,
379
+ ref: renderRef,
380
+ } = useRenderElement({
381
+ componentProps: props,
382
+ state,
383
+ props: computed(() => attrs),
384
+ stateAttributesMapping: tabsStateAttributesMapping,
385
+ defaultTagName: 'div',
386
+ })
387
+
388
+ function computeActivationDirection(
389
+ oldValue: TabsTabValue,
390
+ newValue: TabsTabValue,
391
+ orientation: TabsRootOrientation,
392
+ tabsMap: Map<Element, CompositeMetadata<TabsTabMetadata> | null>,
393
+ ): TabsTabActivationDirection {
394
+ if (oldValue == null || newValue == null) {
395
+ return 'none'
396
+ }
397
+
398
+ let oldTab: HTMLElement | null = null
399
+ let newTab: HTMLElement | null = null
400
+
401
+ for (const [tabElement, tabMetadata] of tabsMap.entries()) {
402
+ if (tabMetadata == null) {
403
+ continue
404
+ }
405
+
406
+ const tabValue = getTabMetadataValue(tabMetadata)
407
+ if (areTabValuesEqual(oldValue, tabValue)) {
408
+ oldTab = tabElement as HTMLElement
409
+ }
410
+ if (areTabValuesEqual(newValue, tabValue)) {
411
+ newTab = tabElement as HTMLElement
412
+ }
413
+ if (oldTab != null && newTab != null) {
414
+ break
415
+ }
416
+ }
417
+
418
+ if (oldTab == null || newTab == null) {
419
+ if (
420
+ oldTab !== newTab
421
+ && isDirectionFallbackValue(oldValue)
422
+ && isDirectionFallbackValue(newValue)
423
+ && typeof oldValue === typeof newValue
424
+ ) {
425
+ if (orientation === 'horizontal') {
426
+ return newValue > oldValue ? 'right' : 'left'
427
+ }
428
+ return newValue > oldValue ? 'down' : 'up'
429
+ }
430
+ return 'none'
431
+ }
432
+
433
+ const oldRect = oldTab.getBoundingClientRect()
434
+ const newRect = newTab.getBoundingClientRect()
435
+
436
+ if (orientation === 'horizontal') {
437
+ if (newRect.left < oldRect.left) {
438
+ return 'left'
439
+ }
440
+ if (newRect.left > oldRect.left) {
441
+ return 'right'
442
+ }
443
+ }
444
+ else {
445
+ if (newRect.top < oldRect.top) {
446
+ return 'up'
447
+ }
448
+ if (newRect.top > oldRect.top) {
449
+ return 'down'
450
+ }
451
+ }
452
+
453
+ return 'none'
454
+ }
455
+
456
+ function isDirectionFallbackValue(value: TabsTabValue): value is number | string {
457
+ return typeof value === 'number' || typeof value === 'string'
458
+ }
459
+ </script>
460
+
461
+ <template>
462
+ <CompositeList :elements-ref="tabPanelRefsHolder.elementsRef">
463
+ <slot v-if="renderless" :ref="renderRef" :props="mergedProps" :state="state" />
464
+ <component :is="tag" v-else :ref="renderRef" v-bind="mergedProps">
465
+ <slot :state="state" />
466
+ </component>
467
+ </CompositeList>
468
+ </template>
@@ -0,0 +1,56 @@
1
+ import type { ComputedRef, InjectionKey, Ref, ShallowRef } from 'vue'
2
+ import type { CompositeMetadata } from '../../composite/list/CompositeList.vue'
3
+ import type { TabsTabActivationDirection, TabsTabMetadata, TabsTabValue } from '../tab/TabsTab.vue'
4
+ import type { TabsRootChangeEventDetails } from './TabsRoot.vue'
5
+ import { inject } from 'vue'
6
+
7
+ export interface TabsRootContext {
8
+ /**
9
+ * The currently active tab's value.
10
+ */
11
+ value: Readonly<Ref<TabsTabValue>>
12
+ /**
13
+ * Callback for setting new value.
14
+ */
15
+ onValueChange: (value: TabsTabValue, eventDetails: TabsRootChangeEventDetails) => void
16
+ /**
17
+ * The component orientation (layout flow direction).
18
+ */
19
+ orientation: ComputedRef<'horizontal' | 'vertical'>
20
+ /**
21
+ * Gets the element of the Tab with the given value.
22
+ */
23
+ getTabElementBySelectedValue: (selectedValue: TabsTabValue | undefined) => HTMLElement | null
24
+ /**
25
+ * Gets the `id` attribute of the Tab that corresponds to the given TabPanel value.
26
+ */
27
+ getTabIdByPanelValue: (panelValue: TabsTabValue) => string | undefined
28
+ /**
29
+ * Gets the `id` attribute of the TabPanel that corresponds to the given Tab value.
30
+ */
31
+ getTabPanelIdByValue: (tabValue: TabsTabValue) => string | undefined
32
+ registerMountedTabPanel: (panelValue: TabsTabValue | number, panelId: string) => void
33
+ setTabMap: (map: Map<Element, CompositeMetadata<TabsTabMetadata> | null>) => void
34
+ /**
35
+ * The position of the active tab relative to the previously active tab.
36
+ */
37
+ tabActivationDirection: Readonly<Ref<TabsTabActivationDirection>>
38
+ tabMap: ShallowRef<Map<Element, CompositeMetadata<TabsTabMetadata> | null>>
39
+ tabPanelRefs: Ref<Array<HTMLElement | null>>
40
+ unregisterMountedTabPanel: (panelValue: TabsTabValue | number, panelId: string) => void
41
+ }
42
+
43
+ export const tabsRootContextKey: InjectionKey<TabsRootContext> = Symbol('TabsRootContext')
44
+
45
+ export function useTabsRootContext(optional: true): TabsRootContext | undefined
46
+ export function useTabsRootContext(optional?: false): TabsRootContext
47
+ export function useTabsRootContext(optional = false) {
48
+ const context = inject(tabsRootContextKey, undefined)
49
+ if (!context && !optional) {
50
+ throw new Error(
51
+ 'Base UI Vue: TabsRootContext is missing. Tabs parts must be placed within <TabsRoot>.',
52
+ )
53
+ }
54
+
55
+ return context
56
+ }
@@ -0,0 +1,12 @@
1
+ export enum TabsRootDataAttributes {
2
+ /**
3
+ * Indicates the direction of the activation (based on the previous active tab).
4
+ * @type {'left' | 'right' | 'up' | 'down' | 'none'}
5
+ */
6
+ activationDirection = 'data-activation-direction',
7
+ /**
8
+ * Indicates the orientation of the tabs.
9
+ * @type {'horizontal' | 'vertical'}
10
+ */
11
+ orientation = 'data-orientation',
12
+ }
@@ -0,0 +1,9 @@
1
+ import type { StateAttributesMapping } from '../../utils/getStateAttributesProps'
2
+ import type { TabsRootState } from './TabsRoot.vue'
3
+ import { TabsRootDataAttributes } from './TabsRootDataAttributes'
4
+
5
+ export const tabsStateAttributesMapping: StateAttributesMapping<TabsRootState> = {
6
+ tabActivationDirection: direction => ({
7
+ [TabsRootDataAttributes.activationDirection]: direction,
8
+ }),
9
+ }