diff --git a/src/components/Slider/Slider.api.md b/src/components/Slider/Slider.api.md
index 9135c43e1..97f4d48ac 100644
--- a/src/components/Slider/Slider.api.md
+++ b/src/components/Slider/Slider.api.md
@@ -21,7 +21,7 @@
},
{
name: 'min',
- description: 'Minimum allowed slider value.',
+ description: 'Minimum allowed slider value. Negative values enable bidirectional fill from zero.',
required: false,
type: 'number',
default: '0'
diff --git a/src/components/Slider/Slider.cy.ts b/src/components/Slider/Slider.cy.ts
index b52aefef3..5c35c1c68 100644
--- a/src/components/Slider/Slider.cy.ts
+++ b/src/components/Slider/Slider.cy.ts
@@ -102,4 +102,33 @@ describe('Slider', () => {
cy.get('#sl-err').should('have.attr', 'data-state', 'invalid')
})
})
+
+ describe('bidirectional fill', () => {
+ // Targets the range element: the only absolutely-positioned element inside the control.
+ const range = () => cy.get('[data-slot="control"] .absolute')
+
+ it('fills from zero-crossing to a positive value', () => {
+ cy.mount(Slider, { props: { modelValue: [50], min: -100, max: 100 } })
+ range()
+ .should('have.attr', 'style')
+ .and('include', 'left: 50%')
+ .and('include', 'right: 25%')
+ })
+
+ it('fills from a negative value to the zero-crossing', () => {
+ cy.mount(Slider, { props: { modelValue: [-50], min: -100, max: 100 } })
+ range()
+ .should('have.attr', 'style')
+ .and('include', 'left: 25%')
+ .and('include', 'right: 50%')
+ })
+
+ it('renders zero-width fill when value is at zero', () => {
+ cy.mount(Slider, { props: { modelValue: [0], min: -100, max: 100 } })
+ range()
+ .should('have.attr', 'style')
+ .and('include', 'left: 50%')
+ .and('include', 'right: 50%')
+ })
+ })
})
diff --git a/src/components/Slider/Slider.md b/src/components/Slider/Slider.md
index 1befeb282..c7e4e52ae 100644
--- a/src/components/Slider/Slider.md
+++ b/src/components/Slider/Slider.md
@@ -14,6 +14,12 @@ Use a two-element `modelValue` to render two thumbs.
+## Negative Values
+
+When `min` is negative the slider fills bidirectionally from the zero-crossing, so positive and negative values are visually distinct.
+
+
+
## Labeling
diff --git a/src/components/Slider/Slider.vue b/src/components/Slider/Slider.vue
index bfdcb2d2a..91e959bcf 100644
--- a/src/components/Slider/Slider.vue
+++ b/src/components/Slider/Slider.vue
@@ -53,6 +53,21 @@ const {
disabled: () => props.disabled,
})
+const isBidirectional = computed(() => props.min < 0 && props.max > 0)
+
+const bidirectionalRangeStyles = computed(() => {
+ const { min, max } = props
+ const range = max - min
+ const zeroPos = -min / range
+ const thumbPositions = sliderValue.value.map((v) => (v - min) / range)
+ const allPositions =
+ thumbPositions.length === 1 ? [zeroPos, thumbPositions[0]] : thumbPositions
+ return {
+ left: `${Math.min(...allPositions) * 100}%`,
+ right: `${(1 - Math.max(...allPositions)) * 100}%`,
+ }
+})
+
const trackClasses = computed(() => {
return [
'relative grow rounded',
@@ -68,6 +83,13 @@ const rangeClasses = computed(() => {
]
})
+const rootClasses = computed(() => {
+ return [
+ 'relative flex w-full select-none touch-none items-center',
+ props.size === 'md' ? 'h-5' : 'h-4',
+ ]
+})
+
const thumbClasses = computed(() => {
return [
'rounded-full bg-surface-white shadow-md ring-gray-600/20 transition-shadow duration-200 ease-out hover:ring-[6px] focus:outline-none dark:bg-surface-gray-7 dark:ring-gray-100/20',
@@ -85,10 +107,10 @@ const onValueCommit = (value: SliderValue) => {
const hasLabeling = computed(() => {
return Boolean(
props.label ||
- slots.label ||
- showDescription.value ||
- slots.description ||
- hasError.value,
+ slots.label ||
+ showDescription.value ||
+ slots.description ||
+ hasError.value,
)
})
@@ -110,7 +132,7 @@ const hasLabeling = computed(() => {
{
@value-commit="onValueCommit"
>
-
+
+
+import { ref } from 'vue'
+import { Slider } from 'frappe-ui'
+
+const value = ref([25])
+
+
+
+
+
+
+
diff --git a/src/components/Slider/types.ts b/src/components/Slider/types.ts
index e2838475f..201a053d6 100644
--- a/src/components/Slider/types.ts
+++ b/src/components/Slider/types.ts
@@ -20,7 +20,7 @@ export interface SliderProps extends InputLabelingProps {
/** Maximum allowed slider value. */
max?: number
- /** Minimum allowed slider value. */
+ /** Minimum allowed slider value. Negative values enable bidirectional fill from zero. */
min?: number
/** Visual size of the slider. */