base-ui-vue 0.1.0 → 0.2.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/README.md +41 -1
- package/dist/button/Button.cjs +53 -12
- package/dist/button/Button.cjs.map +1 -1
- package/dist/button/Button.js +26 -15
- package/dist/button/Button.js.map +1 -1
- package/dist/button/ToolbarButton.cjs +367 -0
- package/dist/button/ToolbarButton.cjs.map +1 -0
- package/dist/button/ToolbarButton.js +320 -0
- package/dist/button/ToolbarButton.js.map +1 -0
- package/dist/button/ToolbarButtonDataAttributes.cjs +27 -0
- package/dist/button/ToolbarButtonDataAttributes.cjs.map +1 -0
- package/dist/button/ToolbarButtonDataAttributes.js +21 -0
- package/dist/button/ToolbarButtonDataAttributes.js.map +1 -0
- package/dist/checkbox/index.cjs +1173 -0
- package/dist/checkbox/index.cjs.map +1 -0
- package/dist/checkbox/index.js +1048 -0
- package/dist/checkbox/index.js.map +1 -0
- package/dist/checkbox-group/CheckboxGroup.cjs +629 -0
- package/dist/checkbox-group/CheckboxGroup.cjs.map +1 -0
- package/dist/checkbox-group/CheckboxGroup.js +540 -0
- package/dist/checkbox-group/CheckboxGroup.js.map +1 -0
- package/dist/checkbox-group/CheckboxGroupDataAttributes.cjs +18 -0
- package/dist/checkbox-group/CheckboxGroupDataAttributes.cjs.map +1 -0
- package/dist/checkbox-group/CheckboxGroupDataAttributes.js +12 -0
- package/dist/checkbox-group/CheckboxGroupDataAttributes.js.map +1 -0
- package/dist/composite/composite.cjs +167 -0
- package/dist/composite/composite.cjs.map +1 -1
- package/dist/composite/composite.js +96 -1
- package/dist/composite/composite.js.map +1 -1
- package/dist/composite/constants.cjs +12 -0
- package/dist/composite/constants.cjs.map +1 -0
- package/dist/composite/constants.js +6 -0
- package/dist/composite/constants.js.map +1 -0
- package/dist/control/FieldControl.cjs +18 -343
- package/dist/control/FieldControl.cjs.map +1 -1
- package/dist/control/FieldControl.js +14 -285
- package/dist/control/FieldControl.js.map +1 -1
- package/dist/control/SliderControl.cjs +636 -0
- package/dist/control/SliderControl.cjs.map +1 -0
- package/dist/control/SliderControl.js +553 -0
- package/dist/control/SliderControl.js.map +1 -0
- package/dist/control/SliderControlDataAttributes.cjs +47 -0
- package/dist/control/SliderControlDataAttributes.cjs.map +1 -0
- package/dist/control/SliderControlDataAttributes.js +41 -0
- package/dist/control/SliderControlDataAttributes.js.map +1 -0
- package/dist/csp-provider/CSPContext.cjs +32 -0
- package/dist/csp-provider/CSPContext.cjs.map +1 -0
- package/dist/csp-provider/CSPContext.js +21 -0
- package/dist/csp-provider/CSPContext.js.map +1 -0
- package/dist/csp-provider/CSPProvider.cjs +46 -0
- package/dist/csp-provider/CSPProvider.cjs.map +1 -0
- package/dist/csp-provider/CSPProvider.js +41 -0
- package/dist/csp-provider/CSPProvider.js.map +1 -0
- package/dist/description/FieldDescription.cjs +5 -5
- package/dist/description/FieldDescription.cjs.map +1 -1
- package/dist/description/FieldDescription.js +1 -1
- package/dist/direction-provider/DirectionProvider.cjs +2 -2
- package/dist/direction-provider/DirectionProvider.cjs.map +1 -1
- package/dist/direction-provider/DirectionProvider.js +1 -1
- package/dist/error/FieldError.cjs +10 -288
- package/dist/error/FieldError.cjs.map +1 -1
- package/dist/error/FieldError.js +4 -246
- package/dist/error/FieldError.js.map +1 -1
- package/dist/form/Form.cjs +5 -4
- package/dist/form/Form.cjs.map +1 -1
- package/dist/form/Form.js +5 -4
- package/dist/form/Form.js.map +1 -1
- package/dist/group/ToolbarGroup.cjs +92 -0
- package/dist/group/ToolbarGroup.cjs.map +1 -0
- package/dist/group/ToolbarGroup.js +87 -0
- package/dist/group/ToolbarGroup.js.map +1 -0
- package/dist/group/ToolbarGroupDataAttributes.cjs +23 -0
- package/dist/group/ToolbarGroupDataAttributes.cjs.map +1 -0
- package/dist/group/ToolbarGroupDataAttributes.js +17 -0
- package/dist/group/ToolbarGroupDataAttributes.js.map +1 -0
- package/dist/header/AccordionHeader.cjs +2 -2
- package/dist/header/AccordionHeader.js +1 -1
- package/dist/image/AvatarImage.cjs +4 -4
- package/dist/image/AvatarImage.cjs.map +1 -1
- package/dist/image/AvatarImage.js +1 -1
- package/dist/index.cjs +80 -10
- package/dist/index.d.cts +2751 -612
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +2751 -612
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -5
- package/dist/index2.cjs +3651 -370
- package/dist/index2.cjs.map +1 -1
- package/dist/index2.js +3365 -270
- package/dist/index2.js.map +1 -1
- package/package.json +8 -4
- package/src/accordion/root/AccordionRoot.vue +2 -1
- package/src/checkbox/index.ts +23 -0
- package/src/checkbox/indicator/CheckboxIndicator.vue +102 -0
- package/src/checkbox/indicator/CheckboxIndicatorDataAttributes.ts +61 -0
- package/src/checkbox/root/CheckboxRoot.vue +632 -0
- package/src/checkbox/root/CheckboxRootContext.ts +22 -0
- package/src/checkbox/root/CheckboxRootDataAttributes.ts +54 -0
- package/src/checkbox/utils/useStateAttributesMapping.ts +30 -0
- package/src/checkbox-group/CheckboxGroup.vue +241 -0
- package/src/checkbox-group/CheckboxGroupContext.ts +39 -0
- package/src/checkbox-group/CheckboxGroupDataAttributes.ts +6 -0
- package/src/checkbox-group/index.ts +11 -0
- package/src/checkbox-group/useCheckboxGroupParent.ts +173 -0
- package/src/collapsible/panel/useCollapsiblePanel.ts +2 -1
- package/src/collapsible/root/useCollapsibleRoot.ts +3 -1
- package/src/composite/composite.ts +2 -0
- package/src/composite/item/CompositeItem.vue +7 -8
- package/src/composite/root/CompositeRoot.vue +12 -1
- package/src/csp-provider/CSPContext.ts +26 -0
- package/src/csp-provider/CSPProvider.vue +40 -0
- package/src/csp-provider/index.ts +5 -0
- package/src/field/item/FieldItem.vue +6 -1
- package/src/field/label/FieldLabel.vue +10 -51
- package/src/field/root/FieldRoot.vue +16 -3
- package/src/floating-ui-vue/types.ts +1 -4
- package/src/floating-ui-vue/utils/element.ts +12 -0
- package/src/floating-ui-vue/utils/shadowDom.ts +44 -0
- package/src/floating-ui-vue/utils.ts +3 -0
- package/src/form/Form.vue +5 -3
- package/src/index.ts +9 -0
- package/src/labelable-provider/LabelableContext.ts +2 -2
- package/src/labelable-provider/LabelableProvider.vue +21 -4
- package/src/labelable-provider/index.ts +2 -0
- package/src/labelable-provider/useAriaLabelledBy.ts +9 -9
- package/src/labelable-provider/useLabel.ts +115 -0
- package/src/labelable-provider/useLabelableId.ts +12 -10
- package/src/separator/Separator.vue +65 -0
- package/src/separator/SeparatorDataAttributes.ts +7 -0
- package/src/separator/index.ts +3 -0
- package/src/slider/control/SliderControl.vue +497 -0
- package/src/slider/control/SliderControlDataAttributes.ts +35 -0
- package/src/slider/index.ts +35 -0
- package/src/slider/indicator/SliderIndicator.vue +144 -0
- package/src/slider/indicator/SliderIndicatorDataAttributes.ts +35 -0
- package/src/slider/label/SliderLabel.vue +75 -0
- package/src/slider/root/SliderRoot.vue +557 -0
- package/src/slider/root/SliderRootContext.ts +126 -0
- package/src/slider/root/SliderRootDataAttributes.ts +35 -0
- package/src/slider/root/stateAttributesMapping.ts +13 -0
- package/src/slider/thumb/SliderThumb.vue +601 -0
- package/src/slider/thumb/SliderThumbDataAttributes.ts +39 -0
- package/src/slider/thumb/prehydrationScript.min.ts +5 -0
- package/src/slider/thumb/prehydrationScript.template.js +69 -0
- package/src/slider/track/SliderTrack.vue +48 -0
- package/src/slider/track/SliderTrackDataAttributes.ts +10 -0
- package/src/slider/utils/asc.ts +3 -0
- package/src/slider/utils/getMidpoint.ts +9 -0
- package/src/slider/utils/getPushedThumbValues.ts +68 -0
- package/src/slider/utils/getSliderValue.ts +25 -0
- package/src/slider/utils/replaceArrayItemAtIndex.ts +15 -0
- package/src/slider/utils/resolveThumbCollision.ts +177 -0
- package/src/slider/utils/roundValueToStep.ts +19 -0
- package/src/slider/utils/test-utils.ts +25 -0
- package/src/slider/utils/validateMinimumDistance.ts +20 -0
- package/src/slider/utils/valueArrayToPercentages.ts +10 -0
- package/src/slider/value/SliderValue.vue +90 -0
- package/src/slider/value/SliderValueDataAttributes.ts +35 -0
- package/src/switch/index.ts +14 -0
- package/src/switch/root/SwitchRoot.vue +448 -0
- package/src/switch/root/SwitchRootContext.ts +22 -0
- package/src/switch/root/SwitchRootDataAttributes.ts +46 -0
- package/src/switch/stateAttributesMapping.ts +23 -0
- package/src/switch/thumb/SwitchThumb.vue +59 -0
- package/src/switch/thumb/SwitchThumbDataAttributes.ts +46 -0
- package/src/toggle/Toggle.vue +211 -0
- package/src/toggle/ToggleDataAttributes.ts +6 -0
- package/src/toggle/index.ts +3 -0
- package/src/toggle-group/ToggleGroup.vue +224 -0
- package/src/toggle-group/ToggleGroupContext.ts +45 -0
- package/src/toggle-group/ToggleGroupDataAttributes.ts +15 -0
- package/src/toggle-group/index.ts +5 -0
- package/src/toolbar/button/ToolbarButton.vue +99 -0
- package/src/toolbar/button/ToolbarButtonDataAttributes.ts +15 -0
- package/src/toolbar/group/ToolbarGroup.vue +70 -0
- package/src/toolbar/group/ToolbarGroupContext.ts +23 -0
- package/src/toolbar/group/ToolbarGroupDataAttributes.ts +11 -0
- package/src/toolbar/index.ts +27 -0
- package/src/toolbar/input/ToolbarInput.vue +114 -0
- package/src/toolbar/input/ToolbarInputDataAttributes.ts +15 -0
- package/src/toolbar/link/ToolbarLink.vue +54 -0
- package/src/toolbar/link/ToolbarLinkDataAttributes.ts +7 -0
- package/src/toolbar/root/ToolbarRoot.vue +144 -0
- package/src/toolbar/root/ToolbarRootContext.ts +29 -0
- package/src/toolbar/root/ToolbarRootDataAttributes.ts +11 -0
- package/src/toolbar/separator/ToolbarSeparator.vue +41 -0
- package/src/toolbar/separator/ToolbarSeparatorDataAttributes.ts +7 -0
- package/src/use-button/useButton.ts +2 -1
- package/src/utils/areArraysEqual.ts +12 -0
- package/src/utils/clamp.ts +7 -0
- package/src/utils/createBaseUIEventDetails.ts +9 -0
- package/src/utils/formatNumber.ts +7 -0
- package/src/utils/owner.ts +5 -0
- package/src/utils/resolveAriaLabelledBy.ts +10 -0
- package/src/utils/useControllableState.ts +78 -14
- package/src/utils/useFocusableWhenDisabled.ts +6 -1
- package/src/utils/useMergedRefs.ts +26 -2
- package/src/utils/useRegisteredLabelId.ts +21 -0
- package/src/utils/valueToPercent.ts +7 -0
- package/src/utils/visuallyHidden.ts +24 -0
- package/dist/direction-provider/DirectionContext.cjs +0 -26
- package/dist/direction-provider/DirectionContext.cjs.map +0 -1
- package/dist/direction-provider/DirectionContext.js +0 -15
- package/dist/direction-provider/DirectionContext.js.map +0 -1
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { FieldRootState } from '../../field/root/FieldRoot.vue'
|
|
3
|
+
import type { BaseUIChangeEventDetails } from '../../utils/createBaseUIEventDetails'
|
|
4
|
+
import type { BaseUIComponentProps, NonNativeButtonProps } from '../../utils/types'
|
|
5
|
+
import { computed, getCurrentInstance, provide, ref, useAttrs, watch, watchEffect } from 'vue'
|
|
6
|
+
import { useCheckboxGroupContext } from '../../checkbox-group/CheckboxGroupContext'
|
|
7
|
+
import { useFieldItemContext } from '../../field/item/FieldItemContext'
|
|
8
|
+
import { useFieldRootContext } from '../../field/root/FieldRootContext'
|
|
9
|
+
import { useField } from '../../field/useField'
|
|
10
|
+
import { useFormContext } from '../../form/FormContext'
|
|
11
|
+
import { useLabelableContext } from '../../labelable-provider/LabelableContext'
|
|
12
|
+
import { useAriaLabelledBy } from '../../labelable-provider/useAriaLabelledBy'
|
|
13
|
+
import { mergeProps } from '../../merge-props/mergeProps'
|
|
14
|
+
import { useButton } from '../../use-button'
|
|
15
|
+
import { createChangeEventDetails } from '../../utils/createBaseUIEventDetails'
|
|
16
|
+
import { EMPTY_OBJECT } from '../../utils/empty'
|
|
17
|
+
import { REASONS } from '../../utils/reasons'
|
|
18
|
+
import { useBaseUiId } from '../../utils/useBaseUiId'
|
|
19
|
+
import { useControllableState } from '../../utils/useControllableState'
|
|
20
|
+
import { useMergedRefs } from '../../utils/useMergedRefs'
|
|
21
|
+
import { useRenderElement } from '../../utils/useRenderElement'
|
|
22
|
+
import { visuallyHidden, visuallyHiddenInput } from '../../utils/visuallyHidden'
|
|
23
|
+
import { useCheckboxStateAttributesMapping } from '../utils/useStateAttributesMapping'
|
|
24
|
+
import { checkboxRootContextKey } from './CheckboxRootContext'
|
|
25
|
+
|
|
26
|
+
interface GroupCheckboxProps {
|
|
27
|
+
'id'?: string
|
|
28
|
+
'checked'?: boolean
|
|
29
|
+
'indeterminate'?: boolean
|
|
30
|
+
'aria-controls'?: string
|
|
31
|
+
'onCheckedChange'?: (
|
|
32
|
+
checked: boolean,
|
|
33
|
+
eventDetails: BaseUIChangeEventDetails<typeof REASONS.none>,
|
|
34
|
+
) => void
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface CheckboxRootState extends FieldRootState {
|
|
38
|
+
/**
|
|
39
|
+
* Whether the checkbox is currently ticked.
|
|
40
|
+
*/
|
|
41
|
+
checked: boolean
|
|
42
|
+
/**
|
|
43
|
+
* Whether the component should ignore user interaction.
|
|
44
|
+
*/
|
|
45
|
+
disabled: boolean
|
|
46
|
+
/**
|
|
47
|
+
* Whether the user should be unable to tick or untick the checkbox.
|
|
48
|
+
*/
|
|
49
|
+
readOnly: boolean
|
|
50
|
+
/**
|
|
51
|
+
* Whether the user must tick the checkbox before submitting a form.
|
|
52
|
+
*/
|
|
53
|
+
required: boolean
|
|
54
|
+
/**
|
|
55
|
+
* Whether the checkbox is in a mixed state: neither ticked, nor unticked.
|
|
56
|
+
*/
|
|
57
|
+
indeterminate: boolean
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface CheckboxRootProps
|
|
61
|
+
extends
|
|
62
|
+
NonNativeButtonProps,
|
|
63
|
+
BaseUIComponentProps<CheckboxRootState> {
|
|
64
|
+
/**
|
|
65
|
+
* The id of the input element.
|
|
66
|
+
*/
|
|
67
|
+
'id'?: string
|
|
68
|
+
/**
|
|
69
|
+
* Identifies the field when a form is submitted.
|
|
70
|
+
* @default undefined
|
|
71
|
+
*/
|
|
72
|
+
'name'?: string
|
|
73
|
+
/**
|
|
74
|
+
* Identifies the form that owns the hidden input.
|
|
75
|
+
* Useful when the checkbox is rendered outside the form.
|
|
76
|
+
*/
|
|
77
|
+
'form'?: string
|
|
78
|
+
/**
|
|
79
|
+
* Whether the checkbox is currently ticked.
|
|
80
|
+
*
|
|
81
|
+
* To render an uncontrolled checkbox, use the `defaultChecked` prop instead.
|
|
82
|
+
* @default undefined
|
|
83
|
+
*/
|
|
84
|
+
'checked'?: boolean
|
|
85
|
+
/**
|
|
86
|
+
* Whether the checkbox is initially ticked.
|
|
87
|
+
*
|
|
88
|
+
* To render a controlled checkbox, use the `checked` prop instead.
|
|
89
|
+
* @default false
|
|
90
|
+
*/
|
|
91
|
+
'defaultChecked'?: boolean
|
|
92
|
+
/**
|
|
93
|
+
* Whether the component should ignore user interaction.
|
|
94
|
+
* @default false
|
|
95
|
+
*/
|
|
96
|
+
'disabled'?: boolean
|
|
97
|
+
/**
|
|
98
|
+
* Whether the user should be unable to tick or untick the checkbox.
|
|
99
|
+
* @default false
|
|
100
|
+
*/
|
|
101
|
+
'readOnly'?: boolean
|
|
102
|
+
/**
|
|
103
|
+
* Whether the user must tick the checkbox before submitting a form.
|
|
104
|
+
* @default false
|
|
105
|
+
*/
|
|
106
|
+
'required'?: boolean
|
|
107
|
+
/**
|
|
108
|
+
* Whether the checkbox is in a mixed state: neither ticked, nor unticked.
|
|
109
|
+
* @default false
|
|
110
|
+
*/
|
|
111
|
+
'indeterminate'?: boolean
|
|
112
|
+
/**
|
|
113
|
+
* A ref to access the hidden `<input>` element.
|
|
114
|
+
*/
|
|
115
|
+
'inputRef'?: any
|
|
116
|
+
/**
|
|
117
|
+
* Whether the checkbox controls a group of child checkboxes.
|
|
118
|
+
*
|
|
119
|
+
* Must be used in a [Checkbox Group](https://baseui-vue.com/docs/components/checkbox-group).
|
|
120
|
+
* @default false
|
|
121
|
+
*/
|
|
122
|
+
'parent'?: boolean
|
|
123
|
+
/**
|
|
124
|
+
* The value submitted with the form when the checkbox is unchecked.
|
|
125
|
+
* By default, unchecked checkboxes do not submit any value, matching native checkbox behavior.
|
|
126
|
+
*/
|
|
127
|
+
'uncheckedValue'?: string
|
|
128
|
+
/**
|
|
129
|
+
* The value of the selected checkbox.
|
|
130
|
+
*/
|
|
131
|
+
'value'?: string
|
|
132
|
+
// eslint-disable-next-line vue/prop-name-casing
|
|
133
|
+
'aria-labelledby'?: string
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Represents the checkbox itself.
|
|
138
|
+
* Renders a `<span>` element and a hidden `<input>` beside.
|
|
139
|
+
*
|
|
140
|
+
* Documentation: [Base UI Vue Checkbox](https://baseui-vue.com/docs/components/checkbox)
|
|
141
|
+
*/
|
|
142
|
+
defineOptions({
|
|
143
|
+
name: 'CheckboxRoot',
|
|
144
|
+
inheritAttrs: false,
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
const props = withDefaults(defineProps<CheckboxRootProps>(), {
|
|
148
|
+
as: 'span',
|
|
149
|
+
defaultChecked: false,
|
|
150
|
+
disabled: false,
|
|
151
|
+
readOnly: false,
|
|
152
|
+
required: false,
|
|
153
|
+
indeterminate: false,
|
|
154
|
+
parent: false,
|
|
155
|
+
nativeButton: false,
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
const emit = defineEmits<{
|
|
159
|
+
/**
|
|
160
|
+
* Event handler called when the checkbox is ticked or unticked.
|
|
161
|
+
*/
|
|
162
|
+
checkedChange: [
|
|
163
|
+
checked: boolean,
|
|
164
|
+
eventDetails: BaseUIChangeEventDetails<typeof REASONS.none>,
|
|
165
|
+
]
|
|
166
|
+
}>()
|
|
167
|
+
|
|
168
|
+
const attrs = useAttrs()
|
|
169
|
+
const attrsObject = attrs as Record<string, any>
|
|
170
|
+
const instance = getCurrentInstance()
|
|
171
|
+
const controlSource = Symbol('checkbox-control')
|
|
172
|
+
|
|
173
|
+
const { clearErrors } = useFormContext()
|
|
174
|
+
const {
|
|
175
|
+
disabled: rootDisabled,
|
|
176
|
+
name: fieldName,
|
|
177
|
+
setDirty,
|
|
178
|
+
setFilled,
|
|
179
|
+
setFocused,
|
|
180
|
+
setTouched,
|
|
181
|
+
state: fieldState,
|
|
182
|
+
validationMode,
|
|
183
|
+
validityData,
|
|
184
|
+
shouldValidateOnChange,
|
|
185
|
+
validation: localValidation,
|
|
186
|
+
} = useFieldRootContext()
|
|
187
|
+
const fieldItemContext = useFieldItemContext()
|
|
188
|
+
const labelableContext = useLabelableContext()
|
|
189
|
+
const groupContext = useCheckboxGroupContext(true)
|
|
190
|
+
|
|
191
|
+
const parentContext = computed(() => groupContext?.parent)
|
|
192
|
+
const isGroupedWithParent = computed(() =>
|
|
193
|
+
Boolean(parentContext.value && groupContext && groupContext.allValues.value !== undefined),
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
const disabled = computed(() =>
|
|
197
|
+
rootDisabled.value
|
|
198
|
+
|| fieldItemContext.disabled.value
|
|
199
|
+
|| groupContext?.disabled.value
|
|
200
|
+
|| props.disabled,
|
|
201
|
+
)
|
|
202
|
+
const name = computed(() => fieldName.value ?? props.name)
|
|
203
|
+
const checkboxValue = computed(() => (props.value !== undefined ? props.value : name.value))
|
|
204
|
+
|
|
205
|
+
const rootElementId = useBaseUiId()
|
|
206
|
+
const generatedInputId = useBaseUiId()
|
|
207
|
+
const groupParentId = useBaseUiId()
|
|
208
|
+
|
|
209
|
+
const inputId = computed(() => {
|
|
210
|
+
// Parent/child checkboxes inside a group share a predictable id scheme so the
|
|
211
|
+
// parent can identify itself. Child controls register their real input ids.
|
|
212
|
+
if (isGroupedWithParent.value && parentContext.value) {
|
|
213
|
+
if (props.parent) {
|
|
214
|
+
return parentContext.value.id ?? groupParentId
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (props.id) {
|
|
219
|
+
return props.id
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return labelableContext.controlId.value ?? generatedInputId
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
watchEffect((onCleanup) => {
|
|
226
|
+
// Register the focusable/native control id with the surrounding label system.
|
|
227
|
+
labelableContext.registerControlId(controlSource, inputId.value)
|
|
228
|
+
|
|
229
|
+
onCleanup(() => {
|
|
230
|
+
labelableContext.registerControlId(controlSource, undefined)
|
|
231
|
+
})
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
watch(
|
|
235
|
+
() => ({
|
|
236
|
+
groupedWithParent: isGroupedWithParent.value,
|
|
237
|
+
checkboxValue: checkboxValue.value,
|
|
238
|
+
inputId: inputId.value,
|
|
239
|
+
isParent: props.parent,
|
|
240
|
+
}),
|
|
241
|
+
(nextState, _prevState, onCleanup) => {
|
|
242
|
+
if (
|
|
243
|
+
!nextState.groupedWithParent
|
|
244
|
+
|| !groupContext
|
|
245
|
+
|| nextState.isParent
|
|
246
|
+
|| nextState.checkboxValue === undefined
|
|
247
|
+
) {
|
|
248
|
+
return
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
groupContext.parent.registerChildControlId(nextState.checkboxValue, nextState.inputId)
|
|
252
|
+
|
|
253
|
+
onCleanup(() => {
|
|
254
|
+
groupContext.parent.registerChildControlId(nextState.checkboxValue!, undefined)
|
|
255
|
+
})
|
|
256
|
+
},
|
|
257
|
+
{ immediate: true },
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
const groupProps = computed<GroupCheckboxProps>(() => {
|
|
261
|
+
if (!isGroupedWithParent.value || !groupContext) {
|
|
262
|
+
return {}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (props.parent) {
|
|
266
|
+
return groupContext.parent.getParentProps()
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (checkboxValue.value !== undefined) {
|
|
270
|
+
return groupContext.parent.getChildProps(checkboxValue.value)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return {}
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
const hasCheckedProp = computed(() =>
|
|
277
|
+
Boolean(instance?.vnode.props && Object.prototype.hasOwnProperty.call(instance.vnode.props, 'checked')),
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
const groupChecked = computed(() => {
|
|
281
|
+
const checked = groupProps.value.checked
|
|
282
|
+
return checked === undefined
|
|
283
|
+
? (hasCheckedProp.value ? props.checked : undefined)
|
|
284
|
+
: checked
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
const groupIndeterminate = computed(() => {
|
|
288
|
+
const indeterminate = groupProps.value.indeterminate
|
|
289
|
+
return indeterminate === undefined ? props.indeterminate : indeterminate
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
const validation = groupContext?.validation ?? localValidation
|
|
293
|
+
|
|
294
|
+
const { value: checked, setValue: setCheckedState } = useControllableState<boolean>({
|
|
295
|
+
controlled: () => {
|
|
296
|
+
// Child checkboxes in a group derive checked state from the shared value array.
|
|
297
|
+
if (groupContext && !props.parent && checkboxValue.value !== undefined) {
|
|
298
|
+
return groupContext.value.value.includes(checkboxValue.value)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return groupChecked.value
|
|
302
|
+
},
|
|
303
|
+
default:
|
|
304
|
+
() => (
|
|
305
|
+
groupContext && !props.parent && checkboxValue.value !== undefined
|
|
306
|
+
? groupContext.defaultValue.value.includes(checkboxValue.value)
|
|
307
|
+
: props.defaultChecked
|
|
308
|
+
),
|
|
309
|
+
name: 'CheckboxRoot',
|
|
310
|
+
state: 'checked',
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
const computedChecked = computed(() =>
|
|
314
|
+
isGroupedWithParent.value ? Boolean(groupChecked.value) : checked.value,
|
|
315
|
+
)
|
|
316
|
+
const computedIndeterminate = computed(() =>
|
|
317
|
+
isGroupedWithParent.value
|
|
318
|
+
? Boolean(groupIndeterminate.value || props.indeterminate)
|
|
319
|
+
: props.indeterminate,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
const controlRef = ref<HTMLElement | null>(null)
|
|
323
|
+
const inputElementRef = ref<HTMLInputElement | null>(null)
|
|
324
|
+
|
|
325
|
+
useField({
|
|
326
|
+
// Standalone checkboxes participate in Field/Form registration directly.
|
|
327
|
+
// Grouped checkboxes defer validation state to the group container instead.
|
|
328
|
+
enabled: computed(() => !groupContext),
|
|
329
|
+
id: computed(() => rootElementId),
|
|
330
|
+
commit: (value: unknown) => validation.commit(value),
|
|
331
|
+
value: checked,
|
|
332
|
+
controlRef,
|
|
333
|
+
name,
|
|
334
|
+
getValue: () => checked.value,
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
const ariaLabelledBy = useAriaLabelledBy({
|
|
338
|
+
ariaLabelledBy: computed(() => props['aria-labelledby']),
|
|
339
|
+
labelId: labelableContext.labelId,
|
|
340
|
+
// Use the hidden input for native-label fallback detection because it is the
|
|
341
|
+
// element associated with sibling/wrapping `<label>` elements.
|
|
342
|
+
labelSourceRef: inputElementRef,
|
|
343
|
+
enableFallback: !props.nativeButton,
|
|
344
|
+
labelSourceId: inputId,
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
watchEffect(() => {
|
|
348
|
+
if (inputElementRef.value) {
|
|
349
|
+
inputElementRef.value.indeterminate = computedIndeterminate.value
|
|
350
|
+
}
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
watchEffect(() => {
|
|
354
|
+
// Preserve filled field state for initially checked checkboxes without
|
|
355
|
+
// resetting a shared group field back to unfilled.
|
|
356
|
+
if (computedChecked.value) {
|
|
357
|
+
setFilled(true)
|
|
358
|
+
}
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
watchEffect(() => {
|
|
362
|
+
if (!inputElementRef.value) {
|
|
363
|
+
return
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
validation.setInputRef(inputElementRef.value)
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
watch(
|
|
370
|
+
() => checked.value,
|
|
371
|
+
(nextChecked) => {
|
|
372
|
+
if (groupContext) {
|
|
373
|
+
return
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
clearErrors(name.value)
|
|
377
|
+
setFilled(nextChecked)
|
|
378
|
+
setDirty(nextChecked !== validityData.value.initialValue)
|
|
379
|
+
|
|
380
|
+
if (shouldValidateOnChange()) {
|
|
381
|
+
void validation.commit(nextChecked)
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
void validation.commit(nextChecked, true)
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
{ flush: 'sync' },
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
watch(
|
|
391
|
+
() => ({
|
|
392
|
+
parentContext: parentContext.value,
|
|
393
|
+
checkboxValue: checkboxValue.value,
|
|
394
|
+
disabled: disabled.value,
|
|
395
|
+
}),
|
|
396
|
+
(nextState, _prevState, onCleanup) => {
|
|
397
|
+
if (!nextState.parentContext || nextState.checkboxValue === undefined) {
|
|
398
|
+
return
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const checkboxKey = nextState.checkboxValue
|
|
402
|
+
const nextDisabledStates = new Map(nextState.parentContext.disabledStatesRef.value)
|
|
403
|
+
nextDisabledStates.set(checkboxKey, nextState.disabled)
|
|
404
|
+
nextState.parentContext.disabledStatesRef.value = nextDisabledStates
|
|
405
|
+
|
|
406
|
+
onCleanup(() => {
|
|
407
|
+
if (!nextState.parentContext) {
|
|
408
|
+
return
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const cleanupDisabledStates = new Map(nextState.parentContext.disabledStatesRef.value)
|
|
412
|
+
cleanupDisabledStates.delete(checkboxKey)
|
|
413
|
+
nextState.parentContext.disabledStatesRef.value = cleanupDisabledStates
|
|
414
|
+
})
|
|
415
|
+
},
|
|
416
|
+
{ immediate: true },
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
const { getButtonProps, buttonRef } = useButton({
|
|
420
|
+
disabled,
|
|
421
|
+
native: computed(() => props.nativeButton),
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
function combineDescriptionProps(
|
|
425
|
+
localProps: Record<string, any>,
|
|
426
|
+
validationProps: Record<string, any>,
|
|
427
|
+
) {
|
|
428
|
+
const localDescribedBy = localProps['aria-describedby']
|
|
429
|
+
const validationDescribedBy = validationProps['aria-describedby']
|
|
430
|
+
const describedBy = Array.from(
|
|
431
|
+
new Set(
|
|
432
|
+
[localDescribedBy, validationDescribedBy]
|
|
433
|
+
.filter(Boolean)
|
|
434
|
+
.flatMap(value => String(value).split(/\s+/).filter(Boolean)),
|
|
435
|
+
),
|
|
436
|
+
).join(' ') || undefined
|
|
437
|
+
|
|
438
|
+
return {
|
|
439
|
+
...localProps,
|
|
440
|
+
...validationProps,
|
|
441
|
+
// Avoid duplicating ids when both field descriptions and validation props
|
|
442
|
+
// contribute the same `aria-describedby` tokens.
|
|
443
|
+
'aria-describedby': describedBy,
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function applyCheckedChange(
|
|
448
|
+
nextChecked: boolean,
|
|
449
|
+
event: Event,
|
|
450
|
+
onApplied?: () => void,
|
|
451
|
+
) {
|
|
452
|
+
const details = createChangeEventDetails(REASONS.none, event)
|
|
453
|
+
|
|
454
|
+
emit('checkedChange', nextChecked, details)
|
|
455
|
+
|
|
456
|
+
if (details.isCanceled) {
|
|
457
|
+
return false
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
groupProps.value.onCheckedChange?.(nextChecked, details)
|
|
461
|
+
onApplied?.()
|
|
462
|
+
setCheckedState(nextChecked)
|
|
463
|
+
|
|
464
|
+
// Standalone children of a CheckboxGroup update the shared value array here;
|
|
465
|
+
// parent checkboxes use the group's parent-controller helpers instead.
|
|
466
|
+
if (groupContext && !isGroupedWithParent.value && !props.parent && checkboxValue.value !== undefined) {
|
|
467
|
+
const currentValue = groupContext.value.value.slice()
|
|
468
|
+
const nextValue = nextChecked
|
|
469
|
+
? Array.from(new Set([...currentValue, checkboxValue.value]))
|
|
470
|
+
: currentValue.filter((item: string) => item !== checkboxValue.value)
|
|
471
|
+
|
|
472
|
+
groupContext.setValue(nextValue, details)
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return true
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function handleInputChange(event: Event) {
|
|
479
|
+
const target = event.currentTarget as HTMLInputElement
|
|
480
|
+
|
|
481
|
+
if (props.readOnly || disabled.value || (!props.parent && props.indeterminate && !groupContext)) {
|
|
482
|
+
event.preventDefault()
|
|
483
|
+
event.stopPropagation()
|
|
484
|
+
target.checked = computedChecked.value
|
|
485
|
+
target.indeterminate = computedIndeterminate.value
|
|
486
|
+
return
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const applied = applyCheckedChange(target.checked, event)
|
|
490
|
+
if (!applied) {
|
|
491
|
+
target.checked = computedChecked.value
|
|
492
|
+
target.indeterminate = computedIndeterminate.value
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function handleRootClick(event: MouseEvent | KeyboardEvent) {
|
|
497
|
+
if (props.readOnly || disabled.value || (!props.parent && props.indeterminate)) {
|
|
498
|
+
return
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
event.preventDefault()
|
|
502
|
+
|
|
503
|
+
const nextChecked = !computedChecked.value
|
|
504
|
+
|
|
505
|
+
applyCheckedChange(nextChecked, event, () => {
|
|
506
|
+
if (inputElementRef.value) {
|
|
507
|
+
// Keep the hidden native input in sync so form submission mirrors button state.
|
|
508
|
+
inputElementRef.value.checked = nextChecked
|
|
509
|
+
}
|
|
510
|
+
})
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function handleFocus() {
|
|
514
|
+
setFocused(true)
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function handleBlur() {
|
|
518
|
+
setTouched(true)
|
|
519
|
+
setFocused(false)
|
|
520
|
+
|
|
521
|
+
if (validationMode.value === 'onBlur') {
|
|
522
|
+
void validation.commit(groupContext ? groupContext.value.value : checked.value)
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const state = computed<CheckboxRootState>(() => ({
|
|
527
|
+
...fieldState.value,
|
|
528
|
+
checked: computedChecked.value,
|
|
529
|
+
disabled: disabled.value,
|
|
530
|
+
readOnly: props.readOnly,
|
|
531
|
+
required: props.required,
|
|
532
|
+
indeterminate: computedIndeterminate.value,
|
|
533
|
+
}))
|
|
534
|
+
|
|
535
|
+
provide(checkboxRootContextKey, state)
|
|
536
|
+
|
|
537
|
+
const stateAttributesMapping = useCheckboxStateAttributesMapping(state)
|
|
538
|
+
|
|
539
|
+
const mergedRootRef = useMergedRefs(
|
|
540
|
+
buttonRef,
|
|
541
|
+
controlRef,
|
|
542
|
+
groupContext?.registerControlRef,
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
const rootProps = computed(() => {
|
|
546
|
+
const localDescriptionProps = labelableContext.getDescriptionProps()
|
|
547
|
+
const validationProps = validation.getValidationProps()
|
|
548
|
+
const buttonProps = getButtonProps({
|
|
549
|
+
onClick: handleRootClick,
|
|
550
|
+
})
|
|
551
|
+
|
|
552
|
+
return mergeProps(
|
|
553
|
+
buttonProps,
|
|
554
|
+
combineDescriptionProps(localDescriptionProps, validationProps),
|
|
555
|
+
groupProps.value['aria-controls'] ? { 'aria-controls': groupProps.value['aria-controls'] } : EMPTY_OBJECT,
|
|
556
|
+
{
|
|
557
|
+
'id': groupProps.value.id ?? (props.nativeButton ? inputId.value : rootElementId),
|
|
558
|
+
'role': 'checkbox',
|
|
559
|
+
'aria-checked': computedIndeterminate.value ? 'mixed' : computedChecked.value,
|
|
560
|
+
'aria-readonly': props.readOnly || undefined,
|
|
561
|
+
'aria-required': props.required || undefined,
|
|
562
|
+
'aria-labelledby': ariaLabelledBy.value,
|
|
563
|
+
'data-parent': props.parent ? '' : undefined,
|
|
564
|
+
'onFocus': handleFocus,
|
|
565
|
+
'onBlur': handleBlur,
|
|
566
|
+
},
|
|
567
|
+
attrsObject,
|
|
568
|
+
)
|
|
569
|
+
})
|
|
570
|
+
|
|
571
|
+
const {
|
|
572
|
+
tag,
|
|
573
|
+
mergedProps,
|
|
574
|
+
renderless,
|
|
575
|
+
ref: renderRef,
|
|
576
|
+
} = useRenderElement({
|
|
577
|
+
componentProps: props,
|
|
578
|
+
state,
|
|
579
|
+
props: rootProps,
|
|
580
|
+
stateAttributesMapping,
|
|
581
|
+
defaultTagName: 'span',
|
|
582
|
+
ref: mergedRootRef,
|
|
583
|
+
})
|
|
584
|
+
|
|
585
|
+
const inputProps = computed(() => {
|
|
586
|
+
const localDescriptionProps = labelableContext.getDescriptionProps()
|
|
587
|
+
const validationProps = groupContext
|
|
588
|
+
? validation.getValidationProps()
|
|
589
|
+
: validation.getInputValidationProps()
|
|
590
|
+
|
|
591
|
+
return mergeProps(
|
|
592
|
+
{
|
|
593
|
+
'checked': computedChecked.value,
|
|
594
|
+
'disabled': disabled.value,
|
|
595
|
+
'form': props.form,
|
|
596
|
+
'name': props.parent ? undefined : name.value,
|
|
597
|
+
'id': props.nativeButton ? undefined : inputId.value,
|
|
598
|
+
'required': props.required,
|
|
599
|
+
'ref': useMergedRefs(inputElementRef, props.inputRef),
|
|
600
|
+
'type': 'checkbox',
|
|
601
|
+
'aria-hidden': true,
|
|
602
|
+
'tabindex': -1,
|
|
603
|
+
// This hidden input carries the native form semantics while the rendered
|
|
604
|
+
// root element exposes the custom checkbox interaction model.
|
|
605
|
+
'style': name.value ? visuallyHiddenInput : visuallyHidden,
|
|
606
|
+
'onChange': handleInputChange,
|
|
607
|
+
onFocus() {
|
|
608
|
+
controlRef.value?.focus()
|
|
609
|
+
},
|
|
610
|
+
},
|
|
611
|
+
checkboxValue.value !== undefined
|
|
612
|
+
? { value: groupContext ? (computedChecked.value ? checkboxValue.value : '') : checkboxValue.value }
|
|
613
|
+
: EMPTY_OBJECT,
|
|
614
|
+
combineDescriptionProps(localDescriptionProps, validationProps),
|
|
615
|
+
)
|
|
616
|
+
})
|
|
617
|
+
</script>
|
|
618
|
+
|
|
619
|
+
<template>
|
|
620
|
+
<slot v-if="renderless" :ref="renderRef" :props="mergedProps" :state="state" />
|
|
621
|
+
<component :is="tag" v-else :ref="renderRef" v-bind="mergedProps">
|
|
622
|
+
<slot :state="state" />
|
|
623
|
+
</component>
|
|
624
|
+
<input
|
|
625
|
+
v-if="!computedChecked && !groupContext && name && !parent && uncheckedValue !== undefined"
|
|
626
|
+
type="hidden"
|
|
627
|
+
:form="form"
|
|
628
|
+
:name="name"
|
|
629
|
+
:value="uncheckedValue"
|
|
630
|
+
>
|
|
631
|
+
<input v-bind="inputProps">
|
|
632
|
+
</template>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { InjectionKey, Ref } from 'vue'
|
|
2
|
+
import type { CheckboxRootState } from './CheckboxRoot.vue'
|
|
3
|
+
import { inject } from 'vue'
|
|
4
|
+
|
|
5
|
+
export type CheckboxRootContext = Readonly<Ref<CheckboxRootState>>
|
|
6
|
+
|
|
7
|
+
export const checkboxRootContextKey: InjectionKey<CheckboxRootContext> = Symbol(
|
|
8
|
+
'CheckboxRootContext',
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
export function useCheckboxRootContext(optional: true): CheckboxRootContext | undefined
|
|
12
|
+
export function useCheckboxRootContext(optional?: false): CheckboxRootContext
|
|
13
|
+
export function useCheckboxRootContext(optional = false) {
|
|
14
|
+
const context = inject(checkboxRootContextKey, undefined)
|
|
15
|
+
if (!context && !optional) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
'Base UI Vue: CheckboxRootContext is missing. Checkbox parts must be placed within <CheckboxRoot>.',
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return context
|
|
22
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export enum CheckboxRootDataAttributes {
|
|
2
|
+
/**
|
|
3
|
+
* Present when the checkbox is checked.
|
|
4
|
+
*/
|
|
5
|
+
checked = 'data-checked',
|
|
6
|
+
/**
|
|
7
|
+
* Present when the checkbox is not checked.
|
|
8
|
+
*/
|
|
9
|
+
unchecked = 'data-unchecked',
|
|
10
|
+
/**
|
|
11
|
+
* Present when the checkbox is in an indeterminate state.
|
|
12
|
+
*/
|
|
13
|
+
indeterminate = 'data-indeterminate',
|
|
14
|
+
/**
|
|
15
|
+
* Present when the checkbox is disabled.
|
|
16
|
+
*/
|
|
17
|
+
disabled = 'data-disabled',
|
|
18
|
+
/**
|
|
19
|
+
* Present when the checkbox is readonly.
|
|
20
|
+
*/
|
|
21
|
+
readonly = 'data-readonly',
|
|
22
|
+
/**
|
|
23
|
+
* Present when the checkbox is required.
|
|
24
|
+
*/
|
|
25
|
+
required = 'data-required',
|
|
26
|
+
/**
|
|
27
|
+
* Present when the checkbox is in valid state (when wrapped in FieldRoot).
|
|
28
|
+
*/
|
|
29
|
+
valid = 'data-valid',
|
|
30
|
+
/**
|
|
31
|
+
* Present when the checkbox is in invalid state (when wrapped in FieldRoot).
|
|
32
|
+
*/
|
|
33
|
+
invalid = 'data-invalid',
|
|
34
|
+
/**
|
|
35
|
+
* Present when the checkbox has been touched (when wrapped in FieldRoot).
|
|
36
|
+
*/
|
|
37
|
+
touched = 'data-touched',
|
|
38
|
+
/**
|
|
39
|
+
* Present when the checkbox's value has changed (when wrapped in FieldRoot).
|
|
40
|
+
*/
|
|
41
|
+
dirty = 'data-dirty',
|
|
42
|
+
/**
|
|
43
|
+
* Present when the checkbox is checked (when wrapped in FieldRoot).
|
|
44
|
+
*/
|
|
45
|
+
filled = 'data-filled',
|
|
46
|
+
/**
|
|
47
|
+
* Present when the checkbox is focused (when wrapped in FieldRoot).
|
|
48
|
+
*/
|
|
49
|
+
focused = 'data-focused',
|
|
50
|
+
/**
|
|
51
|
+
* Present when the checkbox is acting as the parent checkbox for a group.
|
|
52
|
+
*/
|
|
53
|
+
parent = 'data-parent',
|
|
54
|
+
}
|