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,69 @@
1
+ (function prehydration() {
2
+ const firstThumb = document.currentScript?.parentElement
3
+ if (!firstThumb) {
4
+ return
5
+ }
6
+
7
+ const control = firstThumb.closest('[data-base-ui-slider-control]')
8
+ if (!control) {
9
+ return
10
+ }
11
+
12
+ const indicator = control.querySelector('[data-base-ui-slider-indicator]')
13
+ const controlRect = control.getBoundingClientRect()
14
+ const vertical = control.getAttribute('data-orientation') === 'vertical'
15
+ const side = vertical ? 'height' : 'width'
16
+ const inputElems = control.querySelectorAll('input[type="range"]')
17
+ const range = inputElems.length > 1
18
+ const lastIndex = inputElems.length - 1
19
+
20
+ let startPosition = null
21
+ let relativeSize = null
22
+
23
+ for (let i = 0; i < inputElems.length; i += 1) {
24
+ const input = inputElems[i]
25
+
26
+ const value = Number.parseFloat(input.getAttribute('value') ?? '')
27
+
28
+ if (Number.isNaN(value)) {
29
+ return
30
+ }
31
+
32
+ const thumb = input.parentElement
33
+ if (!thumb) {
34
+ return
35
+ }
36
+
37
+ const max = Number.parseFloat(input.getAttribute('max') ?? '100')
38
+ const min = Number.parseFloat(input.getAttribute('min') ?? '0')
39
+
40
+ const thumbRect = thumb?.getBoundingClientRect()
41
+
42
+ const controlSize = controlRect[side] - thumbRect[side]
43
+ const thumbValuePercent = max === min ? 0 : ((value - min) * 100) / (max - min)
44
+ const thumbOffsetFromControlEdge
45
+ = thumbRect[side] / 2 + (controlSize * thumbValuePercent) / 100
46
+ const percent = (thumbOffsetFromControlEdge / controlRect[side]) * 100
47
+
48
+ if (Number.isFinite(percent)) {
49
+ thumb.style.setProperty(`--position`, `${percent}%`)
50
+ thumb.style.removeProperty('visibility')
51
+
52
+ if (indicator) {
53
+ if (i === 0) {
54
+ startPosition = percent
55
+ indicator.style.setProperty('--start-position', `${percent}%`)
56
+ if (!range) {
57
+ indicator.style.removeProperty('visibility')
58
+ }
59
+ }
60
+ else if (i === lastIndex) {
61
+ relativeSize = percent - (startPosition ?? 0)
62
+ indicator.style.setProperty('--end-position', `${percent}%`)
63
+ indicator.style.setProperty('--relative-size', `${relativeSize}%`)
64
+ indicator.style.removeProperty('visibility')
65
+ }
66
+ }
67
+ }
68
+ }
69
+ })()
@@ -0,0 +1,48 @@
1
+ <script setup lang="ts">
2
+ import type { BaseUIComponentProps } from '../../utils/types'
3
+ import type { SliderRootState } from '../root/SliderRoot.vue'
4
+ import { computed, useAttrs } from 'vue'
5
+ import { mergeProps } from '../../merge-props/mergeProps'
6
+ import { useRenderElement } from '../../utils/useRenderElement'
7
+ import { useSliderRootContext } from '../root/SliderRootContext'
8
+ import { sliderStateAttributesMapping } from '../root/stateAttributesMapping'
9
+
10
+ export interface SliderTrackState extends SliderRootState {}
11
+ export interface SliderTrackProps extends BaseUIComponentProps<SliderTrackState> {}
12
+
13
+ defineOptions({
14
+ name: 'SliderTrack',
15
+ inheritAttrs: false,
16
+ })
17
+
18
+ const props = withDefaults(defineProps<SliderTrackProps>(), {
19
+ as: 'div',
20
+ })
21
+
22
+ const attrs = useAttrs()
23
+ const rootContext = useSliderRootContext()
24
+
25
+ const trackProps = computed(() => mergeProps(
26
+ attrs as Record<string, unknown>,
27
+ {
28
+ style: {
29
+ position: 'relative',
30
+ },
31
+ },
32
+ ))
33
+
34
+ const { tag, mergedProps, renderless, ref: renderRef } = useRenderElement({
35
+ componentProps: props,
36
+ state: rootContext.state,
37
+ props: trackProps,
38
+ defaultTagName: 'div',
39
+ stateAttributesMapping: sliderStateAttributesMapping,
40
+ })
41
+ </script>
42
+
43
+ <template>
44
+ <slot v-if="renderless" :ref="renderRef" :props="mergedProps" :state="rootContext.state" />
45
+ <component :is="tag" v-else :ref="renderRef" v-bind="mergedProps">
46
+ <slot />
47
+ </component>
48
+ </template>
@@ -0,0 +1,10 @@
1
+ export enum SliderTrackDataAttributes {
2
+ dragging = 'data-dragging',
3
+ orientation = 'data-orientation',
4
+ disabled = 'data-disabled',
5
+ valid = 'data-valid',
6
+ invalid = 'data-invalid',
7
+ touched = 'data-touched',
8
+ dirty = 'data-dirty',
9
+ focused = 'data-focused',
10
+ }
@@ -0,0 +1,3 @@
1
+ export function asc(a: number, b: number): number {
2
+ return a - b
3
+ }
@@ -0,0 +1,9 @@
1
+ import type { Coords } from '../../floating-ui-vue/types'
2
+
3
+ export function getMidpoint(element: HTMLElement): Coords {
4
+ const rect = element.getBoundingClientRect()
5
+ return {
6
+ x: (rect.left + rect.right) / 2,
7
+ y: (rect.top + rect.bottom) / 2,
8
+ }
9
+ }
@@ -0,0 +1,68 @@
1
+ import { clamp } from '../../utils/clamp'
2
+
3
+ interface GetPushedThumbValuesParams {
4
+ values: readonly number[]
5
+ index: number
6
+ nextValue: number
7
+ min: number
8
+ max: number
9
+ step: number
10
+ minStepsBetweenValues: number
11
+ initialValues?: readonly number[] | undefined
12
+ }
13
+
14
+ export function getPushedThumbValues({
15
+ values,
16
+ index,
17
+ nextValue,
18
+ min,
19
+ max,
20
+ step,
21
+ minStepsBetweenValues,
22
+ initialValues,
23
+ }: GetPushedThumbValuesParams): number[] {
24
+ if (values.length === 0) {
25
+ return []
26
+ }
27
+
28
+ const nextValues = values.slice()
29
+ const minValueDifference = step * minStepsBetweenValues
30
+ const lastIndex = nextValues.length - 1
31
+ const baseInitialValues = initialValues ?? values
32
+
33
+ const indexMin = min + index * minValueDifference
34
+ const indexMax = max - (lastIndex - index) * minValueDifference
35
+ nextValues[index] = clamp(nextValue, indexMin, indexMax)
36
+
37
+ for (let i = index + 1; i <= lastIndex; i += 1) {
38
+ const minAllowed = nextValues[i - 1] + minValueDifference
39
+ const maxAllowed = max - (lastIndex - i) * minValueDifference
40
+ const initialValue = baseInitialValues[i] ?? nextValues[i]
41
+ let candidate = Math.max(nextValues[i], minAllowed)
42
+
43
+ if (initialValue < candidate) {
44
+ candidate = Math.max(initialValue, minAllowed)
45
+ }
46
+
47
+ nextValues[i] = clamp(candidate, minAllowed, maxAllowed)
48
+ }
49
+
50
+ for (let i = index - 1; i >= 0; i -= 1) {
51
+ const maxAllowed = nextValues[i + 1] - minValueDifference
52
+ const minAllowed = min + i * minValueDifference
53
+ const initialValue = baseInitialValues[i] ?? nextValues[i]
54
+ let candidate = Math.min(nextValues[i], maxAllowed)
55
+
56
+ if (initialValue > candidate) {
57
+ candidate = Math.min(initialValue, maxAllowed)
58
+ }
59
+
60
+ nextValues[i] = clamp(candidate, minAllowed, maxAllowed)
61
+ }
62
+
63
+ for (let i = 0; i <= lastIndex; i += 1) {
64
+ nextValues[i] = Number(nextValues[i].toFixed(12))
65
+ }
66
+
67
+ return nextValues
68
+ }
@@ -0,0 +1,25 @@
1
+ import { clamp } from '../../utils/clamp'
2
+ import { replaceArrayItemAtIndex } from './replaceArrayItemAtIndex'
3
+
4
+ export function getSliderValue(
5
+ valueInput: number,
6
+ index: number,
7
+ min: number,
8
+ max: number,
9
+ range: boolean,
10
+ values: readonly number[],
11
+ ): number | number[] {
12
+ let newValue: number | number[] = valueInput
13
+
14
+ newValue = clamp(newValue, min, max)
15
+
16
+ if (range) {
17
+ newValue = replaceArrayItemAtIndex(
18
+ values,
19
+ index,
20
+ clamp(newValue, values[index - 1] ?? -Infinity, values[index + 1] ?? Infinity),
21
+ )
22
+ }
23
+
24
+ return newValue
25
+ }
@@ -0,0 +1,15 @@
1
+ import { asc } from './asc'
2
+
3
+ export function replaceArrayItemAtIndex(
4
+ array: readonly number[],
5
+ index: number,
6
+ newValue: number,
7
+ ): number[] {
8
+ if (!Number.isInteger(index) || index < 0 || index >= array.length) {
9
+ throw new RangeError(`replaceArrayItemAtIndex index out of bounds: ${index}`)
10
+ }
11
+
12
+ const output = array.slice()
13
+ output[index] = newValue
14
+ return output.sort(asc)
15
+ }
@@ -0,0 +1,177 @@
1
+ import { clamp } from '../../utils/clamp'
2
+ import { getPushedThumbValues } from './getPushedThumbValues'
3
+
4
+ export interface ResolveThumbCollisionParams {
5
+ behavior: 'push' | 'swap' | 'none'
6
+ values: readonly number[]
7
+ currentValues?: readonly number[] | null | undefined
8
+ initialValues?: readonly number[] | null | undefined
9
+ pressedIndex: number
10
+ nextValue: number
11
+ min: number
12
+ max: number
13
+ step: number
14
+ minStepsBetweenValues: number
15
+ }
16
+
17
+ export interface ResolveThumbCollisionResult {
18
+ value: number | number[]
19
+ thumbIndex: number
20
+ didSwap: boolean
21
+ }
22
+
23
+ export function resolveThumbCollision({
24
+ behavior,
25
+ values,
26
+ currentValues,
27
+ initialValues,
28
+ pressedIndex,
29
+ nextValue,
30
+ min,
31
+ max,
32
+ step,
33
+ minStepsBetweenValues,
34
+ }: ResolveThumbCollisionParams): ResolveThumbCollisionResult {
35
+ const activeValues = currentValues ?? values
36
+ const baselineValues = initialValues ?? values
37
+ const range = activeValues.length > 1
38
+
39
+ if (!range) {
40
+ return {
41
+ value: nextValue,
42
+ thumbIndex: 0,
43
+ didSwap: false,
44
+ }
45
+ }
46
+
47
+ const minValueDifference = step * minStepsBetweenValues
48
+
49
+ switch (behavior) {
50
+ case 'swap': {
51
+ const pressedInitialValue = activeValues[pressedIndex]
52
+ const epsilon = 1e-7
53
+ const candidateValues = activeValues.slice()
54
+ const previousNeighbor = candidateValues[pressedIndex - 1]
55
+ const nextNeighbor = candidateValues[pressedIndex + 1]
56
+
57
+ const lowerBound = previousNeighbor != null ? previousNeighbor + minValueDifference : min
58
+ const upperBound = nextNeighbor != null ? nextNeighbor - minValueDifference : max
59
+
60
+ const constrainedValue = clamp(nextValue, lowerBound, upperBound)
61
+ const pressedValueAfterClamp = Number(constrainedValue.toFixed(12))
62
+ candidateValues[pressedIndex] = pressedValueAfterClamp
63
+
64
+ const movingForward = nextValue > pressedInitialValue
65
+ const movingBackward = nextValue < pressedInitialValue
66
+
67
+ const shouldSwapForward
68
+ = movingForward && nextNeighbor != null && nextValue >= nextNeighbor - epsilon
69
+ const shouldSwapBackward
70
+ = movingBackward && previousNeighbor != null && nextValue <= previousNeighbor + epsilon
71
+
72
+ if (!shouldSwapForward && !shouldSwapBackward) {
73
+ return {
74
+ value: candidateValues,
75
+ thumbIndex: pressedIndex,
76
+ didSwap: false,
77
+ }
78
+ }
79
+
80
+ const targetIndex = shouldSwapForward ? pressedIndex + 1 : pressedIndex - 1
81
+
82
+ const initialValuesForPush = candidateValues.map((_, index) => {
83
+ if (index === pressedIndex) {
84
+ return pressedValueAfterClamp
85
+ }
86
+
87
+ const baseline = baselineValues[index]
88
+ if (baseline != null) {
89
+ return baseline
90
+ }
91
+
92
+ return activeValues[index]
93
+ })
94
+
95
+ let nextValueForTarget = nextValue
96
+ if (shouldSwapForward) {
97
+ nextValueForTarget = Math.max(nextValue, candidateValues[targetIndex])
98
+ }
99
+ else {
100
+ nextValueForTarget = Math.min(nextValue, candidateValues[targetIndex])
101
+ }
102
+
103
+ const adjustedValues = getPushedThumbValues({
104
+ values: candidateValues,
105
+ index: targetIndex,
106
+ nextValue: nextValueForTarget,
107
+ min,
108
+ max,
109
+ step,
110
+ minStepsBetweenValues,
111
+ initialValues: initialValuesForPush,
112
+ })
113
+
114
+ const neighborIndex = shouldSwapForward ? targetIndex - 1 : targetIndex + 1
115
+
116
+ if (neighborIndex >= 0 && neighborIndex < adjustedValues.length) {
117
+ const previousValue = adjustedValues[neighborIndex - 1]
118
+ const nextValueAfter = adjustedValues[neighborIndex + 1]
119
+
120
+ let neighborLowerBound = previousValue != null ? previousValue + minValueDifference : min
121
+ neighborLowerBound = Math.max(neighborLowerBound, min + neighborIndex * minValueDifference)
122
+
123
+ let neighborUpperBound = nextValueAfter != null ? nextValueAfter - minValueDifference : max
124
+ neighborUpperBound = Math.min(
125
+ neighborUpperBound,
126
+ max - (adjustedValues.length - 1 - neighborIndex) * minValueDifference,
127
+ )
128
+
129
+ const restoredValue = clamp(pressedValueAfterClamp, neighborLowerBound, neighborUpperBound)
130
+ adjustedValues[neighborIndex] = Number(restoredValue.toFixed(12))
131
+ }
132
+
133
+ return {
134
+ value: adjustedValues,
135
+ thumbIndex: targetIndex,
136
+ didSwap: true,
137
+ }
138
+ }
139
+
140
+ case 'push': {
141
+ const nextValues = getPushedThumbValues({
142
+ values: activeValues,
143
+ index: pressedIndex,
144
+ nextValue,
145
+ min,
146
+ max,
147
+ step,
148
+ minStepsBetweenValues,
149
+ })
150
+
151
+ return {
152
+ value: nextValues,
153
+ thumbIndex: pressedIndex,
154
+ didSwap: false,
155
+ }
156
+ }
157
+
158
+ case 'none':
159
+ default: {
160
+ const candidateValues = activeValues.slice()
161
+ const previousNeighbor = candidateValues[pressedIndex - 1]
162
+ const nextNeighbor = candidateValues[pressedIndex + 1]
163
+
164
+ const lowerBound = previousNeighbor != null ? previousNeighbor + minValueDifference : min
165
+ const upperBound = nextNeighbor != null ? nextNeighbor - minValueDifference : max
166
+
167
+ const constrainedValue = clamp(nextValue, lowerBound, upperBound)
168
+ candidateValues[pressedIndex] = Number(constrainedValue.toFixed(12))
169
+
170
+ return {
171
+ value: candidateValues,
172
+ thumbIndex: pressedIndex,
173
+ didSwap: false,
174
+ }
175
+ }
176
+ }
177
+ }
@@ -0,0 +1,19 @@
1
+ function getDecimalPrecision(num: number) {
2
+ if (Math.abs(num) < 1) {
3
+ const parts = num.toExponential().split('e-')
4
+ const mantissaDecimalPart = parts[0].split('.')[1]
5
+ return (mantissaDecimalPart ? mantissaDecimalPart.length : 0) + Number.parseInt(parts[1], 10)
6
+ }
7
+
8
+ const decimalPart = num.toString().split('.')[1]
9
+ return decimalPart ? decimalPart.length : 0
10
+ }
11
+
12
+ export function roundValueToStep(value: number, step: number, min: number) {
13
+ if (!Number.isFinite(step) || step <= 0) {
14
+ return value
15
+ }
16
+
17
+ const nearest = Math.round((value - min) / step) * step + min
18
+ return Number(nearest.toFixed(getDecimalPrecision(step)))
19
+ }
@@ -0,0 +1,25 @@
1
+ type Touches = Array<Pick<Touch, 'identifier' | 'clientX' | 'clientY'>>
2
+
3
+ export function createTouches(touches: Touches) {
4
+ return {
5
+ changedTouches: touches.map(touch =>
6
+ new Touch({
7
+ target: document.body,
8
+ ...touch,
9
+ })),
10
+ }
11
+ }
12
+
13
+ export function getHorizontalSliderRect(width = 100) {
14
+ return {
15
+ width,
16
+ height: 10,
17
+ bottom: 10,
18
+ left: 0,
19
+ x: 0,
20
+ y: 0,
21
+ top: 0,
22
+ right: width,
23
+ toJSON() {},
24
+ }
25
+ }
@@ -0,0 +1,20 @@
1
+ export function validateMinimumDistance(
2
+ values: number | readonly number[],
3
+ step: number,
4
+ minStepsBetweenValues: number,
5
+ ) {
6
+ if (!Array.isArray(values)) {
7
+ return true
8
+ }
9
+
10
+ const distances = values.reduce((acc: number[], val, index, vals) => {
11
+ if (index === vals.length - 1) {
12
+ return acc
13
+ }
14
+
15
+ acc.push(Math.abs(val - vals[index + 1]))
16
+ return acc
17
+ }, [])
18
+
19
+ return Math.min(...distances) >= step * minStepsBetweenValues
20
+ }
@@ -0,0 +1,10 @@
1
+ import { clamp } from '../../utils/clamp'
2
+ import { valueToPercent } from '../../utils/valueToPercent'
3
+
4
+ export function valueArrayToPercentages(values: number[], min: number, max: number): number[] {
5
+ const output: number[] = []
6
+ for (let i = 0; i < values.length; i += 1) {
7
+ output.push(clamp(valueToPercent(values[i], min, max), 0, 100))
8
+ }
9
+ return output
10
+ }
@@ -0,0 +1,90 @@
1
+ <script setup lang="ts">
2
+ import type { BaseUIComponentProps } from '../../utils/types'
3
+ import type { SliderRootState } from '../root/SliderRoot.vue'
4
+ import { computed, useAttrs } from 'vue'
5
+ import { mergeProps } from '../../merge-props/mergeProps'
6
+ import { formatNumber } from '../../utils/formatNumber'
7
+ import { useRenderElement } from '../../utils/useRenderElement'
8
+ import { useSliderRootContext } from '../root/SliderRootContext'
9
+ import { sliderStateAttributesMapping } from '../root/stateAttributesMapping'
10
+
11
+ export interface SliderValueState extends SliderRootState {}
12
+ export interface SliderValueProps extends BaseUIComponentProps<SliderValueState> {
13
+ ariaLive?: 'off' | 'polite' | 'assertive'
14
+ }
15
+
16
+ /**
17
+ * Displays the current value of the slider as text.
18
+ * Renders an `<output>` element.
19
+ *
20
+ * Documentation: [Base UI Slider](https://baseui-vue.com/docs/components/slider)
21
+ */
22
+ defineOptions({
23
+ name: 'SliderValue',
24
+ inheritAttrs: false,
25
+ })
26
+
27
+ const props = withDefaults(defineProps<SliderValueProps>(), {
28
+ as: 'output',
29
+ ariaLive: 'off',
30
+ })
31
+
32
+ const attrs = useAttrs()
33
+ const rootContext = useSliderRootContext()
34
+
35
+ const outputFor = computed(() => {
36
+ let htmlFor = ''
37
+ for (const thumbMetadata of rootContext.thumbMap.value.values()) {
38
+ if (thumbMetadata?.inputId) {
39
+ htmlFor += `${thumbMetadata.inputId} `
40
+ }
41
+ }
42
+
43
+ return htmlFor.trim() === '' ? undefined : htmlFor.trim()
44
+ })
45
+
46
+ const formattedValues = computed(() =>
47
+ rootContext.values.value.map(value =>
48
+ formatNumber(value, rootContext.locale.value, rootContext.formatOptionsRef.value),
49
+ ))
50
+
51
+ const defaultDisplayValue = computed(() =>
52
+ rootContext.values.value
53
+ .map((value, index) => formattedValues.value[index] || String(value))
54
+ .join(' – '))
55
+
56
+ const valueProps = computed(() => mergeProps(
57
+ attrs as Record<string, unknown>,
58
+ {
59
+ 'aria-live': props.ariaLive,
60
+ 'for': outputFor.value,
61
+ },
62
+ ))
63
+
64
+ const { tag, mergedProps, renderless, ref: renderRef } = useRenderElement({
65
+ componentProps: props,
66
+ state: rootContext.state,
67
+ props: valueProps,
68
+ defaultTagName: 'output',
69
+ stateAttributesMapping: sliderStateAttributesMapping,
70
+ })
71
+ </script>
72
+
73
+ <template>
74
+ <slot
75
+ v-if="renderless"
76
+ :ref="renderRef"
77
+ :props="mergedProps"
78
+ :state="rootContext.state"
79
+ :formatted-values="formattedValues"
80
+ :values="rootContext.values.value"
81
+ />
82
+ <component :is="tag" v-else :ref="renderRef" v-bind="mergedProps">
83
+ <slot
84
+ :formatted-values="formattedValues"
85
+ :values="rootContext.values.value"
86
+ >
87
+ {{ defaultDisplayValue }}
88
+ </slot>
89
+ </component>
90
+ </template>
@@ -0,0 +1,35 @@
1
+ export enum SliderValueDataAttributes {
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,14 @@
1
+ export { default as SwitchRoot } from './root/SwitchRoot.vue'
2
+ export type {
3
+ SwitchRootChangeEventDetails,
4
+ SwitchRootChangeEventReason,
5
+ SwitchRootProps,
6
+ SwitchRootState,
7
+ } from './root/SwitchRoot.vue'
8
+ export { switchRootContextKey, useSwitchRootContext } from './root/SwitchRootContext'
9
+ export type { SwitchRootContext } from './root/SwitchRootContext'
10
+ export { SwitchRootDataAttributes } from './root/SwitchRootDataAttributes'
11
+
12
+ export { default as SwitchThumb } from './thumb/SwitchThumb.vue'
13
+ export type { SwitchThumbProps, SwitchThumbState } from './thumb/SwitchThumb.vue'
14
+ export { SwitchThumbDataAttributes } from './thumb/SwitchThumbDataAttributes'