Files
v10/dist/dev/dom/ui/slider.js

215 lines
6.2 KiB
JavaScript

import { getPercentFromPointerEvent } from "../utils/pointer.js";
import { createState } from "@videojs/store";
import { isNull } from "@videojs/utils/predicate";
import { clamp, roundToStep } from "@videojs/utils/number";
import { throttle } from "@videojs/utils/function";
//#region src/dom/ui/slider.ts
function createSlider(options) {
const input = createState({
pointerPercent: 0,
dragPercent: 0,
dragging: false,
pointing: false,
focused: false
});
const abort = new AbortController();
const changeThrottleMs = options.changeThrottle ?? 0;
let isDragging = false, cachedRTL = false, cachedRect = null, capturedPointerId = null, lastDragPercent = 0, committedOnRelease = false;
const throttledChange = changeThrottleMs > 0 ? throttle((percent) => options.onValueChange?.(percent), changeThrottleMs, { leading: true }) : null;
/** Fire `onValueChange` — throttled during drag when `changeThrottle > 0`. */
function fireChange(percent, duringDrag) {
if (duringDrag && throttledChange) throttledChange(percent);
else options.onValueChange?.(percent);
}
function releaseCapture() {
if (isNull(capturedPointerId)) return;
const id = capturedPointerId;
capturedPointerId = null;
try {
options.getElement().releasePointerCapture(id);
} catch {}
}
function endDrag() {
if (!isDragging) input.patch({ pointing: false });
else {
if (!committedOnRelease) options.onValueCommit?.(lastDragPercent);
isDragging = false;
input.patch({
dragging: false,
pointing: false
});
options.onDragEnd?.();
}
committedOnRelease = false;
cleanup();
}
function cleanup() {
throttledChange?.cancel();
capturedPointerId = null;
cachedRect = null;
}
const rootProps = {
onPointerDown(event) {
if (options.isDisabled()) return;
event.stopPropagation();
event.preventDefault();
const el = options.getElement();
cachedRect = el.getBoundingClientRect();
cachedRTL = options.isRTL();
committedOnRelease = false;
releaseCapture();
capturedPointerId = event.pointerId;
el.setPointerCapture(event.pointerId);
const percent = getPercentFromPointerEvent(event, cachedRect, options.getOrientation(), cachedRTL);
isDragging = true;
lastDragPercent = percent;
input.patch({
pointing: true,
dragging: true,
pointerPercent: percent,
dragPercent: percent
});
options.onDragStart?.();
options.onValueChange?.(percent);
options.getThumbElement?.()?.focus({
preventScroll: true,
focusVisible: false
});
},
onPointerMove(event) {
if (options.isDisabled()) return;
if (!isNull(capturedPointerId)) {
if (event.pointerType !== "touch" && event.buttons === 0) {
endDrag();
return;
}
const percent = getPercentFromPointerEvent(event, cachedRect, options.getOrientation(), cachedRTL);
lastDragPercent = percent;
input.patch({
dragPercent: percent,
pointerPercent: percent
});
fireChange(percent, true);
return;
}
const percent = getPercentFromPointerEvent(event, options.getElement().getBoundingClientRect(), options.getOrientation(), options.isRTL());
input.patch({
pointing: true,
pointerPercent: percent
});
},
onPointerUp(event) {
if (options.isDisabled()) return;
event.stopPropagation();
if (isNull(capturedPointerId)) return;
const percent = getPercentFromPointerEvent(event, cachedRect, options.getOrientation(), cachedRTL);
throttledChange?.cancel();
options.onValueChange?.(percent);
options.onValueCommit?.(percent);
committedOnRelease = true;
},
onPointerLeave() {
if (!isNull(capturedPointerId)) return;
input.patch({ pointing: false });
},
onLostPointerCapture() {
endDrag();
}
};
const thumbProps = {
onKeyDown(event) {
if (options.isDisabled()) {
if (event.key !== "Tab") event.preventDefault();
return;
}
const stepPercent = options.getStepPercent();
const largeStepPercent = options.getLargeStepPercent();
const rounded = roundToStep(options.getPercent(), stepPercent, 0);
const horizontalSign = options.isRTL() ? -1 : 1;
const step = event.shiftKey ? largeStepPercent : stepPercent;
let newPercent = null;
switch (event.key) {
case "ArrowRight":
newPercent = rounded + step * horizontalSign;
break;
case "ArrowLeft":
newPercent = rounded - step * horizontalSign;
break;
case "ArrowUp":
newPercent = rounded + step;
break;
case "ArrowDown":
newPercent = rounded - step;
break;
case "PageUp":
newPercent = rounded + largeStepPercent;
break;
case "PageDown":
newPercent = rounded - largeStepPercent;
break;
case "Home":
newPercent = 0;
break;
case "End":
newPercent = 100;
break;
}
if (newPercent !== null) {
event.preventDefault();
newPercent = clamp(newPercent, 0, 100);
input.patch({
pointerPercent: newPercent,
dragPercent: newPercent
});
options.onValueChange?.(newPercent);
options.onValueCommit?.(newPercent);
}
},
onFocus() {
input.patch({ focused: true });
},
onBlur() {
input.patch({ focused: false });
}
};
function adjustForAlignment(state) {
if (!options.adjustPercent || state.thumbAlignment !== "edge") return state;
const rootEl = options.getElement();
const thumbEl = options.getThumbElement?.();
if (!thumbEl) return state;
const isHorizontal = state.orientation === "horizontal";
const thumbSize = isHorizontal ? thumbEl.offsetWidth : thumbEl.offsetHeight;
const trackSize = isHorizontal ? rootEl.offsetWidth : rootEl.offsetHeight;
return {
...state,
fillPercent: options.adjustPercent(state.fillPercent, thumbSize, trackSize),
pointerPercent: options.adjustPercent(state.pointerPercent, thumbSize, trackSize)
};
}
let resizeObserver = null;
if (options.onResize) {
resizeObserver = new ResizeObserver(() => options.onResize());
resizeObserver.observe(options.getElement());
}
return {
input,
rootProps,
rootStyle: {
touchAction: "none",
userSelect: "none"
},
thumbProps,
adjustForAlignment,
destroy() {
if (abort.signal.aborted) return;
abort.abort();
resizeObserver?.disconnect();
releaseCapture();
cleanup();
}
};
}
//#endregion
export { createSlider };
//# sourceMappingURL=slider.js.map