UNPKG

@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
"use strict"; '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";