DuyTa's picture
Upload folder using huggingface_hub
bc20498 verified
import { addEventListener, addMeltEventListener, makeElement, makeElementArray, createElHelpers, disabledAttr, effect, executeCallbacks, generateIds, getElementByMeltId, isBrowser, isHTMLElement, kbd, omit, overridable, snapValueToStep, styleToString, toWritableStores, } from '../../internal/helpers/index.js';
import { derived, writable } from 'svelte/store';
import { withGet } from '../../internal/helpers/withGet.js';
const defaults = {
defaultValue: [],
min: 0,
max: 100,
step: 1,
orientation: 'horizontal',
dir: 'ltr',
disabled: false,
};
const { name } = createElHelpers('slider');
export const createSlider = (props) => {
const withDefaults = { ...defaults, ...props };
const options = toWritableStores(omit(withDefaults, 'value', 'onValueChange', 'defaultValue'));
const { min, max, step, orientation, dir, disabled } = options;
const valueWritable = withDefaults.value ?? writable(withDefaults.defaultValue);
const value = overridable(valueWritable, withDefaults?.onValueChange);
const isActive = withGet(writable(false));
const currentThumbIndex = withGet(writable(0));
const activeThumb = withGet(writable(null));
const meltIds = generateIds(['root']);
// Helpers
const updatePosition = (val, index) => {
value.update((prev) => {
if (!prev)
return [val];
if (prev[index] === val)
return prev;
const newValue = [...prev];
const direction = newValue[index] > val ? -1 : +1;
function swap() {
newValue[index] = newValue[index + direction];
newValue[index + direction] = val;
const thumbs = getAllThumbs();
if (thumbs) {
thumbs[index + direction].focus();
activeThumb.set({ thumb: thumbs[index + direction], index: index + direction });
}
}
if (direction === -1 && val < newValue[index - 1]) {
swap();
return newValue;
}
else if (direction === 1 && val > newValue[index + 1]) {
swap();
return newValue;
}
const $min = min.get();
const $max = max.get();
const $step = step.get();
newValue[index] = snapValueToStep(val, $min, $max, $step);
return newValue;
});
};
const getAllThumbs = () => {
const root = getElementByMeltId(meltIds.root);
if (!root)
return null;
return Array.from(root.querySelectorAll('[data-melt-part="thumb"]')).filter((thumb) => isHTMLElement(thumb));
};
// States
const position = derived([min, max], ([$min, $max]) => {
return (val) => {
const pos = ((val - $min) / ($max - $min)) * 100;
return pos;
};
});
const direction = withGet.derived([orientation, dir], ([$orientation, $dir]) => {
if ($orientation === 'horizontal') {
return $dir === 'rtl' ? 'rl' : 'lr';
}
else {
return $dir === 'rtl' ? 'tb' : 'bt';
}
});
// Elements
const root = makeElement(name(), {
stores: [disabled, orientation, dir],
returned: ([$disabled, $orientation, $dir]) => {
return {
dir: $dir,
disabled: disabledAttr($disabled),
'data-disabled': disabledAttr($disabled),
'data-orientation': $orientation,
style: $disabled
? undefined
: `touch-action: ${$orientation === 'horizontal' ? 'pan-y' : 'pan-x'}`,
'data-melt-id': meltIds.root,
};
},
});
const range = makeElement(name('range'), {
stores: [value, direction, position],
returned: ([$value, $direction, $position]) => {
const minimum = $value.length > 1 ? $position(Math.min(...$value) ?? 0) : 0;
const maximum = 100 - $position(Math.max(...$value) ?? 0);
const style = {
position: 'absolute',
};
switch ($direction) {
case 'lr': {
style.left = `${minimum}%`;
style.right = `${maximum}%`;
break;
}
case 'rl': {
style.right = `${minimum}%`;
style.left = `${maximum}%`;
break;
}
case 'bt': {
style.bottom = `${minimum}%`;
style.top = `${maximum}%`;
break;
}
case 'tb': {
style.top = `${minimum}%`;
style.bottom = `${maximum}%`;
break;
}
}
return {
style: styleToString(style),
};
},
});
const thumbs = makeElementArray(name('thumb'), {
stores: [value, position, min, max, disabled, orientation, direction],
returned: ([$value, $position, $min, $max, $disabled, $orientation, $direction]) => {
const result = Array.from({ length: $value.length || 1 }, (_, i) => {
const currentThumb = currentThumbIndex.get();
if (currentThumb < $value.length) {
currentThumbIndex.update((prev) => prev + 1);
}
const thumbValue = $value[i];
const thumbPosition = `${$position(thumbValue)}%`;
const style = {
position: 'absolute',
};
switch ($direction) {
case 'lr': {
style.left = thumbPosition;
style.translate = '-50% 0';
break;
}
case 'rl': {
style.right = thumbPosition;
style.translate = '50% 0';
break;
}
case 'bt': {
style.bottom = thumbPosition;
style.translate = '0 50%';
break;
}
case 'tb': {
style.top = thumbPosition;
style.translate = '0 -50%';
break;
}
}
return {
role: 'slider',
'aria-valuemin': $min,
'aria-valuemax': $max,
'aria-valuenow': thumbValue,
'aria-disabled': disabledAttr($disabled),
'aria-orientation': $orientation,
'data-melt-part': 'thumb',
'data-value': thumbValue,
style: styleToString(style),
tabindex: $disabled ? -1 : 0,
};
});
return result;
},
action: (node) => {
const unsub = addMeltEventListener(node, 'keydown', (event) => {
if (disabled.get())
return;
const target = event.currentTarget;
if (!isHTMLElement(target))
return;
const thumbs = getAllThumbs();
if (!thumbs?.length)
return;
const index = thumbs.indexOf(target);
currentThumbIndex.set(index);
if (![
kbd.ARROW_LEFT,
kbd.ARROW_RIGHT,
kbd.ARROW_UP,
kbd.ARROW_DOWN,
kbd.HOME,
kbd.END,
].includes(event.key)) {
return;
}
event.preventDefault();
const $min = min.get();
const $max = max.get();
const $step = step.get();
const $value = value.get();
const $orientation = orientation.get();
const $direction = direction.get();
const thumbValue = $value[index];
switch (event.key) {
case kbd.HOME: {
updatePosition($min, index);
break;
}
case kbd.END: {
updatePosition($max, index);
break;
}
case kbd.ARROW_LEFT: {
if ($orientation !== 'horizontal')
break;
if (event.metaKey) {
const newValue = $direction === 'rl' ? $max : $min;
updatePosition(newValue, index);
}
else if ($direction === 'rl' && thumbValue < $max) {
updatePosition(thumbValue + $step, index);
}
else if ($direction === 'lr' && thumbValue > $min) {
updatePosition(thumbValue - $step, index);
}
break;
}
case kbd.ARROW_RIGHT: {
if ($orientation !== 'horizontal')
break;
if (event.metaKey) {
const newValue = $direction === 'rl' ? $min : $max;
updatePosition(newValue, index);
}
else if ($direction === 'rl' && thumbValue > $min) {
updatePosition(thumbValue - $step, index);
}
else if ($direction === 'lr' && thumbValue < $max) {
updatePosition(thumbValue + $step, index);
}
break;
}
case kbd.ARROW_UP: {
if (event.metaKey) {
const newValue = $direction === 'tb' ? $min : $max;
updatePosition(newValue, index);
}
else if ($direction === 'tb' && thumbValue > $min) {
updatePosition(thumbValue - $step, index);
}
else if ($direction !== 'tb' && thumbValue < $max) {
updatePosition(thumbValue + $step, index);
}
break;
}
case kbd.ARROW_DOWN: {
if (event.metaKey) {
const newValue = $direction === 'tb' ? $max : $min;
updatePosition(newValue, index);
}
else if ($direction === 'tb' && thumbValue < $max) {
updatePosition(thumbValue + $step, index);
}
else if ($direction !== 'tb' && thumbValue > $min) {
updatePosition(thumbValue - $step, index);
}
break;
}
}
});
return {
destroy: unsub,
};
},
});
const ticks = makeElementArray(name('tick'), {
stores: [value, min, max, step, direction],
returned: ([$value, $min, $max, $step, $direction]) => {
const difference = $max - $min;
// min = 0, max = 8, step = 3:
// ----------------------------
// 0, 3, 6
// (8 - 0) / 3 = 2.666... = 3 ceiled
let count = Math.ceil(difference / $step);
// min = 0, max = 9, step = 3:
// ---------------------------
// 0, 3, 6, 9
// (9 - 0) / 3 = 3
// We need to add 1 because `difference` is a multiple of `step`.
if (difference % $step == 0) {
count++;
}
return Array.from({ length: count }, (_, i) => {
// The track is divided into sections of ratio `step / (max - min)`
const tickPosition = `${i * ($step / ($max - $min)) * 100}%`;
// Offset each tick by -50% to center it, except the first and last ticks.
// The first tick is already positioned at the start of the slider.
// The last tick is offset by -100% to prevent it from being rendered outside.
const isFirst = i === 0;
const isLast = i === count - 1;
const offsetPercentage = isFirst ? 0 : isLast ? -100 : -50;
const style = {
position: 'absolute',
};
switch ($direction) {
case 'lr': {
style.left = tickPosition;
style.translate = `${offsetPercentage}% 0`;
break;
}
case 'rl': {
style.right = tickPosition;
style.translate = `${-offsetPercentage}% 0`;
break;
}
case 'bt': {
style.bottom = tickPosition;
style.translate = `0 ${-offsetPercentage}%`;
break;
}
case 'tb': {
style.top = tickPosition;
style.translate = `0 ${offsetPercentage}%`;
break;
}
}
const tickValue = $min + i * $step;
const bounded = $value.length === 1
? tickValue <= $value[0]
: $value[0] <= tickValue && tickValue <= $value[$value.length - 1];
return {
'data-bounded': bounded ? true : undefined,
'data-value': tickValue,
style: styleToString(style),
};
});
},
});
// Effects
effect([root, min, max, disabled, orientation, direction, step], ([$root, $min, $max, $disabled, $orientation, $direction, $step]) => {
if (!isBrowser || $disabled)
return;
const applyPosition = (clientXY, activeThumbIdx, start, end) => {
const percent = (clientXY - start) / (end - start);
const val = percent * ($max - $min) + $min;
if (val < $min) {
updatePosition($min, activeThumbIdx);
}
else if (val > $max) {
updatePosition($max, activeThumbIdx);
}
else {
const step = $step;
const min = $min;
const currentStep = Math.floor((val - min) / step);
const midpointOfCurrentStep = min + currentStep * step + step / 2;
const midpointOfNextStep = min + (currentStep + 1) * step + step / 2;
const newValue = val >= midpointOfCurrentStep && val < midpointOfNextStep
? (currentStep + 1) * step + min
: currentStep * step + min;
if (newValue <= $max) {
updatePosition(newValue, activeThumbIdx);
}
}
};
const getClosestThumb = (e) => {
const thumbs = getAllThumbs();
if (!thumbs)
return;
thumbs.forEach((thumb) => thumb.blur());
const distances = thumbs.map((thumb) => {
if ($orientation === 'horizontal') {
const { left, right } = thumb.getBoundingClientRect();
return Math.abs(e.clientX - (left + right) / 2);
}
else {
const { top, bottom } = thumb.getBoundingClientRect();
return Math.abs(e.clientY - (top + bottom) / 2);
}
});
const thumb = thumbs[distances.indexOf(Math.min(...distances))];
const index = thumbs.indexOf(thumb);
return { thumb, index };
};
const pointerMove = (e) => {
if (!isActive.get())
return;
e.preventDefault();
e.stopPropagation();
const sliderEl = getElementByMeltId($root['data-melt-id']);
const closestThumb = activeThumb.get();
if (!sliderEl || !closestThumb)
return;
closestThumb.thumb.focus();
const { left, right, top, bottom } = sliderEl.getBoundingClientRect();
switch ($direction) {
case 'lr': {
applyPosition(e.clientX, closestThumb.index, left, right);
break;
}
case 'rl': {
applyPosition(e.clientX, closestThumb.index, right, left);
break;
}
case 'bt': {
applyPosition(e.clientY, closestThumb.index, bottom, top);
break;
}
case 'tb': {
applyPosition(e.clientY, closestThumb.index, top, bottom);
break;
}
}
};
const pointerDown = (e) => {
if (e.button !== 0)
return;
const sliderEl = getElementByMeltId($root['data-melt-id']);
const closestThumb = getClosestThumb(e);
if (!closestThumb || !sliderEl)
return;
const target = e.target;
if (!isHTMLElement(target) || !sliderEl.contains(target)) {
return;
}
e.preventDefault();
activeThumb.set(closestThumb);
closestThumb.thumb.focus();
isActive.set(true);
pointerMove(e);
};
const pointerUp = () => {
isActive.set(false);
};
const unsub = executeCallbacks(addEventListener(document, 'pointerdown', pointerDown), addEventListener(document, 'pointerup', pointerUp), addEventListener(document, 'pointerleave', pointerUp), addEventListener(document, 'pointermove', pointerMove));
return () => {
unsub();
};
});
effect([step, min, max, value], function fixValue([$step, $min, $max, $value]) {
const isValidValue = (v) => {
const snappedValue = snapValueToStep(v, $min, $max, $step);
return snappedValue === v;
};
const gcv = (v) => {
return snapValueToStep(v, $min, $max, $step);
};
if ($value.some((v) => !isValidValue(v))) {
value.update((prev) => {
return prev.map(gcv);
});
}
});
return {
elements: {
root,
thumbs,
range,
ticks,
},
states: {
value,
},
options,
};
};