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.
Files changed (204) hide show
  1. package/README.md +41 -1
  2. package/dist/button/Button.cjs +53 -12
  3. package/dist/button/Button.cjs.map +1 -1
  4. package/dist/button/Button.js +26 -15
  5. package/dist/button/Button.js.map +1 -1
  6. package/dist/button/ToolbarButton.cjs +367 -0
  7. package/dist/button/ToolbarButton.cjs.map +1 -0
  8. package/dist/button/ToolbarButton.js +320 -0
  9. package/dist/button/ToolbarButton.js.map +1 -0
  10. package/dist/button/ToolbarButtonDataAttributes.cjs +27 -0
  11. package/dist/button/ToolbarButtonDataAttributes.cjs.map +1 -0
  12. package/dist/button/ToolbarButtonDataAttributes.js +21 -0
  13. package/dist/button/ToolbarButtonDataAttributes.js.map +1 -0
  14. package/dist/checkbox/index.cjs +1173 -0
  15. package/dist/checkbox/index.cjs.map +1 -0
  16. package/dist/checkbox/index.js +1048 -0
  17. package/dist/checkbox/index.js.map +1 -0
  18. package/dist/checkbox-group/CheckboxGroup.cjs +629 -0
  19. package/dist/checkbox-group/CheckboxGroup.cjs.map +1 -0
  20. package/dist/checkbox-group/CheckboxGroup.js +540 -0
  21. package/dist/checkbox-group/CheckboxGroup.js.map +1 -0
  22. package/dist/checkbox-group/CheckboxGroupDataAttributes.cjs +18 -0
  23. package/dist/checkbox-group/CheckboxGroupDataAttributes.cjs.map +1 -0
  24. package/dist/checkbox-group/CheckboxGroupDataAttributes.js +12 -0
  25. package/dist/checkbox-group/CheckboxGroupDataAttributes.js.map +1 -0
  26. package/dist/composite/composite.cjs +167 -0
  27. package/dist/composite/composite.cjs.map +1 -1
  28. package/dist/composite/composite.js +96 -1
  29. package/dist/composite/composite.js.map +1 -1
  30. package/dist/composite/constants.cjs +12 -0
  31. package/dist/composite/constants.cjs.map +1 -0
  32. package/dist/composite/constants.js +6 -0
  33. package/dist/composite/constants.js.map +1 -0
  34. package/dist/control/FieldControl.cjs +18 -343
  35. package/dist/control/FieldControl.cjs.map +1 -1
  36. package/dist/control/FieldControl.js +14 -285
  37. package/dist/control/FieldControl.js.map +1 -1
  38. package/dist/control/SliderControl.cjs +636 -0
  39. package/dist/control/SliderControl.cjs.map +1 -0
  40. package/dist/control/SliderControl.js +553 -0
  41. package/dist/control/SliderControl.js.map +1 -0
  42. package/dist/control/SliderControlDataAttributes.cjs +47 -0
  43. package/dist/control/SliderControlDataAttributes.cjs.map +1 -0
  44. package/dist/control/SliderControlDataAttributes.js +41 -0
  45. package/dist/control/SliderControlDataAttributes.js.map +1 -0
  46. package/dist/csp-provider/CSPContext.cjs +32 -0
  47. package/dist/csp-provider/CSPContext.cjs.map +1 -0
  48. package/dist/csp-provider/CSPContext.js +21 -0
  49. package/dist/csp-provider/CSPContext.js.map +1 -0
  50. package/dist/csp-provider/CSPProvider.cjs +46 -0
  51. package/dist/csp-provider/CSPProvider.cjs.map +1 -0
  52. package/dist/csp-provider/CSPProvider.js +41 -0
  53. package/dist/csp-provider/CSPProvider.js.map +1 -0
  54. package/dist/description/FieldDescription.cjs +5 -5
  55. package/dist/description/FieldDescription.cjs.map +1 -1
  56. package/dist/description/FieldDescription.js +1 -1
  57. package/dist/direction-provider/DirectionProvider.cjs +2 -2
  58. package/dist/direction-provider/DirectionProvider.cjs.map +1 -1
  59. package/dist/direction-provider/DirectionProvider.js +1 -1
  60. package/dist/error/FieldError.cjs +10 -288
  61. package/dist/error/FieldError.cjs.map +1 -1
  62. package/dist/error/FieldError.js +4 -246
  63. package/dist/error/FieldError.js.map +1 -1
  64. package/dist/form/Form.cjs +5 -4
  65. package/dist/form/Form.cjs.map +1 -1
  66. package/dist/form/Form.js +5 -4
  67. package/dist/form/Form.js.map +1 -1
  68. package/dist/group/ToolbarGroup.cjs +92 -0
  69. package/dist/group/ToolbarGroup.cjs.map +1 -0
  70. package/dist/group/ToolbarGroup.js +87 -0
  71. package/dist/group/ToolbarGroup.js.map +1 -0
  72. package/dist/group/ToolbarGroupDataAttributes.cjs +23 -0
  73. package/dist/group/ToolbarGroupDataAttributes.cjs.map +1 -0
  74. package/dist/group/ToolbarGroupDataAttributes.js +17 -0
  75. package/dist/group/ToolbarGroupDataAttributes.js.map +1 -0
  76. package/dist/header/AccordionHeader.cjs +2 -2
  77. package/dist/header/AccordionHeader.js +1 -1
  78. package/dist/image/AvatarImage.cjs +4 -4
  79. package/dist/image/AvatarImage.cjs.map +1 -1
  80. package/dist/image/AvatarImage.js +1 -1
  81. package/dist/index.cjs +80 -10
  82. package/dist/index.d.cts +2751 -612
  83. package/dist/index.d.cts.map +1 -1
  84. package/dist/index.d.ts +2751 -612
  85. package/dist/index.d.ts.map +1 -1
  86. package/dist/index.js +15 -5
  87. package/dist/index2.cjs +3651 -370
  88. package/dist/index2.cjs.map +1 -1
  89. package/dist/index2.js +3365 -270
  90. package/dist/index2.js.map +1 -1
  91. package/package.json +8 -4
  92. package/src/accordion/root/AccordionRoot.vue +2 -1
  93. package/src/checkbox/index.ts +23 -0
  94. package/src/checkbox/indicator/CheckboxIndicator.vue +102 -0
  95. package/src/checkbox/indicator/CheckboxIndicatorDataAttributes.ts +61 -0
  96. package/src/checkbox/root/CheckboxRoot.vue +632 -0
  97. package/src/checkbox/root/CheckboxRootContext.ts +22 -0
  98. package/src/checkbox/root/CheckboxRootDataAttributes.ts +54 -0
  99. package/src/checkbox/utils/useStateAttributesMapping.ts +30 -0
  100. package/src/checkbox-group/CheckboxGroup.vue +241 -0
  101. package/src/checkbox-group/CheckboxGroupContext.ts +39 -0
  102. package/src/checkbox-group/CheckboxGroupDataAttributes.ts +6 -0
  103. package/src/checkbox-group/index.ts +11 -0
  104. package/src/checkbox-group/useCheckboxGroupParent.ts +173 -0
  105. package/src/collapsible/panel/useCollapsiblePanel.ts +2 -1
  106. package/src/collapsible/root/useCollapsibleRoot.ts +3 -1
  107. package/src/composite/composite.ts +2 -0
  108. package/src/composite/item/CompositeItem.vue +7 -8
  109. package/src/composite/root/CompositeRoot.vue +12 -1
  110. package/src/csp-provider/CSPContext.ts +26 -0
  111. package/src/csp-provider/CSPProvider.vue +40 -0
  112. package/src/csp-provider/index.ts +5 -0
  113. package/src/field/item/FieldItem.vue +6 -1
  114. package/src/field/label/FieldLabel.vue +10 -51
  115. package/src/field/root/FieldRoot.vue +16 -3
  116. package/src/floating-ui-vue/types.ts +1 -4
  117. package/src/floating-ui-vue/utils/element.ts +12 -0
  118. package/src/floating-ui-vue/utils/shadowDom.ts +44 -0
  119. package/src/floating-ui-vue/utils.ts +3 -0
  120. package/src/form/Form.vue +5 -3
  121. package/src/index.ts +9 -0
  122. package/src/labelable-provider/LabelableContext.ts +2 -2
  123. package/src/labelable-provider/LabelableProvider.vue +21 -4
  124. package/src/labelable-provider/index.ts +2 -0
  125. package/src/labelable-provider/useAriaLabelledBy.ts +9 -9
  126. package/src/labelable-provider/useLabel.ts +115 -0
  127. package/src/labelable-provider/useLabelableId.ts +12 -10
  128. package/src/separator/Separator.vue +65 -0
  129. package/src/separator/SeparatorDataAttributes.ts +7 -0
  130. package/src/separator/index.ts +3 -0
  131. package/src/slider/control/SliderControl.vue +497 -0
  132. package/src/slider/control/SliderControlDataAttributes.ts +35 -0
  133. package/src/slider/index.ts +35 -0
  134. package/src/slider/indicator/SliderIndicator.vue +144 -0
  135. package/src/slider/indicator/SliderIndicatorDataAttributes.ts +35 -0
  136. package/src/slider/label/SliderLabel.vue +75 -0
  137. package/src/slider/root/SliderRoot.vue +557 -0
  138. package/src/slider/root/SliderRootContext.ts +126 -0
  139. package/src/slider/root/SliderRootDataAttributes.ts +35 -0
  140. package/src/slider/root/stateAttributesMapping.ts +13 -0
  141. package/src/slider/thumb/SliderThumb.vue +601 -0
  142. package/src/slider/thumb/SliderThumbDataAttributes.ts +39 -0
  143. package/src/slider/thumb/prehydrationScript.min.ts +5 -0
  144. package/src/slider/thumb/prehydrationScript.template.js +69 -0
  145. package/src/slider/track/SliderTrack.vue +48 -0
  146. package/src/slider/track/SliderTrackDataAttributes.ts +10 -0
  147. package/src/slider/utils/asc.ts +3 -0
  148. package/src/slider/utils/getMidpoint.ts +9 -0
  149. package/src/slider/utils/getPushedThumbValues.ts +68 -0
  150. package/src/slider/utils/getSliderValue.ts +25 -0
  151. package/src/slider/utils/replaceArrayItemAtIndex.ts +15 -0
  152. package/src/slider/utils/resolveThumbCollision.ts +177 -0
  153. package/src/slider/utils/roundValueToStep.ts +19 -0
  154. package/src/slider/utils/test-utils.ts +25 -0
  155. package/src/slider/utils/validateMinimumDistance.ts +20 -0
  156. package/src/slider/utils/valueArrayToPercentages.ts +10 -0
  157. package/src/slider/value/SliderValue.vue +90 -0
  158. package/src/slider/value/SliderValueDataAttributes.ts +35 -0
  159. package/src/switch/index.ts +14 -0
  160. package/src/switch/root/SwitchRoot.vue +448 -0
  161. package/src/switch/root/SwitchRootContext.ts +22 -0
  162. package/src/switch/root/SwitchRootDataAttributes.ts +46 -0
  163. package/src/switch/stateAttributesMapping.ts +23 -0
  164. package/src/switch/thumb/SwitchThumb.vue +59 -0
  165. package/src/switch/thumb/SwitchThumbDataAttributes.ts +46 -0
  166. package/src/toggle/Toggle.vue +211 -0
  167. package/src/toggle/ToggleDataAttributes.ts +6 -0
  168. package/src/toggle/index.ts +3 -0
  169. package/src/toggle-group/ToggleGroup.vue +224 -0
  170. package/src/toggle-group/ToggleGroupContext.ts +45 -0
  171. package/src/toggle-group/ToggleGroupDataAttributes.ts +15 -0
  172. package/src/toggle-group/index.ts +5 -0
  173. package/src/toolbar/button/ToolbarButton.vue +99 -0
  174. package/src/toolbar/button/ToolbarButtonDataAttributes.ts +15 -0
  175. package/src/toolbar/group/ToolbarGroup.vue +70 -0
  176. package/src/toolbar/group/ToolbarGroupContext.ts +23 -0
  177. package/src/toolbar/group/ToolbarGroupDataAttributes.ts +11 -0
  178. package/src/toolbar/index.ts +27 -0
  179. package/src/toolbar/input/ToolbarInput.vue +114 -0
  180. package/src/toolbar/input/ToolbarInputDataAttributes.ts +15 -0
  181. package/src/toolbar/link/ToolbarLink.vue +54 -0
  182. package/src/toolbar/link/ToolbarLinkDataAttributes.ts +7 -0
  183. package/src/toolbar/root/ToolbarRoot.vue +144 -0
  184. package/src/toolbar/root/ToolbarRootContext.ts +29 -0
  185. package/src/toolbar/root/ToolbarRootDataAttributes.ts +11 -0
  186. package/src/toolbar/separator/ToolbarSeparator.vue +41 -0
  187. package/src/toolbar/separator/ToolbarSeparatorDataAttributes.ts +7 -0
  188. package/src/use-button/useButton.ts +2 -1
  189. package/src/utils/areArraysEqual.ts +12 -0
  190. package/src/utils/clamp.ts +7 -0
  191. package/src/utils/createBaseUIEventDetails.ts +9 -0
  192. package/src/utils/formatNumber.ts +7 -0
  193. package/src/utils/owner.ts +5 -0
  194. package/src/utils/resolveAriaLabelledBy.ts +10 -0
  195. package/src/utils/useControllableState.ts +78 -14
  196. package/src/utils/useFocusableWhenDisabled.ts +6 -1
  197. package/src/utils/useMergedRefs.ts +26 -2
  198. package/src/utils/useRegisteredLabelId.ts +21 -0
  199. package/src/utils/valueToPercent.ts +7 -0
  200. package/src/utils/visuallyHidden.ts +24 -0
  201. package/dist/direction-provider/DirectionContext.cjs +0 -26
  202. package/dist/direction-provider/DirectionContext.cjs.map +0 -1
  203. package/dist/direction-provider/DirectionContext.js +0 -15
  204. 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
+ }