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,35 @@
1
+ export enum SliderRootDataAttributes {
2
+ /**
3
+ * Present while the user is dragging.
4
+ */
5
+ dragging = 'data-dragging',
6
+ /**
7
+ * Indicates the orientation of the slider.
8
+ * @type {'horizontal' | 'vertical'}
9
+ */
10
+ orientation = 'data-orientation',
11
+ /**
12
+ * Present when the slider is disabled.
13
+ */
14
+ disabled = 'data-disabled',
15
+ /**
16
+ * Present when the slider is in valid state (when wrapped in Field.Root).
17
+ */
18
+ valid = 'data-valid',
19
+ /**
20
+ * Present when the slider is in invalid state (when wrapped in Field.Root).
21
+ */
22
+ invalid = 'data-invalid',
23
+ /**
24
+ * Present when the slider has been touched (when wrapped in Field.Root).
25
+ */
26
+ touched = 'data-touched',
27
+ /**
28
+ * Present when the slider's value has changed (when wrapped in Field.Root).
29
+ */
30
+ dirty = 'data-dirty',
31
+ /**
32
+ * Present when the slider is focused (when wrapped in Field.Root).
33
+ */
34
+ focused = 'data-focused',
35
+ }
@@ -0,0 +1,13 @@
1
+ import type { StateAttributesMapping } from '../../utils/getStateAttributesProps'
2
+ import type { SliderRootState } from './SliderRoot.vue'
3
+ import { fieldValidityMapping } from '../../field/utils/constants'
4
+
5
+ export const sliderStateAttributesMapping: StateAttributesMapping<SliderRootState> = {
6
+ activeThumbIndex: () => null,
7
+ max: () => null,
8
+ min: () => null,
9
+ minStepsBetweenValues: () => null,
10
+ step: () => null,
11
+ values: () => null,
12
+ ...fieldValidityMapping,
13
+ }
@@ -0,0 +1,601 @@
1
+ <script setup lang="ts">
2
+ import type { ComponentPublicInstance, CSSProperties, Ref } from 'vue'
3
+ import type { LabelableContext } from '../../labelable-provider/LabelableContext'
4
+ import type { BaseUIComponentProps } from '../../utils/types'
5
+ import type { SliderRootState } from '../root/SliderRoot.vue'
6
+ import { computed, onMounted, ref, useAttrs, watch, watchEffect } from 'vue'
7
+ import {
8
+ ARROW_DOWN,
9
+ ARROW_LEFT,
10
+ ARROW_RIGHT,
11
+ ARROW_UP,
12
+ COMPOSITE_KEYS,
13
+ END,
14
+ HOME,
15
+ PAGE_DOWN,
16
+ PAGE_UP,
17
+ } from '../../composite/composite'
18
+ import { useCompositeListItem } from '../../composite/list/useCompositeListItem'
19
+ import { useCSPContext } from '../../csp-provider/CSPContext'
20
+ import { useDirection } from '../../direction-provider'
21
+ import { useFieldRootContext } from '../../field/root/FieldRootContext'
22
+ import { matchesFocusVisible } from '../../floating-ui-vue/utils'
23
+ import { useLabelableId } from '../../labelable-provider/useLabelableId'
24
+ import { mergeProps } from '../../merge-props/mergeProps'
25
+ import { createGenericEventDetails } from '../../utils/createBaseUIEventDetails'
26
+ import { formatNumber } from '../../utils/formatNumber'
27
+ import { REASONS } from '../../utils/reasons'
28
+ import { useBaseUiId } from '../../utils/useBaseUiId'
29
+ import { useMergedRefs } from '../../utils/useMergedRefs'
30
+ import { useRenderElement } from '../../utils/useRenderElement'
31
+ import { valueToPercent } from '../../utils/valueToPercent'
32
+ import { visuallyHidden } from '../../utils/visuallyHidden'
33
+ import { useSliderRootContext } from '../root/SliderRootContext'
34
+ import { sliderStateAttributesMapping } from '../root/stateAttributesMapping'
35
+ import { getMidpoint } from '../utils/getMidpoint'
36
+ import { getSliderValue } from '../utils/getSliderValue'
37
+ import { roundValueToStep } from '../utils/roundValueToStep'
38
+ import { script as prehydrationScript } from './prehydrationScript.min'
39
+ import { SliderThumbDataAttributes } from './SliderThumbDataAttributes'
40
+
41
+ /**
42
+ * The draggable part of the slider at the tip of the indicator.
43
+ * Renders a `<div>` element and a nested `<input type="range">`.
44
+ *
45
+ * Documentation: [Base UI Vue Slider](https://baseui-vue.com/docs/components/slider)
46
+ */
47
+ defineOptions({
48
+ name: 'SliderThumb',
49
+ inheritAttrs: false,
50
+ })
51
+ const props = withDefaults(defineProps<SliderThumbProps>(), {
52
+ as: 'div',
53
+ disabled: false,
54
+ })
55
+ const ALL_KEYS = new Set([
56
+ ARROW_UP,
57
+ ARROW_DOWN,
58
+ ARROW_LEFT,
59
+ ARROW_RIGHT,
60
+ HOME,
61
+ END,
62
+ PAGE_UP,
63
+ PAGE_DOWN,
64
+ ])
65
+
66
+ function getDefaultAriaValueText(
67
+ values: readonly number[],
68
+ index: number,
69
+ format: Intl.NumberFormatOptions | undefined,
70
+ locale: Intl.LocalesArgument | undefined,
71
+ ): string | undefined {
72
+ if (index < 0) {
73
+ return undefined
74
+ }
75
+
76
+ if (values.length === 2) {
77
+ if (index === 0) {
78
+ return `${formatNumber(values[index], locale, format)} start range`
79
+ }
80
+
81
+ return `${formatNumber(values[index], locale, format)} end range`
82
+ }
83
+
84
+ return format ? formatNumber(values[index], locale, format) : undefined
85
+ }
86
+
87
+ function getNewValue(
88
+ thumbValue: number,
89
+ step: number,
90
+ direction: 1 | -1,
91
+ min: number,
92
+ max: number,
93
+ ): number {
94
+ return direction === 1 ? Math.min(thumbValue + step, max) : Math.max(thumbValue - step, min)
95
+ }
96
+
97
+ export interface ThumbMetadata {
98
+ inputId: LabelableContext['controlId']
99
+ }
100
+
101
+ export interface SliderThumbState extends SliderRootState {}
102
+ export interface SliderThumbProps extends Omit<BaseUIComponentProps<SliderThumbState>, 'style'> {
103
+ /**
104
+ * Whether the thumb should ignore user interaction.
105
+ * @default false
106
+ */
107
+ disabled?: boolean
108
+ /**
109
+ * A function which returns a string value for the [`aria-label`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-label) attribute of the `input`.
110
+ */
111
+ getAriaLabel?: ((index: number) => string) | null
112
+ /**
113
+ * A function which returns a string value for the [`aria-valuetext`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-valuetext) attribute of the `input`.
114
+ * This is important for screen reader users.
115
+ */
116
+ getAriaValueText?: ((formattedValue: string, value: number, index: number) => string) | null
117
+ /**
118
+ * The index of the thumb which corresponds to the index of its value in the
119
+ * `modelValue` or `defaultValue` array.
120
+ * This prop is required to support server-side rendering for range sliders
121
+ * with multiple thumbs.
122
+ * @example
123
+ * ```vue
124
+ * <SliderRoot :default-value="[10, 20]">
125
+ * <SliderThumb :index="0" />
126
+ * <SliderThumb :index="1" />
127
+ * </SliderRoot>
128
+ * ```
129
+ */
130
+ index?: number
131
+ /**
132
+ * A ref to access the nested input element.
133
+ */
134
+ inputRef?: ((el: HTMLInputElement | null) => void) | Ref<HTMLInputElement | null> | null
135
+ /**
136
+ * Optional tab index attribute forwarded to the `input`.
137
+ */
138
+ tabIndex?: number
139
+ id?: string
140
+ ariaLabel?: string
141
+ ariaLabelledby?: string
142
+ ariaDescribedby?: string
143
+ style?: CSSProperties | ((state: SliderThumbState) => CSSProperties)
144
+ }
145
+
146
+ const attrs = useAttrs()
147
+ const { nonce } = useCSPContext()
148
+ const direction = useDirection()
149
+ const rootContext = useSliderRootContext()
150
+ const { setTouched, setFocused, validationMode, validation } = useFieldRootContext()
151
+
152
+ const id = useBaseUiId(props.id)
153
+ const disabled = computed(() => props.disabled || rootContext.disabled.value)
154
+ const range = computed(() => rootContext.values.value.length > 1)
155
+ const vertical = computed(() => rootContext.orientation.value === 'vertical')
156
+ const rtl = computed(() => direction.value === 'rtl')
157
+
158
+ const thumbRef = ref<HTMLElement | null>(null)
159
+ const inputRef = ref<HTMLInputElement | null>(null)
160
+ const restoringFocusVisibleRef = ref(false)
161
+
162
+ const defaultInputId = useBaseUiId()
163
+ const labelableId = useLabelableId()
164
+ const inputId = computed(() => (range.value ? defaultInputId : labelableId.value))
165
+
166
+ const { ref: listItemRef, index: compositeIndex } = useCompositeListItem<ThumbMetadata>({
167
+ metadata: () => ({
168
+ inputId,
169
+ }),
170
+ index: () => props.index,
171
+ })
172
+
173
+ const index = computed(() => (!range.value ? 0 : (props.index ?? compositeIndex.value)))
174
+ const last = computed(() => index.value === rootContext.values.value.length - 1)
175
+ const thumbValue = computed(() => rootContext.values.value[index.value])
176
+ const thumbValuePercent = computed(() => valueToPercent(thumbValue.value, rootContext.min.value, rootContext.max.value))
177
+
178
+ const isMounted = ref(false)
179
+ const positionPercent = ref<number | undefined>(undefined)
180
+
181
+ onMounted(() => {
182
+ isMounted.value = true
183
+ })
184
+
185
+ const safeLastUsedThumbIndex = computed(() =>
186
+ rootContext.lastUsedThumbIndex.value >= 0 && rootContext.lastUsedThumbIndex.value < rootContext.values.value.length
187
+ ? rootContext.lastUsedThumbIndex.value
188
+ : -1)
189
+
190
+ function setIndicatorPositionForIndex(nextInsetPosition: number | undefined) {
191
+ rootContext.setIndicatorPosition((prevPosition) => {
192
+ const next = [...prevPosition]
193
+ if (index.value === 0) {
194
+ next[0] = nextInsetPosition
195
+ }
196
+ else if (last.value) {
197
+ next[1] = nextInsetPosition
198
+ }
199
+ return next
200
+ })
201
+ }
202
+
203
+ function getInsetPosition() {
204
+ const control = rootContext.controlRef.value
205
+ const thumb = thumbRef.value
206
+ if (!control || !thumb) {
207
+ return
208
+ }
209
+
210
+ const thumbRect = thumb.getBoundingClientRect()
211
+ const controlRect = control.getBoundingClientRect()
212
+ const side = vertical.value ? 'height' : 'width'
213
+ const controlSize = controlRect[side] - thumbRect[side]
214
+ const thumbOffsetFromControlEdge = thumbRect[side] / 2 + (controlSize * thumbValuePercent.value) / 100
215
+ const nextPositionPercent = (thumbOffsetFromControlEdge / controlRect[side]) * 100
216
+ const nextInsetPosition = Number.isFinite(nextPositionPercent) ? nextPositionPercent : undefined
217
+
218
+ positionPercent.value = nextInsetPosition
219
+ setIndicatorPositionForIndex(nextInsetPosition)
220
+ }
221
+
222
+ watch(
223
+ () => [rootContext.inset.value, thumbValuePercent.value, isMounted.value],
224
+ () => {
225
+ if (rootContext.inset.value) {
226
+ queueMicrotask(getInsetPosition)
227
+ }
228
+ },
229
+ { immediate: true },
230
+ )
231
+
232
+ watchEffect((onCleanup) => {
233
+ if (!rootContext.inset.value || typeof ResizeObserver !== 'function') {
234
+ return
235
+ }
236
+
237
+ const control = rootContext.controlRef.value
238
+ const thumb = thumbRef.value
239
+ if (!control || !thumb) {
240
+ return
241
+ }
242
+
243
+ const observer = new ResizeObserver(getInsetPosition)
244
+ observer.observe(control)
245
+ observer.observe(thumb)
246
+
247
+ onCleanup(() => {
248
+ observer.disconnect()
249
+ })
250
+ })
251
+
252
+ const thumbStyle = computed(() => {
253
+ const startEdge = vertical.value ? 'bottom' : 'insetInlineStart'
254
+ const crossOffsetProperty = vertical.value ? 'left' : 'top'
255
+
256
+ let zIndex: number | undefined
257
+ if (range.value) {
258
+ if (rootContext.active.value === index.value) {
259
+ zIndex = 2
260
+ }
261
+ else if (safeLastUsedThumbIndex.value === index.value) {
262
+ zIndex = 1
263
+ }
264
+ }
265
+ else if (rootContext.active.value === index.value) {
266
+ zIndex = 1
267
+ }
268
+
269
+ if (!rootContext.inset.value) {
270
+ if (!Number.isFinite(thumbValuePercent.value)) {
271
+ return visuallyHidden
272
+ }
273
+
274
+ return {
275
+ position: 'absolute',
276
+ [startEdge]: `${thumbValuePercent.value}%`,
277
+ [crossOffsetProperty]: '50%',
278
+ translate: `${(vertical.value || !rtl.value ? -1 : 1) * 50}% ${(vertical.value ? 1 : -1) * 50}%`,
279
+ zIndex,
280
+ } satisfies CSSProperties
281
+ }
282
+
283
+ return {
284
+ '--position': `${positionPercent.value ?? 0}%`,
285
+ 'visibility':
286
+ (rootContext.renderBeforeHydration.value && !isMounted.value) || positionPercent.value === undefined
287
+ ? 'hidden'
288
+ : undefined,
289
+ 'position': 'absolute',
290
+ [startEdge]: 'var(--position)',
291
+ [crossOffsetProperty]: '50%',
292
+ 'translate': `${(vertical.value || !rtl.value ? -1 : 1) * 50}% ${(vertical.value ? 1 : -1) * 50}%`,
293
+ zIndex,
294
+ } satisfies CSSProperties & Record<string, unknown>
295
+ })
296
+
297
+ const cssWritingMode = computed<CSSProperties['writingMode'] | undefined>(() => {
298
+ if (rootContext.orientation.value === 'vertical') {
299
+ return rtl.value ? 'vertical-rl' : 'vertical-lr'
300
+ }
301
+
302
+ return undefined
303
+ })
304
+
305
+ const ariaLabel = computed(() =>
306
+ typeof props.getAriaLabel === 'function' ? props.getAriaLabel(index.value) : props.ariaLabel)
307
+
308
+ function setInputRef(node: HTMLInputElement | null) {
309
+ inputRef.value = node
310
+ validation.setInputRef(node)
311
+
312
+ const forwardedInputRef = props.inputRef
313
+
314
+ if (!forwardedInputRef) {
315
+ return
316
+ }
317
+
318
+ if (typeof forwardedInputRef === 'function') {
319
+ forwardedInputRef(node)
320
+ }
321
+ else {
322
+ forwardedInputRef.value = node
323
+ }
324
+ }
325
+
326
+ function setInputVNodeRef(node: Element | ComponentPublicInstance | null) {
327
+ setInputRef(node instanceof HTMLInputElement ? node : null)
328
+ }
329
+
330
+ function handleBlur(event: FocusEvent) {
331
+ if (restoringFocusVisibleRef.value) {
332
+ event.stopPropagation()
333
+ return
334
+ }
335
+
336
+ if (!thumbRef.value) {
337
+ return
338
+ }
339
+
340
+ rootContext.setActive(-1)
341
+ setTouched(true)
342
+ setFocused(false)
343
+
344
+ if (validationMode.value === 'onBlur') {
345
+ validation.commit(getSliderValue(
346
+ thumbValue.value,
347
+ index.value,
348
+ rootContext.min.value,
349
+ rootContext.max.value,
350
+ range.value,
351
+ rootContext.values.value,
352
+ ))
353
+ }
354
+ }
355
+
356
+ function handleKeydown(event: KeyboardEvent) {
357
+ if (!ALL_KEYS.has(event.key)) {
358
+ return
359
+ }
360
+
361
+ if (COMPOSITE_KEYS.has(event.key)) {
362
+ event.stopPropagation()
363
+ }
364
+
365
+ let newValue: number | null = null
366
+ const roundedValue = roundValueToStep(thumbValue.value, rootContext.step.value, rootContext.min.value)
367
+ switch (event.key) {
368
+ case ARROW_UP:
369
+ newValue = getNewValue(roundedValue, event.shiftKey ? rootContext.largeStep.value : rootContext.step.value, 1, rootContext.min.value, rootContext.max.value)
370
+ break
371
+ case ARROW_RIGHT:
372
+ newValue = getNewValue(
373
+ roundedValue,
374
+ event.shiftKey ? rootContext.largeStep.value : rootContext.step.value,
375
+ rtl.value ? -1 : 1,
376
+ rootContext.min.value,
377
+ rootContext.max.value,
378
+ )
379
+ break
380
+ case ARROW_DOWN:
381
+ newValue = getNewValue(roundedValue, event.shiftKey ? rootContext.largeStep.value : rootContext.step.value, -1, rootContext.min.value, rootContext.max.value)
382
+ break
383
+ case ARROW_LEFT:
384
+ newValue = getNewValue(
385
+ roundedValue,
386
+ event.shiftKey ? rootContext.largeStep.value : rootContext.step.value,
387
+ rtl.value ? 1 : -1,
388
+ rootContext.min.value,
389
+ rootContext.max.value,
390
+ )
391
+ break
392
+ case PAGE_UP:
393
+ newValue = getNewValue(roundedValue, rootContext.largeStep.value, 1, rootContext.min.value, rootContext.max.value)
394
+ break
395
+ case PAGE_DOWN:
396
+ newValue = getNewValue(roundedValue, rootContext.largeStep.value, -1, rootContext.min.value, rootContext.max.value)
397
+ break
398
+ case END:
399
+ newValue = rootContext.max.value
400
+ if (range.value) {
401
+ newValue = Number.isFinite(rootContext.values.value[index.value + 1])
402
+ ? rootContext.values.value[index.value + 1] - rootContext.step.value * rootContext.minStepsBetweenValues.value
403
+ : rootContext.max.value
404
+ }
405
+ break
406
+ case HOME:
407
+ newValue = rootContext.min.value
408
+ if (range.value) {
409
+ newValue = Number.isFinite(rootContext.values.value[index.value - 1])
410
+ ? rootContext.values.value[index.value - 1] + rootContext.step.value * rootContext.minStepsBetweenValues.value
411
+ : rootContext.min.value
412
+ }
413
+ break
414
+ default:
415
+ break
416
+ }
417
+
418
+ if (newValue !== null) {
419
+ const input = event.currentTarget as HTMLInputElement
420
+ if (!matchesFocusVisible(input)) {
421
+ restoringFocusVisibleRef.value = true
422
+ input.blur()
423
+ ;(input as HTMLInputElement & { focusVisible?: boolean }).focus({
424
+ preventScroll: true,
425
+ focusVisible: true,
426
+ } as FocusOptions)
427
+ }
428
+
429
+ rootContext.handleInputChange(newValue, index.value, event)
430
+ rootContext.onValueCommitted(
431
+ rootContext.lastChangedValueRef.value ?? getSliderValue(
432
+ newValue,
433
+ index.value,
434
+ rootContext.min.value,
435
+ rootContext.max.value,
436
+ range.value,
437
+ rootContext.values.value,
438
+ ),
439
+ createGenericEventDetails(REASONS.keyboard, event),
440
+ )
441
+ event.preventDefault()
442
+ }
443
+ }
444
+
445
+ const inputStyle = computed(() => ({
446
+ ...visuallyHidden,
447
+ width: '100%',
448
+ height: '100%',
449
+ writingMode: cssWritingMode.value,
450
+ }))
451
+
452
+ const inputValidationProps = computed(() => validation.getInputValidationProps())
453
+ const forwardedInputAttrs = computed(() => {
454
+ const attrsObject = attrs as Record<string, unknown>
455
+
456
+ return {
457
+ onBlur: attrsObject.onBlur,
458
+ onFocus: attrsObject.onFocus,
459
+ onKeydown: attrsObject.onKeydown,
460
+ }
461
+ })
462
+
463
+ const thumbAttrs = computed(() => {
464
+ const {
465
+ onBlur: _onBlur,
466
+ onFocus: _onFocus,
467
+ onKeydown: _onKeydown,
468
+ ...rest
469
+ } = attrs as Record<string, unknown>
470
+
471
+ return rest
472
+ })
473
+
474
+ const inputProps = computed(() => mergeProps(
475
+ inputValidationProps.value,
476
+ forwardedInputAttrs.value,
477
+ {
478
+ 'aria-label': ariaLabel.value,
479
+ 'aria-labelledby': props.ariaLabelledby ?? (ariaLabel.value == null ? rootContext.labelId.value : undefined),
480
+ 'aria-describedby': props.ariaDescribedby ?? inputValidationProps.value['aria-describedby'],
481
+ 'aria-orientation': rootContext.orientation.value,
482
+ 'aria-valuenow': thumbValue.value,
483
+ 'aria-valuetext':
484
+ typeof props.getAriaValueText === 'function'
485
+ ? props.getAriaValueText(
486
+ formatNumber(
487
+ thumbValue.value,
488
+ rootContext.locale.value,
489
+ rootContext.formatOptionsRef.value,
490
+ ),
491
+ thumbValue.value,
492
+ index.value,
493
+ )
494
+ : getDefaultAriaValueText(
495
+ rootContext.values.value,
496
+ index.value,
497
+ rootContext.formatOptionsRef.value,
498
+ rootContext.locale.value,
499
+ ),
500
+ 'disabled': disabled.value,
501
+ 'form': rootContext.form.value,
502
+ 'id': inputId.value,
503
+ 'max': rootContext.max.value,
504
+ 'min': rootContext.min.value,
505
+ 'name': rootContext.name.value,
506
+ onChange(event: Event) {
507
+ rootContext.handleInputChange((event.currentTarget as HTMLInputElement).valueAsNumber, index.value, event)
508
+ },
509
+ onFocus(event: FocusEvent) {
510
+ const isRestoringFocusVisible = restoringFocusVisibleRef.value
511
+ restoringFocusVisibleRef.value = false
512
+ rootContext.setActive(index.value)
513
+ setFocused(true)
514
+
515
+ if (isRestoringFocusVisible) {
516
+ event.stopPropagation()
517
+ }
518
+ },
519
+ 'onBlur': handleBlur,
520
+ 'onKeydown': handleKeydown,
521
+ 'step': rootContext.step.value,
522
+ 'style': inputStyle.value,
523
+ 'tabIndex': props.tabIndex ?? undefined,
524
+ 'type': 'range',
525
+ 'value': thumbValue.value ?? '',
526
+ },
527
+ ))
528
+
529
+ const mergedRef = useMergedRefs(listItemRef, thumbRef)
530
+ const thumbProps = computed(() => mergeProps(
531
+ thumbAttrs.value,
532
+ {
533
+ [SliderThumbDataAttributes.index]: index.value,
534
+ id,
535
+ onPointerdown(event: PointerEvent) {
536
+ rootContext.pressedThumbIndexRef.value = index.value
537
+
538
+ if (thumbRef.value != null) {
539
+ const axis = rootContext.orientation.value === 'horizontal' ? 'x' : 'y'
540
+ const midpoint = getMidpoint(thumbRef.value)
541
+ const offset = (rootContext.orientation.value === 'horizontal' ? event.clientX : event.clientY) - midpoint[axis]
542
+ rootContext.pressedThumbCenterOffsetRef.value = offset
543
+ }
544
+
545
+ if (inputRef.value != null && rootContext.pressedInputRef.value !== inputRef.value) {
546
+ rootContext.pressedInputRef.value = inputRef.value
547
+ }
548
+ },
549
+ style: thumbStyle.value,
550
+ suppressHydrationWarning: rootContext.renderBeforeHydration.value || undefined,
551
+ },
552
+ ))
553
+
554
+ const { tag, mergedProps, renderless, ref: renderRef } = useRenderElement({
555
+ componentProps: props,
556
+ state: rootContext.state,
557
+ props: thumbProps,
558
+ defaultTagName: 'div',
559
+ ref: mergedRef,
560
+ stateAttributesMapping: sliderStateAttributesMapping,
561
+ })
562
+
563
+ watch(
564
+ () => rootContext.active.value,
565
+ () => {
566
+ if (disabled.value && rootContext.active.value === index.value) {
567
+ rootContext.setActive(-1)
568
+ }
569
+ },
570
+ )
571
+ </script>
572
+
573
+ <template>
574
+ <slot
575
+ v-if="renderless"
576
+ :ref="renderRef"
577
+ :props="mergedProps"
578
+ :state="rootContext.state"
579
+ />
580
+ <component
581
+ :is="tag"
582
+ v-else
583
+ :ref="renderRef"
584
+ v-bind="mergedProps"
585
+ >
586
+ <slot />
587
+ <input
588
+ :ref="setInputVNodeRef"
589
+ v-bind="inputProps"
590
+ >
591
+ <!-- eslint-disable-next-line vue/require-component-is -->
592
+ <component
593
+ is="script"
594
+ v-if="rootContext.inset.value && !isMounted && rootContext.renderBeforeHydration.value && last"
595
+ :nonce="nonce"
596
+ :suppress-hydration-warning="true"
597
+ >
598
+ {{ prehydrationScript }}
599
+ </component>
600
+ </component>
601
+ </template>
@@ -0,0 +1,39 @@
1
+ export enum SliderThumbDataAttributes {
2
+ /**
3
+ * Indicates the index of the thumb in range sliders.
4
+ */
5
+ index = 'data-index',
6
+ /**
7
+ * Present while the user is dragging.
8
+ */
9
+ dragging = 'data-dragging',
10
+ /**
11
+ * Indicates the orientation of the slider.
12
+ * @type {'horizontal' | 'vertical'}
13
+ */
14
+ orientation = 'data-orientation',
15
+ /**
16
+ * Present when the slider is disabled.
17
+ */
18
+ disabled = 'data-disabled',
19
+ /**
20
+ * Present when the slider is in valid state (when wrapped in Field.Root).
21
+ */
22
+ valid = 'data-valid',
23
+ /**
24
+ * Present when the slider is in invalid state (when wrapped in Field.Root).
25
+ */
26
+ invalid = 'data-invalid',
27
+ /**
28
+ * Present when the slider has been touched (when wrapped in Field.Root).
29
+ */
30
+ touched = 'data-touched',
31
+ /**
32
+ * Present when the slider's value has changed (when wrapped in Field.Root).
33
+ */
34
+ dirty = 'data-dirty',
35
+ /**
36
+ * Present when the slider is focused (when wrapped in Field.Root).
37
+ */
38
+ focused = 'data-focused',
39
+ }
@@ -0,0 +1,5 @@
1
+ // This file is autogenerated. Do not edit it directly.
2
+ // To update it, modify the corresponding source file and run `pnpm inline-scripts`.
3
+
4
+ // prettier-ignore
5
+ export const script = "(function prehydration() {\n const firstThumb = document.currentScript?.parentElement\n if (!firstThumb) {\n return\n }\n\n const control = firstThumb.closest('[data-base-ui-slider-control]')\n if (!control) {\n return\n }\n\n const indicator = control.querySelector('[data-base-ui-slider-indicator]')\n const controlRect = control.getBoundingClientRect()\n const vertical = control.getAttribute('data-orientation') === 'vertical'\n const side = vertical ? 'height' : 'width'\n const inputElems = control.querySelectorAll('input[type=\"range\"]')\n const range = inputElems.length > 1\n const lastIndex = inputElems.length - 1\n\n let startPosition = null\n let relativeSize = null\n\n for (let i = 0; i < inputElems.length; i += 1) {\n const input = inputElems[i]\n\n const value = Number.parseFloat(input.getAttribute('value') ?? '')\n\n if (Number.isNaN(value)) {\n return\n }\n\n const thumb = input.parentElement\n if (!thumb) {\n return\n }\n\n const max = Number.parseFloat(input.getAttribute('max') ?? '100')\n const min = Number.parseFloat(input.getAttribute('min') ?? '0')\n\n const thumbRect = thumb?.getBoundingClientRect()\n\n const controlSize = controlRect[side] - thumbRect[side]\n const thumbValuePercent = max === min ? 0 : ((value - min) * 100) / (max - min)\n const thumbOffsetFromControlEdge\n = thumbRect[side] / 2 + (controlSize * thumbValuePercent) / 100\n const percent = (thumbOffsetFromControlEdge / controlRect[side]) * 100\n\n if (Number.isFinite(percent)) {\n thumb.style.setProperty(`--position`, `${percent}%`)\n thumb.style.removeProperty('visibility')\n\n if (indicator) {\n if (i === 0) {\n startPosition = percent\n indicator.style.setProperty('--start-position', `${percent}%`)\n if (!range) {\n indicator.style.removeProperty('visibility')\n }\n }\n else if (i === lastIndex) {\n relativeSize = percent - (startPosition ?? 0)\n indicator.style.setProperty('--end-position', `${percent}%`)\n indicator.style.setProperty('--relative-size', `${relativeSize}%`)\n indicator.style.removeProperty('visibility')\n }\n }\n }\n }\n})()";