@base-ui-components/react
Version:
Base UI is a library of headless ('unstyled') React components and low-level hooks. You gain complete control over your app's CSS and accessibility features.
280 lines (278 loc) • 10.7 kB
JavaScript
;
'use client';
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.SliderThumb = void 0;
var React = _interopRequireWildcard(require("react"));
var _useMergedRefs = require("@base-ui-components/utils/useMergedRefs");
var _useIsoLayoutEffect = require("@base-ui-components/utils/useIsoLayoutEffect");
var _reactVersion = require("@base-ui-components/utils/reactVersion");
var _visuallyHidden = require("@base-ui-components/utils/visuallyHidden");
var _formatNumber = require("../../utils/formatNumber");
var _getStyleHookProps = require("../../utils/getStyleHookProps");
var _mergeProps = require("../../merge-props");
var _resolveClassName = require("../../utils/resolveClassName");
var _useBaseUiId = require("../../utils/useBaseUiId");
var _composite = require("../../composite/composite");
var _useCompositeListItem = require("../../composite/list/useCompositeListItem");
var _DirectionContext = require("../../direction-provider/DirectionContext");
var _FieldRootContext = require("../../field/root/FieldRootContext");
var _getSliderValue = require("../utils/getSliderValue");
var _roundValueToStep = require("../utils/roundValueToStep");
var _valueArrayToPercentages = require("../utils/valueArrayToPercentages");
var _SliderRootContext = require("../root/SliderRootContext");
var _SliderThumbDataAttributes = require("./SliderThumbDataAttributes");
var _jsxRuntime = require("react/jsx-runtime");
const PAGE_UP = 'PageUp';
const PAGE_DOWN = 'PageDown';
const ALL_KEYS = new Set([_composite.ARROW_UP, _composite.ARROW_DOWN, _composite.ARROW_LEFT, _composite.ARROW_RIGHT, _composite.HOME, _composite.END, PAGE_UP, PAGE_DOWN]);
function defaultRender(props, inputProps) {
const {
children,
...thumbProps
} = props;
return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
...thumbProps,
children: [children, /*#__PURE__*/(0, _jsxRuntime.jsx)("input", {
...inputProps
})]
});
}
function getDefaultAriaValueText(values, index, format, locale) {
if (index < 0) {
return undefined;
}
if (values.length === 2) {
if (index === 0) {
return `${(0, _formatNumber.formatNumber)(values[index], locale, format)} start range`;
}
return `${(0, _formatNumber.formatNumber)(values[index], locale, format)} end range`;
}
return format ? (0, _formatNumber.formatNumber)(values[index], locale, format) : undefined;
}
function getNewValue(thumbValue, step, direction, min, max) {
return direction === 1 ? Math.min(thumbValue + step, max) : Math.max(thumbValue - step, min);
}
/**
* The draggable part of the the slider at the tip of the indicator.
* Renders a `<div>` element.
*
* Documentation: [Base UI Slider](https://base-ui.com/react/components/slider)
*/
const SliderThumb = exports.SliderThumb = /*#__PURE__*/React.forwardRef(function SliderThumb(componentProps, forwardedRef) {
const {
render: renderProp,
className,
disabled: disabledProp = false,
getAriaLabel: getAriaLabelProp,
getAriaValueText: getAriaValueTextProp,
id: idProp,
onBlur: onBlurProp,
onFocus: onFocusProp,
onKeyDown: onKeyDownProp,
tabIndex: tabIndexProp,
...elementProps
} = componentProps;
const id = (0, _useBaseUiId.useBaseUiId)(idProp);
const inputId = `${id}-input`;
const render = renderProp ?? defaultRender;
const {
active: activeIndex,
disabled: contextDisabled,
fieldControlValidation,
formatOptionsRef,
handleInputChange,
labelId,
largeStep,
locale,
max,
min,
minStepsBetweenValues,
orientation,
setActive,
state,
step,
tabIndex: contextTabIndex,
values: sliderValues
} = (0, _SliderRootContext.useSliderRootContext)();
let renderPropRef = null;
if (typeof render !== 'function') {
renderPropRef = (0, _reactVersion.isReactVersionAtLeast)(19) ? render.props.ref : render.ref;
}
const disabled = disabledProp || contextDisabled;
const externalTabIndex = tabIndexProp ?? contextTabIndex;
const direction = (0, _DirectionContext.useDirection)();
const {
controlId,
setControlId,
setTouched,
setFocused,
validationMode
} = (0, _FieldRootContext.useFieldRootContext)();
const thumbRef = React.useRef(null);
(0, _useIsoLayoutEffect.useIsoLayoutEffect)(() => {
setControlId(inputId);
return () => {
setControlId(undefined);
};
}, [controlId, inputId, setControlId]);
const thumbMetadata = React.useMemo(() => ({
inputId
}), [inputId]);
const {
ref: listItemRef,
index
} = (0, _useCompositeListItem.useCompositeListItem)({
metadata: thumbMetadata
});
const mergedThumbRef = (0, _useMergedRefs.useMergedRefs)(renderPropRef, forwardedRef, listItemRef, thumbRef);
const thumbValue = sliderValues[index];
const percentageValues = (0, _valueArrayToPercentages.valueArrayToPercentages)(sliderValues.slice(), min, max);
// for SSR, don't wait for the index if there's only one thumb
const percent = percentageValues.length === 1 ? percentageValues[0] : percentageValues[index];
const isRtl = direction === 'rtl';
const getThumbStyle = React.useCallback(() => {
const isVertical = orientation === 'vertical';
if (!Number.isFinite(percent)) {
return _visuallyHidden.visuallyHidden;
}
return {
position: 'absolute',
[{
horizontal: 'insetInlineStart',
vertical: 'bottom'
}[orientation]]: `${percent}%`,
[isVertical ? 'left' : 'top']: '50%',
transform: `translate(${(isVertical || !isRtl ? -1 : 1) * 50}%, ${(isVertical ? 1 : -1) * 50}%)`,
zIndex: activeIndex === index ? 1 : undefined
};
}, [activeIndex, isRtl, orientation, percent, index]);
const styleHooks = React.useMemo(() => (0, _getStyleHookProps.getStyleHookProps)({
disabled,
dragging: index !== -1 && activeIndex === index
}), [activeIndex, disabled, index]);
const thumbProps = (0, _mergeProps.mergeProps)({
[_SliderThumbDataAttributes.SliderThumbDataAttributes.index]: index,
className: (0, _resolveClassName.resolveClassName)(className, state),
id,
onFocus() {
setActive(index);
setFocused(true);
},
onBlur() {
if (!thumbRef.current) {
return;
}
setActive(-1);
setTouched(true);
setFocused(false);
if (validationMode === 'onBlur') {
fieldControlValidation.commitValidation((0, _getSliderValue.getSliderValue)(thumbValue, index, min, max, sliderValues.length > 1, sliderValues));
}
},
onKeyDown(event) {
if (!ALL_KEYS.has(event.key)) {
return;
}
if (_composite.COMPOSITE_KEYS.has(event.key)) {
event.stopPropagation();
}
let newValue = null;
const isRange = sliderValues.length > 1;
const roundedValue = (0, _roundValueToStep.roundValueToStep)(thumbValue, step, min);
switch (event.key) {
case _composite.ARROW_UP:
newValue = getNewValue(roundedValue, event.shiftKey ? largeStep : step, 1, min, max);
break;
case _composite.ARROW_RIGHT:
newValue = getNewValue(roundedValue, event.shiftKey ? largeStep : step, isRtl ? -1 : 1, min, max);
break;
case _composite.ARROW_DOWN:
newValue = getNewValue(roundedValue, event.shiftKey ? largeStep : step, -1, min, max);
break;
case _composite.ARROW_LEFT:
newValue = getNewValue(roundedValue, event.shiftKey ? largeStep : step, isRtl ? 1 : -1, min, max);
break;
case PAGE_UP:
newValue = getNewValue(roundedValue, largeStep, 1, min, max);
break;
case PAGE_DOWN:
newValue = getNewValue(roundedValue, largeStep, -1, min, max);
break;
case _composite.END:
newValue = max;
if (isRange) {
newValue = Number.isFinite(sliderValues[index + 1]) ? sliderValues[index + 1] - step * minStepsBetweenValues : max;
}
break;
case _composite.HOME:
newValue = min;
if (isRange) {
newValue = Number.isFinite(sliderValues[index - 1]) ? sliderValues[index - 1] + step * minStepsBetweenValues : min;
}
break;
default:
break;
}
if (newValue !== null) {
handleInputChange(newValue, index, event);
event.preventDefault();
}
},
ref: mergedThumbRef,
style: getThumbStyle(),
tabIndex: externalTabIndex ?? (disabled ? undefined : 0)
}, styleHooks, elementProps);
let cssWritingMode;
if (orientation === 'vertical') {
cssWritingMode = isRtl ? 'vertical-rl' : 'vertical-lr';
}
const inputProps = (0, _mergeProps.mergeProps)({
'aria-label': typeof getAriaLabelProp === 'function' ? getAriaLabelProp(index) : elementProps['aria-label'],
'aria-labelledby': labelId,
'aria-orientation': orientation,
'aria-valuemax': max,
'aria-valuemin': min,
'aria-valuenow': thumbValue,
'aria-valuetext': typeof getAriaValueTextProp === 'function' ? getAriaValueTextProp((0, _formatNumber.formatNumber)(thumbValue, locale, formatOptionsRef.current ?? undefined), thumbValue, index) : elementProps['aria-valuetext'] || getDefaultAriaValueText(sliderValues, index, formatOptionsRef.current ?? undefined, locale),
[_SliderThumbDataAttributes.SliderThumbDataAttributes.index]: index,
disabled,
id: inputId,
max,
min,
onChange(event) {
handleInputChange(event.target.valueAsNumber, index, event);
},
step,
style: {
..._visuallyHidden.visuallyHidden,
// So that VoiceOver's focus indicator matches the thumb's dimensions
width: '100%',
height: '100%',
writingMode: cssWritingMode
},
tabIndex: -1,
type: 'range',
value: thumbValue ?? ''
}, fieldControlValidation.getValidationProps);
if (typeof render === 'function') {
return render(thumbProps, inputProps, state);
}
const {
children: renderPropsChildren,
...otherRenderProps
} = render.props;
const children = thumbProps.children ?? renderPropsChildren;
return /*#__PURE__*/React.cloneElement(render, (0, _mergeProps.mergeProps)(thumbProps, {
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(React.Fragment, {
children: [typeof children === 'function' ? children() : children, /*#__PURE__*/(0, _jsxRuntime.jsx)("input", {
...inputProps
})]
})
}, otherRenderProps, {
ref: thumbProps.ref
}));
});
if (process.env.NODE_ENV !== "production") SliderThumb.displayName = "SliderThumb";