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,26 @@
1
+ export enum TabsIndicatorCssVars {
2
+ /**
3
+ * Indicates the distance on the left side from the parent's container if the tab is active.
4
+ */
5
+ activeTabLeft = '--active-tab-left',
6
+ /**
7
+ * Indicates the distance on the right side from the parent's container if the tab is active.
8
+ */
9
+ activeTabRight = '--active-tab-right',
10
+ /**
11
+ * Indicates the distance on the top side from the parent's container if the tab is active.
12
+ */
13
+ activeTabTop = '--active-tab-top',
14
+ /**
15
+ * Indicates the distance on the bottom side from the parent's container if the tab is active.
16
+ */
17
+ activeTabBottom = '--active-tab-bottom',
18
+ /**
19
+ * Indicates the width of the tab if it is active.
20
+ */
21
+ activeTabWidth = '--active-tab-width',
22
+ /**
23
+ * Indicates the height of the tab if it is active.
24
+ */
25
+ activeTabHeight = '--active-tab-height',
26
+ }
@@ -0,0 +1,12 @@
1
+ export enum TabsIndicatorDataAttributes {
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,5 @@
1
+ // This file is autogenerated. Do not edit it directly.
2
+ // To update it, modify the corresponding source file and run `pnpm inline-scripts`.
3
+
4
+ // prettier-ignore
5
+ export const script = "(function prehydration() {\n const indicator = document.currentScript?.previousElementSibling\n if (!indicator) {\n return\n }\n\n const tabsList = indicator.closest('[role=\"tablist\"]')\n if (!tabsList) {\n return\n }\n\n const activeTab = tabsList.querySelector('[data-active]')\n if (!activeTab) {\n return\n }\n\n if (activeTab.offsetWidth === 0 || tabsList.offsetWidth === 0) {\n return\n }\n\n let left = 0\n let right = 0\n let top = 0\n let bottom = 0\n let width = 0\n let height = 0\n\n function getCssDimensions(element) {\n const css = getComputedStyle(element)\n let cssWidth = Number.parseFloat(css.width) || 0\n let cssHeight = Number.parseFloat(css.height) || 0\n const shouldFallback\n = Math.round(cssWidth) !== element.offsetWidth\n || Math.round(cssHeight) !== element.offsetHeight\n\n if (shouldFallback) {\n cssWidth = element.offsetWidth\n cssHeight = element.offsetHeight\n }\n\n return {\n width: cssWidth,\n height: cssHeight,\n }\n }\n\n const { width: computedWidth, height: computedHeight } = getCssDimensions(activeTab)\n const { width: tabsListWidth, height: tabsListHeight } = getCssDimensions(tabsList)\n const tabRect = activeTab.getBoundingClientRect()\n const tabsListRect = tabsList.getBoundingClientRect()\n const scaleX = tabsListWidth > 0 ? tabsListRect.width / tabsListWidth : 1\n const scaleY = tabsListHeight > 0 ? tabsListRect.height / tabsListHeight : 1\n const hasNonZeroScale\n = Math.abs(scaleX) > Number.EPSILON && Math.abs(scaleY) > Number.EPSILON\n\n if (hasNonZeroScale) {\n const tabLeftDelta = tabRect.left - tabsListRect.left\n const tabTopDelta = tabRect.top - tabsListRect.top\n\n left = tabLeftDelta / scaleX + tabsList.scrollLeft - tabsList.clientLeft\n top = tabTopDelta / scaleY + tabsList.scrollTop - tabsList.clientTop\n }\n else {\n left = activeTab.offsetLeft\n top = activeTab.offsetTop\n }\n\n width = computedWidth\n height = computedHeight\n right = tabsList.scrollWidth - left - width\n bottom = tabsList.scrollHeight - top - height\n\n function setProp(name, value) {\n indicator.style.setProperty(`--active-tab-${name}`, `${value}px`)\n }\n\n setProp('left', left)\n setProp('right', right)\n setProp('top', top)\n setProp('bottom', bottom)\n setProp('width', width)\n setProp('height', height)\n\n if (width > 0 && height > 0) {\n indicator.removeAttribute('hidden')\n }\n})()";
@@ -0,0 +1,87 @@
1
+ (function prehydration() {
2
+ const indicator = document.currentScript?.previousElementSibling
3
+ if (!indicator) {
4
+ return
5
+ }
6
+
7
+ const tabsList = indicator.closest('[role="tablist"]')
8
+ if (!tabsList) {
9
+ return
10
+ }
11
+
12
+ const activeTab = tabsList.querySelector('[data-active]')
13
+ if (!activeTab) {
14
+ return
15
+ }
16
+
17
+ if (activeTab.offsetWidth === 0 || tabsList.offsetWidth === 0) {
18
+ return
19
+ }
20
+
21
+ let left = 0
22
+ let right = 0
23
+ let top = 0
24
+ let bottom = 0
25
+ let width = 0
26
+ let height = 0
27
+
28
+ function getCssDimensions(element) {
29
+ const css = getComputedStyle(element)
30
+ let cssWidth = Number.parseFloat(css.width) || 0
31
+ let cssHeight = Number.parseFloat(css.height) || 0
32
+ const shouldFallback
33
+ = Math.round(cssWidth) !== element.offsetWidth
34
+ || Math.round(cssHeight) !== element.offsetHeight
35
+
36
+ if (shouldFallback) {
37
+ cssWidth = element.offsetWidth
38
+ cssHeight = element.offsetHeight
39
+ }
40
+
41
+ return {
42
+ width: cssWidth,
43
+ height: cssHeight,
44
+ }
45
+ }
46
+
47
+ const { width: computedWidth, height: computedHeight } = getCssDimensions(activeTab)
48
+ const { width: tabsListWidth, height: tabsListHeight } = getCssDimensions(tabsList)
49
+ const tabRect = activeTab.getBoundingClientRect()
50
+ const tabsListRect = tabsList.getBoundingClientRect()
51
+ const scaleX = tabsListWidth > 0 ? tabsListRect.width / tabsListWidth : 1
52
+ const scaleY = tabsListHeight > 0 ? tabsListRect.height / tabsListHeight : 1
53
+ const hasNonZeroScale
54
+ = Math.abs(scaleX) > Number.EPSILON && Math.abs(scaleY) > Number.EPSILON
55
+
56
+ if (hasNonZeroScale) {
57
+ const tabLeftDelta = tabRect.left - tabsListRect.left
58
+ const tabTopDelta = tabRect.top - tabsListRect.top
59
+
60
+ left = tabLeftDelta / scaleX + tabsList.scrollLeft - tabsList.clientLeft
61
+ top = tabTopDelta / scaleY + tabsList.scrollTop - tabsList.clientTop
62
+ }
63
+ else {
64
+ left = activeTab.offsetLeft
65
+ top = activeTab.offsetTop
66
+ }
67
+
68
+ width = computedWidth
69
+ height = computedHeight
70
+ right = tabsList.scrollWidth - left - width
71
+ bottom = tabsList.scrollHeight - top - height
72
+
73
+ function setProp(name, value) {
74
+ indicator.style.setProperty(`--active-tab-${name}`, `${value}px`)
75
+ }
76
+
77
+ setProp('left', left)
78
+ setProp('right', right)
79
+ setProp('top', top)
80
+ setProp('bottom', bottom)
81
+ setProp('width', width)
82
+ setProp('height', height)
83
+
84
+ if (width > 0 && height > 0) {
85
+ indicator.removeAttribute('hidden')
86
+ }
87
+ })()
@@ -0,0 +1,201 @@
1
+ <script setup lang="ts">
2
+ import type { CompositeMetadata } from '../../composite/list/CompositeList.vue'
3
+ import type { BaseUIComponentProps, HTMLProps } from '../../utils/types'
4
+ import type { TabsRootState } from '../root/TabsRoot.vue'
5
+ import type { TabsTabMetadata, TabsTabValue } from '../tab/TabsTab.vue'
6
+ import { computed, onBeforeUnmount, provide, reactive, ref, useAttrs, watch } from 'vue'
7
+ import CompositeList from '../../composite/list/CompositeList.vue'
8
+ import { compositeRootContextKey } from '../../composite/root/CompositeRootContext'
9
+ import { useCompositeRoot } from '../../composite/root/useCompositeRoot'
10
+ import { useDirection } from '../../direction-provider/DirectionContext'
11
+ import { useRenderElement } from '../../utils/useRenderElement'
12
+ import { tabsStateAttributesMapping } from '../root/stateAttributesMapping'
13
+ import { useTabsRootContext } from '../root/TabsRootContext'
14
+ import { areTabValuesEqual } from '../utils/areTabValuesEqual'
15
+ import { tabsListContextKey } from './TabsListContext'
16
+
17
+ export interface TabsListState extends TabsRootState {}
18
+
19
+ export interface TabsListProps extends BaseUIComponentProps<TabsListState> {
20
+ /**
21
+ * Whether to automatically change the active tab on arrow key focus.
22
+ * Otherwise, tabs will be activated using Enter or Space key press.
23
+ * @default false
24
+ */
25
+ activateOnFocus?: boolean
26
+ /**
27
+ * Whether to loop keyboard focus back to the first item when the end of the list is reached.
28
+ * @default true
29
+ */
30
+ loopFocus?: boolean
31
+ }
32
+
33
+ /**
34
+ * Groups the individual tab buttons.
35
+ * Renders a `<div>` element.
36
+ *
37
+ * Documentation: [Base UI Vue Tabs](https://baseui-vue.com/docs/components/tabs)
38
+ */
39
+ defineOptions({
40
+ name: 'TabsList',
41
+ inheritAttrs: false,
42
+ })
43
+
44
+ const props = withDefaults(defineProps<TabsListProps>(), {
45
+ as: 'div',
46
+ activateOnFocus: false,
47
+ loopFocus: true,
48
+ })
49
+
50
+ const attrs = useAttrs()
51
+ const rootCtx = useTabsRootContext()
52
+ const direction = useDirection()
53
+ const highlightedTabIndex = ref(0)
54
+ const tabsListElement = ref<HTMLElement | null>(null)
55
+ const indicatorUpdateListeners = new Set<() => void>()
56
+ const tabResizeObserverElements = new Set<HTMLElement>()
57
+ let resizeObserver: ResizeObserver | null = null
58
+
59
+ const root = useCompositeRoot({
60
+ orientation: () => rootCtx.orientation.value,
61
+ loopFocus: () => props.loopFocus,
62
+ highlightedIndex: () => highlightedTabIndex.value,
63
+ onHighlightedIndexChange: index => setHighlightedTabIndex(index),
64
+ enableHomeAndEndKeys: () => true,
65
+ stopEventPropagation: () => true,
66
+ disabledIndices: () => [],
67
+ direction: () => direction.value,
68
+ rootRef: tabsListElement,
69
+ })
70
+
71
+ provide(
72
+ compositeRootContextKey,
73
+ reactive({
74
+ highlightedIndex: root.highlightedIndex,
75
+ onHighlightedIndexChange: root.onHighlightedIndexChange,
76
+ highlightItemOnHover: computed(() => false),
77
+ relayKeyboardEvent: root.relayKeyboardEvent,
78
+ }),
79
+ )
80
+
81
+ function notifyIndicatorUpdate() {
82
+ indicatorUpdateListeners.forEach(listener => listener())
83
+ }
84
+
85
+ function resetResizeObserver() {
86
+ resizeObserver?.disconnect()
87
+ resizeObserver = null
88
+
89
+ if (typeof ResizeObserver === 'undefined') {
90
+ return
91
+ }
92
+
93
+ resizeObserver = new ResizeObserver(notifyIndicatorUpdate)
94
+
95
+ if (tabsListElement.value) {
96
+ resizeObserver.observe(tabsListElement.value)
97
+ }
98
+
99
+ tabResizeObserverElements.forEach((element) => {
100
+ resizeObserver?.observe(element)
101
+ })
102
+ }
103
+
104
+ watch(tabsListElement, resetResizeObserver, { flush: 'post' })
105
+
106
+ onBeforeUnmount(() => {
107
+ resizeObserver?.disconnect()
108
+ resizeObserver = null
109
+ indicatorUpdateListeners.clear()
110
+ tabResizeObserverElements.clear()
111
+ })
112
+
113
+ function registerIndicatorUpdateListener(listener: () => void) {
114
+ indicatorUpdateListeners.add(listener)
115
+ return () => {
116
+ indicatorUpdateListeners.delete(listener)
117
+ }
118
+ }
119
+
120
+ function registerTabResizeObserverElement(element: HTMLElement) {
121
+ tabResizeObserverElements.add(element)
122
+ resizeObserver?.observe(element)
123
+ notifyIndicatorUpdate()
124
+
125
+ return () => {
126
+ tabResizeObserverElements.delete(element)
127
+ resizeObserver?.unobserve(element)
128
+ notifyIndicatorUpdate()
129
+ }
130
+ }
131
+
132
+ function setHighlightedTabIndex(index: number) {
133
+ highlightedTabIndex.value = index
134
+ }
135
+
136
+ function onTabActivation(newValue: TabsTabValue, eventDetails: Parameters<typeof rootCtx.onValueChange>[1]) {
137
+ if (!areTabValuesEqual(newValue, rootCtx.value.value)) {
138
+ rootCtx.onValueChange(newValue, eventDetails)
139
+ }
140
+ }
141
+
142
+ provide(tabsListContextKey, {
143
+ activateOnFocus: computed(() => props.activateOnFocus),
144
+ highlightedTabIndex,
145
+ onTabActivation,
146
+ registerIndicatorUpdateListener,
147
+ registerTabResizeObserverElement,
148
+ setHighlightedTabIndex,
149
+ tabsListElement,
150
+ })
151
+
152
+ const state = computed<TabsListState>(() => ({
153
+ orientation: rootCtx.orientation.value,
154
+ tabActivationDirection: rootCtx.tabActivationDirection.value,
155
+ }))
156
+
157
+ const rootProps = computed<HTMLProps>(() => {
158
+ const externalProps = {
159
+ ...attrs,
160
+ role: 'tablist',
161
+ }
162
+ const compositeProps = root.getRootProps(externalProps)
163
+
164
+ compositeProps['aria-orientation']
165
+ = rootCtx.orientation.value === 'vertical' ? 'vertical' : undefined
166
+
167
+ return compositeProps
168
+ })
169
+
170
+ const {
171
+ tag,
172
+ mergedProps,
173
+ renderless,
174
+ ref: renderRef,
175
+ } = useRenderElement({
176
+ componentProps: props,
177
+ state,
178
+ props: rootProps,
179
+ stateAttributesMapping: tabsStateAttributesMapping,
180
+ defaultTagName: 'div',
181
+ ref: root.mergedRef,
182
+ })
183
+
184
+ function handleMapChange(map: Map<Element, CompositeMetadata<TabsTabMetadata> | null>) {
185
+ rootCtx.setTabMap(map)
186
+ root.onMapChange(map)
187
+ notifyIndicatorUpdate()
188
+ }
189
+ </script>
190
+
191
+ <template>
192
+ <CompositeList
193
+ :elements-ref="root.elementsRef"
194
+ :on-map-change="handleMapChange"
195
+ >
196
+ <slot v-if="renderless" :ref="renderRef" :props="mergedProps" :state="state" />
197
+ <component :is="tag" v-else :ref="renderRef" v-bind="mergedProps">
198
+ <slot :state="state" />
199
+ </component>
200
+ </CompositeList>
201
+ </template>
@@ -0,0 +1,29 @@
1
+ import type { InjectionKey, Ref } from 'vue'
2
+ import type { TabsRootChangeEventDetails } from '../root/TabsRoot.vue'
3
+ import type { TabsTabValue } from '../tab/TabsTab.vue'
4
+ import { inject } from 'vue'
5
+
6
+ export interface TabsListContext {
7
+ activateOnFocus: Ref<boolean>
8
+ highlightedTabIndex: Ref<number>
9
+ registerIndicatorUpdateListener: (listener: () => void) => () => void
10
+ registerTabResizeObserverElement: (element: HTMLElement) => () => void
11
+ onTabActivation: (newValue: TabsTabValue, eventDetails: TabsRootChangeEventDetails) => void
12
+ setHighlightedTabIndex: (index: number) => void
13
+ tabsListElement: Ref<HTMLElement | null>
14
+ }
15
+
16
+ export const tabsListContextKey: InjectionKey<TabsListContext> = Symbol('TabsListContext')
17
+
18
+ export function useTabsListContext(optional: true): TabsListContext | undefined
19
+ export function useTabsListContext(optional?: false): TabsListContext
20
+ export function useTabsListContext(optional = false) {
21
+ const context = inject(tabsListContextKey, undefined)
22
+ if (!context && !optional) {
23
+ throw new Error(
24
+ 'Base UI Vue: TabsListContext is missing. TabsList parts must be placed within <TabsList>.',
25
+ )
26
+ }
27
+
28
+ return context
29
+ }
@@ -0,0 +1,12 @@
1
+ export enum TabsListDataAttributes {
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,167 @@
1
+ <script setup lang="ts">
2
+ import type { ComponentPublicInstance } from 'vue'
3
+ import type { BaseUIComponentProps } from '../../utils/types'
4
+ import type { TransitionStatus } from '../../utils/useTransitionStatus'
5
+ import type { TabsRootState } from '../root/TabsRoot.vue'
6
+ import type { TabsTabValue } from '../tab/TabsTab.vue'
7
+ import { computed, ref, useAttrs, watch } from 'vue'
8
+ import { useCompositeListItem } from '../../composite/list/useCompositeListItem'
9
+ import { mergeProps } from '../../merge-props/mergeProps'
10
+ import { transitionStatusMapping } from '../../utils/transitionStatusMapping'
11
+ import { useBaseUiId } from '../../utils/useBaseUiId'
12
+ import { useOpenChangeComplete } from '../../utils/useOpenChangeComplete'
13
+ import { useRenderElement } from '../../utils/useRenderElement'
14
+ import { useTransitionStatus } from '../../utils/useTransitionStatus'
15
+ import { tabsStateAttributesMapping } from '../root/stateAttributesMapping'
16
+ import { useTabsRootContext } from '../root/TabsRootContext'
17
+ import { areTabValuesEqual } from '../utils/areTabValuesEqual'
18
+ import { TabsPanelDataAttributes } from './TabsPanelDataAttributes'
19
+
20
+ export interface TabsPanelMetadata {
21
+ id?: string | undefined
22
+ value: TabsTabValue
23
+ }
24
+
25
+ export interface TabsPanelState extends TabsRootState {
26
+ /**
27
+ * Whether the component is hidden.
28
+ */
29
+ hidden: boolean
30
+ /**
31
+ * The transition status of the component.
32
+ */
33
+ transitionStatus: TransitionStatus
34
+ }
35
+
36
+ export interface TabsPanelProps extends BaseUIComponentProps<TabsPanelState> {
37
+ /**
38
+ * The value of the TabPanel. It will be shown when the Tab with the corresponding value is active.
39
+ */
40
+ value: TabsTabValue
41
+ /**
42
+ * Whether to keep the HTML element in the DOM while the panel is hidden.
43
+ * @default false
44
+ */
45
+ keepMounted?: boolean
46
+ /**
47
+ * The id of the TabPanel element.
48
+ */
49
+ id?: string
50
+ }
51
+
52
+ /**
53
+ * A panel displayed when the corresponding tab is active.
54
+ * Renders a `<div>` element.
55
+ *
56
+ * Documentation: [Base UI Vue Tabs](https://baseui-vue.com/docs/components/tabs)
57
+ */
58
+ defineOptions({
59
+ name: 'TabsPanel',
60
+ inheritAttrs: false,
61
+ })
62
+
63
+ const props = withDefaults(defineProps<TabsPanelProps>(), {
64
+ as: 'div',
65
+ keepMounted: false,
66
+ })
67
+
68
+ const attrs = useAttrs()
69
+ const rootCtx = useTabsRootContext()
70
+ const id = useBaseUiId(props.id)
71
+ const panelRef = ref<HTMLElement | null>(null)
72
+
73
+ const metadata = computed<TabsPanelMetadata>(() => ({
74
+ id,
75
+ value: props.value,
76
+ }))
77
+
78
+ const { ref: listItemRef, index } = useCompositeListItem<TabsPanelMetadata>({
79
+ metadata: () => metadata.value,
80
+ })
81
+
82
+ const open = computed(() => areTabValuesEqual(props.value, rootCtx.value.value))
83
+ const { mounted, transitionStatus, setMounted } = useTransitionStatus(open)
84
+ const hidden = computed(() => !mounted.value)
85
+ const correspondingTabId = computed(() => rootCtx.getTabIdByPanelValue(props.value))
86
+
87
+ const state = computed<TabsPanelState>(() => ({
88
+ hidden: hidden.value,
89
+ orientation: rootCtx.orientation.value,
90
+ tabActivationDirection: rootCtx.tabActivationDirection.value,
91
+ transitionStatus: transitionStatus.value,
92
+ }))
93
+
94
+ useOpenChangeComplete({
95
+ open,
96
+ ref: panelRef,
97
+ onComplete() {
98
+ if (!open.value) {
99
+ setMounted(false)
100
+ }
101
+ },
102
+ })
103
+
104
+ watch(
105
+ [hidden, () => props.keepMounted, () => props.value],
106
+ (_value, _oldValue, onCleanup) => {
107
+ if (hidden.value && !props.keepMounted) {
108
+ return
109
+ }
110
+
111
+ if (id == null) {
112
+ return
113
+ }
114
+
115
+ rootCtx.registerMountedTabPanel(props.value, id)
116
+ onCleanup(() => {
117
+ rootCtx.unregisterMountedTabPanel(props.value, id)
118
+ })
119
+ },
120
+ { immediate: true },
121
+ )
122
+
123
+ const shouldRender = computed(() => props.keepMounted || mounted.value)
124
+
125
+ const panelProps = computed(() => mergeProps(
126
+ {
127
+ 'aria-labelledby': correspondingTabId.value,
128
+ 'hidden': hidden.value ? true : undefined,
129
+ id,
130
+ 'inert': !open.value ? '' : undefined,
131
+ 'role': 'tabpanel',
132
+ 'tabindex': open.value ? 0 : -1,
133
+ [TabsPanelDataAttributes.index]: index.value,
134
+ },
135
+ attrs as Record<string, unknown>,
136
+ ))
137
+
138
+ const stateAttributesMapping = {
139
+ ...tabsStateAttributesMapping,
140
+ ...transitionStatusMapping,
141
+ }
142
+
143
+ const {
144
+ tag,
145
+ mergedProps,
146
+ renderless,
147
+ ref: renderRef,
148
+ } = useRenderElement({
149
+ componentProps: props,
150
+ state,
151
+ props: panelProps,
152
+ stateAttributesMapping,
153
+ defaultTagName: 'div',
154
+ ref: (node: Element | ComponentPublicInstance | null) => {
155
+ const element = node instanceof HTMLElement ? node : null
156
+ panelRef.value = element
157
+ listItemRef(element)
158
+ },
159
+ })
160
+ </script>
161
+
162
+ <template>
163
+ <slot v-if="renderless && shouldRender" :ref="renderRef" :props="mergedProps" :state="state" />
164
+ <component :is="tag" v-else-if="shouldRender" :ref="renderRef" v-bind="mergedProps">
165
+ <slot :state="state" />
166
+ </component>
167
+ </template>
@@ -0,0 +1,28 @@
1
+ export enum TabsPanelDataAttributes {
2
+ /**
3
+ * Indicates the index of the tab panel.
4
+ */
5
+ index = 'data-index',
6
+ /**
7
+ * Indicates the direction of the activation (based on the previous active tab).
8
+ * @type {'left' | 'right' | 'up' | 'down' | 'none'}
9
+ */
10
+ activationDirection = 'data-activation-direction',
11
+ /**
12
+ * Indicates the orientation of the tabs.
13
+ * @type {'horizontal' | 'vertical'}
14
+ */
15
+ orientation = 'data-orientation',
16
+ /**
17
+ * Present when the panel is hidden.
18
+ */
19
+ hidden = 'data-hidden',
20
+ /**
21
+ * Present when the panel is animating in.
22
+ */
23
+ startingStyle = 'data-starting-style',
24
+ /**
25
+ * Present when the panel is animating out.
26
+ */
27
+ endingStyle = 'data-ending-style',
28
+ }