Initial Commit
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
// saladui/components/slider.js
|
||||
import Component from "../core/component";
|
||||
import SaladUI from "../index";
|
||||
|
||||
class SliderComponent extends Component {
|
||||
constructor(el, hookContext) {
|
||||
super(el, { hookContext });
|
||||
|
||||
// Get slider elements
|
||||
this.track = this.getPart("track");
|
||||
this.range = this.getPart("range");
|
||||
this.thumb = this.getPart("thumb");
|
||||
|
||||
// Initialize values
|
||||
this.parseValues();
|
||||
|
||||
// Add drag handling
|
||||
this.isDragging = false;
|
||||
this.setupDragHandling();
|
||||
|
||||
// Set initial position
|
||||
this.updatePosition();
|
||||
}
|
||||
|
||||
parseValues() {
|
||||
// Get values from options with defaults
|
||||
this.min = parseFloat(this.options.min || 0);
|
||||
this.max = parseFloat(this.options.max || 100);
|
||||
this.step = parseFloat(this.options.step || 1);
|
||||
this.disabled = !!this.options.disabled;
|
||||
|
||||
// Get value from data attribute, fallback to defaultValue from options, then to min
|
||||
const dataValue = this.el.dataset.value;
|
||||
const defaultValue = this.options.defaultValue;
|
||||
this.value = parseFloat(
|
||||
dataValue !== undefined && dataValue !== null
|
||||
? dataValue
|
||||
: (defaultValue !== undefined ? defaultValue : this.min),
|
||||
);
|
||||
|
||||
// Clamp value to min/max
|
||||
this.value = Math.max(this.min, Math.min(this.max, this.value));
|
||||
}
|
||||
|
||||
getComponentConfig() {
|
||||
return {
|
||||
stateMachine: {
|
||||
idle: {
|
||||
enter: "onIdleEnter",
|
||||
transitions: {
|
||||
drag: "dragging",
|
||||
},
|
||||
},
|
||||
dragging: {
|
||||
enter: "onDraggingEnter",
|
||||
exit: "onDraggingExit",
|
||||
transitions: {
|
||||
end: "idle",
|
||||
},
|
||||
},
|
||||
},
|
||||
events: {
|
||||
idle: {
|
||||
keyMap: {
|
||||
ArrowLeft: () => this.decrementValue(),
|
||||
ArrowRight: () => this.incrementValue(),
|
||||
ArrowDown: () => this.decrementValue(),
|
||||
ArrowUp: () => this.incrementValue(),
|
||||
Home: () => this.setValueAndUpdate(this.min),
|
||||
End: () => this.setValueAndUpdate(this.max),
|
||||
},
|
||||
},
|
||||
dragging: {
|
||||
// These are handled by event listeners
|
||||
},
|
||||
},
|
||||
ariaConfig: {
|
||||
root: {
|
||||
all: {
|
||||
role: "slider",
|
||||
valuemin: () => this.min?.toString(),
|
||||
valuemax: () => this.max?.toString(),
|
||||
valuenow: () => this.value?.toString(),
|
||||
valuetext: () => this.value?.toString(),
|
||||
orientation: "horizontal",
|
||||
disabled: () => (this.disabled ? "true" : null),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
setupDragHandling() {
|
||||
// Set up event handlers with proper binding
|
||||
this.onPointerMove = this.onPointerMove.bind(this);
|
||||
this.onPointerUp = this.onPointerUp.bind(this);
|
||||
this.onPointerDown = this.onPointerDown.bind(this);
|
||||
// Mouse events
|
||||
this.el.addEventListener("mousedown", this.onPointerDown);
|
||||
|
||||
// Touch events
|
||||
this.el.addEventListener("touchstart", this.onPointerDown, {
|
||||
passive: false,
|
||||
});
|
||||
}
|
||||
|
||||
onIdleEnter() {
|
||||
// Set proper tabindex when component is idle (not being dragged)
|
||||
this.el.setAttribute("tabindex", "0");
|
||||
}
|
||||
|
||||
onDraggingEnter() {
|
||||
// Add global event listeners
|
||||
document.addEventListener("mousemove", this.onPointerMove);
|
||||
document.addEventListener("touchmove", this.onPointerMove, {
|
||||
passive: false,
|
||||
});
|
||||
document.addEventListener("mouseup", this.onPointerUp);
|
||||
document.addEventListener("touchend", this.onPointerUp);
|
||||
}
|
||||
|
||||
onDraggingExit() {
|
||||
// Remove global event listeners
|
||||
document.removeEventListener("mousemove", this.onPointerMove);
|
||||
document.removeEventListener("touchmove", this.onPointerMove);
|
||||
document.removeEventListener("mouseup", this.onPointerUp);
|
||||
document.removeEventListener("touchend", this.onPointerUp);
|
||||
}
|
||||
|
||||
onPointerDown(event) {
|
||||
// Skip if disabled
|
||||
if (this.disabled) return;
|
||||
|
||||
// Prevent default to avoid text selection during drag
|
||||
event.preventDefault();
|
||||
|
||||
// Handle mousedown or touchstart
|
||||
this.transition("drag");
|
||||
|
||||
// Update value based on pointer position
|
||||
this.updateValueFromPointer(event);
|
||||
}
|
||||
|
||||
onPointerMove(event) {
|
||||
// Prevent default to avoid scrolling during drag
|
||||
event.preventDefault();
|
||||
|
||||
// Update value based on pointer position
|
||||
this.updateValueFromPointer(event);
|
||||
}
|
||||
|
||||
onPointerUp() {
|
||||
// End dragging
|
||||
this.transition("end");
|
||||
|
||||
// Notify of value change
|
||||
this.pushEvent("value-changed", { value: this.value });
|
||||
}
|
||||
|
||||
updateValueFromPointer(event) {
|
||||
// Get coordinates depending on event type
|
||||
const clientX = event.touches ? event.touches[0].clientX : event.clientX;
|
||||
|
||||
// Get track bounds
|
||||
const trackRect = this.track.getBoundingClientRect();
|
||||
|
||||
// Calculate percentage within track
|
||||
let percentage = Math.max(
|
||||
0,
|
||||
Math.min(1, (clientX - trackRect.left) / trackRect.width),
|
||||
);
|
||||
|
||||
// Calculate value based on percentage
|
||||
const rawValue = this.min + percentage * (this.max - this.min);
|
||||
|
||||
// Snap to step
|
||||
const steppedValue = Math.round(rawValue / this.step) * this.step;
|
||||
|
||||
// Set and update
|
||||
this.setValueAndUpdate(steppedValue);
|
||||
}
|
||||
|
||||
incrementValue() {
|
||||
this.setValueAndUpdate(Math.min(this.max, this.value + this.step));
|
||||
this.pushEvent("value-changed", { value: this.value });
|
||||
}
|
||||
|
||||
decrementValue() {
|
||||
this.setValueAndUpdate(Math.max(this.min, this.value - this.step));
|
||||
this.pushEvent("value-changed", { value: this.value });
|
||||
}
|
||||
|
||||
setValueAndUpdate(newValue) {
|
||||
// Ensure value is within bounds
|
||||
this.value = Math.max(this.min, Math.min(this.max, newValue));
|
||||
|
||||
// Update visual position
|
||||
this.updatePosition();
|
||||
|
||||
// Update ARIA attributes
|
||||
this.el.setAttribute("aria-valuenow", this.value.toString());
|
||||
this.el.setAttribute("aria-valuetext", this.value.toString());
|
||||
}
|
||||
|
||||
updatePosition() {
|
||||
// Calculate percentage for positioning
|
||||
const percentage = ((this.value - this.min) / (this.max - this.min)) * 100;
|
||||
|
||||
// Update range width
|
||||
this.range.style.width = `${percentage}%`;
|
||||
|
||||
// Get track and thumb dimensions
|
||||
const trackRect = this.track.getBoundingClientRect();
|
||||
const thumbRect = this.thumb.getBoundingClientRect();
|
||||
|
||||
// Calculate the percentage offset needed to keep thumb fully within track
|
||||
// This accounts for the thumb's width relative to the track
|
||||
const thumbHalfWidthPercentage =
|
||||
(thumbRect.width / 2 / trackRect.width) * 100;
|
||||
|
||||
// Constrain the thumb position to keep it fully inside the track
|
||||
const thumbPercentage = Math.max(
|
||||
thumbHalfWidthPercentage,
|
||||
Math.min(100 - thumbHalfWidthPercentage, percentage),
|
||||
);
|
||||
|
||||
// Update thumb position
|
||||
this.thumb.style.left = `${thumbPercentage}%`;
|
||||
this.thumb.style.transform = "translateX(-50%)";
|
||||
}
|
||||
|
||||
// Handle direct server-side commands
|
||||
handleCommand(command, params) {
|
||||
if (command === "setValue") {
|
||||
this.setValueAndUpdate(parseFloat(params.value));
|
||||
return true;
|
||||
}
|
||||
return super.handleCommand(command, params);
|
||||
}
|
||||
|
||||
// Clean up
|
||||
beforeDestroy() {
|
||||
document.removeEventListener("mousemove", this.onPointerMove);
|
||||
document.removeEventListener("touchmove", this.onPointerMove);
|
||||
document.removeEventListener("mouseup", this.onPointerUp);
|
||||
document.removeEventListener("touchend", this.onPointerUp);
|
||||
// Remove local event listeners
|
||||
this.el.removeEventListener("mousedown", this.onPointerDown);
|
||||
this.el.removeEventListener("touchstart", this.onPointerDown);
|
||||
}
|
||||
}
|
||||
|
||||
// Register component
|
||||
SaladUI.register("slider", SliderComponent);
|
||||
|
||||
export default SliderComponent;
|
||||
Reference in New Issue
Block a user