|
import m, { FactoryComponent, Attributes } from 'mithril'; |
|
|
|
export type MinMax = [min: number, max: number]; |
|
|
|
export interface DoubleRangeSliderAttrs extends Attributes { |
|
/** Minimum value */ |
|
min: number; |
|
/** Maximum value */ |
|
max: number; |
|
/** Selected range */ |
|
range?: MinMax; |
|
/** Step size, @default 1 */ |
|
step?: number; |
|
/** Minimum value between selected minimum and maximum, e.g. to avoid the user selecting a range of 5-5, @default 1 */ |
|
rangeMin?: number; |
|
/** Onchange handler */ |
|
onchange?: (range: MinMax) => void; |
|
} |
|
|
|
export const DoubleRangeSlider: FactoryComponent<DoubleRangeSliderAttrs> = () => { |
|
let rangeElement: HTMLSpanElement; |
|
let minRangeInput: HTMLInputElement; |
|
let maxRangeInput: HTMLInputElement; |
|
let minValueInput: HTMLInputElement; |
|
let maxValueInput: HTMLInputElement; |
|
let range: MinMax = [0, 0]; |
|
|
|
return { |
|
oninit: ({ attrs }) => { |
|
const { min, max } = attrs; |
|
range = attrs.range || [min, max]; |
|
}, |
|
view: ({ attrs }) => { |
|
const { min, max, onchange, className, step = 1, rangeMin = 1 } = attrs; |
|
|
|
const handleInput = (e: Event) => { |
|
const isMin = (e.target as HTMLInputElement).classList.contains('min'); |
|
const minRange = parseInt(minRangeInput.value); |
|
const maxRange = parseInt(maxRangeInput.value); |
|
if (maxRange - minRange < rangeMin) { |
|
if (isMin) { |
|
minRangeInput.value = (maxRange - rangeMin).toString(); |
|
range = [maxRange - rangeMin, maxRange]; |
|
} else { |
|
maxRangeInput.value = (minRange + rangeMin).toString(); |
|
range = [minRange, minRange + rangeMin]; |
|
} |
|
} else { |
|
minValueInput.value = minRange.toString(); |
|
maxValueInput.value = maxRange.toString(); |
|
range = [minRange, maxRange]; |
|
} |
|
onchange && onchange(range); |
|
}; |
|
|
|
const handleValueInput = (e: Event) => { |
|
const isMin = (e.target as HTMLInputElement).classList.contains('min'); |
|
const minValue = parseInt(minValueInput.value); |
|
const maxValue = parseInt(maxValueInput.value); |
|
if (maxValue - minValue >= rangeMin && maxValue <= max) { |
|
if (isMin) { |
|
minRangeInput.value = minValue.toString(); |
|
} else { |
|
maxRangeInput.value = maxValue.toString(); |
|
} |
|
} |
|
range = [minValue, maxValue]; |
|
onchange && onchange(range); |
|
}; |
|
|
|
return m('.double-range-slider', { className }, [ |
|
m('.range-slider', [ |
|
m('span.range-selected', { |
|
style: { |
|
left: `${((range[0] - min) / (max - min)) * 100}%`, |
|
right: `${100 - ((range[1] - min) / (max - min)) * 100}%`, |
|
}, |
|
oncreate: ({ dom }) => (rangeElement = dom as HTMLSpanElement), |
|
}), |
|
]), |
|
m('.range-input', [ |
|
m('input[type=range].min', { |
|
min, |
|
max, |
|
value: range[0], |
|
step, |
|
oninput: handleInput, |
|
oncreate: ({ dom }) => (minRangeInput = dom as HTMLInputElement), |
|
}), |
|
m('input[type=range].max', { |
|
min, |
|
max, |
|
value: range[1], |
|
step, |
|
oninput: handleInput, |
|
oncreate: ({ dom }) => (maxRangeInput = dom as HTMLInputElement), |
|
}), |
|
]), |
|
m('.range-value', [ |
|
m('label', { for: 'min' }, 'Min'), |
|
m('input[type=number][name=min]', { |
|
min, |
|
max, |
|
value: range[0], |
|
oninput: handleValueInput, |
|
oncreate: ({ dom }) => (minValueInput = dom as HTMLInputElement), |
|
}), |
|
m('label', { for: 'max' }, 'Max'), |
|
m('input[type=number][name=max]', { |
|
min, |
|
max, |
|
value: range[1], |
|
oninput: handleValueInput, |
|
oncreate: ({ dom }) => (maxValueInput = dom as HTMLInputElement), |
|
}), |
|
]), |
|
]); |
|
}, |
|
}; |
|
}; |