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.
- package/dist/arrow/TooltipArrow.cjs +609 -0
- package/dist/arrow/TooltipArrow.cjs.map +1 -0
- package/dist/arrow/TooltipArrow.js +478 -0
- package/dist/arrow/TooltipArrow.js.map +1 -0
- package/dist/button/Button.cjs +5 -317
- package/dist/button/Button.cjs.map +1 -1
- package/dist/button/Button.js +3 -249
- package/dist/button/Button.js.map +1 -1
- package/dist/button/ToolbarButton.cjs +13 -6
- package/dist/button/ToolbarButton.cjs.map +1 -1
- package/dist/button/ToolbarButton.js +3 -2
- package/dist/button/ToolbarButton.js.map +1 -1
- package/dist/checkbox/index.cjs +20 -9
- package/dist/checkbox/index.cjs.map +1 -1
- package/dist/checkbox/index.js +7 -2
- package/dist/checkbox/index.js.map +1 -1
- package/dist/checkbox-group/CheckboxGroup.cjs +14 -13
- package/dist/checkbox-group/CheckboxGroup.cjs.map +1 -1
- package/dist/checkbox-group/CheckboxGroup.js +2 -1
- package/dist/checkbox-group/CheckboxGroup.js.map +1 -1
- package/dist/components/FloatingPortal.cjs +66 -0
- package/dist/components/FloatingPortal.cjs.map +1 -0
- package/dist/components/FloatingPortal.js +55 -0
- package/dist/components/FloatingPortal.js.map +1 -0
- package/dist/components/FloatingRootStore.cjs +101 -0
- package/dist/components/FloatingRootStore.cjs.map +1 -0
- package/dist/components/FloatingRootStore.js +84 -0
- package/dist/components/FloatingRootStore.js.map +1 -0
- package/dist/components/FloatingTree.cjs +33 -0
- package/dist/components/FloatingTree.cjs.map +1 -0
- package/dist/components/FloatingTree.js +22 -0
- package/dist/components/FloatingTree.js.map +1 -0
- package/dist/composite/composite.cjs +6 -0
- package/dist/composite/composite.js +1 -1
- package/dist/content/ScrollAreaContent.cjs +3 -3
- package/dist/content/ScrollAreaContent.cjs.map +1 -1
- package/dist/content/ScrollAreaContent.js +1 -1
- package/dist/control/FieldControl.cjs +3 -3
- package/dist/control/FieldControl.cjs.map +1 -1
- package/dist/control/FieldControl.js +1 -1
- package/dist/control/SliderControl.cjs +3 -3
- package/dist/control/SliderControl.cjs.map +1 -1
- package/dist/control/SliderControl.js +1 -1
- package/dist/corner/ScrollAreaCorner.cjs +3 -3
- package/dist/corner/ScrollAreaCorner.cjs.map +1 -1
- package/dist/corner/ScrollAreaCorner.js +1 -1
- package/dist/decrement/NumberFieldDecrement.cjs +10 -3
- package/dist/decrement/NumberFieldDecrement.cjs.map +1 -1
- package/dist/decrement/NumberFieldDecrement.js +3 -2
- package/dist/decrement/NumberFieldDecrement.js.map +1 -1
- package/dist/description/FieldDescription.cjs +2 -2
- package/dist/description/FieldDescription.cjs.map +1 -1
- package/dist/description/FieldDescription.js +1 -1
- package/dist/error/FieldError.cjs +2 -2
- package/dist/error/FieldError.cjs.map +1 -1
- package/dist/error/FieldError.js +1 -1
- package/dist/fallback/AvatarFallback.cjs +2 -2
- package/dist/fallback/AvatarFallback.cjs.map +1 -1
- package/dist/fallback/AvatarFallback.js +1 -1
- package/dist/floating-ui-vue/index.js +638 -0
- package/dist/floating-ui-vue/index.js.map +1 -0
- package/dist/form/Form.cjs +3 -3
- package/dist/form/Form.cjs.map +1 -1
- package/dist/form/Form.js +1 -1
- package/dist/group/NumberFieldGroup.cjs +3 -3
- package/dist/group/NumberFieldGroup.cjs.map +1 -1
- package/dist/group/NumberFieldGroup.js +1 -1
- package/dist/group/ToolbarGroup.cjs +2 -2
- package/dist/group/ToolbarGroup.cjs.map +1 -1
- package/dist/group/ToolbarGroup.js +1 -1
- package/dist/header/AccordionHeader.cjs +2 -2
- package/dist/header/AccordionHeader.cjs.map +1 -1
- package/dist/header/AccordionHeader.js +1 -1
- package/dist/hooks/useDismiss.cjs +502 -0
- package/dist/hooks/useDismiss.cjs.map +1 -0
- package/dist/hooks/useHoverFloatingInteraction.cjs +233 -0
- package/dist/hooks/useHoverFloatingInteraction.cjs.map +1 -0
- package/dist/image/AvatarImage.cjs +4 -4
- package/dist/image/AvatarImage.cjs.map +1 -1
- package/dist/image/AvatarImage.js +1 -1
- package/dist/increment/NumberFieldIncrement.cjs +3 -2
- package/dist/increment/NumberFieldIncrement.cjs.map +1 -1
- package/dist/increment/NumberFieldIncrement.js +2 -1
- package/dist/increment/NumberFieldIncrement.js.map +1 -1
- package/dist/index.cjs +60 -7
- package/dist/index.d.cts +3838 -1726
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +3838 -1726
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index2.cjs +7475 -3089
- package/dist/index2.cjs.map +1 -1
- package/dist/index2.js +7215 -3106
- package/dist/index2.js.map +1 -1
- package/dist/rolldown-runtime.js +36 -0
- package/package.json +1 -1
- package/src/floating-ui-vue/components/FloatingPortal.ts +171 -0
- package/src/floating-ui-vue/components/FloatingPortal.vue +259 -0
- package/src/floating-ui-vue/components/FloatingRootStore.ts +83 -0
- package/src/floating-ui-vue/components/FloatingTree.ts +64 -0
- package/src/floating-ui-vue/components/FloatingTreeStore.ts +21 -0
- package/src/floating-ui-vue/hooks/useDismiss.ts +689 -0
- package/src/floating-ui-vue/hooks/useHoverFloatingInteraction.ts +244 -0
- package/src/floating-ui-vue/hooks/useHoverInteractionSharedState.ts +125 -0
- package/src/floating-ui-vue/hooks/useHoverShared.ts +53 -0
- package/src/floating-ui-vue/index.ts +48 -0
- package/src/floating-ui-vue/middleware/arrow.ts +114 -0
- package/src/floating-ui-vue/types.ts +117 -0
- package/src/floating-ui-vue/utils/composite.ts +20 -0
- package/src/floating-ui-vue/utils/constants.ts +2 -0
- package/src/floating-ui-vue/utils/createAttribute.ts +3 -0
- package/src/floating-ui-vue/utils/createEventEmitter.ts +20 -0
- package/src/floating-ui-vue/utils/element.ts +59 -0
- package/src/floating-ui-vue/utils/event.ts +15 -0
- package/src/floating-ui-vue/utils/nodes.ts +14 -0
- package/src/floating-ui-vue/utils/tabbable.ts +309 -0
- package/src/index.ts +4 -0
- package/src/radio/index.ts +28 -0
- package/src/radio/indicator/RadioIndicator.vue +90 -0
- package/src/radio/indicator/RadioIndicatorDataAttributes.ts +54 -0
- package/src/radio/root/RadioRoot.vue +544 -0
- package/src/radio/root/RadioRootContext.ts +18 -0
- package/src/radio/root/RadioRootDataAttributes.ts +46 -0
- package/src/radio/utils/stateAttributesMapping.ts +29 -0
- package/src/radio-group/RadioGroup.vue +381 -0
- package/src/radio-group/RadioGroupContext.ts +42 -0
- package/src/radio-group/RadioGroupDataAttributes.ts +6 -0
- package/src/radio-group/index.ts +10 -0
- package/src/tabs/index.ts +38 -0
- package/src/tabs/indicator/TabsIndicator.vue +201 -0
- package/src/tabs/indicator/TabsIndicatorCssVars.ts +26 -0
- package/src/tabs/indicator/TabsIndicatorDataAttributes.ts +12 -0
- package/src/tabs/indicator/prehydrationScript.min.ts +5 -0
- package/src/tabs/indicator/prehydrationScript.template.js +87 -0
- package/src/tabs/list/TabsList.vue +201 -0
- package/src/tabs/list/TabsListContext.ts +29 -0
- package/src/tabs/list/TabsListDataAttributes.ts +12 -0
- package/src/tabs/panel/TabsPanel.vue +167 -0
- package/src/tabs/panel/TabsPanelDataAttributes.ts +28 -0
- package/src/tabs/root/TabsRoot.vue +468 -0
- package/src/tabs/root/TabsRootContext.ts +56 -0
- package/src/tabs/root/TabsRootDataAttributes.ts +12 -0
- package/src/tabs/root/stateAttributesMapping.ts +9 -0
- package/src/tabs/tab/TabsTab.vue +274 -0
- package/src/tabs/tab/TabsTabDataAttributes.ts +20 -0
- package/src/tabs/utils/areTabValuesEqual.ts +6 -0
- package/src/tooltip/arrow/TooltipArrow.vue +116 -0
- package/src/tooltip/arrow/TooltipArrowDataAttributes.ts +31 -0
- package/src/tooltip/index.ts +54 -0
- package/src/tooltip/popup/TooltipPopup.vue +142 -0
- package/src/tooltip/popup/TooltipPopupDataAttributes.ts +35 -0
- package/src/tooltip/portal/TooltipPortal.vue +83 -0
- package/src/tooltip/portal/TooltipPortalContext.ts +19 -0
- package/src/tooltip/positioner/TooltipPositioner.vue +221 -0
- package/src/tooltip/positioner/TooltipPositionerContext.ts +23 -0
- package/src/tooltip/positioner/TooltipPositionerCssVars.ts +27 -0
- package/src/tooltip/positioner/TooltipPositionerDataAttributes.ts +26 -0
- package/src/tooltip/provider/TooltipProvider.vue +78 -0
- package/src/tooltip/provider/TooltipProviderContext.ts +19 -0
- package/src/tooltip/root/TooltipRoot.vue +401 -0
- package/src/tooltip/root/TooltipRootContext.ts +71 -0
- package/src/tooltip/store/TooltipHandle.ts +215 -0
- package/src/tooltip/trigger/TooltipTrigger.vue +343 -0
- package/src/tooltip/trigger/TooltipTriggerDataAttributes.ts +12 -0
- package/src/tooltip/utils/constants.ts +2 -0
- package/src/tooltip/viewport/TooltipViewport.vue +149 -0
- package/src/tooltip/viewport/TooltipViewportCssVars.ts +14 -0
- package/src/tooltip/viewport/TooltipViewportDataAttributes.ts +26 -0
- package/src/utils/FloatingPortalLite.vue +66 -0
- package/src/utils/FocusGuard.vue +36 -0
- package/src/utils/adaptiveOriginMiddleware.ts +79 -0
- package/src/utils/addEventListener.ts +16 -0
- package/src/utils/constants.ts +11 -0
- package/src/utils/createBaseUIEventDetails.ts +2 -0
- package/src/utils/getCssDimensions.ts +19 -0
- package/src/utils/getDisabledMountTransitionStyles.ts +9 -0
- package/src/utils/hideMiddleware.ts +19 -0
- package/src/utils/mergeCleanups.ts +7 -0
- package/src/utils/platform.ts +18 -0
- package/src/utils/popupStateMapping.ts +103 -0
- package/src/utils/reasons.ts +4 -0
- package/src/utils/serializeValue.ts +17 -0
- package/src/utils/useAnchorPositioning.ts +644 -0
- package/src/utils/usePopupAutoResize.ts +254 -0
- package/src/utils/usePopupViewport.ts +398 -0
- package/src/utils/usePositioner.ts +61 -0
- 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
|
+
}
|