Open schube opened 3 years ago
This is not 100% standard to domino v2, but is really close a little bit of work and this could be integrated into domino.
css:
.dualslider input {
--start: 0%;
--stop: 100%;
-webkit-appearance: none;
appearance: none;
background: none;
pointer-events: none;
position: absolute;
height: 5px;
width: 100%;
align-items: center;
gap: var(--dui-slider-gap);
margin: var(--dui-slider-margin);
}
.dualslider input:first-of-type {
background-image: linear-gradient(to right, lightgrey var(--start), var(--dui-accent-l-1) var(--start), var(--dui-accent-l-1) var(--stop), lightgrey var(--stop));
}
.dualslider ::-moz-range-thumb {
cursor: pointer;
pointer-events: auto;
}
.dualslider ::-webkit-slider-thumb {
cursor: pointer;
pointer-events: auto;
}
.dualslider-thumb {
background-color: var(--dui-slider-thumb-background, var(--dui-accent-l-1));
position: absolute;
border: var(--dui-slider-thumb-border);
transform-origin: var(--dui-slider-thumb-transform-origin);
transform: var(--dui-slider-thumb-transform);
border-radius: var(--dui-slider-thumb-value-radius);
height: var(--dui-slider-thumb-value-height);
width: var(--dui-slider-thumb-value-width);
top: -50px;
margin: var(--dui-slider-thumb-value-margin);
-webkit-transition: var(--dui-slider-thumb-transition);
transition: var(--dui-slider-thumb-transition);
transition-property: var(--dui-slider-thumb-property);
}
code:
package com.howudodat.ui.beans;
import static org.dominokit.domino.ui.style.GenericCss.dui_active;
import static org.dominokit.domino.ui.utils.Domino.*;
import java.util.HashSet;
import java.util.Set;
import org.dominokit.domino.ui.elements.DivElement;
import org.dominokit.domino.ui.elements.InputElement;
import org.dominokit.domino.ui.elements.LabelElement;
import org.dominokit.domino.ui.elements.SpanElement;
import org.dominokit.domino.ui.events.EventOptions;
import org.dominokit.domino.ui.events.EventType;
import org.dominokit.domino.ui.forms.FormsStyles;
import org.dominokit.domino.ui.sliders.SliderStyles;
import org.dominokit.domino.ui.utils.BaseDominoElement;
import org.dominokit.domino.ui.utils.HasChangeListeners;
import org.dominokit.domino.ui.utils.LazyChild;
import elemental2.dom.HTMLElement;
import elemental2.dom.Text;
public class DualSlider extends BaseDominoElement<HTMLElement, DualSlider>
implements HasChangeListeners<DualSlider, Double[]>, SliderStyles, FormsStyles {
private final DivElement root;
protected final LazyChild<LabelElement> labelElement;
private Text labelText = text();
private Double[] oldValue;
private final InputElement inputLow;
private final SpanElement thumbLow;
private final SpanElement valueElementLow;
private final InputElement inputHigh;
private final SpanElement thumbHigh;
private final SpanElement valueElementHigh;
private boolean withThumb;
private boolean mouseDown;
private Set<ChangeListener<? super Double[]>> changeListeners = new HashSet<>();
private boolean changeListenersPaused;
/**
* Creates a slider with a specified maximum value.
*
* @param max the maximum value for the slider
* @return a new slider instance
*/
public static DualSlider create(double max) {
return create(max, 0, new Double[] { 0.0, max });
}
/**
* Creates a slider with a specified maximum and minimum value.
*
* @param max the maximum value for the slider
* @param min the minimum value for the slider
* @return a new slider instance
*/
public static DualSlider create(double max, double min) {
return create(max, min, new Double[] { min, max });
}
/**
* Creates a slider with specified maximum, minimum, and initial value.
*
* @param max the maximum value for the slider
* @param min the minimum value for the slider
* @param value the initial value for the slider
* @return a new slider instance
*/
public static DualSlider create(double max, double min, Double[] value) {
return new DualSlider(max, min, value);
}
/**
* Main constructor to create a Slider.
*
* @param max the maximum value
* @param min the minimum value
* @param value the initial value
*/
public DualSlider(double max, double min, Double[] value) {
root = div().addCss("dui-slider").addCss("dualslider")
.appendChild(inputLow = input("range").setAttribute("step", "any"))
.appendChild(thumbLow = span().addCss("dualslider-thumb").collapse()
.appendChild(valueElementLow = span().addCss(dui_slider_value)))
.appendChild(inputHigh = input("range").setAttribute("step", "any"))
.appendChild(thumbHigh = span().addCss("dualslider-thumb").collapse()
.appendChild(valueElementHigh = span().addCss(dui_slider_value)));
labelElement = LazyChild.of(label().addCss(dui_field_label), root);
labelElement.whenInitialized(() -> labelElement.element().appendChild(labelText));
setMaxValue(max);
setMinValue(min);
setValue(value);
inputLow.addEventListener(EventType.input, evt -> onMouseMove(inputLow));
inputLow.addEventListener(EventType.touchmove, evt -> onMouseMove(inputLow));
inputLow.addEventListener(EventType.mousemove, evt -> onMouseMove(inputLow));
inputHigh.addEventListener(EventType.input, evt -> onMouseMove(inputHigh));
inputHigh.addEventListener(EventType.mousemove, evt -> onMouseMove(inputHigh));
inputHigh.addEventListener(EventType.input, evt -> onMouseMove(inputHigh));
inputLow.addEventListener(EventType.change, evt -> triggerChangeListeners(oldValue, getValue()));
inputHigh.addEventListener(EventType.change, evt -> triggerChangeListeners(oldValue, getValue()));
inputLow.addEventListener(EventType.mousedown, evt -> onMouseDown(inputLow));
inputLow.addEventListener(EventType.touchstart, evt -> onMouseDown(inputLow), EventOptions.of().setPassive(true));
inputHigh.addEventListener(EventType.mousedown, evt -> onMouseDown(inputHigh));
inputHigh.addEventListener(EventType.touchstart, evt -> onMouseDown(inputHigh), EventOptions.of().setPassive(true));
inputLow.addEventListener(EventType.mouseup, evt -> onMouseUp(inputLow));
inputLow.addEventListener(EventType.touchend, evt -> onMouseUp(inputLow), EventOptions.of().setPassive(true));
inputHigh.addEventListener(EventType.mouseup, evt -> onMouseUp(inputHigh));
inputHigh.addEventListener(EventType.touchend, evt -> onMouseUp(inputHigh), EventOptions.of().setPassive(true));
inputLow.addEventListener(EventType.mouseout, evt -> hideThumb(inputLow));
inputLow.addEventListener(EventType.blur, evt -> hideThumb(inputLow));
inputHigh.addEventListener(EventType.mouseout, evt -> hideThumb(inputHigh));
inputHigh.addEventListener(EventType.blur, evt -> hideThumb(inputHigh));
}
private void onMouseMove(InputElement el) {
if (el.equals(inputLow))
onAdjustLow();
else
onAdjustHigh();
if (mouseDown) {
if (withThumb) {
evaluateThumbPosition(el);
updateThumbValue(el);
}
}
}
private void onAdjustLow() {
// test for bounds
Double val = Double.parseDouble(inputLow.getValue());
Double valMax = Double.parseDouble(inputHigh.getValue());
if (val > valMax) {
val = valMax;
inputLow.element().value = "" + val;
}
val = (val / Double.parseDouble(inputLow.element().max) * 100);
inputLow.style().setCssProperty("--start", val + "%");
}
private void onAdjustHigh() {
Double val = Double.parseDouble(inputHigh.getValue());
Double valMin = Double.parseDouble(inputLow.getValue());
if (val < valMin) {
val = valMin;
inputHigh.element().value = "" + val;
}
val = (val / Double.parseDouble(inputHigh.element().max) * 100);
inputLow.style().setCssProperty("--stop", val + "%");
}
private void onMouseDown(InputElement el) {
this.oldValue = getValue();
el.addCss(dui_active);
this.mouseDown = true;
if (withThumb) {
showThumb(el);
evaluateThumbPosition(el);
}
}
private void onMouseUp(InputElement el) {
mouseDown = false;
el.removeCss(dui_active);
hideThumb(el);
}
/**
* Sets the maximum value of the slider.
*
* @param max the maximum value to be set
* @return the current slider instance
*/
public DualSlider setMaxValue(double max) {
inputLow.element().max = String.valueOf(max);
inputHigh.element().max = String.valueOf(max);
return this;
}
/**
* Sets the minimum value of the slider.
*
* @param min the minimum value to be set
* @return the current slider instance
*/
public DualSlider setMinValue(double min) {
inputLow.element().min = String.valueOf(min);
inputHigh.element().min = String.valueOf(min);
return this;
}
/**
* Sets the value of the slider and optionally triggers change listeners.
*
* @param newValue the new value to be set
* @param silent if true, change listeners won't be triggered
* @return the current slider instance
*/
public DualSlider setValue(Double[] newValue, boolean silent) {
Double[] oldValue = getValue();
inputLow.element().value = String.valueOf(newValue[0]);
inputHigh.element().value = String.valueOf(newValue[1]);
if (!silent) {
triggerChangeListeners(oldValue, newValue);
}
return this;
}
/**
* Sets the value of the slider and triggers change listeners.
*
* @param newValue the new value to be set
* @return the current slider instance
*/
public DualSlider setValue(Double[] newValue) {
return setValue(newValue, false);
}
/**
* Gets the current value of the slider.
*
* @return the current value
*/
public Double[] getValue() {
return new Double[] { Double.parseDouble(inputLow.element().value),
Double.parseDouble(inputHigh.element().value) };
}
/**
* Gets the maximum value of the slider.
*
* @return the maximum value
*/
public double getMax() {
return Double.parseDouble(inputHigh.element().max);
}
/**
* Gets the minimum value of the slider.
*
* @return the minimum value
*/
public double getMin() {
return Double.parseDouble(inputLow.element().min);
}
/**
* Sets the stepping value of the slider.
*
* @param step the stepping value to be set
* @return the current slider instance
*/
public DualSlider setStep(double step) {
inputLow.element().step = String.valueOf(step);
inputHigh.element().step = String.valueOf(step);
return this;
}
/**
* Sets whether the slider should show its thumb.
*
* @param withThumb if true, the thumb will be shown
* @return the current slider instance
*/
public DualSlider setShowThumb(boolean withThumb) {
this.withThumb = withThumb;
return this;
}
/** Shows the slider's thumb and updates its value display. */
private void showThumb(InputElement el) {
if (el.equals(inputLow)) {
// thumbLow.style().setTop((root.getBoundingClientRect().top - 40) + "px");
thumbLow.expand();
} else {
// thumbHigh.style().setTop((root.getBoundingClientRect().top - 40) + "px");
thumbHigh.expand();
}
updateThumbValue(el);
}
/** Hides the slider's thumb. */
private void hideThumb(InputElement el) {
if (el.equals(inputLow))
thumbLow.collapse();
else
thumbHigh.collapse();
}
/**
* Updates the display value of the slider's thumb based on its current value.
*/
private void updateThumbValue(InputElement el) {
if (withThumb) {
if (el.equals(inputLow))
valueElementLow.setTextContent(String.valueOf(Double.valueOf(getValue()[0])));
else
valueElementHigh.setTextContent(String.valueOf(Double.valueOf(getValue()[1]).intValue()));
}
}
/**
* Evaluates the position of the slider's thumb based on the current value of
* the slider.
*/
private void evaluateThumbPosition(InputElement el) {
if (mouseDown) {
if (el.equals(inputLow)) {
thumbLow.style().setLeft(calculateRangeOffset(el) + "px");
} else {
thumbHigh.style().setLeft(calculateRangeOffset(el) + "px");
}
}
}
/**
* Calculates the range offset of the slider's thumb based on its current value.
*
* @return the calculated range offset in pixels
*/
private double calculateRangeOffset(InputElement el) {
InputElement input = (el.equals(inputLow) ? inputLow : inputHigh);
Double val = (el.equals(inputLow) ? getValue()[0] : getValue()[1]);
int width = input.element().offsetWidth - 15;
double percent = (val - getMin()) / (getMax() - getMin());
return percent * width + input.element().offsetLeft;
}
@Override
public HTMLElement element() {
return root.element();
}
/**
* Adds a change listener to the slider. This listener will be notified of value
* changes.
*
* @param changeListener the listener to be added
* @return the current slider instance
*/
@Override
public DualSlider addChangeListener(ChangeListener<? super Double[]> changeListener) {
changeListeners.add(changeListener);
return this;
}
/**
* Pauses the change listeners so they won't get triggered on value changes.
*
* @return the current slider instance
*/
@Override
public DualSlider pauseChangeListeners() {
this.changeListenersPaused = true;
return this;
}
/**
* Resumes the change listeners so they get triggered on value changes.
*
* @return the current slider instance
*/
@Override
public DualSlider resumeChangeListeners() {
this.changeListenersPaused = false;
return this;
}
/**
* Toggles the pause state of the change listeners.
*
* @param toggle if true, pause the listeners, otherwise resume them
* @return the current slider instance
*/
@Override
public DualSlider togglePauseChangeListeners(boolean toggle) {
this.changeListenersPaused = toggle;
return this;
}
/**
* Gets the set of change listeners attached to the slider.
*
* @return the set of change listeners
*/
@Override
public Set<ChangeListener<? super Double[]>> getChangeListeners() {
return changeListeners;
}
/**
* Checks if the change listeners are currently paused.
*
* @return true if listeners are paused, false otherwise
*/
@Override
public boolean isChangeListenersPaused() {
return this.changeListenersPaused;
}
/**
* Triggers the change listeners manually with given old and new values.
*
* @param oldValue the previous value
* @param newValue the current value
* @return the current slider instance
*/
@Override
public DualSlider triggerChangeListeners(Double[] oldValue, Double[] newValue) {
if (!isChangeListenersPaused()) {
changeListeners.forEach(changeListener -> changeListener.onValueChanged(oldValue, newValue));
}
return this;
}
/**
* Sets the label for this form element.
*
* @param label The label to set.
* @return This form element instance.
*/
public DualSlider setLabel(String label) {
labelElement.get();
labelText.textContent = label;
return this;
}
/**
* Gets the label of this form element.
*
* @return The label of this form element.
*/
public String getLabel() {
if (labelElement.isInitialized()) {
return labelElement.get().getTextContent();
}
return "";
}
}
to use:
protected DualSlider sldPriceRange = DualSlider.create(10, 0);
sldPriceRange.setShowThumb(true).setStep(.5).setLabel("Price Range");
Very cool, thank you. The project where I needed such a component is long finished, but next time when I need a range slider, I will get back to this code. Thank you!!!
The slider currently allows only to select one value. I am asking for an enhanced slider - a range slider. There you can select a range, i.e. a lower and an upper value.
This screenshots describes what I mean: https://flutter.github.io/assets-for-api-docs/assets/material/range_slider.png
Thank you!