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,557 @@
1
+ <script setup lang="ts">
2
+ import type { ShallowRef } from 'vue'
3
+ import type { CompositeMetadata } from '../../composite/list/CompositeList.vue'
4
+ import type { FieldRootState } from '../../field/root/FieldRoot.vue'
5
+ import type { BaseUIChangeEventDetails, BaseUIGenericEventDetails } from '../../utils/createBaseUIEventDetails'
6
+ import type { BaseUIComponentProps, Orientation } from '../../utils/types'
7
+ import type { ThumbMetadata } from '../thumb/SliderThumb.vue'
8
+ import { computed, provide, ref, shallowRef, useAttrs, watch } from 'vue'
9
+ import CompositeList from '../../composite/list/CompositeList.vue'
10
+ import { useFieldRootContext } from '../../field/root/FieldRootContext'
11
+ import { useField } from '../../field/useField'
12
+ import { activeElement, contains } from '../../floating-ui-vue/utils'
13
+ import { useFormContext } from '../../form/FormContext'
14
+ import { useLabelableContext } from '../../labelable-provider/LabelableContext'
15
+ import { mergeProps } from '../../merge-props/mergeProps'
16
+ import { areArraysEqual } from '../../utils/areArraysEqual'
17
+ import { clamp } from '../../utils/clamp'
18
+ import { createChangeEventDetails, createGenericEventDetails } from '../../utils/createBaseUIEventDetails'
19
+ import { ownerDocument } from '../../utils/owner'
20
+ import { REASONS } from '../../utils/reasons'
21
+ import { getDefaultLabelId, resolveAriaLabelledBy } from '../../utils/resolveAriaLabelledBy'
22
+ import { useBaseUiId } from '../../utils/useBaseUiId'
23
+ import { useControllableState } from '../../utils/useControllableState'
24
+ import { useMergedRefs } from '../../utils/useMergedRefs'
25
+ import { useRenderElement } from '../../utils/useRenderElement'
26
+ import { warn } from '../../utils/warn'
27
+ import { asc } from '../utils/asc'
28
+ import { getSliderValue } from '../utils/getSliderValue'
29
+ import { validateMinimumDistance } from '../utils/validateMinimumDistance'
30
+ import { sliderRootContextKey } from './SliderRootContext'
31
+ import { sliderStateAttributesMapping } from './stateAttributesMapping'
32
+
33
+ export type SliderRootChangeEventReason
34
+ = | typeof REASONS.inputChange
35
+ | typeof REASONS.trackPress
36
+ | typeof REASONS.drag
37
+ | typeof REASONS.keyboard
38
+ | typeof REASONS.none
39
+
40
+ export type SliderRootCommitEventReason = SliderRootChangeEventReason
41
+
42
+ export type SliderRootChangeEventDetails = BaseUIChangeEventDetails<
43
+ SliderRootChangeEventReason,
44
+ {
45
+ /**
46
+ * The index of the active thumb at the time of the change.
47
+ */
48
+ activeThumbIndex: number
49
+ }
50
+ >
51
+ export type SliderRootCommitEventDetails = BaseUIGenericEventDetails<SliderRootCommitEventReason>
52
+
53
+ export interface SliderRootState extends FieldRootState {
54
+ /**
55
+ * The index of the active thumb.
56
+ */
57
+ activeThumbIndex: number
58
+ /**
59
+ * Whether the component should ignore user interaction.
60
+ */
61
+ disabled: boolean
62
+ /**
63
+ * Whether the thumb is currently being dragged.
64
+ */
65
+ dragging: boolean
66
+ /**
67
+ * The maximum value.
68
+ */
69
+ max: number
70
+ /**
71
+ * The minimum value.
72
+ */
73
+ min: number
74
+ /**
75
+ * The minimum steps between values in a range slider.
76
+ * @default 0
77
+ */
78
+ minStepsBetweenValues: number
79
+ /**
80
+ * The component orientation.
81
+ */
82
+ orientation: Orientation
83
+ /**
84
+ * The step increment of the slider when incrementing or decrementing. It will snap
85
+ * to multiples of this value. Decimal values are supported.
86
+ * @default 1
87
+ */
88
+ step: number
89
+ /**
90
+ * The raw number value of the slider.
91
+ */
92
+ values: readonly number[]
93
+ }
94
+
95
+ export interface SliderRootProps<Value extends number | readonly number[] = number | readonly number[]>
96
+ extends BaseUIComponentProps<SliderRootState> {
97
+ /**
98
+ * The uncontrolled value of the slider when it's initially rendered.
99
+ *
100
+ * To render a controlled slider, use the `value` prop instead.
101
+ */
102
+ defaultValue?: Value
103
+ /**
104
+ * Whether the slider should ignore user interaction.
105
+ * @default false
106
+ */
107
+ disabled?: boolean
108
+ /**
109
+ * Options to format the input value.
110
+ */
111
+ format?: Intl.NumberFormatOptions
112
+ /**
113
+ * The locale used by `Intl.NumberFormat` when formatting the value.
114
+ * Defaults to the user's runtime locale.
115
+ */
116
+ locale?: Intl.LocalesArgument
117
+ /**
118
+ * The maximum allowed value of the slider.
119
+ * Should not be equal to min.
120
+ * @default 100
121
+ */
122
+ max?: number
123
+ /**
124
+ * The minimum allowed value of the slider.
125
+ * Should not be equal to min.
126
+ * @default 0
127
+ */
128
+ min?: number
129
+ /**
130
+ * The minimum steps between values in a range slider.
131
+ * @default 0
132
+ */
133
+ minStepsBetweenValues?: number
134
+ /**
135
+ * Identifies the field when a form is submitted.
136
+ */
137
+ name?: string
138
+ /**
139
+ * Identifies the form that owns the slider inputs.
140
+ * Useful when the slider is rendered outside the form.
141
+ */
142
+ form?: string
143
+ /**
144
+ * The component orientation.
145
+ * @default 'horizontal'
146
+ */
147
+ orientation?: Orientation
148
+ /**
149
+ * The granularity with which the slider can step through values. (A "discrete" slider.)
150
+ * The `min` prop serves as the origin for the valid values.
151
+ * We recommend (max - min) to be evenly divisible by the step.
152
+ * @default 1
153
+ */
154
+ step?: number
155
+ /**
156
+ * The granularity with which the slider can step through values when using Page Up/Page Down or Shift + Arrow Up/Arrow Down.
157
+ * @default 10
158
+ */
159
+ largeStep?: number
160
+ /**
161
+ * How the thumb(s) are aligned relative to `Slider.Control` when the value is at `min` or `max`:
162
+ * - `center`: The center of the thumb is aligned with the control edge
163
+ * - `edge`: The thumb is inset within the control such that its edge is aligned with the control edge
164
+ * - `edge-client-only`: Same as `edge` but renders after React hydration on the client, reducing bundle size in return
165
+ * @default 'center'
166
+ */
167
+ thumbAlignment?: 'center' | 'edge' | 'edge-client-only'
168
+ /**
169
+ * Controls how thumbs behave when they collide during pointer interactions.
170
+ *
171
+ * - `'push'` (default): Thumbs push each other without restoring their previous positions when dragged back.
172
+ * - `'swap'`: Thumbs swap places when dragged past each other.
173
+ * - `'none'`: Thumbs cannot move past each other; excess movement is ignored.
174
+ *
175
+ * @default 'push'
176
+ */
177
+ thumbCollisionBehavior?: 'push' | 'swap' | 'none'
178
+ /**
179
+ * The value of the slider.
180
+ * For ranged sliders, provide an array with two values.
181
+ */
182
+ value?: Value
183
+ id?: string
184
+ ariaLabelledby?: string
185
+ }
186
+
187
+ export type IntlNumberFormatOptionsRef = Readonly<{ value: Intl.NumberFormatOptions | undefined }>
188
+
189
+ /**
190
+ * Groups all parts of the slider.
191
+ * Renders a `<div>` element.
192
+ *
193
+ * Documentation: [Base UI Vue Slider](https://baseui-vue.com/docs/components/slider)
194
+ */
195
+ defineOptions({
196
+ name: 'SliderRoot',
197
+ inheritAttrs: false,
198
+ })
199
+
200
+ const props = withDefaults(defineProps<SliderRootProps>(), {
201
+ as: 'div',
202
+ disabled: false,
203
+ largeStep: 10,
204
+ max: 100,
205
+ min: 0,
206
+ minStepsBetweenValues: 0,
207
+ orientation: 'horizontal',
208
+ step: 1,
209
+ thumbAlignment: 'center',
210
+ thumbCollisionBehavior: 'push',
211
+ })
212
+
213
+ const emit = defineEmits<{
214
+ /**
215
+ * Callback function that is fired when the slider's value changed.
216
+ * You can pull out the new value by accessing `event.target.value` (any).
217
+ *
218
+ * The `eventDetails.reason` indicates what triggered the change:
219
+ *
220
+ * - `'input-change'` when the hidden range input emits a change event (for example, via form integration)
221
+ * - `'track-press'` when the control track is pressed
222
+ * - `'drag'` while dragging a thumb
223
+ * - `'keyboard'` for keyboard input
224
+ * - `'none'` when the change is triggered without a specific interaction
225
+ */
226
+ valueChange: [value: number | readonly number[], details: SliderRootChangeEventDetails]
227
+ /**
228
+ * Callback function that is fired when the `pointerup` is triggered.
229
+ * **Warning**: This is a generic event not a change event.
230
+ *
231
+ * The `eventDetails.reason` indicates what triggered the commit:
232
+ *
233
+ * - `'drag'` while dragging a thumb
234
+ * - `'track-press'` when the control track is pressed
235
+ * - `'keyboard'` for keyboard input
236
+ * - `'input-change'` when the hidden range input emits a change event (for example, via form integration)
237
+ * - `'none'` when the commit occurs without a specific interaction
238
+ */
239
+ valueCommitted: [value: number | readonly number[], details: SliderRootCommitEventDetails]
240
+ }>()
241
+
242
+ const attrs = useAttrs()
243
+ const attrsObject = attrs as Record<string, unknown>
244
+ const explicitAriaLabelledBy = computed(() =>
245
+ props.ariaLabelledby ?? attrs['aria-labelledby'] as string | undefined,
246
+ )
247
+
248
+ const id = useBaseUiId(props.id)
249
+ const idRef = computed(() => id)
250
+ const defaultLabelId = computed(() => getDefaultLabelId(id))
251
+
252
+ const { clearErrors } = useFormContext()
253
+ const {
254
+ state: fieldState,
255
+ disabled: fieldDisabled,
256
+ name: fieldName,
257
+ setTouched,
258
+ setDirty,
259
+ validation,
260
+ validityData,
261
+ shouldValidateOnChange,
262
+ } = useFieldRootContext()
263
+ const { labelId: fieldLabelId } = useLabelableContext()
264
+
265
+ const localLabelId = ref<string | undefined>(undefined)
266
+
267
+ const ariaLabelledBy = computed(() =>
268
+ explicitAriaLabelledBy.value ?? resolveAriaLabelledBy(fieldLabelId.value, localLabelId.value),
269
+ )
270
+ const disabled = computed(() => fieldDisabled.value || props.disabled)
271
+ const name = computed(() => fieldName.value ?? props.name)
272
+
273
+ const controllableValue = useControllableState<number | readonly number[]>({
274
+ controlled: () => props.value,
275
+ default: () => props.defaultValue ?? props.min,
276
+ name: 'Slider',
277
+ state: 'value',
278
+ })
279
+ const valueUnwrapped = controllableValue.value
280
+ const setValueUnwrapped = controllableValue.setValue
281
+
282
+ const sliderRef = ref<HTMLElement | null>(null)
283
+ const controlRef = ref<HTMLElement | null>(null)
284
+ const thumbRefs = ref<(HTMLElement | null)[]>([])
285
+ const pressedInputRef = ref<HTMLInputElement | null>(null)
286
+ const pressedThumbCenterOffsetRef = ref<number | null>(null)
287
+ const pressedThumbIndexRef = ref(-1)
288
+ const pressedValuesRef = ref<readonly number[] | null>(null)
289
+ const lastChangedValueRef = ref<number | readonly number[] | null>(null)
290
+ const lastChangeReasonRef = ref<SliderRootChangeEventReason>(REASONS.none)
291
+ const formatOptionsRef = computed(() => props.format)
292
+
293
+ const active = ref(-1)
294
+ const lastUsedThumbIndex = ref(-1)
295
+ const dragging = ref(false)
296
+ const thumbMap = shallowRef<Map<Element, CompositeMetadata<ThumbMetadata> | null>>(new Map())
297
+ const indicatorPosition = ref<(number | undefined)[]>([undefined, undefined])
298
+
299
+ function setActive(value: number) {
300
+ active.value = value
301
+ if (value !== -1) {
302
+ lastUsedThumbIndex.value = value
303
+ }
304
+ }
305
+
306
+ function registerFieldControlRef(element: HTMLElement | null) {
307
+ if (element) {
308
+ controlRef.value = element
309
+ }
310
+ }
311
+
312
+ function setThumbMap(nextMap: Map<Element, CompositeMetadata<ThumbMetadata> | null>) {
313
+ thumbMap.value = nextMap
314
+ }
315
+
316
+ const compositeListProps = {
317
+ elementsRef: thumbRefs,
318
+ onMapChange: setThumbMap,
319
+ }
320
+
321
+ const range = computed(() => Array.isArray(valueUnwrapped.value))
322
+ const values = computed<readonly number[]>(() => {
323
+ if (!range.value) {
324
+ return [clamp(valueUnwrapped.value as number, props.min, props.max)]
325
+ }
326
+
327
+ return [...(valueUnwrapped.value as readonly number[])].sort(asc)
328
+ })
329
+
330
+ useField({
331
+ id: idRef,
332
+ commit: (value: unknown) => validation.commit(value),
333
+ value: computed(() => valueUnwrapped.value),
334
+ controlRef,
335
+ name,
336
+ getValue: () => valueUnwrapped.value,
337
+ })
338
+
339
+ watch(
340
+ () => valueUnwrapped.value,
341
+ () => {
342
+ clearErrors(name.value)
343
+
344
+ if (shouldValidateOnChange()) {
345
+ validation.commit(valueUnwrapped.value)
346
+ }
347
+ else {
348
+ validation.commit(valueUnwrapped.value, true)
349
+ }
350
+
351
+ const initialValue = validityData.value.initialValue as number | readonly number[] | undefined
352
+ let isDirty: boolean
353
+ if (Array.isArray(valueUnwrapped.value) && Array.isArray(initialValue)) {
354
+ isDirty = !areArraysEqual(valueUnwrapped.value, initialValue)
355
+ }
356
+ else {
357
+ isDirty = valueUnwrapped.value !== initialValue
358
+ }
359
+ setDirty(isDirty)
360
+ },
361
+ )
362
+
363
+ function areValuesEqual(
364
+ newValue: number | readonly number[],
365
+ oldValue: number | readonly number[],
366
+ ) {
367
+ if (typeof newValue === 'number' && typeof oldValue === 'number') {
368
+ return newValue === oldValue
369
+ }
370
+ if (Array.isArray(newValue) && Array.isArray(oldValue)) {
371
+ return areArraysEqual(newValue, oldValue)
372
+ }
373
+ return false
374
+ }
375
+
376
+ function setValue(
377
+ newValue: number | number[],
378
+ details?: SliderRootChangeEventDetails,
379
+ ) {
380
+ if (Number.isNaN(newValue) || areValuesEqual(newValue, valueUnwrapped.value)) {
381
+ return
382
+ }
383
+
384
+ const changeDetails
385
+ = details
386
+ ?? createChangeEventDetails(REASONS.none, undefined, undefined, { activeThumbIndex: -1 })
387
+
388
+ lastChangeReasonRef.value = changeDetails.reason
389
+
390
+ const nativeEvent = changeDetails.event
391
+ const EventConstructor = (nativeEvent.constructor as typeof Event | undefined) ?? Event
392
+ const clonedEvent = new EventConstructor(nativeEvent.type, nativeEvent as EventInit)
393
+ Object.defineProperty(clonedEvent, 'target', {
394
+ writable: true,
395
+ value: { value: newValue, name: name.value },
396
+ })
397
+
398
+ changeDetails.event = clonedEvent as typeof changeDetails.event
399
+ lastChangedValueRef.value = newValue
400
+ emit('valueChange', newValue, changeDetails)
401
+
402
+ if (changeDetails.isCanceled) {
403
+ return
404
+ }
405
+
406
+ setValueUnwrapped(newValue)
407
+ }
408
+
409
+ function getSliderChangeEventReason(event: KeyboardEvent | Event): SliderRootChangeEventReason {
410
+ return event instanceof KeyboardEvent ? REASONS.keyboard : REASONS.inputChange
411
+ }
412
+
413
+ function handleInputChange(valueInput: number, index: number, event: KeyboardEvent | Event) {
414
+ const newValue = getSliderValue(
415
+ valueInput,
416
+ index,
417
+ props.min,
418
+ props.max,
419
+ range.value,
420
+ values.value,
421
+ )
422
+
423
+ if (validateMinimumDistance(newValue, props.step, props.minStepsBetweenValues)) {
424
+ const reason = getSliderChangeEventReason(event)
425
+ setValue(
426
+ newValue,
427
+ createChangeEventDetails(reason, event, undefined, {
428
+ activeThumbIndex: index,
429
+ }),
430
+ )
431
+ setTouched(true)
432
+
433
+ const nextValue = lastChangedValueRef.value ?? newValue
434
+ emit('valueCommitted', nextValue, createGenericEventDetails(reason, event))
435
+ }
436
+ }
437
+
438
+ if (process.env.NODE_ENV !== 'production' && props.min >= props.max) {
439
+ warn('Slider `max` must be greater than `min`.')
440
+ }
441
+
442
+ watch(
443
+ () => disabled.value,
444
+ (nextDisabled) => {
445
+ const activeEl = sliderRef.value ? activeElement(ownerDocument(sliderRef.value)!) : null
446
+ if (nextDisabled && activeEl instanceof HTMLElement && contains(sliderRef.value, activeEl)) {
447
+ activeEl.blur()
448
+ }
449
+
450
+ if (nextDisabled && active.value !== -1) {
451
+ setActive(-1)
452
+ }
453
+ },
454
+ )
455
+
456
+ const state = computed<SliderRootState>(() => ({
457
+ ...fieldState.value,
458
+ activeThumbIndex: active.value,
459
+ disabled: disabled.value,
460
+ dragging: dragging.value,
461
+ orientation: props.orientation,
462
+ max: props.max,
463
+ min: props.min,
464
+ minStepsBetweenValues: props.minStepsBetweenValues,
465
+ step: props.step,
466
+ values: values.value,
467
+ }))
468
+
469
+ provide(sliderRootContextKey, {
470
+ active,
471
+ lastUsedThumbIndex,
472
+ controlRef,
473
+ dragging,
474
+ disabled,
475
+ validation,
476
+ formatOptionsRef,
477
+ handleInputChange,
478
+ indicatorPosition,
479
+ inset: computed(() => props.thumbAlignment !== 'center'),
480
+ labelId: computed(() => ariaLabelledBy.value),
481
+ rootLabelId: defaultLabelId,
482
+ largeStep: computed(() => props.largeStep),
483
+ lastChangedValueRef,
484
+ lastChangeReasonRef,
485
+ locale: computed(() => props.locale),
486
+ max: computed(() => props.max),
487
+ min: computed(() => props.min),
488
+ minStepsBetweenValues: computed(() => props.minStepsBetweenValues),
489
+ form: computed(() => props.form),
490
+ name,
491
+ onValueCommitted(value, details) {
492
+ emit('valueCommitted', value, details)
493
+ },
494
+ orientation: computed(() => props.orientation),
495
+ pressedInputRef,
496
+ pressedThumbCenterOffsetRef,
497
+ pressedThumbIndexRef,
498
+ pressedValuesRef,
499
+ registerFieldControlRef,
500
+ renderBeforeHydration: computed(() => props.thumbAlignment === 'edge'),
501
+ setActive,
502
+ setDragging(value: boolean) {
503
+ dragging.value = value
504
+ },
505
+ setIndicatorPosition(updater) {
506
+ indicatorPosition.value = updater(indicatorPosition.value)
507
+ },
508
+ setLabelId(value) {
509
+ localLabelId.value = value
510
+ },
511
+ setValue,
512
+ state,
513
+ step: computed(() => props.step),
514
+ thumbCollisionBehavior: computed(() => props.thumbCollisionBehavior),
515
+ thumbMap: thumbMap as ShallowRef<Map<Element, CompositeMetadata<ThumbMetadata> | null>>,
516
+ thumbRefs,
517
+ values,
518
+ })
519
+
520
+ const rootProps = computed(() => mergeProps(
521
+ attrsObject,
522
+ validation.getValidationProps(),
523
+ {
524
+ 'aria-labelledby': ariaLabelledBy.value,
525
+ 'id': id,
526
+ 'role': 'group',
527
+ },
528
+ ))
529
+
530
+ const {
531
+ tag,
532
+ mergedProps,
533
+ renderless,
534
+ ref: renderRef,
535
+ } = useRenderElement({
536
+ componentProps: props,
537
+ state,
538
+ ref: useMergedRefs(sliderRef),
539
+ props: rootProps,
540
+ defaultTagName: 'div',
541
+ stateAttributesMapping: sliderStateAttributesMapping,
542
+ })
543
+ </script>
544
+
545
+ <template>
546
+ <CompositeList v-bind="compositeListProps">
547
+ <slot v-if="renderless" :ref="renderRef" :props="mergedProps" :state="state" />
548
+ <component
549
+ :is="tag"
550
+ v-else
551
+ :ref="renderRef"
552
+ v-bind="mergedProps"
553
+ >
554
+ <slot />
555
+ </component>
556
+ </CompositeList>
557
+ </template>
@@ -0,0 +1,126 @@
1
+ import type { InjectionKey, Ref, ShallowRef } from 'vue'
2
+ import type { CompositeMetadata } from '../../composite/list/CompositeList.vue'
3
+ import type { UseFieldValidationReturnValue } from '../../field/root/useFieldValidation'
4
+ import type { BaseUIChangeEventDetails, BaseUIGenericEventDetails } from '../../utils/createBaseUIEventDetails'
5
+ import type { REASONS } from '../../utils/reasons'
6
+ import type { Orientation } from '../../utils/types'
7
+ import type { ThumbMetadata } from '../thumb/SliderThumb.vue'
8
+ import type { IntlNumberFormatOptionsRef, SliderRootState } from './SliderRoot.vue'
9
+ import { inject } from 'vue'
10
+
11
+ export interface SliderRootContext {
12
+ /**
13
+ * The index of the active thumb.
14
+ */
15
+ active: Ref<number>
16
+ /**
17
+ * The index of the most recently interacted thumb.
18
+ */
19
+ lastUsedThumbIndex: Ref<number>
20
+ controlRef: Ref<HTMLElement | null>
21
+ dragging: Ref<boolean>
22
+ disabled: Ref<boolean>
23
+ validation: UseFieldValidationReturnValue
24
+ formatOptionsRef: IntlNumberFormatOptionsRef
25
+ handleInputChange: (
26
+ valueInput: number,
27
+ index: number,
28
+ event: KeyboardEvent | Event,
29
+ ) => void
30
+ indicatorPosition: Ref<(number | undefined)[]>
31
+ inset: Ref<boolean>
32
+ labelId: Ref<string | undefined>
33
+ rootLabelId: Ref<string | undefined>
34
+ /**
35
+ * The large step value of the slider when incrementing or decrementing while the shift key is held,
36
+ * or when using Page-Up or Page-Down keys. Snaps to multiples of this value.
37
+ * @default 10
38
+ */
39
+ largeStep: Ref<number>
40
+ lastChangedValueRef: Ref<number | readonly number[] | null>
41
+ lastChangeReasonRef: Ref<SliderRootChangeReason>
42
+ /**
43
+ * The locale used by `Intl.NumberFormat` when formatting the value.
44
+ * Defaults to the user's runtime locale.
45
+ */
46
+ locale: Ref<Intl.LocalesArgument | undefined>
47
+ /**
48
+ * The maximum allowed value of the slider.
49
+ */
50
+ max: Ref<number>
51
+ /**
52
+ * The minimum allowed value of the slider.
53
+ */
54
+ min: Ref<number>
55
+ /**
56
+ * The minimum steps between values in a range slider.
57
+ */
58
+ minStepsBetweenValues: Ref<number>
59
+ form: Ref<string | undefined>
60
+ name: Ref<string | undefined>
61
+ /**
62
+ * Function to be called when drag ends and the pointer is released.
63
+ */
64
+ onValueCommitted: (
65
+ value: number | readonly number[],
66
+ details: BaseUIGenericEventDetails<SliderRootChangeReason>,
67
+ ) => void
68
+ /**
69
+ * The component orientation.
70
+ * @default 'horizontal'
71
+ */
72
+ orientation: Ref<Orientation>
73
+ pressedInputRef: Ref<HTMLInputElement | null>
74
+ pressedThumbCenterOffsetRef: Ref<number | null>
75
+ pressedThumbIndexRef: Ref<number>
76
+ pressedValuesRef: Ref<readonly number[] | null>
77
+ registerFieldControlRef: (element: HTMLElement | null) => void
78
+ renderBeforeHydration: Ref<boolean>
79
+ setActive: (value: number) => void
80
+ setDragging: (value: boolean) => void
81
+ setIndicatorPosition: (updater: (prev: (number | undefined)[]) => (number | undefined)[]) => void
82
+ setLabelId: (value: string | undefined) => void
83
+ /**
84
+ * Callback fired when dragging and invokes onValueChange.
85
+ */
86
+ setValue: (
87
+ value: number | number[],
88
+ details?: BaseUIChangeEventDetails<SliderRootChangeReason, { activeThumbIndex: number }>,
89
+ ) => void
90
+ state: Ref<SliderRootState>
91
+ /**
92
+ * The step increment of the slider when incrementing or decrementing. It will snap
93
+ * to multiples of this value. Decimal values are supported.
94
+ * @default 1
95
+ */
96
+ step: Ref<number>
97
+ thumbCollisionBehavior: Ref<'push' | 'swap' | 'none'>
98
+ thumbMap: ShallowRef<Map<Element, CompositeMetadata<ThumbMetadata> | null>>
99
+ thumbRefs: Ref<(HTMLElement | null)[]>
100
+ /**
101
+ * The value(s) of the slider
102
+ */
103
+ values: Ref<readonly number[]>
104
+ }
105
+
106
+ export type SliderRootChangeReason
107
+ = | typeof REASONS.inputChange
108
+ | typeof REASONS.trackPress
109
+ | typeof REASONS.drag
110
+ | typeof REASONS.keyboard
111
+ | typeof REASONS.none
112
+
113
+ export const sliderRootContextKey: InjectionKey<SliderRootContext> = Symbol('SliderRootContext')
114
+
115
+ export function useSliderRootContext(optional: true): SliderRootContext | undefined
116
+ export function useSliderRootContext(optional?: false): SliderRootContext
117
+ export function useSliderRootContext(optional = false) {
118
+ const context = inject(sliderRootContextKey, undefined)
119
+ if (!context && !optional) {
120
+ throw new Error(
121
+ 'Base UI Vue: SliderRootContext is missing. Slider parts must be placed within <SliderRoot>.',
122
+ )
123
+ }
124
+
125
+ return context
126
+ }