@kubit-ui-web/react-components
Version:
Kubit React Components is a customizable, accessible library of React web components, designed to enhance your application's user experience
228 lines • 9.45 kB
JavaScript
import { jsx as _jsx } from "react/jsx-runtime";
import React from 'react';
import { useStyles } from '../../hooks/useStyles/useStyles';
import { ErrorBoundary } from '../../provider/errorBoundary/errorBoundary';
import { FallbackComponent } from '../../provider/errorBoundary/fallbackComponent';
import { ROLES } from '../../types/role/role';
import { SliderStandAlone } from './sliderStandAlone';
import { SliderType } from './types/type';
import { calcNewValueAfterKeyPress, decrementValue, incrementValue, } from './utils/accessibility.utils';
import { calcDefaultValue, calcNewRangeValue, calcScaleValue, calcValueByOffset, calculateChange, equalsRangeValues, getOffset, getScale, } from './utils/slider.utils';
const ON_CHANGE_DEBOUNCE_TIMEOUT = 50;
const SLIDER_STYLES = 'SLIDER_STYLES';
export const SliderComponent = React.forwardRef(({ variant, type = SliderType.DISCRETE, range = false, min = 0, max = 100, step = 1, initialStepOffset, disabled = false, defaultValue, value: propValue, onChange, onDragStart, onDragEnd, tooltip, rightTooltip, leftHelperText, rightHelperText,
// label
label,
// Increment and decrement buttons
incrementButton, decrementButton,
// Thumb icons
thumbIcon, rightThumbIcon,
// Aria helpers
ariaLabel, rightAriaLabel, ariaLabelBy, rightAriaLabelBy, ctv, }, ref) => {
const _calcDefaultValue = _value => calcDefaultValue({ range, max, min, step, initialStepOffset, value: _value });
const containerRef = React.useRef(null);
const [value, setValue] = React.useState(_calcDefaultValue(propValue ?? defaultValue));
const [hover, setHover] = React.useState(false);
const [pressed, setPressed] = React.useState(false);
const activePointer = React.useRef('left');
const [offsetBoundaries, setOffsetBoundaries] = React.useState({
min: 0,
max: 100,
});
const styles = useStyles(SLIDER_STYLES, variant, ctv);
// thumbExceedsTrack by default is true
const thumbExceedsTrack = React.useMemo(() => styles?.thumbExceedsTrack ?? true, [styles?.thumbExceedsTrack]);
// When !thumbExceedsTrack, the ratio of the thumb to the track could change
// So the offset boundaries should be recalculated
// When thumbExceedsTrack is true, the offset boundaries are always 0 and 100
const initialiceBoundaries = React.useCallback(() => {
if (thumbExceedsTrack) {
setOffsetBoundaries({ min: 0, max: 100 });
return;
}
if (!containerRef.current) {
return;
}
// Select by ROLES.Slider
const thumb = containerRef.current.querySelector(`[role="${ROLES.SLIDER}"]`);
if (!thumb) {
return;
}
// Get container width
const containerWidth = containerRef.current.getBoundingClientRect().width;
// Get thumb width
const thumbWidth = thumb.getBoundingClientRect().width;
// Calc thumbWidth as percentage of the containerWidth
const thumbWidthPercentage = (thumbWidth / containerWidth) * 100.0;
// Calc min and max offset boundaries
const minOffset = thumbWidthPercentage / 2;
const maxOffset = 100 - minOffset;
setOffsetBoundaries({ min: minOffset, max: maxOffset });
}, [thumbExceedsTrack]);
React.useEffect(() => {
initialiceBoundaries();
// Only add resize event listener if thumbExceedsTrack is false
// When false, the ratio of the thumb to the track could change
if (!thumbExceedsTrack) {
window.addEventListener('resize', initialiceBoundaries);
}
return () => {
if (!thumbExceedsTrack) {
window.removeEventListener('resize', initialiceBoundaries);
}
};
}, [thumbExceedsTrack]);
// Update inner value when new propValue is received
React.useEffect(() => {
if (propValue !== undefined) {
if ((range &&
!equalsRangeValues({ values1: propValue, values2: value })) ||
(!range && propValue !== value)) {
setValue(_calcDefaultValue(propValue));
}
}
}, [propValue]);
// Debounce callback to avoid calling onChange too many times
const timeoutOnChange = React.useRef();
const onChangeDebounce = React.useCallback((val) => {
if (!onChange) {
return;
}
clearTimeout(timeoutOnChange.current);
timeoutOnChange.current = setTimeout(() => {
onChange(val);
}, ON_CHANGE_DEBOUNCE_TIMEOUT);
}, [onchange]);
// On unmount call unbindEventListeners
React.useEffect(() => {
return unbindEventListeners;
}, []);
const onMouseDown = event => {
onDragStart?.();
setPressed(true);
handleChange(event);
window.addEventListener('mousemove', handleChange);
window.addEventListener('mouseup', onMouseUp);
};
const onMouseUp = () => {
onDragEnd?.();
setPressed(false);
unbindEventListeners();
};
const onTouchStart = event => {
onDragStart?.();
setPressed(true);
handleChange(event);
window.addEventListener('touchend', onMouseUp);
};
const handleChange = (event) => {
if (!containerRef.current || disabled) {
return;
}
// Offset percentaje
const _offset = calculateChange({ event, container: containerRef.current, offsetBoundaries });
const _value = calcValueByOffset({
max,
min,
step,
offset: _offset,
offsetBoundaries: offsetBoundaries,
});
const newValue = calcScaleValue({ max, min, step, initialStepOffset, value: _value });
setValue(prevValue => {
let _newValue = newValue;
if (range) {
_newValue = calcNewRangeValue({
prevValue: prevValue,
newValue,
activePointer,
});
}
onChangeDebounce(_newValue);
return _newValue;
});
};
const unbindEventListeners = () => {
window.removeEventListener('mousemove', handleChange);
window.removeEventListener('mouseup', onMouseUp);
window.removeEventListener('touchend', onMouseUp);
};
const handleKeyPress = event => {
if (disabled) {
return;
}
const newValue = calcNewValueAfterKeyPress({
event,
range,
activePointer: activePointer.current,
max,
min,
step,
value,
});
if (newValue !== undefined) {
setValue(newValue);
onChangeDebounce(newValue);
}
};
const handleIncrementClick = event => {
const newValue = incrementValue({
activePointer: activePointer.current,
range,
max,
step,
value,
});
if (newValue !== undefined) {
setValue(newValue);
onChangeDebounce(newValue);
}
incrementButton?.onClick?.(event);
};
const handleDecrementClick = event => {
const newValue = decrementValue({
activePointer: activePointer.current,
range,
min,
step,
value,
});
if (newValue !== undefined) {
setValue(newValue);
onChangeDebounce(newValue);
}
decrementButton?.onClick?.(event);
};
const { showScale, scaleOffsets } = getScale({
type,
max,
min,
step,
initialStepOffset,
});
const { offset, offsetLeft, offsetRight } = getOffset({
range,
max,
min,
value,
offsetBoundaries,
});
return (_jsx(SliderStandAlone, { ref: ref, activePointer: activePointer, ariaLabel: ariaLabel, ariaLabelBy: ariaLabelBy, containerRef: containerRef, decrementButton: {
...decrementButton,
onClick: handleDecrementClick,
disabled: disabled || decrementButton?.disabled,
}, disabled: disabled, hover: hover, incrementButton: {
...incrementButton,
onClick: handleIncrementClick,
disabled: disabled || incrementButton?.disabled,
}, label: label, leftHelperText: leftHelperText, max: max, min: min, offset: offset, offsetLeft: offsetLeft, offsetRight: offsetRight, pressed: pressed, range: range, rightAriaLabel: rightAriaLabel, rightAriaLabelBy: rightAriaLabelBy, rightHelperText: rightHelperText, rightThumbIcon: rightThumbIcon, rightTooltip: rightTooltip, scaleOffsets: scaleOffsets, setHover: setHover, showScale: showScale, step: step, styles: styles, thumbIcon: thumbIcon, tooltip: tooltip, value: value, onChange: handleChange, onKeyPress: handleKeyPress, onMouseDown: onMouseDown, onTouchStart: onTouchStart }));
});
SliderComponent.displayName = 'SliderComponent';
const SliderBoundary = (props, ref) => (_jsx(ErrorBoundary, { fallBackComponent: _jsx(FallbackComponent, { children: _jsx(SliderStandAlone, { ...props, ref: ref }) }), children: _jsx(SliderComponent, { ...props, ref: ref }) }));
/**
* @description
* Slider component is used to select a value from a range of values.
*/
const Slider = React.forwardRef(SliderBoundary);
export { Slider };
//# sourceMappingURL=slider.js.map