base-ui-vue 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +19 -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 +3 -3
- 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 +868 -0
- package/dist/decrement/NumberFieldDecrement.cjs.map +1 -0
- package/dist/decrement/NumberFieldDecrement.js +701 -0
- package/dist/decrement/NumberFieldDecrement.js.map +1 -0
- 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 +4 -48
- package/dist/fallback/AvatarFallback.cjs.map +1 -1
- package/dist/fallback/AvatarFallback.js +4 -42
- package/dist/fallback/AvatarFallback.js.map +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 +72 -0
- package/dist/group/NumberFieldGroup.cjs.map +1 -0
- package/dist/group/NumberFieldGroup.js +67 -0
- package/dist/group/NumberFieldGroup.js.map +1 -0
- 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 +113 -0
- package/dist/increment/NumberFieldIncrement.cjs.map +1 -0
- package/dist/increment/NumberFieldIncrement.js +108 -0
- package/dist/increment/NumberFieldIncrement.js.map +1 -0
- package/dist/index.cjs +80 -8
- package/dist/index.d.cts +4328 -1600
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +4328 -1600
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -4
- package/dist/index2.cjs +9435 -3236
- package/dist/index2.cjs.map +1 -1
- package/dist/index2.js +8905 -3049
- 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 +7 -1
- package/src/number-field/decrement/NumberFieldDecrement.vue +109 -0
- package/src/number-field/group/NumberFieldGroup.vue +47 -0
- package/src/number-field/increment/NumberFieldIncrement.vue +109 -0
- package/src/number-field/index.ts +42 -0
- package/src/number-field/input/NumberFieldInput.vue +455 -0
- package/src/number-field/root/NumberFieldRoot.vue +626 -0
- package/src/number-field/root/NumberFieldRootContext.ts +94 -0
- package/src/number-field/root/useNumberFieldButton.ts +171 -0
- package/src/number-field/scrub-area/NumberFieldScrubArea.vue +359 -0
- package/src/number-field/scrub-area/NumberFieldScrubAreaContext.ts +26 -0
- package/src/number-field/scrub-area-cursor/NumberFieldScrubAreaCursor.vue +75 -0
- package/src/number-field/utils/constants.ts +4 -0
- package/src/number-field/utils/getViewportRect.ts +34 -0
- package/src/number-field/utils/parse.ts +248 -0
- package/src/number-field/utils/stateAttributesMapping.ts +9 -0
- package/src/number-field/utils/subscribeToVisualViewportResize.ts +27 -0
- package/src/number-field/utils/types.ts +24 -0
- package/src/number-field/utils/validate.ts +120 -0
- package/src/otp-field/index.ts +22 -0
- package/src/otp-field/input/OtpFieldInput.vue +336 -0
- package/src/otp-field/root/OtpFieldRoot.vue +583 -0
- package/src/otp-field/root/OtpFieldRootContext.ts +81 -0
- package/src/otp-field/utils/otp.ts +135 -0
- package/src/otp-field/utils/stateAttributesMapping.ts +16 -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/detectBrowser.ts +15 -0
- package/src/utils/formatNumber.ts +35 -2
- 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/useInterval.ts +45 -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/usePressAndHold.ts +260 -0
- package/src/utils/useRenderElement.ts +3 -0
- package/src/utils/useValueChanged.ts +21 -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
|
+
}
|