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,381 @@
|
|
|
1
|
+
<script setup lang="ts" generic="Value = unknown">
|
|
2
|
+
import type { InjectionKey, Ref } from 'vue'
|
|
3
|
+
import type { FieldRootState } from '../field/root/FieldRoot.vue'
|
|
4
|
+
import type { BaseUIChangeEventDetails } from '../utils/createBaseUIEventDetails'
|
|
5
|
+
import type { REASONS } from '../utils/reasons'
|
|
6
|
+
import type { BaseUIComponentProps } from '../utils/types'
|
|
7
|
+
import type { RadioGroupContext } from './RadioGroupContext'
|
|
8
|
+
import { computed, provide, ref, useAttrs, watch } from 'vue'
|
|
9
|
+
import { SHIFT } from '../composite/composite'
|
|
10
|
+
import CompositeRoot from '../composite/root/CompositeRoot.vue'
|
|
11
|
+
import { useFieldRootContext } from '../field/root/FieldRootContext'
|
|
12
|
+
import { useField } from '../field/useField'
|
|
13
|
+
import { fieldValidityMapping } from '../field/utils/constants'
|
|
14
|
+
import { useFieldsetRootContext } from '../fieldset/root/FieldsetRootContext'
|
|
15
|
+
import { useFormContext } from '../form/FormContext'
|
|
16
|
+
import { useLabelableContext } from '../labelable-provider/LabelableContext'
|
|
17
|
+
import { mergeProps } from '../merge-props/mergeProps'
|
|
18
|
+
import { Slot } from '../utils/slot'
|
|
19
|
+
import { useBaseUiId } from '../utils/useBaseUiId'
|
|
20
|
+
import { useControllableState } from '../utils/useControllableState'
|
|
21
|
+
import { useMergedRefs } from '../utils/useMergedRefs'
|
|
22
|
+
import { radioGroupContextKey } from './RadioGroupContext'
|
|
23
|
+
|
|
24
|
+
export interface RadioGroupState extends FieldRootState {
|
|
25
|
+
/**
|
|
26
|
+
* Whether the component should ignore user interaction.
|
|
27
|
+
*/
|
|
28
|
+
disabled: boolean
|
|
29
|
+
/**
|
|
30
|
+
* Whether the user should be unable to select a different radio button.
|
|
31
|
+
*/
|
|
32
|
+
readOnly: boolean
|
|
33
|
+
/**
|
|
34
|
+
* Whether the user must choose a radio button before submitting a form.
|
|
35
|
+
*/
|
|
36
|
+
required: boolean
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface RadioGroupProps<Value = unknown> extends BaseUIComponentProps<RadioGroupState> {
|
|
40
|
+
/**
|
|
41
|
+
* Whether the component should ignore user interaction.
|
|
42
|
+
* @default false
|
|
43
|
+
*/
|
|
44
|
+
disabled?: boolean
|
|
45
|
+
/**
|
|
46
|
+
* Whether the user should be unable to select a different radio button.
|
|
47
|
+
* @default false
|
|
48
|
+
*/
|
|
49
|
+
readOnly?: boolean
|
|
50
|
+
/**
|
|
51
|
+
* Whether the user must choose a value before submitting a form.
|
|
52
|
+
* @default false
|
|
53
|
+
*/
|
|
54
|
+
required?: boolean
|
|
55
|
+
/**
|
|
56
|
+
* Identifies the field when a form is submitted.
|
|
57
|
+
*/
|
|
58
|
+
name?: string
|
|
59
|
+
/**
|
|
60
|
+
* Identifies the form that owns the radio inputs.
|
|
61
|
+
* Useful when the radio group is rendered outside the form.
|
|
62
|
+
*/
|
|
63
|
+
form?: string
|
|
64
|
+
/**
|
|
65
|
+
* The controlled value of the radio item that should be selected.
|
|
66
|
+
*
|
|
67
|
+
* To render an uncontrolled radio group, use the `defaultValue` prop instead.
|
|
68
|
+
*/
|
|
69
|
+
value?: Value
|
|
70
|
+
/**
|
|
71
|
+
* The uncontrolled value of the radio button that should be initially selected.
|
|
72
|
+
*
|
|
73
|
+
* To render a controlled radio group, use the `value` prop instead.
|
|
74
|
+
*/
|
|
75
|
+
defaultValue?: Value
|
|
76
|
+
/**
|
|
77
|
+
* A ref to access the currently selected hidden input element.
|
|
78
|
+
*/
|
|
79
|
+
inputRef?: Ref<HTMLInputElement | null> | ((element: HTMLInputElement | null) => void) | null
|
|
80
|
+
/**
|
|
81
|
+
* The id of the radio group element.
|
|
82
|
+
*/
|
|
83
|
+
id?: string
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export type RadioGroupChangeEventReason = typeof REASONS.none
|
|
87
|
+
export type RadioGroupChangeEventDetails = BaseUIChangeEventDetails<RadioGroupChangeEventReason>
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Provides shared state to a series of radio buttons.
|
|
91
|
+
* Renders a `<div>` element.
|
|
92
|
+
*
|
|
93
|
+
* Documentation: [Base UI Vue Radio](https://baseui-vue.com/docs/components/radio)
|
|
94
|
+
*/
|
|
95
|
+
defineOptions({
|
|
96
|
+
name: 'RadioGroup',
|
|
97
|
+
inheritAttrs: false,
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
const props = withDefaults(defineProps<RadioGroupProps<Value>>(), {
|
|
101
|
+
as: 'div',
|
|
102
|
+
disabled: false,
|
|
103
|
+
readOnly: false,
|
|
104
|
+
required: false,
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
const emit = defineEmits<{
|
|
108
|
+
/**
|
|
109
|
+
* Event handler called when the group value changes.
|
|
110
|
+
*/
|
|
111
|
+
valueChange: [
|
|
112
|
+
value: Value,
|
|
113
|
+
eventDetails: BaseUIChangeEventDetails<typeof REASONS.none>,
|
|
114
|
+
]
|
|
115
|
+
}>()
|
|
116
|
+
|
|
117
|
+
const MODIFIER_KEYS = [SHIFT]
|
|
118
|
+
|
|
119
|
+
const attrs = useAttrs()
|
|
120
|
+
const attrsObject = attrs as Record<string, unknown>
|
|
121
|
+
|
|
122
|
+
const {
|
|
123
|
+
disabled: fieldDisabled,
|
|
124
|
+
name: fieldName,
|
|
125
|
+
state: fieldState,
|
|
126
|
+
validation,
|
|
127
|
+
setDirty,
|
|
128
|
+
setFilled,
|
|
129
|
+
setFocused,
|
|
130
|
+
setTouched: setFieldTouched,
|
|
131
|
+
validationMode,
|
|
132
|
+
shouldValidateOnChange,
|
|
133
|
+
validityData,
|
|
134
|
+
} = useFieldRootContext()
|
|
135
|
+
const fieldsetContext = useFieldsetRootContext(true)
|
|
136
|
+
const labelableContext = useLabelableContext()
|
|
137
|
+
const { clearErrors } = useFormContext()
|
|
138
|
+
|
|
139
|
+
const disabled = computed(() =>
|
|
140
|
+
fieldDisabled.value
|
|
141
|
+
|| Boolean(fieldsetContext?.disabled.value)
|
|
142
|
+
|| props.disabled,
|
|
143
|
+
)
|
|
144
|
+
const name = computed(() => fieldName.value ?? props.name)
|
|
145
|
+
const groupId = useBaseUiId(props.id)
|
|
146
|
+
const id = computed(() => props.id ?? groupId)
|
|
147
|
+
|
|
148
|
+
const { value: checkedValue, setValue: setCheckedValueState } = useControllableState<unknown>({
|
|
149
|
+
controlled: () => props.value,
|
|
150
|
+
default: () => props.defaultValue,
|
|
151
|
+
name: 'RadioGroup',
|
|
152
|
+
state: 'value',
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
const touched = ref(false)
|
|
156
|
+
const controlRef = ref<HTMLElement | null>(null)
|
|
157
|
+
const groupInputRef = ref<HTMLInputElement | null>(null)
|
|
158
|
+
let currentInputElement: HTMLInputElement | null = null
|
|
159
|
+
let firstEnabledInputElement: HTMLInputElement | null = null
|
|
160
|
+
const setInputRef = useMergedRefs(
|
|
161
|
+
groupInputRef,
|
|
162
|
+
props.inputRef,
|
|
163
|
+
(element: HTMLInputElement | null) => {
|
|
164
|
+
validation.setInputRef(element)
|
|
165
|
+
},
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
function assignInputRef(element: HTMLInputElement | null) {
|
|
169
|
+
if (currentInputElement === element) {
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
currentInputElement = element
|
|
174
|
+
setInputRef?.(element)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function setCheckedValue(
|
|
178
|
+
value: Value,
|
|
179
|
+
eventDetails: BaseUIChangeEventDetails<typeof REASONS.none>,
|
|
180
|
+
) {
|
|
181
|
+
emit('valueChange', value, eventDetails)
|
|
182
|
+
|
|
183
|
+
if (eventDetails.isCanceled) {
|
|
184
|
+
return
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
setCheckedValueState(value)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function registerControlRef(element: HTMLElement | null, isDisabled = false) {
|
|
191
|
+
if (!element) {
|
|
192
|
+
return
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (isDisabled) {
|
|
196
|
+
if (controlRef.value === element) {
|
|
197
|
+
controlRef.value = null
|
|
198
|
+
}
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (!controlRef.value) {
|
|
203
|
+
controlRef.value = element
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function registerInputRef(input: HTMLInputElement | null, checked = Boolean(input?.checked)) {
|
|
208
|
+
if (!input || input.disabled) {
|
|
209
|
+
return
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (!firstEnabledInputElement) {
|
|
213
|
+
firstEnabledInputElement = input
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (checked || currentInputElement == null || currentInputElement.disabled) {
|
|
217
|
+
assignInputRef(input)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function getFormValue() {
|
|
222
|
+
const input = currentInputElement
|
|
223
|
+
if (!input || input.disabled || !input.checked) {
|
|
224
|
+
return null
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return checkedValue.value ?? null
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function combineDescriptionProps(
|
|
231
|
+
...sources: object[]
|
|
232
|
+
) {
|
|
233
|
+
const describedBy = Array.from(
|
|
234
|
+
new Set(
|
|
235
|
+
sources
|
|
236
|
+
.map(source => (source as { 'aria-describedby'?: unknown })['aria-describedby'])
|
|
237
|
+
.filter(Boolean)
|
|
238
|
+
.flatMap(value => String(value).split(/\s+/).filter(Boolean)),
|
|
239
|
+
),
|
|
240
|
+
).join(' ') || undefined
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
...Object.assign({}, ...sources),
|
|
244
|
+
'aria-describedby': describedBy,
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
useField({
|
|
249
|
+
enabled: computed(() => Boolean(name.value)),
|
|
250
|
+
id,
|
|
251
|
+
commit: (value: unknown) => validation.commit(value),
|
|
252
|
+
value: checkedValue as Ref<unknown>,
|
|
253
|
+
controlRef,
|
|
254
|
+
name,
|
|
255
|
+
getValue: getFormValue,
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
watch(
|
|
259
|
+
() => checkedValue.value,
|
|
260
|
+
(nextValue) => {
|
|
261
|
+
clearErrors(name.value)
|
|
262
|
+
setDirty(nextValue !== validityData.value.initialValue)
|
|
263
|
+
setFilled(nextValue != null)
|
|
264
|
+
|
|
265
|
+
if (shouldValidateOnChange()) {
|
|
266
|
+
void validation.commit(nextValue)
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
void validation.commit(nextValue, true)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const fallbackInput = firstEnabledInputElement
|
|
273
|
+
if (nextValue == null && fallbackInput && !fallbackInput.disabled) {
|
|
274
|
+
assignInputRef(fallbackInput)
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
function handleFocusIn() {
|
|
280
|
+
setFocused(true)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function handleFocusOut(event: FocusEvent) {
|
|
284
|
+
const currentTarget = event.currentTarget as HTMLElement
|
|
285
|
+
const relatedTarget = event.relatedTarget as Node | null
|
|
286
|
+
if (relatedTarget && currentTarget.contains(relatedTarget)) {
|
|
287
|
+
return
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
setFieldTouched(true)
|
|
291
|
+
setFocused(false)
|
|
292
|
+
|
|
293
|
+
if (validationMode.value === 'onBlur') {
|
|
294
|
+
void validation.commit(checkedValue.value)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function handleKeydownCapture(event: KeyboardEvent) {
|
|
299
|
+
if (event.key.startsWith('Arrow')) {
|
|
300
|
+
touched.value = true
|
|
301
|
+
setFocused(true)
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const state = computed<RadioGroupState>(() => ({
|
|
306
|
+
...fieldState.value,
|
|
307
|
+
disabled: disabled.value,
|
|
308
|
+
readOnly: props.readOnly,
|
|
309
|
+
required: props.required,
|
|
310
|
+
}))
|
|
311
|
+
|
|
312
|
+
const contextValue: RadioGroupContext<Value> = {
|
|
313
|
+
disabled,
|
|
314
|
+
readOnly: computed(() => props.readOnly),
|
|
315
|
+
required: computed(() => props.required),
|
|
316
|
+
form: computed(() => props.form),
|
|
317
|
+
name,
|
|
318
|
+
checkedValue: checkedValue as Readonly<Ref<Value | undefined>>,
|
|
319
|
+
touched,
|
|
320
|
+
validation,
|
|
321
|
+
setCheckedValue,
|
|
322
|
+
setTouched(value) {
|
|
323
|
+
touched.value = value
|
|
324
|
+
},
|
|
325
|
+
registerControlRef,
|
|
326
|
+
registerInputRef,
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
provide(radioGroupContextKey as InjectionKey<RadioGroupContext<Value>>, contextValue)
|
|
330
|
+
|
|
331
|
+
const forwardedAttrs = computed(() => {
|
|
332
|
+
const {
|
|
333
|
+
class: _class,
|
|
334
|
+
style: _style,
|
|
335
|
+
'aria-describedby': _ariaDescribedBy,
|
|
336
|
+
...rest
|
|
337
|
+
} = attrsObject
|
|
338
|
+
return rest
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
const rootProps = computed(() => mergeProps(
|
|
342
|
+
combineDescriptionProps(
|
|
343
|
+
{ 'aria-describedby': attrsObject['aria-describedby'] },
|
|
344
|
+
labelableContext.getDescriptionProps(),
|
|
345
|
+
validation.getValidationProps(),
|
|
346
|
+
),
|
|
347
|
+
{
|
|
348
|
+
'id': id.value,
|
|
349
|
+
'role': 'radiogroup',
|
|
350
|
+
'aria-required': props.required || undefined,
|
|
351
|
+
'aria-disabled': disabled.value || undefined,
|
|
352
|
+
'aria-readonly': props.readOnly || undefined,
|
|
353
|
+
'aria-labelledby': attrsObject['aria-labelledby'] ?? labelableContext.labelId.value ?? fieldsetContext?.legendId.value,
|
|
354
|
+
'onFocusin': handleFocusIn,
|
|
355
|
+
'onFocusout': handleFocusOut,
|
|
356
|
+
'onKeydownCapture': handleKeydownCapture,
|
|
357
|
+
},
|
|
358
|
+
forwardedAttrs.value,
|
|
359
|
+
))
|
|
360
|
+
</script>
|
|
361
|
+
|
|
362
|
+
<template>
|
|
363
|
+
<CompositeRoot
|
|
364
|
+
:as="as"
|
|
365
|
+
:class="props.class"
|
|
366
|
+
:style="props.style"
|
|
367
|
+
:state="state"
|
|
368
|
+
:state-attributes-mapping="fieldValidityMapping"
|
|
369
|
+
:enable-home-and-end-keys="false"
|
|
370
|
+
:modifier-keys="MODIFIER_KEYS"
|
|
371
|
+
role="radiogroup"
|
|
372
|
+
v-bind="rootProps"
|
|
373
|
+
>
|
|
374
|
+
<template v-if="as === Slot" #default="{ ref: rootRef, props: compositeProps, state: compositeState }">
|
|
375
|
+
<slot :ref="rootRef" :props="compositeProps" :state="compositeState" />
|
|
376
|
+
</template>
|
|
377
|
+
<template v-else #default="{ state: compositeState }">
|
|
378
|
+
<slot :state="compositeState" />
|
|
379
|
+
</template>
|
|
380
|
+
</CompositeRoot>
|
|
381
|
+
</template>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { InjectionKey, Ref } from 'vue'
|
|
2
|
+
import type { UseFieldValidationReturnValue } from '../field/root/useFieldValidation'
|
|
3
|
+
import type { BaseUIChangeEventDetails } from '../utils/createBaseUIEventDetails'
|
|
4
|
+
import type { REASONS } from '../utils/reasons'
|
|
5
|
+
import { inject } from 'vue'
|
|
6
|
+
|
|
7
|
+
export interface RadioGroupContext<Value = unknown> {
|
|
8
|
+
disabled: Readonly<Ref<boolean | undefined>>
|
|
9
|
+
readOnly: Readonly<Ref<boolean | undefined>>
|
|
10
|
+
required: Readonly<Ref<boolean | undefined>>
|
|
11
|
+
form: Readonly<Ref<string | undefined>>
|
|
12
|
+
name: Readonly<Ref<string | undefined>>
|
|
13
|
+
checkedValue: Readonly<Ref<Value | undefined>>
|
|
14
|
+
touched: Readonly<Ref<boolean>>
|
|
15
|
+
validation?: UseFieldValidationReturnValue
|
|
16
|
+
setCheckedValue: (
|
|
17
|
+
value: Value,
|
|
18
|
+
eventDetails: BaseUIChangeEventDetails<typeof REASONS.none>,
|
|
19
|
+
) => void
|
|
20
|
+
setTouched: (value: boolean) => void
|
|
21
|
+
registerControlRef: (element: HTMLElement | null, disabled?: boolean) => void
|
|
22
|
+
registerInputRef: (element: HTMLInputElement | null, checked?: boolean) => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const radioGroupContextKey: InjectionKey<RadioGroupContext<unknown>> = Symbol(
|
|
26
|
+
'RadioGroupContext',
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
export function useRadioGroupContext<Value = unknown>(
|
|
30
|
+
optional: true,
|
|
31
|
+
): RadioGroupContext<Value> | undefined
|
|
32
|
+
export function useRadioGroupContext<Value = unknown>(optional?: false): RadioGroupContext<Value>
|
|
33
|
+
export function useRadioGroupContext<Value = unknown>(optional = false) {
|
|
34
|
+
const context = inject(radioGroupContextKey, undefined) as RadioGroupContext<Value> | undefined
|
|
35
|
+
if (!context && !optional) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
'Base UI Vue: RadioGroupContext is missing. Radio parts must be placed within a radio group.',
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return context
|
|
42
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { default as RadioGroup } from './RadioGroup.vue'
|
|
2
|
+
export type {
|
|
3
|
+
RadioGroupChangeEventDetails,
|
|
4
|
+
RadioGroupChangeEventReason,
|
|
5
|
+
RadioGroupProps,
|
|
6
|
+
RadioGroupState,
|
|
7
|
+
} from './RadioGroup.vue'
|
|
8
|
+
export { radioGroupContextKey, useRadioGroupContext } from './RadioGroupContext'
|
|
9
|
+
export type { RadioGroupContext } from './RadioGroupContext'
|
|
10
|
+
export { RadioGroupDataAttributes } from './RadioGroupDataAttributes'
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export { default as TabsIndicator } from './indicator/TabsIndicator.vue'
|
|
2
|
+
export type { TabsIndicatorProps, TabsIndicatorState } from './indicator/TabsIndicator.vue'
|
|
3
|
+
export { TabsIndicatorCssVars } from './indicator/TabsIndicatorCssVars'
|
|
4
|
+
export { TabsIndicatorDataAttributes } from './indicator/TabsIndicatorDataAttributes'
|
|
5
|
+
|
|
6
|
+
export { default as TabsList } from './list/TabsList.vue'
|
|
7
|
+
export type { TabsListProps, TabsListState } from './list/TabsList.vue'
|
|
8
|
+
export { tabsListContextKey, useTabsListContext } from './list/TabsListContext'
|
|
9
|
+
export type { TabsListContext } from './list/TabsListContext'
|
|
10
|
+
export { TabsListDataAttributes } from './list/TabsListDataAttributes'
|
|
11
|
+
|
|
12
|
+
export { default as TabsPanel } from './panel/TabsPanel.vue'
|
|
13
|
+
export type { TabsPanelMetadata, TabsPanelProps, TabsPanelState } from './panel/TabsPanel.vue'
|
|
14
|
+
export { TabsPanelDataAttributes } from './panel/TabsPanelDataAttributes'
|
|
15
|
+
|
|
16
|
+
export { default as TabsRoot } from './root/TabsRoot.vue'
|
|
17
|
+
export type {
|
|
18
|
+
TabsRootChangeEventDetails,
|
|
19
|
+
TabsRootChangeEventReason,
|
|
20
|
+
TabsRootOrientation,
|
|
21
|
+
TabsRootProps,
|
|
22
|
+
TabsRootState,
|
|
23
|
+
} from './root/TabsRoot.vue'
|
|
24
|
+
export { tabsRootContextKey, useTabsRootContext } from './root/TabsRootContext'
|
|
25
|
+
export type { TabsRootContext } from './root/TabsRootContext'
|
|
26
|
+
export { TabsRootDataAttributes } from './root/TabsRootDataAttributes'
|
|
27
|
+
|
|
28
|
+
export { default as TabsTab } from './tab/TabsTab.vue'
|
|
29
|
+
export type {
|
|
30
|
+
TabsTabActivationDirection,
|
|
31
|
+
TabsTabMetadata,
|
|
32
|
+
TabsTabPosition,
|
|
33
|
+
TabsTabProps,
|
|
34
|
+
TabsTabSize,
|
|
35
|
+
TabsTabState,
|
|
36
|
+
TabsTabValue,
|
|
37
|
+
} from './tab/TabsTab.vue'
|
|
38
|
+
export { TabsTabDataAttributes } from './tab/TabsTabDataAttributes'
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { StyleValue } from 'vue'
|
|
3
|
+
import type { BaseUIComponentProps, Orientation } from '../../utils/types'
|
|
4
|
+
import type { TabsRootState } from '../root/TabsRoot.vue'
|
|
5
|
+
import type { TabsTabActivationDirection, TabsTabPosition, TabsTabSize } from '../tab/TabsTab.vue'
|
|
6
|
+
import { computed, onBeforeUnmount, onMounted, ref, useAttrs } from 'vue'
|
|
7
|
+
import { useCSPContext } from '../../csp-provider/CSPContext'
|
|
8
|
+
import { mergeProps } from '../../merge-props/mergeProps'
|
|
9
|
+
import { getCssDimensions } from '../../utils/getCssDimensions'
|
|
10
|
+
import { useRenderElement } from '../../utils/useRenderElement'
|
|
11
|
+
import { useTabsListContext } from '../list/TabsListContext'
|
|
12
|
+
import { tabsStateAttributesMapping } from '../root/stateAttributesMapping'
|
|
13
|
+
import { useTabsRootContext } from '../root/TabsRootContext'
|
|
14
|
+
import { script as prehydrationScript } from './prehydrationScript.min'
|
|
15
|
+
import { TabsIndicatorCssVars } from './TabsIndicatorCssVars'
|
|
16
|
+
|
|
17
|
+
export interface TabsIndicatorState extends TabsRootState {
|
|
18
|
+
/**
|
|
19
|
+
* The active tab position.
|
|
20
|
+
*/
|
|
21
|
+
activeTabPosition: TabsTabPosition | null
|
|
22
|
+
/**
|
|
23
|
+
* The active tab size.
|
|
24
|
+
*/
|
|
25
|
+
activeTabSize: TabsTabSize | null
|
|
26
|
+
/**
|
|
27
|
+
* The component orientation.
|
|
28
|
+
*/
|
|
29
|
+
orientation: Orientation
|
|
30
|
+
/**
|
|
31
|
+
* The direction used for tab activation.
|
|
32
|
+
*/
|
|
33
|
+
tabActivationDirection: TabsTabActivationDirection
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface TabsIndicatorProps extends BaseUIComponentProps<TabsIndicatorState> {
|
|
37
|
+
/**
|
|
38
|
+
* Whether to render itself before Vue hydrates.
|
|
39
|
+
* This minimizes the time that the indicator isn't visible after server-side rendering.
|
|
40
|
+
* @default false
|
|
41
|
+
*/
|
|
42
|
+
renderBeforeHydration?: boolean
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A visual indicator that can be styled to match the position of the currently active tab.
|
|
47
|
+
* Renders a `<span>` element.
|
|
48
|
+
*
|
|
49
|
+
* Documentation: [Base UI Vue Tabs](https://baseui-vue.com/docs/components/tabs)
|
|
50
|
+
*/
|
|
51
|
+
defineOptions({
|
|
52
|
+
name: 'TabsIndicator',
|
|
53
|
+
inheritAttrs: false,
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const props = withDefaults(defineProps<TabsIndicatorProps>(), {
|
|
57
|
+
as: 'span',
|
|
58
|
+
renderBeforeHydration: false,
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
const attrs = useAttrs()
|
|
62
|
+
const { nonce } = useCSPContext()
|
|
63
|
+
const rootCtx = useTabsRootContext()
|
|
64
|
+
const listCtx = useTabsListContext()
|
|
65
|
+
const layoutTick = ref(0)
|
|
66
|
+
const isMounted = ref(false)
|
|
67
|
+
|
|
68
|
+
const cleanupIndicatorListener = listCtx.registerIndicatorUpdateListener(() => {
|
|
69
|
+
layoutTick.value += 1
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
onMounted(() => {
|
|
73
|
+
isMounted.value = true
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
onBeforeUnmount(() => {
|
|
77
|
+
cleanupIndicatorListener()
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
const measurement = computed(() => {
|
|
81
|
+
void layoutTick.value
|
|
82
|
+
|
|
83
|
+
const tabsListElement = listCtx.tabsListElement.value
|
|
84
|
+
const selectedValue = rootCtx.value.value
|
|
85
|
+
|
|
86
|
+
if (selectedValue == null || tabsListElement == null) {
|
|
87
|
+
return null
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const activeTab = rootCtx.getTabElementBySelectedValue(selectedValue)
|
|
91
|
+
|
|
92
|
+
if (activeTab == null) {
|
|
93
|
+
return null
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const { width, height } = getCssDimensions(activeTab)
|
|
97
|
+
const { width: tabListWidth, height: tabListHeight } = getCssDimensions(tabsListElement)
|
|
98
|
+
const tabRect = activeTab.getBoundingClientRect()
|
|
99
|
+
const tabsListRect = tabsListElement.getBoundingClientRect()
|
|
100
|
+
const scaleX = tabListWidth > 0 ? tabsListRect.width / tabListWidth : 1
|
|
101
|
+
const scaleY = tabListHeight > 0 ? tabsListRect.height / tabListHeight : 1
|
|
102
|
+
const hasNonZeroScale
|
|
103
|
+
= Math.abs(scaleX) > Number.EPSILON && Math.abs(scaleY) > Number.EPSILON
|
|
104
|
+
|
|
105
|
+
let left = 0
|
|
106
|
+
let top = 0
|
|
107
|
+
|
|
108
|
+
if (hasNonZeroScale) {
|
|
109
|
+
left = (tabRect.left - tabsListRect.left) / scaleX + tabsListElement.scrollLeft - tabsListElement.clientLeft
|
|
110
|
+
top = (tabRect.top - tabsListRect.top) / scaleY + tabsListElement.scrollTop - tabsListElement.clientTop
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
left = activeTab.offsetLeft
|
|
114
|
+
top = activeTab.offsetTop
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const right = tabsListElement.scrollWidth - left - width
|
|
118
|
+
const bottom = tabsListElement.scrollHeight - top - height
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
position: { left, right, top, bottom },
|
|
122
|
+
size: { width, height },
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
const activeTabPosition = computed(() => measurement.value?.position ?? null)
|
|
127
|
+
const activeTabSize = computed(() => measurement.value?.size ?? null)
|
|
128
|
+
const displayIndicator = computed(() =>
|
|
129
|
+
activeTabSize.value != null && activeTabSize.value.width > 0 && activeTabSize.value.height > 0,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
const state = computed<TabsIndicatorState>(() => ({
|
|
133
|
+
orientation: rootCtx.orientation.value,
|
|
134
|
+
activeTabPosition: activeTabPosition.value,
|
|
135
|
+
activeTabSize: activeTabSize.value,
|
|
136
|
+
tabActivationDirection: rootCtx.tabActivationDirection.value,
|
|
137
|
+
}))
|
|
138
|
+
|
|
139
|
+
const indicatorStyle = computed<StyleValue | undefined>(() => {
|
|
140
|
+
const position = activeTabPosition.value
|
|
141
|
+
const size = activeTabSize.value
|
|
142
|
+
|
|
143
|
+
if (position == null || size == null) {
|
|
144
|
+
return undefined
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
[TabsIndicatorCssVars.activeTabLeft]: `${position.left}px`,
|
|
149
|
+
[TabsIndicatorCssVars.activeTabRight]: `${position.right}px`,
|
|
150
|
+
[TabsIndicatorCssVars.activeTabTop]: `${position.top}px`,
|
|
151
|
+
[TabsIndicatorCssVars.activeTabBottom]: `${position.bottom}px`,
|
|
152
|
+
[TabsIndicatorCssVars.activeTabWidth]: `${size.width}px`,
|
|
153
|
+
[TabsIndicatorCssVars.activeTabHeight]: `${size.height}px`,
|
|
154
|
+
} as StyleValue
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
const indicatorProps = computed(() => mergeProps(
|
|
158
|
+
{
|
|
159
|
+
role: 'presentation',
|
|
160
|
+
style: indicatorStyle.value,
|
|
161
|
+
hidden: !displayIndicator.value,
|
|
162
|
+
suppressHydrationWarning: props.renderBeforeHydration || undefined,
|
|
163
|
+
},
|
|
164
|
+
attrs as Record<string, unknown>,
|
|
165
|
+
))
|
|
166
|
+
|
|
167
|
+
const stateAttributesMapping = {
|
|
168
|
+
...tabsStateAttributesMapping,
|
|
169
|
+
activeTabPosition: () => null,
|
|
170
|
+
activeTabSize: () => null,
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const {
|
|
174
|
+
tag,
|
|
175
|
+
mergedProps,
|
|
176
|
+
renderless,
|
|
177
|
+
ref: renderRef,
|
|
178
|
+
} = useRenderElement({
|
|
179
|
+
componentProps: props,
|
|
180
|
+
state,
|
|
181
|
+
props: indicatorProps,
|
|
182
|
+
stateAttributesMapping,
|
|
183
|
+
defaultTagName: 'span',
|
|
184
|
+
})
|
|
185
|
+
</script>
|
|
186
|
+
|
|
187
|
+
<template>
|
|
188
|
+
<template v-if="rootCtx.value.value !== null">
|
|
189
|
+
<slot v-if="renderless" :ref="renderRef" :props="mergedProps" :state="state" />
|
|
190
|
+
<component :is="tag" v-else :ref="renderRef" v-bind="mergedProps" />
|
|
191
|
+
<!-- eslint-disable-next-line vue/require-component-is -->
|
|
192
|
+
<component
|
|
193
|
+
is="script"
|
|
194
|
+
v-if="props.renderBeforeHydration && !isMounted"
|
|
195
|
+
:nonce="nonce"
|
|
196
|
+
:suppress-hydration-warning="true"
|
|
197
|
+
>
|
|
198
|
+
{{ prehydrationScript }}
|
|
199
|
+
</component>
|
|
200
|
+
</template>
|
|
201
|
+
</template>
|