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,448 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
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, getCurrentInstance, provide, ref, useAttrs, watch, watchEffect } from 'vue'
|
|
7
|
+
import { useFieldRootContext } from '../../field/root/FieldRootContext'
|
|
8
|
+
import { useField } from '../../field/useField'
|
|
9
|
+
import { useFormContext } from '../../form/FormContext'
|
|
10
|
+
import { useLabelableContext } from '../../labelable-provider/LabelableContext'
|
|
11
|
+
import { useAriaLabelledBy } from '../../labelable-provider/useAriaLabelledBy'
|
|
12
|
+
import { useLabelableId } from '../../labelable-provider/useLabelableId'
|
|
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 { stateAttributesMapping } from '../stateAttributesMapping'
|
|
24
|
+
import { switchRootContextKey } from './SwitchRootContext'
|
|
25
|
+
|
|
26
|
+
export interface SwitchRootState extends FieldRootState {
|
|
27
|
+
/**
|
|
28
|
+
* Whether the switch is currently active.
|
|
29
|
+
*/
|
|
30
|
+
checked: boolean
|
|
31
|
+
/**
|
|
32
|
+
* Whether the component should ignore user interaction.
|
|
33
|
+
*/
|
|
34
|
+
disabled: boolean
|
|
35
|
+
/**
|
|
36
|
+
* Whether the user should be unable to activate or deactivate the switch.
|
|
37
|
+
*/
|
|
38
|
+
readOnly: boolean
|
|
39
|
+
/**
|
|
40
|
+
* Whether the user must activate the switch before submitting a form.
|
|
41
|
+
*/
|
|
42
|
+
required: boolean
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface SwitchRootProps
|
|
46
|
+
extends NonNativeButtonProps, BaseUIComponentProps<SwitchRootState> {
|
|
47
|
+
/**
|
|
48
|
+
* The id of the switch element.
|
|
49
|
+
*/
|
|
50
|
+
'id'?: string
|
|
51
|
+
/**
|
|
52
|
+
* Whether the switch is currently active.
|
|
53
|
+
*
|
|
54
|
+
* To render an uncontrolled switch, use the `defaultChecked` prop instead.
|
|
55
|
+
*/
|
|
56
|
+
'checked'?: boolean
|
|
57
|
+
/**
|
|
58
|
+
* Whether the switch is initially active.
|
|
59
|
+
*
|
|
60
|
+
* To render a controlled switch, use the `checked` prop instead.
|
|
61
|
+
* @default false
|
|
62
|
+
*/
|
|
63
|
+
'defaultChecked'?: boolean
|
|
64
|
+
/**
|
|
65
|
+
* Whether the component should ignore user interaction.
|
|
66
|
+
* @default false
|
|
67
|
+
*/
|
|
68
|
+
'disabled'?: boolean
|
|
69
|
+
/**
|
|
70
|
+
* A ref to access the hidden `<input>` element.
|
|
71
|
+
*/
|
|
72
|
+
'inputRef'?: Ref<HTMLInputElement | null> | ((element: HTMLInputElement | null) => void) | null
|
|
73
|
+
/**
|
|
74
|
+
* Identifies the field when a form is submitted.
|
|
75
|
+
*/
|
|
76
|
+
'name'?: string
|
|
77
|
+
/**
|
|
78
|
+
* Identifies the form that owns the hidden input.
|
|
79
|
+
* Useful when the switch is rendered outside the form.
|
|
80
|
+
*/
|
|
81
|
+
'form'?: string
|
|
82
|
+
/**
|
|
83
|
+
* Whether the user should be unable to activate or deactivate the switch.
|
|
84
|
+
* @default false
|
|
85
|
+
*/
|
|
86
|
+
'readOnly'?: boolean
|
|
87
|
+
/**
|
|
88
|
+
* Whether the user must activate the switch before submitting a form.
|
|
89
|
+
* @default false
|
|
90
|
+
*/
|
|
91
|
+
'required'?: boolean
|
|
92
|
+
/**
|
|
93
|
+
* The value submitted with the form when the switch is on.
|
|
94
|
+
* By default, switch submits the "on" value, matching native checkbox behavior.
|
|
95
|
+
*/
|
|
96
|
+
'value'?: string
|
|
97
|
+
/**
|
|
98
|
+
* The value submitted with the form when the switch is off.
|
|
99
|
+
* By default, unchecked switches do not submit any value, matching native checkbox behavior.
|
|
100
|
+
*/
|
|
101
|
+
'uncheckedValue'?: string
|
|
102
|
+
/**
|
|
103
|
+
* Identifies the element that labels the switch.
|
|
104
|
+
* When omitted, Base UI Vue falls back to the surrounding label system.
|
|
105
|
+
*/
|
|
106
|
+
// eslint-disable-next-line vue/prop-name-casing
|
|
107
|
+
'aria-labelledby'?: string
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export type SwitchRootChangeEventReason = typeof REASONS.none
|
|
111
|
+
export type SwitchRootChangeEventDetails = BaseUIChangeEventDetails<SwitchRootChangeEventReason>
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Represents the switch itself.
|
|
115
|
+
* Renders a `<span>` element and a hidden `<input>` beside.
|
|
116
|
+
*
|
|
117
|
+
* Documentation: [Base UI Vue Switch](https://baseui-vue.com/docs/components/switch)
|
|
118
|
+
*/
|
|
119
|
+
defineOptions({
|
|
120
|
+
name: 'SwitchRoot',
|
|
121
|
+
inheritAttrs: false,
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
const props = withDefaults(defineProps<SwitchRootProps>(), {
|
|
125
|
+
as: 'span',
|
|
126
|
+
defaultChecked: false,
|
|
127
|
+
disabled: false,
|
|
128
|
+
nativeButton: false,
|
|
129
|
+
readOnly: false,
|
|
130
|
+
required: false,
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
const emit = defineEmits<{
|
|
134
|
+
/**
|
|
135
|
+
* Event handler called when the switch is activated or deactivated.
|
|
136
|
+
*/
|
|
137
|
+
checkedChange: [
|
|
138
|
+
checked: boolean,
|
|
139
|
+
eventDetails: BaseUIChangeEventDetails<typeof REASONS.none>,
|
|
140
|
+
]
|
|
141
|
+
}>()
|
|
142
|
+
|
|
143
|
+
const attrs = useAttrs()
|
|
144
|
+
const attrsObject = attrs as Record<string, unknown>
|
|
145
|
+
const instance = getCurrentInstance()
|
|
146
|
+
|
|
147
|
+
const { clearErrors } = useFormContext()
|
|
148
|
+
const {
|
|
149
|
+
disabled: fieldDisabled,
|
|
150
|
+
name: fieldName,
|
|
151
|
+
setDirty,
|
|
152
|
+
setFilled,
|
|
153
|
+
setFocused,
|
|
154
|
+
setTouched,
|
|
155
|
+
state: fieldState,
|
|
156
|
+
validationMode,
|
|
157
|
+
validityData,
|
|
158
|
+
shouldValidateOnChange,
|
|
159
|
+
validation,
|
|
160
|
+
} = useFieldRootContext()
|
|
161
|
+
const labelableContext = useLabelableContext()
|
|
162
|
+
|
|
163
|
+
const disabled = computed(() => fieldDisabled.value || props.disabled)
|
|
164
|
+
const name = computed(() => fieldName.value ?? props.name)
|
|
165
|
+
|
|
166
|
+
const rootElementId = useBaseUiId()
|
|
167
|
+
const controlId = useLabelableId({ id: () => props.id })
|
|
168
|
+
const inputId = computed(() => (props.nativeButton ? undefined : controlId.value))
|
|
169
|
+
|
|
170
|
+
const { value: checked, setValue: setCheckedState } = useControllableState<boolean>({
|
|
171
|
+
controlled: () => (
|
|
172
|
+
instance?.vnode.props && Object.prototype.hasOwnProperty.call(instance.vnode.props, 'checked')
|
|
173
|
+
? props.checked
|
|
174
|
+
: undefined
|
|
175
|
+
),
|
|
176
|
+
default: () => props.defaultChecked,
|
|
177
|
+
name: 'Switch',
|
|
178
|
+
state: 'checked',
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
const controlRef = ref<HTMLElement | null>(null)
|
|
182
|
+
const inputElementRef = ref<HTMLInputElement | null>(null)
|
|
183
|
+
const mergedInputRef = useMergedRefs(inputElementRef, props.inputRef)
|
|
184
|
+
|
|
185
|
+
useField({
|
|
186
|
+
id: computed(() => rootElementId),
|
|
187
|
+
commit: (value: unknown) => validation.commit(value),
|
|
188
|
+
value: checked,
|
|
189
|
+
controlRef,
|
|
190
|
+
name,
|
|
191
|
+
getValue: () => checked.value,
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
const ariaLabelledBy = useAriaLabelledBy({
|
|
195
|
+
ariaLabelledBy: computed(() => props['aria-labelledby']),
|
|
196
|
+
labelId: labelableContext.labelId,
|
|
197
|
+
labelSourceRef: inputElementRef,
|
|
198
|
+
enableFallback: !props.nativeButton,
|
|
199
|
+
labelSourceId: inputId,
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
watchEffect(() => {
|
|
203
|
+
if (checked.value) {
|
|
204
|
+
setFilled(true)
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
watchEffect(() => {
|
|
209
|
+
if (!inputElementRef.value) {
|
|
210
|
+
return
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
validation.setInputRef(inputElementRef.value)
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
watch(
|
|
217
|
+
() => checked.value,
|
|
218
|
+
(nextChecked) => {
|
|
219
|
+
clearErrors(name.value)
|
|
220
|
+
setFilled(nextChecked)
|
|
221
|
+
setDirty(nextChecked !== validityData.value.initialValue)
|
|
222
|
+
|
|
223
|
+
if (shouldValidateOnChange()) {
|
|
224
|
+
void validation.commit(nextChecked)
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
void validation.commit(nextChecked, true)
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
{ flush: 'sync' },
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
const { getButtonProps, buttonRef } = useButton({
|
|
234
|
+
disabled,
|
|
235
|
+
native: computed(() => props.nativeButton),
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
function combineDescriptionProps<
|
|
239
|
+
LocalProps extends object,
|
|
240
|
+
ValidationProps extends object,
|
|
241
|
+
>(
|
|
242
|
+
localProps: LocalProps,
|
|
243
|
+
validationProps: ValidationProps,
|
|
244
|
+
): LocalProps & ValidationProps & { 'aria-describedby'?: string } {
|
|
245
|
+
const localDescribedBy = (localProps as { 'aria-describedby'?: unknown })['aria-describedby']
|
|
246
|
+
const validationDescribedBy = (validationProps as { 'aria-describedby'?: unknown })['aria-describedby']
|
|
247
|
+
const describedBy = Array.from(
|
|
248
|
+
new Set(
|
|
249
|
+
[localDescribedBy, validationDescribedBy]
|
|
250
|
+
.filter(Boolean)
|
|
251
|
+
.flatMap(value => String(value).split(/\s+/).filter(Boolean)),
|
|
252
|
+
),
|
|
253
|
+
).join(' ') || undefined
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
...localProps,
|
|
257
|
+
...validationProps,
|
|
258
|
+
'aria-describedby': describedBy,
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function applyCheckedChange(
|
|
263
|
+
nextChecked: boolean,
|
|
264
|
+
event: Event,
|
|
265
|
+
onApplied?: () => void,
|
|
266
|
+
) {
|
|
267
|
+
const details = createChangeEventDetails(REASONS.none, event)
|
|
268
|
+
|
|
269
|
+
emit('checkedChange', nextChecked, details)
|
|
270
|
+
|
|
271
|
+
if (details.isCanceled) {
|
|
272
|
+
return false
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
onApplied?.()
|
|
276
|
+
setCheckedState(nextChecked)
|
|
277
|
+
return true
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function handleInputChange(event: Event) {
|
|
281
|
+
const target = event.currentTarget as HTMLInputElement
|
|
282
|
+
|
|
283
|
+
if (props.readOnly || disabled.value) {
|
|
284
|
+
event.preventDefault()
|
|
285
|
+
event.stopPropagation()
|
|
286
|
+
target.checked = checked.value
|
|
287
|
+
return
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const applied = applyCheckedChange(target.checked, event)
|
|
291
|
+
if (!applied) {
|
|
292
|
+
target.checked = checked.value
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function handleRootClick(event: MouseEvent | KeyboardEvent) {
|
|
297
|
+
if (props.readOnly || disabled.value) {
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
event.preventDefault()
|
|
302
|
+
|
|
303
|
+
const nextChecked = !checked.value
|
|
304
|
+
applyCheckedChange(nextChecked, event, () => {
|
|
305
|
+
if (inputElementRef.value) {
|
|
306
|
+
inputElementRef.value.checked = nextChecked
|
|
307
|
+
}
|
|
308
|
+
})
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function handleFocus() {
|
|
312
|
+
if (!disabled.value) {
|
|
313
|
+
setFocused(true)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function handleBlur() {
|
|
318
|
+
if (disabled.value) {
|
|
319
|
+
return
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
setTouched(true)
|
|
323
|
+
setFocused(false)
|
|
324
|
+
|
|
325
|
+
if (validationMode.value === 'onBlur') {
|
|
326
|
+
void validation.commit(checked.value)
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const state = computed<SwitchRootState>(() => ({
|
|
331
|
+
...fieldState.value,
|
|
332
|
+
checked: checked.value,
|
|
333
|
+
disabled: disabled.value,
|
|
334
|
+
readOnly: props.readOnly,
|
|
335
|
+
required: props.required,
|
|
336
|
+
}))
|
|
337
|
+
|
|
338
|
+
provide(switchRootContextKey, state)
|
|
339
|
+
defineExpose({
|
|
340
|
+
element: controlRef,
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
const mergedRootRef = useMergedRefs(buttonRef, controlRef)
|
|
344
|
+
|
|
345
|
+
const buttonInteractionAttrs = computed(() => ({
|
|
346
|
+
onClick: attrsObject.onClick,
|
|
347
|
+
onKeydown: attrsObject.onKeydown,
|
|
348
|
+
onKeyup: attrsObject.onKeyup,
|
|
349
|
+
onMousedown: attrsObject.onMousedown,
|
|
350
|
+
onPointerdown: attrsObject.onPointerdown,
|
|
351
|
+
}))
|
|
352
|
+
|
|
353
|
+
const passthroughAttrs = computed(() => {
|
|
354
|
+
const {
|
|
355
|
+
onClick,
|
|
356
|
+
onKeydown,
|
|
357
|
+
onKeyup,
|
|
358
|
+
onMousedown,
|
|
359
|
+
onPointerdown,
|
|
360
|
+
...rest
|
|
361
|
+
} = attrsObject
|
|
362
|
+
|
|
363
|
+
return rest
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
const rootProps = computed(() => {
|
|
367
|
+
const localDescriptionProps = labelableContext.getDescriptionProps()
|
|
368
|
+
const validationProps = validation.getValidationProps()
|
|
369
|
+
const buttonProps = getButtonProps(
|
|
370
|
+
mergeProps(buttonInteractionAttrs.value, {
|
|
371
|
+
onClick: handleRootClick,
|
|
372
|
+
}),
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
return mergeProps(
|
|
376
|
+
buttonProps,
|
|
377
|
+
combineDescriptionProps(localDescriptionProps, validationProps),
|
|
378
|
+
{
|
|
379
|
+
'id': props.nativeButton ? controlId.value : rootElementId,
|
|
380
|
+
'role': 'switch',
|
|
381
|
+
'aria-checked': checked.value,
|
|
382
|
+
'aria-readonly': props.readOnly || undefined,
|
|
383
|
+
'aria-required': props.required || undefined,
|
|
384
|
+
'aria-labelledby': ariaLabelledBy.value,
|
|
385
|
+
'onFocus': handleFocus,
|
|
386
|
+
'onBlur': handleBlur,
|
|
387
|
+
},
|
|
388
|
+
passthroughAttrs.value,
|
|
389
|
+
)
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
const {
|
|
393
|
+
tag,
|
|
394
|
+
mergedProps,
|
|
395
|
+
renderless,
|
|
396
|
+
ref: renderRef,
|
|
397
|
+
} = useRenderElement({
|
|
398
|
+
componentProps: props,
|
|
399
|
+
state,
|
|
400
|
+
props: rootProps,
|
|
401
|
+
stateAttributesMapping,
|
|
402
|
+
defaultTagName: 'span',
|
|
403
|
+
ref: mergedRootRef,
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
const inputProps = computed(() => {
|
|
407
|
+
const localDescriptionProps = labelableContext.getDescriptionProps()
|
|
408
|
+
const validationProps = validation.getInputValidationProps()
|
|
409
|
+
|
|
410
|
+
return mergeProps(
|
|
411
|
+
{
|
|
412
|
+
'checked': checked.value,
|
|
413
|
+
'disabled': disabled.value,
|
|
414
|
+
'form': props.form,
|
|
415
|
+
'id': props.nativeButton ? undefined : inputId.value,
|
|
416
|
+
'name': name.value,
|
|
417
|
+
'required': props.required,
|
|
418
|
+
'type': 'checkbox',
|
|
419
|
+
'aria-hidden': true,
|
|
420
|
+
'tabindex': -1,
|
|
421
|
+
'style': name.value ? visuallyHiddenInput : visuallyHidden,
|
|
422
|
+
'onChange': handleInputChange,
|
|
423
|
+
onFocus() {
|
|
424
|
+
controlRef.value?.focus()
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
props.value !== undefined
|
|
428
|
+
? { value: props.value }
|
|
429
|
+
: EMPTY_OBJECT,
|
|
430
|
+
combineDescriptionProps(localDescriptionProps, validationProps),
|
|
431
|
+
)
|
|
432
|
+
})
|
|
433
|
+
</script>
|
|
434
|
+
|
|
435
|
+
<template>
|
|
436
|
+
<slot v-if="renderless" :ref="renderRef" :props="mergedProps" :state="state" />
|
|
437
|
+
<component :is="tag" v-else :ref="renderRef" v-bind="mergedProps">
|
|
438
|
+
<slot :state="state" />
|
|
439
|
+
</component>
|
|
440
|
+
<input
|
|
441
|
+
v-if="!checked && !disabled && name && uncheckedValue !== undefined"
|
|
442
|
+
type="hidden"
|
|
443
|
+
:form="form"
|
|
444
|
+
:name="name"
|
|
445
|
+
:value="uncheckedValue"
|
|
446
|
+
>
|
|
447
|
+
<input :ref="mergedInputRef" v-bind="inputProps">
|
|
448
|
+
</template>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { InjectionKey, Ref } from 'vue'
|
|
2
|
+
import type { SwitchRootState } from './SwitchRoot.vue'
|
|
3
|
+
import { inject } from 'vue'
|
|
4
|
+
|
|
5
|
+
export type SwitchRootContext = Readonly<Ref<SwitchRootState>>
|
|
6
|
+
|
|
7
|
+
export const switchRootContextKey: InjectionKey<SwitchRootContext> = Symbol(
|
|
8
|
+
'SwitchRootContext',
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
export function useSwitchRootContext(optional: true): SwitchRootContext | undefined
|
|
12
|
+
export function useSwitchRootContext(optional?: false): SwitchRootContext
|
|
13
|
+
export function useSwitchRootContext(optional = false) {
|
|
14
|
+
const context = inject(switchRootContextKey, undefined)
|
|
15
|
+
if (!context && !optional) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
'Base UI Vue: SwitchRootContext is missing. Switch parts must be placed within <SwitchRoot>.',
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return context
|
|
22
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export enum SwitchRootDataAttributes {
|
|
2
|
+
/**
|
|
3
|
+
* Present when the switch is checked.
|
|
4
|
+
*/
|
|
5
|
+
checked = 'data-checked',
|
|
6
|
+
/**
|
|
7
|
+
* Present when the switch is not checked.
|
|
8
|
+
*/
|
|
9
|
+
unchecked = 'data-unchecked',
|
|
10
|
+
/**
|
|
11
|
+
* Present when the switch is disabled.
|
|
12
|
+
*/
|
|
13
|
+
disabled = 'data-disabled',
|
|
14
|
+
/**
|
|
15
|
+
* Present when the switch is readonly.
|
|
16
|
+
*/
|
|
17
|
+
readonly = 'data-readonly',
|
|
18
|
+
/**
|
|
19
|
+
* Present when the switch is required.
|
|
20
|
+
*/
|
|
21
|
+
required = 'data-required',
|
|
22
|
+
/**
|
|
23
|
+
* Present when the switch is in valid state (when wrapped in FieldRoot).
|
|
24
|
+
*/
|
|
25
|
+
valid = 'data-valid',
|
|
26
|
+
/**
|
|
27
|
+
* Present when the switch is in invalid state (when wrapped in FieldRoot).
|
|
28
|
+
*/
|
|
29
|
+
invalid = 'data-invalid',
|
|
30
|
+
/**
|
|
31
|
+
* Present when the switch has been touched (when wrapped in FieldRoot).
|
|
32
|
+
*/
|
|
33
|
+
touched = 'data-touched',
|
|
34
|
+
/**
|
|
35
|
+
* Present when the switch's value has changed (when wrapped in FieldRoot).
|
|
36
|
+
*/
|
|
37
|
+
dirty = 'data-dirty',
|
|
38
|
+
/**
|
|
39
|
+
* Present when the switch is active (when wrapped in FieldRoot).
|
|
40
|
+
*/
|
|
41
|
+
filled = 'data-filled',
|
|
42
|
+
/**
|
|
43
|
+
* Present when the switch is focused (when wrapped in FieldRoot).
|
|
44
|
+
*/
|
|
45
|
+
focused = 'data-focused',
|
|
46
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { StateAttributesMapping } from '../utils/getStateAttributesProps'
|
|
2
|
+
import type { SwitchRootState } from './root/SwitchRoot.vue'
|
|
3
|
+
import { fieldValidityMapping } from '../field/utils/constants'
|
|
4
|
+
import { SwitchRootDataAttributes } from './root/SwitchRootDataAttributes'
|
|
5
|
+
|
|
6
|
+
const CHECKED_ATTRS: Record<string, string> = {
|
|
7
|
+
[SwitchRootDataAttributes.checked]: '',
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const UNCHECKED_ATTRS: Record<string, string> = {
|
|
11
|
+
[SwitchRootDataAttributes.unchecked]: '',
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const stateAttributesMapping: StateAttributesMapping<SwitchRootState> = {
|
|
15
|
+
...fieldValidityMapping,
|
|
16
|
+
checked(value) {
|
|
17
|
+
if (value) {
|
|
18
|
+
return CHECKED_ATTRS
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return UNCHECKED_ATTRS
|
|
22
|
+
},
|
|
23
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { BaseUIComponentProps } from '../../utils/types'
|
|
3
|
+
import type { SwitchRootState } from '../root/SwitchRoot.vue'
|
|
4
|
+
import { computed, useAttrs } from 'vue'
|
|
5
|
+
import { useFieldRootContext } from '../../field/root/FieldRootContext'
|
|
6
|
+
import { useRenderElement } from '../../utils/useRenderElement'
|
|
7
|
+
import { useSwitchRootContext } from '../root/SwitchRootContext'
|
|
8
|
+
import { stateAttributesMapping } from '../stateAttributesMapping'
|
|
9
|
+
|
|
10
|
+
export interface SwitchThumbState extends SwitchRootState {}
|
|
11
|
+
|
|
12
|
+
export interface SwitchThumbProps extends BaseUIComponentProps<SwitchThumbState> {}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The movable part of the switch that indicates whether the switch is on or off.
|
|
16
|
+
* Renders a `<span>`.
|
|
17
|
+
*
|
|
18
|
+
* Documentation: [Base UI Vue Switch](https://baseui-vue.com/docs/components/switch)
|
|
19
|
+
*/
|
|
20
|
+
defineOptions({
|
|
21
|
+
name: 'SwitchThumb',
|
|
22
|
+
inheritAttrs: false,
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const props = withDefaults(defineProps<SwitchThumbProps>(), {
|
|
26
|
+
as: 'span',
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const attrs = useAttrs()
|
|
30
|
+
const attrsObject = attrs as Record<string, unknown>
|
|
31
|
+
|
|
32
|
+
const { state: fieldState } = useFieldRootContext()
|
|
33
|
+
const rootState = useSwitchRootContext()
|
|
34
|
+
|
|
35
|
+
const state = computed<SwitchThumbState>(() => ({
|
|
36
|
+
...fieldState.value,
|
|
37
|
+
...rootState.value,
|
|
38
|
+
}))
|
|
39
|
+
|
|
40
|
+
const {
|
|
41
|
+
tag,
|
|
42
|
+
mergedProps,
|
|
43
|
+
renderless,
|
|
44
|
+
ref: renderRef,
|
|
45
|
+
} = useRenderElement({
|
|
46
|
+
componentProps: props,
|
|
47
|
+
state,
|
|
48
|
+
props: computed(() => attrsObject),
|
|
49
|
+
stateAttributesMapping,
|
|
50
|
+
defaultTagName: 'span',
|
|
51
|
+
})
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<template>
|
|
55
|
+
<slot v-if="renderless" :ref="renderRef" :props="mergedProps" :state="state" />
|
|
56
|
+
<component :is="tag" v-else :ref="renderRef" v-bind="mergedProps">
|
|
57
|
+
<slot :state="state" />
|
|
58
|
+
</component>
|
|
59
|
+
</template>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export enum SwitchThumbDataAttributes {
|
|
2
|
+
/**
|
|
3
|
+
* Present when the switch is checked.
|
|
4
|
+
*/
|
|
5
|
+
checked = 'data-checked',
|
|
6
|
+
/**
|
|
7
|
+
* Present when the switch is not checked.
|
|
8
|
+
*/
|
|
9
|
+
unchecked = 'data-unchecked',
|
|
10
|
+
/**
|
|
11
|
+
* Present when the switch is disabled.
|
|
12
|
+
*/
|
|
13
|
+
disabled = 'data-disabled',
|
|
14
|
+
/**
|
|
15
|
+
* Present when the switch is readonly.
|
|
16
|
+
*/
|
|
17
|
+
readonly = 'data-readonly',
|
|
18
|
+
/**
|
|
19
|
+
* Present when the switch is required.
|
|
20
|
+
*/
|
|
21
|
+
required = 'data-required',
|
|
22
|
+
/**
|
|
23
|
+
* Present when the switch is in valid state (when wrapped in FieldRoot).
|
|
24
|
+
*/
|
|
25
|
+
valid = 'data-valid',
|
|
26
|
+
/**
|
|
27
|
+
* Present when the switch is in invalid state (when wrapped in FieldRoot).
|
|
28
|
+
*/
|
|
29
|
+
invalid = 'data-invalid',
|
|
30
|
+
/**
|
|
31
|
+
* Present when the switch has been touched (when wrapped in FieldRoot).
|
|
32
|
+
*/
|
|
33
|
+
touched = 'data-touched',
|
|
34
|
+
/**
|
|
35
|
+
* Present when the switch's value has changed (when wrapped in FieldRoot).
|
|
36
|
+
*/
|
|
37
|
+
dirty = 'data-dirty',
|
|
38
|
+
/**
|
|
39
|
+
* Present when the switch is active (when wrapped in FieldRoot).
|
|
40
|
+
*/
|
|
41
|
+
filled = 'data-filled',
|
|
42
|
+
/**
|
|
43
|
+
* Present when the switch is focused (when wrapped in FieldRoot).
|
|
44
|
+
*/
|
|
45
|
+
focused = 'data-focused',
|
|
46
|
+
}
|