base-ui-vue 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -1
- package/dist/button/Button.cjs +53 -12
- package/dist/button/Button.cjs.map +1 -1
- package/dist/button/Button.js +26 -15
- package/dist/button/Button.js.map +1 -1
- package/dist/button/ToolbarButton.cjs +367 -0
- package/dist/button/ToolbarButton.cjs.map +1 -0
- package/dist/button/ToolbarButton.js +320 -0
- package/dist/button/ToolbarButton.js.map +1 -0
- package/dist/button/ToolbarButtonDataAttributes.cjs +27 -0
- package/dist/button/ToolbarButtonDataAttributes.cjs.map +1 -0
- package/dist/button/ToolbarButtonDataAttributes.js +21 -0
- package/dist/button/ToolbarButtonDataAttributes.js.map +1 -0
- package/dist/checkbox/index.cjs +1173 -0
- package/dist/checkbox/index.cjs.map +1 -0
- package/dist/checkbox/index.js +1048 -0
- package/dist/checkbox/index.js.map +1 -0
- package/dist/checkbox-group/CheckboxGroup.cjs +629 -0
- package/dist/checkbox-group/CheckboxGroup.cjs.map +1 -0
- package/dist/checkbox-group/CheckboxGroup.js +540 -0
- package/dist/checkbox-group/CheckboxGroup.js.map +1 -0
- package/dist/checkbox-group/CheckboxGroupDataAttributes.cjs +18 -0
- package/dist/checkbox-group/CheckboxGroupDataAttributes.cjs.map +1 -0
- package/dist/checkbox-group/CheckboxGroupDataAttributes.js +12 -0
- package/dist/checkbox-group/CheckboxGroupDataAttributes.js.map +1 -0
- package/dist/composite/composite.cjs +167 -0
- package/dist/composite/composite.cjs.map +1 -1
- package/dist/composite/composite.js +96 -1
- package/dist/composite/composite.js.map +1 -1
- package/dist/composite/constants.cjs +12 -0
- package/dist/composite/constants.cjs.map +1 -0
- package/dist/composite/constants.js +6 -0
- package/dist/composite/constants.js.map +1 -0
- package/dist/control/FieldControl.cjs +18 -343
- package/dist/control/FieldControl.cjs.map +1 -1
- package/dist/control/FieldControl.js +14 -285
- package/dist/control/FieldControl.js.map +1 -1
- package/dist/control/SliderControl.cjs +636 -0
- package/dist/control/SliderControl.cjs.map +1 -0
- package/dist/control/SliderControl.js +553 -0
- package/dist/control/SliderControl.js.map +1 -0
- package/dist/control/SliderControlDataAttributes.cjs +47 -0
- package/dist/control/SliderControlDataAttributes.cjs.map +1 -0
- package/dist/control/SliderControlDataAttributes.js +41 -0
- package/dist/control/SliderControlDataAttributes.js.map +1 -0
- package/dist/csp-provider/CSPContext.cjs +32 -0
- package/dist/csp-provider/CSPContext.cjs.map +1 -0
- package/dist/csp-provider/CSPContext.js +21 -0
- package/dist/csp-provider/CSPContext.js.map +1 -0
- package/dist/csp-provider/CSPProvider.cjs +46 -0
- package/dist/csp-provider/CSPProvider.cjs.map +1 -0
- package/dist/csp-provider/CSPProvider.js +41 -0
- package/dist/csp-provider/CSPProvider.js.map +1 -0
- package/dist/description/FieldDescription.cjs +5 -5
- package/dist/description/FieldDescription.cjs.map +1 -1
- package/dist/description/FieldDescription.js +1 -1
- package/dist/direction-provider/DirectionProvider.cjs +2 -2
- package/dist/direction-provider/DirectionProvider.cjs.map +1 -1
- package/dist/direction-provider/DirectionProvider.js +1 -1
- package/dist/error/FieldError.cjs +10 -288
- package/dist/error/FieldError.cjs.map +1 -1
- package/dist/error/FieldError.js +4 -246
- package/dist/error/FieldError.js.map +1 -1
- package/dist/form/Form.cjs +5 -4
- package/dist/form/Form.cjs.map +1 -1
- package/dist/form/Form.js +5 -4
- package/dist/form/Form.js.map +1 -1
- package/dist/group/ToolbarGroup.cjs +92 -0
- package/dist/group/ToolbarGroup.cjs.map +1 -0
- package/dist/group/ToolbarGroup.js +87 -0
- package/dist/group/ToolbarGroup.js.map +1 -0
- package/dist/group/ToolbarGroupDataAttributes.cjs +23 -0
- package/dist/group/ToolbarGroupDataAttributes.cjs.map +1 -0
- package/dist/group/ToolbarGroupDataAttributes.js +17 -0
- package/dist/group/ToolbarGroupDataAttributes.js.map +1 -0
- package/dist/header/AccordionHeader.cjs +2 -2
- package/dist/header/AccordionHeader.js +1 -1
- package/dist/image/AvatarImage.cjs +4 -4
- package/dist/image/AvatarImage.cjs.map +1 -1
- package/dist/image/AvatarImage.js +1 -1
- package/dist/index.cjs +80 -10
- package/dist/index.d.cts +2751 -612
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +2751 -612
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -5
- package/dist/index2.cjs +3651 -370
- package/dist/index2.cjs.map +1 -1
- package/dist/index2.js +3365 -270
- package/dist/index2.js.map +1 -1
- package/package.json +8 -4
- package/src/accordion/root/AccordionRoot.vue +2 -1
- package/src/checkbox/index.ts +23 -0
- package/src/checkbox/indicator/CheckboxIndicator.vue +102 -0
- package/src/checkbox/indicator/CheckboxIndicatorDataAttributes.ts +61 -0
- package/src/checkbox/root/CheckboxRoot.vue +632 -0
- package/src/checkbox/root/CheckboxRootContext.ts +22 -0
- package/src/checkbox/root/CheckboxRootDataAttributes.ts +54 -0
- package/src/checkbox/utils/useStateAttributesMapping.ts +30 -0
- package/src/checkbox-group/CheckboxGroup.vue +241 -0
- package/src/checkbox-group/CheckboxGroupContext.ts +39 -0
- package/src/checkbox-group/CheckboxGroupDataAttributes.ts +6 -0
- package/src/checkbox-group/index.ts +11 -0
- package/src/checkbox-group/useCheckboxGroupParent.ts +173 -0
- package/src/collapsible/panel/useCollapsiblePanel.ts +2 -1
- package/src/collapsible/root/useCollapsibleRoot.ts +3 -1
- package/src/composite/composite.ts +2 -0
- package/src/composite/item/CompositeItem.vue +7 -8
- package/src/composite/root/CompositeRoot.vue +12 -1
- package/src/csp-provider/CSPContext.ts +26 -0
- package/src/csp-provider/CSPProvider.vue +40 -0
- package/src/csp-provider/index.ts +5 -0
- package/src/field/item/FieldItem.vue +6 -1
- package/src/field/label/FieldLabel.vue +10 -51
- package/src/field/root/FieldRoot.vue +16 -3
- package/src/floating-ui-vue/types.ts +1 -4
- package/src/floating-ui-vue/utils/element.ts +12 -0
- package/src/floating-ui-vue/utils/shadowDom.ts +44 -0
- package/src/floating-ui-vue/utils.ts +3 -0
- package/src/form/Form.vue +5 -3
- package/src/index.ts +9 -0
- package/src/labelable-provider/LabelableContext.ts +2 -2
- package/src/labelable-provider/LabelableProvider.vue +21 -4
- package/src/labelable-provider/index.ts +2 -0
- package/src/labelable-provider/useAriaLabelledBy.ts +9 -9
- package/src/labelable-provider/useLabel.ts +115 -0
- package/src/labelable-provider/useLabelableId.ts +12 -10
- package/src/separator/Separator.vue +65 -0
- package/src/separator/SeparatorDataAttributes.ts +7 -0
- package/src/separator/index.ts +3 -0
- package/src/slider/control/SliderControl.vue +497 -0
- package/src/slider/control/SliderControlDataAttributes.ts +35 -0
- package/src/slider/index.ts +35 -0
- package/src/slider/indicator/SliderIndicator.vue +144 -0
- package/src/slider/indicator/SliderIndicatorDataAttributes.ts +35 -0
- package/src/slider/label/SliderLabel.vue +75 -0
- package/src/slider/root/SliderRoot.vue +557 -0
- package/src/slider/root/SliderRootContext.ts +126 -0
- package/src/slider/root/SliderRootDataAttributes.ts +35 -0
- package/src/slider/root/stateAttributesMapping.ts +13 -0
- package/src/slider/thumb/SliderThumb.vue +601 -0
- package/src/slider/thumb/SliderThumbDataAttributes.ts +39 -0
- package/src/slider/thumb/prehydrationScript.min.ts +5 -0
- package/src/slider/thumb/prehydrationScript.template.js +69 -0
- package/src/slider/track/SliderTrack.vue +48 -0
- package/src/slider/track/SliderTrackDataAttributes.ts +10 -0
- package/src/slider/utils/asc.ts +3 -0
- package/src/slider/utils/getMidpoint.ts +9 -0
- package/src/slider/utils/getPushedThumbValues.ts +68 -0
- package/src/slider/utils/getSliderValue.ts +25 -0
- package/src/slider/utils/replaceArrayItemAtIndex.ts +15 -0
- package/src/slider/utils/resolveThumbCollision.ts +177 -0
- package/src/slider/utils/roundValueToStep.ts +19 -0
- package/src/slider/utils/test-utils.ts +25 -0
- package/src/slider/utils/validateMinimumDistance.ts +20 -0
- package/src/slider/utils/valueArrayToPercentages.ts +10 -0
- package/src/slider/value/SliderValue.vue +90 -0
- package/src/slider/value/SliderValueDataAttributes.ts +35 -0
- package/src/switch/index.ts +14 -0
- package/src/switch/root/SwitchRoot.vue +448 -0
- package/src/switch/root/SwitchRootContext.ts +22 -0
- package/src/switch/root/SwitchRootDataAttributes.ts +46 -0
- package/src/switch/stateAttributesMapping.ts +23 -0
- package/src/switch/thumb/SwitchThumb.vue +59 -0
- package/src/switch/thumb/SwitchThumbDataAttributes.ts +46 -0
- package/src/toggle/Toggle.vue +211 -0
- package/src/toggle/ToggleDataAttributes.ts +6 -0
- package/src/toggle/index.ts +3 -0
- package/src/toggle-group/ToggleGroup.vue +224 -0
- package/src/toggle-group/ToggleGroupContext.ts +45 -0
- package/src/toggle-group/ToggleGroupDataAttributes.ts +15 -0
- package/src/toggle-group/index.ts +5 -0
- package/src/toolbar/button/ToolbarButton.vue +99 -0
- package/src/toolbar/button/ToolbarButtonDataAttributes.ts +15 -0
- package/src/toolbar/group/ToolbarGroup.vue +70 -0
- package/src/toolbar/group/ToolbarGroupContext.ts +23 -0
- package/src/toolbar/group/ToolbarGroupDataAttributes.ts +11 -0
- package/src/toolbar/index.ts +27 -0
- package/src/toolbar/input/ToolbarInput.vue +114 -0
- package/src/toolbar/input/ToolbarInputDataAttributes.ts +15 -0
- package/src/toolbar/link/ToolbarLink.vue +54 -0
- package/src/toolbar/link/ToolbarLinkDataAttributes.ts +7 -0
- package/src/toolbar/root/ToolbarRoot.vue +144 -0
- package/src/toolbar/root/ToolbarRootContext.ts +29 -0
- package/src/toolbar/root/ToolbarRootDataAttributes.ts +11 -0
- package/src/toolbar/separator/ToolbarSeparator.vue +41 -0
- package/src/toolbar/separator/ToolbarSeparatorDataAttributes.ts +7 -0
- package/src/use-button/useButton.ts +2 -1
- package/src/utils/areArraysEqual.ts +12 -0
- package/src/utils/clamp.ts +7 -0
- package/src/utils/createBaseUIEventDetails.ts +9 -0
- package/src/utils/formatNumber.ts +7 -0
- package/src/utils/owner.ts +5 -0
- package/src/utils/resolveAriaLabelledBy.ts +10 -0
- package/src/utils/useControllableState.ts +78 -14
- package/src/utils/useFocusableWhenDisabled.ts +6 -1
- package/src/utils/useMergedRefs.ts +26 -2
- package/src/utils/useRegisteredLabelId.ts +21 -0
- package/src/utils/valueToPercent.ts +7 -0
- package/src/utils/visuallyHidden.ts +24 -0
- package/dist/direction-provider/DirectionContext.cjs +0 -26
- package/dist/direction-provider/DirectionContext.cjs.map +0 -1
- package/dist/direction-provider/DirectionContext.js +0 -15
- package/dist/direction-provider/DirectionContext.js.map +0 -1
|
@@ -0,0 +1,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,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'
|