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,544 @@
|
|
|
1
|
+
<script setup lang="ts" generic="Value = unknown">
|
|
2
|
+
import type { Ref } from 'vue'
|
|
3
|
+
import type { FieldRootState } from '../../field/root/FieldRoot.vue'
|
|
4
|
+
import type { BaseUIChangeEventDetails } from '../../utils/createBaseUIEventDetails'
|
|
5
|
+
import type { BaseUIComponentProps, NonNativeButtonProps } from '../../utils/types'
|
|
6
|
+
import { computed, provide, ref, useAttrs, watch, watchEffect } from 'vue'
|
|
7
|
+
import { ACTIVE_COMPOSITE_ITEM } from '../../composite/constants'
|
|
8
|
+
import CompositeItem from '../../composite/item/CompositeItem.vue'
|
|
9
|
+
import { useFieldItemContext } from '../../field/item/FieldItemContext'
|
|
10
|
+
import { useFieldRootContext } from '../../field/root/FieldRootContext'
|
|
11
|
+
import { useField } from '../../field/useField'
|
|
12
|
+
import { useFormContext } from '../../form/FormContext'
|
|
13
|
+
import { useLabelableContext } from '../../labelable-provider/LabelableContext'
|
|
14
|
+
import { useAriaLabelledBy } from '../../labelable-provider/useAriaLabelledBy'
|
|
15
|
+
import { useLabelableId } from '../../labelable-provider/useLabelableId'
|
|
16
|
+
import { mergeProps } from '../../merge-props/mergeProps'
|
|
17
|
+
import { useRadioGroupContext } from '../../radio-group/RadioGroupContext'
|
|
18
|
+
import { useButton } from '../../use-button'
|
|
19
|
+
import { createChangeEventDetails } from '../../utils/createBaseUIEventDetails'
|
|
20
|
+
import { EMPTY_OBJECT } from '../../utils/empty'
|
|
21
|
+
import { ownerWindow } from '../../utils/owner'
|
|
22
|
+
import { REASONS } from '../../utils/reasons'
|
|
23
|
+
import { serializeValue } from '../../utils/serializeValue'
|
|
24
|
+
import { useBaseUiId } from '../../utils/useBaseUiId'
|
|
25
|
+
import { useMergedRefs } from '../../utils/useMergedRefs'
|
|
26
|
+
import { useRenderElement } from '../../utils/useRenderElement'
|
|
27
|
+
import { visuallyHidden, visuallyHiddenInput } from '../../utils/visuallyHidden'
|
|
28
|
+
import { stateAttributesMapping } from '../utils/stateAttributesMapping'
|
|
29
|
+
import { radioRootContextKey } from './RadioRootContext'
|
|
30
|
+
|
|
31
|
+
export interface RadioRootState extends FieldRootState {
|
|
32
|
+
/**
|
|
33
|
+
* Whether the radio button is currently selected.
|
|
34
|
+
*/
|
|
35
|
+
checked: boolean
|
|
36
|
+
/**
|
|
37
|
+
* Whether the component should ignore user interaction.
|
|
38
|
+
*/
|
|
39
|
+
disabled: boolean
|
|
40
|
+
/**
|
|
41
|
+
* Whether the user should be unable to select the radio button.
|
|
42
|
+
*/
|
|
43
|
+
readOnly: boolean
|
|
44
|
+
/**
|
|
45
|
+
* Whether the user must choose a value before submitting a form.
|
|
46
|
+
*/
|
|
47
|
+
required: boolean
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type RadioRootChangeEventReason = typeof REASONS.none
|
|
51
|
+
export type RadioRootChangeEventDetails = BaseUIChangeEventDetails<RadioRootChangeEventReason>
|
|
52
|
+
|
|
53
|
+
export interface RadioRootProps<Value = unknown>
|
|
54
|
+
extends NonNativeButtonProps, BaseUIComponentProps<RadioRootState> {
|
|
55
|
+
/**
|
|
56
|
+
* The id of the radio control.
|
|
57
|
+
*/
|
|
58
|
+
'id'?: string
|
|
59
|
+
/**
|
|
60
|
+
* The unique identifying value of the radio in a group.
|
|
61
|
+
*/
|
|
62
|
+
'value': Value
|
|
63
|
+
/**
|
|
64
|
+
* Whether the component should ignore user interaction.
|
|
65
|
+
* @default false
|
|
66
|
+
*/
|
|
67
|
+
'disabled'?: boolean
|
|
68
|
+
/**
|
|
69
|
+
* Whether the user should be unable to select the radio button.
|
|
70
|
+
* @default false
|
|
71
|
+
*/
|
|
72
|
+
'readOnly'?: boolean
|
|
73
|
+
/**
|
|
74
|
+
* Whether the user must choose a value before submitting a form.
|
|
75
|
+
* @default false
|
|
76
|
+
*/
|
|
77
|
+
'required'?: boolean
|
|
78
|
+
/**
|
|
79
|
+
* A ref to access the hidden `<input>` element.
|
|
80
|
+
*/
|
|
81
|
+
'inputRef'?: Ref<HTMLInputElement | null> | ((element: HTMLInputElement | null) => void) | null
|
|
82
|
+
/**
|
|
83
|
+
* Identifies the element that labels the radio.
|
|
84
|
+
* When omitted, Base UI Vue falls back to the surrounding label system.
|
|
85
|
+
*/
|
|
86
|
+
// eslint-disable-next-line vue/prop-name-casing
|
|
87
|
+
'aria-labelledby'?: string
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Represents the radio button itself.
|
|
92
|
+
* Renders a `<span>` element and a hidden `<input>` beside.
|
|
93
|
+
*
|
|
94
|
+
* Documentation: [Base UI Vue Radio](https://baseui-vue.com/docs/components/radio)
|
|
95
|
+
*/
|
|
96
|
+
defineOptions({
|
|
97
|
+
name: 'RadioRoot',
|
|
98
|
+
inheritAttrs: false,
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
const props = withDefaults(defineProps<RadioRootProps<Value>>(), {
|
|
102
|
+
as: 'span',
|
|
103
|
+
disabled: false,
|
|
104
|
+
nativeButton: false,
|
|
105
|
+
readOnly: false,
|
|
106
|
+
required: false,
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
const attrs = useAttrs()
|
|
110
|
+
const attrsObject = attrs as Record<string, unknown>
|
|
111
|
+
|
|
112
|
+
const { clearErrors } = useFormContext()
|
|
113
|
+
const {
|
|
114
|
+
disabled: fieldDisabled,
|
|
115
|
+
name: fieldName,
|
|
116
|
+
setDirty,
|
|
117
|
+
setFilled,
|
|
118
|
+
setFocused,
|
|
119
|
+
setTouched: setFieldTouched,
|
|
120
|
+
state: fieldState,
|
|
121
|
+
validationMode,
|
|
122
|
+
validityData,
|
|
123
|
+
shouldValidateOnChange,
|
|
124
|
+
validation: localValidation,
|
|
125
|
+
} = useFieldRootContext()
|
|
126
|
+
const fieldItemContext = useFieldItemContext()
|
|
127
|
+
const labelableContext = useLabelableContext()
|
|
128
|
+
const groupContext = useRadioGroupContext<Value>(true)
|
|
129
|
+
|
|
130
|
+
const disabled = computed(() =>
|
|
131
|
+
fieldDisabled.value
|
|
132
|
+
|| fieldItemContext.disabled.value
|
|
133
|
+
|| Boolean(groupContext?.disabled.value)
|
|
134
|
+
|| props.disabled,
|
|
135
|
+
)
|
|
136
|
+
const readOnly = computed(() => Boolean(groupContext?.readOnly.value) || props.readOnly)
|
|
137
|
+
const required = computed(() => Boolean(groupContext?.required.value) || props.required)
|
|
138
|
+
const name = computed(() => groupContext?.name.value ?? fieldName.value)
|
|
139
|
+
const form = computed(() => groupContext?.form.value)
|
|
140
|
+
const validation = groupContext?.validation ?? localValidation
|
|
141
|
+
|
|
142
|
+
const checked = computed(() => {
|
|
143
|
+
if (groupContext) {
|
|
144
|
+
return groupContext.checkedValue.value === props.value
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return props.value === ''
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
const rootElementId = useBaseUiId()
|
|
151
|
+
const controlId = useLabelableId({ id: () => props.id })
|
|
152
|
+
const inputId = computed(() => (props.nativeButton ? undefined : controlId.value))
|
|
153
|
+
|
|
154
|
+
const controlRef = ref<HTMLElement | null>(null)
|
|
155
|
+
const inputElementRef = ref<HTMLInputElement | null>(null)
|
|
156
|
+
const mergedInputRef = useMergedRefs(inputElementRef, props.inputRef)
|
|
157
|
+
let pendingInputClickEvent: Event | null = null
|
|
158
|
+
|
|
159
|
+
useField({
|
|
160
|
+
enabled: computed(() => !groupContext),
|
|
161
|
+
id: computed(() => rootElementId),
|
|
162
|
+
commit: (value: unknown) => validation.commit(value),
|
|
163
|
+
value: checked,
|
|
164
|
+
controlRef,
|
|
165
|
+
name,
|
|
166
|
+
getValue: () => checked.value ? props.value : null,
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
const ariaLabelledBy = useAriaLabelledBy({
|
|
170
|
+
ariaLabelledBy: computed(() => props['aria-labelledby']),
|
|
171
|
+
labelId: labelableContext.labelId,
|
|
172
|
+
labelSourceRef: inputElementRef,
|
|
173
|
+
enableFallback: !props.nativeButton,
|
|
174
|
+
labelSourceId: inputId,
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
watchEffect(() => {
|
|
178
|
+
if (checked.value) {
|
|
179
|
+
setFilled(true)
|
|
180
|
+
}
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
watchEffect((onCleanup) => {
|
|
184
|
+
const input = inputElementRef.value
|
|
185
|
+
if (!input) {
|
|
186
|
+
return
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (groupContext) {
|
|
190
|
+
const isChecked = checked.value
|
|
191
|
+
|
|
192
|
+
if (disabled.value && isChecked) {
|
|
193
|
+
groupContext.registerInputRef(null)
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
groupContext.registerInputRef(input, isChecked)
|
|
198
|
+
|
|
199
|
+
onCleanup(() => {
|
|
200
|
+
groupContext.registerInputRef(null)
|
|
201
|
+
})
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
validation.setInputRef(input)
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
watchEffect((onCleanup) => {
|
|
209
|
+
const element = controlRef.value
|
|
210
|
+
if (!element || !groupContext) {
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
groupContext.registerControlRef(element, disabled.value)
|
|
215
|
+
|
|
216
|
+
onCleanup(() => {
|
|
217
|
+
groupContext.registerControlRef(null)
|
|
218
|
+
})
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
watch(
|
|
222
|
+
() => checked.value,
|
|
223
|
+
(nextChecked) => {
|
|
224
|
+
if (groupContext) {
|
|
225
|
+
return
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
clearErrors(name.value)
|
|
229
|
+
setFilled(nextChecked)
|
|
230
|
+
setDirty((nextChecked ? props.value : null) !== validityData.value.initialValue)
|
|
231
|
+
|
|
232
|
+
const nextValue = nextChecked ? props.value : null
|
|
233
|
+
if (shouldValidateOnChange()) {
|
|
234
|
+
void validation.commit(nextValue)
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
void validation.commit(nextValue, true)
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
const { getButtonProps, buttonRef } = useButton({
|
|
243
|
+
disabled,
|
|
244
|
+
native: computed(() => props.nativeButton),
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
function combineDescriptionProps<
|
|
248
|
+
LocalProps extends object,
|
|
249
|
+
ValidationProps extends object,
|
|
250
|
+
>(
|
|
251
|
+
localProps: LocalProps,
|
|
252
|
+
validationProps: ValidationProps,
|
|
253
|
+
externalProps: { 'aria-describedby'?: unknown } = {},
|
|
254
|
+
): LocalProps & ValidationProps & { 'aria-describedby'?: string } {
|
|
255
|
+
const describedBy = Array.from(
|
|
256
|
+
new Set(
|
|
257
|
+
[
|
|
258
|
+
externalProps['aria-describedby'],
|
|
259
|
+
(localProps as { 'aria-describedby'?: unknown })['aria-describedby'],
|
|
260
|
+
(validationProps as { 'aria-describedby'?: unknown })['aria-describedby'],
|
|
261
|
+
]
|
|
262
|
+
.filter(Boolean)
|
|
263
|
+
.flatMap(value => String(value).split(/\s+/).filter(Boolean)),
|
|
264
|
+
),
|
|
265
|
+
).join(' ') || undefined
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
...localProps,
|
|
269
|
+
...validationProps,
|
|
270
|
+
'aria-describedby': describedBy,
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function createInputClickEvent(event: MouseEvent | KeyboardEvent) {
|
|
275
|
+
const input = inputElementRef.value
|
|
276
|
+
if (!input) {
|
|
277
|
+
return undefined
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const win = ownerWindow(input)
|
|
281
|
+
const PointerEventCtor = win.PointerEvent ?? win.MouseEvent
|
|
282
|
+
|
|
283
|
+
return new PointerEventCtor('click', {
|
|
284
|
+
bubbles: true,
|
|
285
|
+
shiftKey: event.shiftKey,
|
|
286
|
+
ctrlKey: event.ctrlKey,
|
|
287
|
+
altKey: event.altKey,
|
|
288
|
+
metaKey: event.metaKey,
|
|
289
|
+
})
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function applyCheckedValue(event: Event) {
|
|
293
|
+
if (disabled.value || readOnly.value || props.value === undefined) {
|
|
294
|
+
return false
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const details = createChangeEventDetails(REASONS.none, pendingInputClickEvent ?? event)
|
|
298
|
+
|
|
299
|
+
if (details.isCanceled) {
|
|
300
|
+
return false
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (groupContext) {
|
|
304
|
+
groupContext.setCheckedValue(props.value, details)
|
|
305
|
+
if (details.isCanceled) {
|
|
306
|
+
return false
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
setFieldTouched(true)
|
|
310
|
+
return !details.isCanceled
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
setFieldTouched(true)
|
|
314
|
+
setDirty(props.value !== validityData.value.initialValue)
|
|
315
|
+
setFilled(true)
|
|
316
|
+
|
|
317
|
+
clearErrors(name.value)
|
|
318
|
+
|
|
319
|
+
if (shouldValidateOnChange()) {
|
|
320
|
+
void validation.commit(props.value)
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
void validation.commit(props.value, true)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return !details.isCanceled
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function handleInputClick(event: MouseEvent) {
|
|
330
|
+
pendingInputClickEvent = event
|
|
331
|
+
|
|
332
|
+
queueMicrotask(() => {
|
|
333
|
+
if (pendingInputClickEvent === event) {
|
|
334
|
+
pendingInputClickEvent = null
|
|
335
|
+
}
|
|
336
|
+
})
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function handleInputChange(event: Event) {
|
|
340
|
+
if (event.defaultPrevented) {
|
|
341
|
+
return
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const target = event.currentTarget as HTMLInputElement
|
|
345
|
+
const applied = applyCheckedValue(event)
|
|
346
|
+
pendingInputClickEvent = null
|
|
347
|
+
|
|
348
|
+
if (!applied) {
|
|
349
|
+
target.checked = checked.value
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function handleRootClick(event: MouseEvent | KeyboardEvent) {
|
|
354
|
+
if (event.defaultPrevented || disabled.value || readOnly.value) {
|
|
355
|
+
return
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
event.preventDefault()
|
|
359
|
+
|
|
360
|
+
const inputClickEvent = createInputClickEvent(event)
|
|
361
|
+
if (!inputClickEvent) {
|
|
362
|
+
return
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
inputElementRef.value?.dispatchEvent(inputClickEvent)
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function handleRootKeydown(event: KeyboardEvent) {
|
|
369
|
+
if (event.key === 'Enter') {
|
|
370
|
+
event.preventDefault()
|
|
371
|
+
;(event as KeyboardEvent & { preventBaseUIHandler?: () => void }).preventBaseUIHandler?.()
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function handleRootFocus(event: FocusEvent) {
|
|
376
|
+
if (event.defaultPrevented || disabled.value || readOnly.value) {
|
|
377
|
+
return
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (groupContext?.touched.value) {
|
|
381
|
+
inputElementRef.value?.click()
|
|
382
|
+
groupContext.setTouched(false)
|
|
383
|
+
return
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (!groupContext) {
|
|
387
|
+
setFocused(true)
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function handleRootBlur() {
|
|
392
|
+
if (disabled.value || groupContext) {
|
|
393
|
+
return
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
setFieldTouched(true)
|
|
397
|
+
setFocused(false)
|
|
398
|
+
|
|
399
|
+
if (validationMode.value === 'onBlur') {
|
|
400
|
+
void validation.commit(checked.value ? props.value : null)
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const state = computed<RadioRootState>(() => ({
|
|
405
|
+
...fieldState.value,
|
|
406
|
+
checked: checked.value,
|
|
407
|
+
disabled: disabled.value,
|
|
408
|
+
readOnly: readOnly.value,
|
|
409
|
+
required: required.value,
|
|
410
|
+
}))
|
|
411
|
+
|
|
412
|
+
provide(radioRootContextKey, state)
|
|
413
|
+
|
|
414
|
+
defineExpose({
|
|
415
|
+
element: controlRef,
|
|
416
|
+
inputElement: inputElementRef,
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
const mergedRootRef = useMergedRefs(buttonRef, controlRef)
|
|
420
|
+
|
|
421
|
+
const buttonInteractionAttrs = computed(() => ({
|
|
422
|
+
onClick: attrsObject.onClick,
|
|
423
|
+
onKeydown: attrsObject.onKeydown,
|
|
424
|
+
onKeyup: attrsObject.onKeyup,
|
|
425
|
+
onMousedown: attrsObject.onMousedown,
|
|
426
|
+
onPointerdown: attrsObject.onPointerdown,
|
|
427
|
+
}))
|
|
428
|
+
|
|
429
|
+
const passthroughAttrs = computed(() => {
|
|
430
|
+
const {
|
|
431
|
+
onClick,
|
|
432
|
+
onKeydown,
|
|
433
|
+
onKeyup,
|
|
434
|
+
onMousedown,
|
|
435
|
+
onPointerdown,
|
|
436
|
+
'aria-describedby': _ariaDescribedBy,
|
|
437
|
+
...rest
|
|
438
|
+
} = attrsObject
|
|
439
|
+
|
|
440
|
+
return rest
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
const rootProps = computed(() => {
|
|
444
|
+
const localDescriptionProps = labelableContext.getDescriptionProps()
|
|
445
|
+
const validationProps = validation.getValidationProps()
|
|
446
|
+
const buttonProps = getButtonProps(
|
|
447
|
+
mergeProps(buttonInteractionAttrs.value, {
|
|
448
|
+
onClick: handleRootClick,
|
|
449
|
+
onKeydown: handleRootKeydown,
|
|
450
|
+
}),
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
return mergeProps(
|
|
454
|
+
buttonProps,
|
|
455
|
+
combineDescriptionProps(
|
|
456
|
+
localDescriptionProps,
|
|
457
|
+
validationProps,
|
|
458
|
+
{ 'aria-describedby': attrsObject['aria-describedby'] },
|
|
459
|
+
),
|
|
460
|
+
{
|
|
461
|
+
'id': props.nativeButton ? controlId.value : rootElementId,
|
|
462
|
+
'role': 'radio',
|
|
463
|
+
'aria-checked': checked.value,
|
|
464
|
+
'aria-readonly': readOnly.value || undefined,
|
|
465
|
+
'aria-required': required.value || undefined,
|
|
466
|
+
'aria-labelledby': ariaLabelledBy.value,
|
|
467
|
+
[ACTIVE_COMPOSITE_ITEM]: checked.value ? '' : undefined,
|
|
468
|
+
'onFocus': handleRootFocus,
|
|
469
|
+
'onBlur': handleRootBlur,
|
|
470
|
+
},
|
|
471
|
+
passthroughAttrs.value,
|
|
472
|
+
)
|
|
473
|
+
})
|
|
474
|
+
|
|
475
|
+
const {
|
|
476
|
+
tag,
|
|
477
|
+
mergedProps,
|
|
478
|
+
renderless,
|
|
479
|
+
ref: renderRef,
|
|
480
|
+
} = useRenderElement({
|
|
481
|
+
componentProps: props,
|
|
482
|
+
state,
|
|
483
|
+
props: rootProps,
|
|
484
|
+
stateAttributesMapping,
|
|
485
|
+
defaultTagName: 'span',
|
|
486
|
+
ref: mergedRootRef,
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
const compositeItemRefs = mergedRootRef ? [mergedRootRef] : []
|
|
490
|
+
const compositeItemProps = [() => rootProps.value]
|
|
491
|
+
|
|
492
|
+
const inputProps = computed(() => {
|
|
493
|
+
const validationProps = groupContext
|
|
494
|
+
? validation.getValidationProps()
|
|
495
|
+
: validation.getInputValidationProps()
|
|
496
|
+
|
|
497
|
+
return mergeProps(
|
|
498
|
+
{
|
|
499
|
+
'checked': checked.value,
|
|
500
|
+
'disabled': disabled.value,
|
|
501
|
+
'form': form.value,
|
|
502
|
+
'id': inputId.value,
|
|
503
|
+
'name': name.value,
|
|
504
|
+
'readOnly': readOnly.value,
|
|
505
|
+
'required': required.value,
|
|
506
|
+
'ref': mergedInputRef,
|
|
507
|
+
'type': 'radio',
|
|
508
|
+
'aria-hidden': true,
|
|
509
|
+
'tabindex': -1,
|
|
510
|
+
'style': name.value ? visuallyHiddenInput : visuallyHidden,
|
|
511
|
+
'onClick': handleInputClick,
|
|
512
|
+
'onChange': handleInputChange,
|
|
513
|
+
onFocus() {
|
|
514
|
+
controlRef.value?.focus()
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
props.value !== undefined
|
|
518
|
+
? { value: serializeValue(props.value) }
|
|
519
|
+
: EMPTY_OBJECT,
|
|
520
|
+
validationProps,
|
|
521
|
+
)
|
|
522
|
+
})
|
|
523
|
+
</script>
|
|
524
|
+
|
|
525
|
+
<template>
|
|
526
|
+
<CompositeItem
|
|
527
|
+
v-if="groupContext"
|
|
528
|
+
v-slot="slotProps"
|
|
529
|
+
:as="as"
|
|
530
|
+
:class="props.class"
|
|
531
|
+
:style="props.style"
|
|
532
|
+
:state="state"
|
|
533
|
+
:refs="compositeItemRefs"
|
|
534
|
+
:props="compositeItemProps"
|
|
535
|
+
:state-attributes-mapping="stateAttributesMapping"
|
|
536
|
+
>
|
|
537
|
+
<slot v-bind="slotProps" />
|
|
538
|
+
</CompositeItem>
|
|
539
|
+
<slot v-else-if="renderless" :ref="renderRef" :props="mergedProps" :state="state" />
|
|
540
|
+
<component :is="tag" v-else :ref="renderRef" v-bind="mergedProps">
|
|
541
|
+
<slot :state="state" />
|
|
542
|
+
</component>
|
|
543
|
+
<input v-bind="inputProps">
|
|
544
|
+
</template>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { InjectionKey, Ref } from 'vue'
|
|
2
|
+
import type { RadioRootState } from './RadioRoot.vue'
|
|
3
|
+
import { inject } from 'vue'
|
|
4
|
+
|
|
5
|
+
export type RadioRootContext = Readonly<Ref<RadioRootState>>
|
|
6
|
+
|
|
7
|
+
export const radioRootContextKey: InjectionKey<RadioRootContext> = Symbol('RadioRootContext')
|
|
8
|
+
|
|
9
|
+
export function useRadioRootContext() {
|
|
10
|
+
const context = inject(radioRootContextKey, undefined)
|
|
11
|
+
if (!context) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
'Base UI Vue: RadioRootContext is missing. Radio parts must be placed within <RadioRoot>.',
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return context
|
|
18
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export enum RadioRootDataAttributes {
|
|
2
|
+
/**
|
|
3
|
+
* Present when the radio is checked.
|
|
4
|
+
*/
|
|
5
|
+
checked = 'data-checked',
|
|
6
|
+
/**
|
|
7
|
+
* Present when the radio is not checked.
|
|
8
|
+
*/
|
|
9
|
+
unchecked = 'data-unchecked',
|
|
10
|
+
/**
|
|
11
|
+
* Present when the radio is disabled.
|
|
12
|
+
*/
|
|
13
|
+
disabled = 'data-disabled',
|
|
14
|
+
/**
|
|
15
|
+
* Present when the radio is readonly.
|
|
16
|
+
*/
|
|
17
|
+
readonly = 'data-readonly',
|
|
18
|
+
/**
|
|
19
|
+
* Present when the radio is required.
|
|
20
|
+
*/
|
|
21
|
+
required = 'data-required',
|
|
22
|
+
/**
|
|
23
|
+
* Present when the radio is in a valid state.
|
|
24
|
+
*/
|
|
25
|
+
valid = 'data-valid',
|
|
26
|
+
/**
|
|
27
|
+
* Present when the radio is in an invalid state.
|
|
28
|
+
*/
|
|
29
|
+
invalid = 'data-invalid',
|
|
30
|
+
/**
|
|
31
|
+
* Present when the radio has been touched.
|
|
32
|
+
*/
|
|
33
|
+
touched = 'data-touched',
|
|
34
|
+
/**
|
|
35
|
+
* Present when the radio's value has changed.
|
|
36
|
+
*/
|
|
37
|
+
dirty = 'data-dirty',
|
|
38
|
+
/**
|
|
39
|
+
* Present when the radio field has a value.
|
|
40
|
+
*/
|
|
41
|
+
filled = 'data-filled',
|
|
42
|
+
/**
|
|
43
|
+
* Present when the radio is focused.
|
|
44
|
+
*/
|
|
45
|
+
focused = 'data-focused',
|
|
46
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { StateAttributesMapping } from '../../utils/getStateAttributesProps'
|
|
2
|
+
import type { TransitionStatus } from '../../utils/useTransitionStatus'
|
|
3
|
+
import { fieldValidityMapping } from '../../field/utils/constants'
|
|
4
|
+
import { transitionStatusMapping } from '../../utils/stateAttributesMapping'
|
|
5
|
+
import { RadioRootDataAttributes } from '../root/RadioRootDataAttributes'
|
|
6
|
+
|
|
7
|
+
const CHECKED_ATTRS: Record<string, string> = {
|
|
8
|
+
[RadioRootDataAttributes.checked]: '',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const UNCHECKED_ATTRS: Record<string, string> = {
|
|
12
|
+
[RadioRootDataAttributes.unchecked]: '',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const stateAttributesMapping = {
|
|
16
|
+
checked(value): Record<string, string> {
|
|
17
|
+
if (value) {
|
|
18
|
+
return CHECKED_ATTRS
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return UNCHECKED_ATTRS
|
|
22
|
+
},
|
|
23
|
+
...transitionStatusMapping,
|
|
24
|
+
...fieldValidityMapping,
|
|
25
|
+
} satisfies StateAttributesMapping<{
|
|
26
|
+
checked: boolean
|
|
27
|
+
transitionStatus: TransitionStatus
|
|
28
|
+
valid: boolean | null
|
|
29
|
+
}>
|