UNPKG

@carbon/react

Version:

React components for the Carbon Design System

1,274 lines (1,243 loc) 44.8 kB
/** * Copyright IBM Corp. 2016, 2023 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ import { defineProperty as _defineProperty, extends as _extends } from '../../_virtual/_rollupPluginBabelHelpers.js'; import React, { PureComponent, createRef } from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; import { ArrowDown, ArrowLeft, ArrowUp, ArrowRight, Enter } from '../../internal/keyboard/keys.js'; import { matches } from '../../internal/keyboard/match.js'; import { PrefixContext } from '../../internal/usePrefix.js'; import { deprecate } from '../../prop-types/deprecate.js'; import { FeatureFlagContext } from '../FeatureFlags/index.js'; import { WarningFilled, WarningAltFilled } from '@carbon/icons-react'; import '../Text/index.js'; import '../Tooltip/DefinitionTooltip.js'; import { Tooltip } from '../Tooltip/Tooltip.js'; import { LowerHandle, LowerHandleFocus, UpperHandle, UpperHandleFocus } from './SliderHandles.js'; import { clamp } from '../../internal/clamp.js'; import { throttle } from '../../node_modules/es-toolkit/dist/compat/function/throttle.js'; import { Text } from '../Text/Text.js'; const ThumbWrapper = ({ hasTooltip, className, style, children, ...rest }) => { if (hasTooltip) { return ( /*#__PURE__*/ // eslint-disable-next-line react/forbid-component-props React.createElement(Tooltip, _extends({ className: className, style: style }, rest), children) ); } else { return ( /*#__PURE__*/ // eslint-disable-next-line react/forbid-dom-props React.createElement("div", { className: className, style: style }, children) ); } }; const translationIds = { autoCorrectAnnouncement: 'carbon.slider.auto-correct-announcement' }; /** * Message ids that will be passed to translateWithId(). */ function translateWithId(translationId, translationState) { if (translationState?.correctedValue) { const { correctedValue } = translationState; return `The inputted value "${correctedValue}" was corrected to the nearest allowed digit.`; } return ''; } const defaultFormatLabel = (value, label) => { return `${value}${label ?? ''}`; }; // TODO: Assuming a 16ms throttle corresponds to 60 FPS, should it be halved, // since many systems can handle 120 FPS? If it doesn't correspond to 60 FPS, // what does it correspond to? /** * Minimum time between processed "drag" events in milliseconds. */ const EVENT_THROTTLE = 16; const DRAG_EVENT_TYPES = new Set(['mousemove', 'touchmove']); const DRAG_STOP_EVENT_TYPES = new Set(['mouseup', 'touchend', 'touchcancel']); var HandlePosition = /*#__PURE__*/function (HandlePosition) { HandlePosition["LOWER"] = "lower"; HandlePosition["UPPER"] = "upper"; return HandlePosition; }(HandlePosition || {}); class Slider extends PureComponent { constructor(props) { super(props); _defineProperty(this, "state", { value: this.props.value, valueUpper: this.props.unstable_valueUpper, left: 0, leftUpper: 0, needsOnRelease: false, isValid: true, isValidUpper: true, activeHandle: undefined, correctedValue: null, correctedPosition: null, isRtl: false }); _defineProperty(this, "thumbRef", void 0); _defineProperty(this, "thumbRefUpper", void 0); _defineProperty(this, "filledTrackRef", void 0); _defineProperty(this, "element", null); _defineProperty(this, "inputId", ''); _defineProperty(this, "track", void 0); _defineProperty(this, "handleDrag", event => { if (event instanceof globalThis.MouseEvent || event instanceof globalThis.TouchEvent) { this.onDrag(event); } }); /** * Sets up "drag" event handlers and calls `this.onDrag` in case dragging * started on somewhere other than the thumb without a corresponding "move" * event. */ _defineProperty(this, "onDragStart", evt => { // Do nothing if component is disabled if (this.props.disabled || this.props.readOnly) { return; } // We're going to force focus on one of the handles later on here, b/c we're // firing on a mousedown event, we need to call event.preventDefault() to // keep the focus from leaving the HTMLElement. // @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus#notes evt.preventDefault(); // Add drag stop handlers DRAG_STOP_EVENT_TYPES.forEach(element => { this.element?.ownerDocument.addEventListener(element, this.onDragStop); }); // Add drag handlers DRAG_EVENT_TYPES.forEach(element => { this.element?.ownerDocument.addEventListener(element, this.handleDrag); }); const clientX = this.getClientXFromEvent(evt.nativeEvent); let activeHandle; if (this.hasTwoHandles()) { if (evt.target == this.thumbRef.current) { activeHandle = HandlePosition.LOWER; } else if (evt.target == this.thumbRefUpper.current) { activeHandle = HandlePosition.UPPER; } else if (clientX) { const distanceToLower = this.calcDistanceToHandle(HandlePosition.LOWER, clientX); const distanceToUpper = this.calcDistanceToHandle(HandlePosition.UPPER, clientX); if (distanceToLower <= distanceToUpper) { activeHandle = HandlePosition.LOWER; } else { activeHandle = HandlePosition.UPPER; } } } // Force focus to the appropriate handle. const focusOptions = { preventScroll: true }; if (this.hasTwoHandles()) { if (this.thumbRef.current && activeHandle === HandlePosition.LOWER) { this.thumbRef.current.focus(focusOptions); } else if (this.thumbRefUpper.current && activeHandle === HandlePosition.UPPER) { this.thumbRefUpper.current.focus(focusOptions); } } else if (this.thumbRef.current) { this.thumbRef.current.focus(focusOptions); } this.setState({ activeHandle }); // Perform first recalculation since we probably didn't click exactly in the // middle of the thumb. this.onDrag(evt.nativeEvent, activeHandle); }); /** * Removes "drag" and "drag stop" event handlers and calls sets the flag * indicating that the `onRelease` callback should be called. */ _defineProperty(this, "onDragStop", () => { // Do nothing if component is disabled if (this.props.disabled || this.props.readOnly) { return; } // Remove drag stop handlers DRAG_STOP_EVENT_TYPES.forEach(element => { this.element?.ownerDocument.removeEventListener(element, this.onDragStop); }); // Remove drag handlers DRAG_EVENT_TYPES.forEach(element => { this.element?.ownerDocument.removeEventListener(element, this.handleDrag); }); // Set needsOnRelease flag so event fires on next update. this.setState({ needsOnRelease: true, isValid: true, isValidUpper: true }); }); /** * Handles a "drag" event by recalculating the value/thumb and setting state * accordingly. * * @param evt The event. * @param activeHandle The first drag event call, we may have an explicit * activeHandle value, which is to be used before state is used. */ _defineProperty(this, "_onDrag", (evt, activeHandle) => { activeHandle = activeHandle ?? this.state.activeHandle; // Do nothing if component is disabled, or we have no event. if (this.props.disabled || this.props.readOnly || !evt) { return; } const clientX = this.getClientXFromEvent(evt); const { value, left } = this.calcValue({ clientX, value: this.state.value }); // If we're set to two handles, negotiate which drag handle is closest to // the users' interaction. if (this.hasTwoHandles() && activeHandle) { this.setValueLeftForHandle(activeHandle, { value: this.nearestStepValue(value), left }); } else { this.setState({ value: this.nearestStepValue(value), left, isValid: true }); } this.setState({ correctedValue: null, correctedPosition: null }); }); /** * Throttles calls to `this._onDrag` by limiting events to being processed at * most once every `EVENT_THROTTLE` milliseconds. */ _defineProperty(this, "onDrag", throttle(this._onDrag, EVENT_THROTTLE, { leading: true, trailing: false })); /** * Handles a `keydown` event by recalculating the value/thumb and setting * state accordingly. */ _defineProperty(this, "onKeyDown", evt => { // Do nothing if component is disabled, or we don't have a valid event if (this.props.disabled || this.props.readOnly) { return; } const { step = 1, stepMultiplier = 4 } = this.props; let delta = 0; if (matches(evt, [ArrowDown, ArrowLeft])) { delta = -step; } else if (matches(evt, [ArrowUp, ArrowRight])) { delta = step; } else { // Ignore keys we don't want to handle return; } // If shift was held, account for the stepMultiplier if (evt.shiftKey) { delta *= stepMultiplier; } if (this.hasTwoHandles() && this.state.activeHandle) { const currentValue = this.state.activeHandle === HandlePosition.LOWER ? this.state.value : this.state.valueUpper; const { value, left } = this.calcValue({ value: this.calcValueForDelta(currentValue ?? this.props.min, delta, this.props.step) }); this.setValueLeftForHandle(this.state.activeHandle, { value: this.nearestStepValue(value), left }); } else { const { value, left } = this.calcValue({ // Ensures custom value from `<input>` won't cause skipping next stepping // point with right arrow key, e.g. Typing 51 in `<input>`, moving focus // onto the thumb and the hitting right arrow key should yield 52 instead // of 54. value: this.calcValueForDelta(this.state.value, delta, this.props.step) }); this.setState({ value: this.nearestStepValue(value), left, isValid: true }); } this.setState({ correctedValue: null, correctedPosition: null }); }); /** * Provides the two-way binding for the input field of the Slider. It also * Handles a change to the input field by recalculating the value/thumb and * setting state accordingly. */ _defineProperty(this, "onChange", evt => { // Do nothing if component is disabled if (this.props.disabled || this.props.readOnly) { return; } // Do nothing if we have no valid event, target, or value if (!evt || !('target' in evt) || typeof evt.target.value !== 'string') { return; } // Avoid calling calcValue for invalid numbers, but still update the state. const activeHandle = evt.target.dataset.handlePosition ?? HandlePosition.LOWER; const targetValue = Number.parseFloat(evt.target.value); if (this.hasTwoHandles()) { if (isNaN(targetValue)) { this.setValueForHandle(activeHandle, evt.target.value); } else if (this.isValidValueForPosition({ handle: activeHandle, value: targetValue, min: this.props.min, max: this.props.max })) { this.processNewInputValue(evt.target); } else { this.setValueForHandle(activeHandle, targetValue); } } else { if (isNaN(targetValue)) { this.setState({ value: evt.target.value }); } else if (this.isValidValue({ value: targetValue, min: this.props.min, max: this.props.max })) { this.processNewInputValue(evt.target); } else { this.setState({ value: targetValue }); } } }); /** * Checks for validity of input value after clicking out of the input. It also * Handles state change to isValid state. */ _defineProperty(this, "onBlur", evt => { // Do nothing if we have no valid event, target, or value if (!evt || !('target' in evt) || typeof evt.target.value !== 'string') { return; } const { value: targetValue } = evt.target; this.processNewInputValue(evt.target); this.props.onBlur?.({ value: targetValue, handlePosition: evt.target.dataset.handlePosition }); }); _defineProperty(this, "onInputKeyDown", evt => { // Do nothing if component is disabled, or we don't have a valid event. if (this.props.disabled || this.props.readOnly || !(evt.target instanceof HTMLInputElement)) { return; } // Do nothing if we have no valid event, target, or value. if (!evt || !('target' in evt) || typeof evt.target.value !== 'string') { return; } if (matches(evt, [Enter])) { this.processNewInputValue(evt.target); } }); _defineProperty(this, "processNewInputValue", input => { this.setState({ correctedValue: null, correctedPosition: null }); const targetValue = Number.parseFloat(input.value); const validity = !isNaN(targetValue); // When there are two handles, we'll also have the data-handle-position // attribute to consider the other value before settling on the validity to // set. const handlePosition = input.dataset.handlePosition; if (handlePosition === HandlePosition.LOWER) { this.setState({ isValid: validity }); } else if (handlePosition === HandlePosition.UPPER) { this.setState({ isValidUpper: validity }); } this.setState({ isValid: validity }); if (validity) { const adjustedValue = handlePosition ? this.getAdjustedValueForPosition({ handle: handlePosition, value: targetValue, min: this.props.min, max: this.props.max }) : this.getAdjustedValue({ value: targetValue, min: this.props.min, max: this.props.max }); if (adjustedValue !== targetValue) { this.setState({ correctedValue: targetValue.toString(), correctedPosition: handlePosition }); } else { this.setState({ correctedValue: null, correctedPosition: null }); } const { value, left } = this.calcValue({ value: adjustedValue, useRawValue: true }); if (handlePosition) { this.setValueLeftForHandle(handlePosition, { value: this.nearestStepValue(value), left }); } else { this.setState({ value, left }); } } }); _defineProperty(this, "calcLeftPercent", ({ clientX, value, range }) => { const boundingRect = this.element?.getBoundingClientRect?.(); let width = boundingRect ? boundingRect.right - boundingRect.left : 0; // Enforce a minimum width of at least 1 for calculations if (width <= 0) { width = 1; } // If a clientX is specified, use it to calculate the leftPercent. If not, // use the provided value to calculate it instead. if (clientX) { const leftOffset = this.state.isRtl ? (boundingRect?.right ?? 0) - clientX : clientX - (boundingRect?.left ?? 0); return leftOffset / width; } else if (value !== null && typeof value !== 'undefined' && range) { // Prevent NaN calculation if the range is 0. return range === 0 ? 0 : (value - this.props.min) / range; } // We should never end up in this scenario, but in case we do, and to // re-assure Typescript, return 0. return 0; }); /** * Calculates the discrete value (snapped to the nearest step) along * with the corresponding handle position percentage. */ _defineProperty(this, "calcDiscreteValueAndPercent", ({ leftPercent }) => { const { step = 1, min, max } = this.props; const numSteps = Math.floor((max - min) / step) + ((max - min) % step === 0 ? 1 : 2); /** Index of the step that corresponds to `leftPercent`. */ const stepIndex = Math.round(leftPercent * (numSteps - 1)); const discreteValue = stepIndex === numSteps - 1 ? max : min + step * stepIndex; /** Percentage corresponding to the step index. */ const discretePercent = stepIndex / (numSteps - 1); return { discreteValue, discretePercent }; }); /** * Calculates the slider's value and handle position based on either a * mouse/touch event or an explicit value. */ _defineProperty(this, "calcValue", ({ clientX, value, useRawValue }) => { const range = this.props.max - this.props.min; const leftPercentRaw = this.calcLeftPercent({ clientX, value, range }); /** `leftPercentRaw` clamped between 0 and 1. */ const leftPercent = clamp(leftPercentRaw, 0, 1); if (useRawValue) { return { value, left: leftPercent * 100 }; } // Use the discrete value and percentage for snapping. const { discreteValue, discretePercent } = this.calcDiscreteValueAndPercent({ leftPercent }); return { value: discreteValue, left: discretePercent * 100 }; }); _defineProperty(this, "calcDistanceToHandle", (handle, clientX) => { const handleBoundingRect = this.getHandleBoundingRect(handle); // x co-ordinate of the midpoint. const handleX = handleBoundingRect.left + handleBoundingRect.width / 2; return Math.abs(handleX - clientX); }); /** * Calculates a new slider value based on the current value, a change delta, * and a step. * * @param currentValue - The starting value from which the slider is moving. * @param delta - The amount to adjust the current value by. * @param step - The step. Defaults to `1`. * @returns The new slider value, rounded to the same number of decimal places * as the step. */ _defineProperty(this, "calcValueForDelta", (currentValue, delta, step = 1) => { const base = delta > 0 ? Math.floor(currentValue / step) * step : currentValue; const newValue = base + delta; const decimals = (step.toString().split('.')[1] || '').length; return Number(newValue.toFixed(decimals)); }); /** * Sets state relevant to the given handle position. * * Guards against setting either lower or upper values beyond its counterpart. */ _defineProperty(this, "setValueLeftForHandle", (handle, { value: newValue, left: newLeft }) => { const { value, valueUpper, left, leftUpper } = this.state; if (handle === HandlePosition.LOWER) { // Don't allow higher than the upper handle. this.setState({ value: valueUpper && newValue > valueUpper ? valueUpper : newValue, left: valueUpper && newValue > valueUpper ? leftUpper : newLeft, isValid: true }); } else { this.setState({ valueUpper: value && newValue < value ? value : newValue, leftUpper: value && newValue < value ? left : newLeft, isValidUpper: true }); } }); _defineProperty(this, "setValueForHandle", (handle, value) => { if (handle === HandlePosition.LOWER) { this.setState({ value, isValid: true }); } else { this.setState({ valueUpper: value, isValidUpper: true }); } }); _defineProperty(this, "isValidValueForPosition", ({ handle, value: newValue, min, max }) => { const { value, valueUpper } = this.state; if (!this.isValidValue({ value: newValue, min, max })) { return false; } if (handle === HandlePosition.LOWER) { return !valueUpper || newValue <= valueUpper; } else if (handle === HandlePosition.UPPER) { return !value || newValue >= value; } return false; }); _defineProperty(this, "isValidValue", ({ value, min, max }) => { return !(value < min || value > max); }); _defineProperty(this, "getAdjustedValueForPosition", ({ handle, value: newValue, min, max }) => { const { value, valueUpper } = this.state; newValue = this.getAdjustedValue({ value: newValue, min, max }); // Next adjust to the opposite handle. if (handle === HandlePosition.LOWER && valueUpper) { newValue = newValue > valueUpper ? valueUpper : newValue; } else if (handle === HandlePosition.UPPER && value) { newValue = newValue < value ? value : newValue; } return newValue; }); _defineProperty(this, "getAdjustedValue", ({ value, min, max }) => { if (value < min) { value = min; } if (value > max) { value = max; } return value; }); /** * Get the bounding rect for the requested handles' DOM element. * * If the bounding rect is not available, a new, empty DOMRect is returned. */ _defineProperty(this, "getHandleBoundingRect", handle => { let boundingRect; if (handle === HandlePosition.LOWER) { boundingRect = this.thumbRef.current?.getBoundingClientRect(); } else { boundingRect = this.thumbRefUpper.current?.getBoundingClientRect(); } return boundingRect ?? new DOMRect(); }); this.thumbRef = /*#__PURE__*/createRef(); this.thumbRefUpper = /*#__PURE__*/createRef(); this.filledTrackRef = /*#__PURE__*/createRef(); } /** * Sets up initial slider position and value in response to component mount. */ componentDidMount() { if (this.element) { const isRtl = document?.dir === 'rtl'; if (this.hasTwoHandles()) { const { value, left } = this.calcValue({ value: this.state.value, useRawValue: true }); const { value: valueUpper, left: leftUpper } = this.calcValue({ value: this.state.valueUpper, useRawValue: true }); this.setState({ isRtl, value, left, valueUpper, leftUpper }); if (this.filledTrackRef.current) { this.filledTrackRef.current.style.transform = this.state.isRtl ? `translate(${100 - this.state.leftUpper}%, -50%) scaleX(${(this.state.leftUpper - this.state.left) / 100})` : `translate(${this.state.left}%, -50%) scaleX(${(this.state.leftUpper - this.state.left) / 100})`; } } else { const { value, left } = this.calcValue({ value: this.state.value, useRawValue: true }); this.setState({ isRtl, value, left }); if (this.filledTrackRef.current) { this.filledTrackRef.current.style.transform = this.state.isRtl ? `translate(100%, -50%) scaleX(-${this.state.left / 100})` : `translate(0%, -50%) scaleX(${this.state.left / 100})`; } } } } /** * Handles firing of `onChange` and `onRelease` callbacks to parent in * response to state changes. * * @param {*} prevProps prevProps * @param {*} prevState The previous Slider state, used to see if callbacks * should be called. */ componentDidUpdate(prevProps, prevState) { // Fire onChange event handler if present, if there's a usable value, and // if the value is different from the last one if (this.hasTwoHandles()) { if (this.filledTrackRef.current) { this.filledTrackRef.current.style.transform = this.state.isRtl ? `translate(${100 - this.state.leftUpper}%, -50%) scaleX(${(this.state.leftUpper - this.state.left) / 100})` : `translate(${this.state.left}%, -50%) scaleX(${(this.state.leftUpper - this.state.left) / 100})`; } } else { if (this.filledTrackRef.current) { this.filledTrackRef.current.style.transform = this.state.isRtl ? `translate(100%, -50%) scaleX(-${this.state.left / 100})` : `translate(0%, -50%) scaleX(${this.state.left / 100})`; } } if ((prevState.value !== this.state.value || prevState.valueUpper !== this.state.valueUpper) && typeof this.props.onChange === 'function') { this.props.onChange({ value: this.state.value, valueUpper: this.state.valueUpper }); } // Fire onRelease event handler if present and if needed if (this.state.needsOnRelease && typeof this.props.onRelease === 'function') { this.props.onRelease({ value: this.state.value, valueUpper: this.state.valueUpper }); // Reset the flag this.setState({ needsOnRelease: false }); } // If value from props does not change, do nothing here. // Otherwise, do prop -> state sync without "value capping". if (prevProps.value === this.props.value && prevProps.unstable_valueUpper === this.props.unstable_valueUpper && prevProps.max === this.props.max && prevProps.min === this.props.min) { return; } this.setState(this.calcValue({ value: this.props.value, useRawValue: true })); if (typeof this.props.unstable_valueUpper !== 'undefined') { const { value: valueUpper, left: leftUpper } = this.calcValue({ value: this.props.unstable_valueUpper, useRawValue: true }); this.setState({ valueUpper, leftUpper }); } else { this.setState({ valueUpper: undefined, leftUpper: undefined }); } } /** * Rounds a given value to the nearest step defined by the slider's `step` * prop. * * @param value - The value to adjust to the nearest step. Defaults to `0`. * @returns The value rounded to the precision determined by the step. */ nearestStepValue(value = 0) { const decimals = (this.props.step?.toString().split('.')[1] || '').length; return Number(value.toFixed(decimals)); } getClientXFromEvent(event) { let clientX; if ('clientX' in event) { clientX = event.clientX; } else if ('touches' in event && 0 in event.touches && 'clientX' in event.touches[0]) { clientX = event.touches[0].clientX; } return clientX; } hasTwoHandles() { return typeof this.state.valueUpper !== 'undefined'; } // syncs invalid state and prop static getDerivedStateFromProps(props, state) { const { isValid, isValidUpper } = state; const derivedState = {}; // Will override state in favor of invalid prop if (props.invalid === true) { if (isValid === true) derivedState.isValid = false; if (isValidUpper === true) derivedState.isValidUpper = false; } else if (props.invalid === false) { if (isValid === false) derivedState.isValid = true; if (isValidUpper === false) derivedState.isValidUpper = true; } return Object.keys(derivedState).length ? derivedState : null; } render() { var _Fragment, _Fragment2, _Fragment3, _Fragment4; const { ariaLabelInput, unstable_ariaLabelInputUpper: ariaLabelInputUpper, className, hideTextInput = false, id = this.inputId = this.inputId || // TODO: // 1. Why isn't `inputId` just set to this value instead of an empty // string? // 2. Why this value instead of something else, like // `crypto.randomUUID()` or `useId()`? `__carbon-slider_${Math.random().toString(36).substr(2)}`, min, minLabel, max, maxLabel, formatLabel = defaultFormatLabel, labelText, hideLabel, step = 1, // TODO: Other properties are deleted below. Why isn't this one? stepMultiplier: _stepMultiplier, inputType = 'number', invalidText, required, disabled = false, name, unstable_nameUpper: nameUpper, light, readOnly = false, warn, warnText, translateWithId: t = translateWithId, ...other } = this.props; const twoHandles = this.hasTwoHandles(); delete other.onRelease; delete other.invalid; delete other.unstable_valueUpper; const { value, valueUpper, isValid, isValidUpper, correctedValue, correctedPosition, isRtl } = this.state; const showWarning = !readOnly && warn || // TODO: https://github.com/carbon-design-system/carbon/issues/18991#issuecomment-2795709637 typeof correctedValue !== null && correctedPosition === HandlePosition.LOWER && isValid; const showWarningUpper = !readOnly && warn || // TODO: https://github.com/carbon-design-system/carbon/issues/18991#issuecomment-2795709637 typeof correctedValue !== null && correctedPosition === (twoHandles ? HandlePosition.UPPER : HandlePosition.LOWER) && (twoHandles ? isValidUpper : isValid); return /*#__PURE__*/React.createElement(PrefixContext.Consumer, null, prefix => { const labelId = `${id}-label`; const labelClasses = cx(`${prefix}--label`, { [`${prefix}--visually-hidden`]: hideLabel, [`${prefix}--label--disabled`]: disabled }); const containerClasses = cx(`${prefix}--slider-container`, { [`${prefix}--slider-container--two-handles`]: twoHandles, [`${prefix}--slider-container--disabled`]: disabled, [`${prefix}--slider-container--readonly`]: readOnly, [`${prefix}--slider-container--rtl`]: isRtl }); const sliderClasses = cx(`${prefix}--slider`, { [`${prefix}--slider--disabled`]: disabled, [`${prefix}--slider--readonly`]: readOnly }); const fixedInputClasses = [`${prefix}--text-input`, `${prefix}--slider-text-input`]; const conditionalInputClasses = { [`${prefix}--text-input--light`]: light }; const lowerInputClasses = cx([...fixedInputClasses, `${prefix}--slider-text-input--lower`, conditionalInputClasses, { [`${prefix}--text-input--invalid`]: !readOnly && !isValid, [`${prefix}--slider-text-input--warn`]: showWarning }]); const upperInputClasses = cx([...fixedInputClasses, `${prefix}--slider-text-input--upper`, conditionalInputClasses, { [`${prefix}--text-input--invalid`]: !readOnly && (twoHandles ? !isValidUpper : !isValid), [`${prefix}--slider-text-input--warn`]: showWarningUpper }]); const lowerInputWrapperClasses = cx([`${prefix}--text-input-wrapper`, `${prefix}--slider-text-input-wrapper`, `${prefix}--slider-text-input-wrapper--lower`, { [`${prefix}--text-input-wrapper--readonly`]: readOnly, [`${prefix}--slider-text-input-wrapper--hidden`]: hideTextInput }]); const upperInputWrapperClasses = cx([`${prefix}--text-input-wrapper`, `${prefix}--slider-text-input-wrapper`, `${prefix}--slider-text-input-wrapper--upper`, { [`${prefix}--text-input-wrapper--readonly`]: readOnly, [`${prefix}--slider-text-input-wrapper--hidden`]: hideTextInput }]); const lowerThumbClasses = cx(`${prefix}--slider__thumb`, { [`${prefix}--slider__thumb--lower`]: twoHandles }); const upperThumbClasses = cx(`${prefix}--slider__thumb`, { [`${prefix}--slider__thumb--upper`]: twoHandles }); const lowerThumbWrapperClasses = cx([`${prefix}--icon-tooltip`, `${prefix}--slider__thumb-wrapper`, { [`${prefix}--slider__thumb-wrapper--lower`]: twoHandles }]); const upperThumbWrapperClasses = cx([`${prefix}--icon-tooltip`, `${prefix}--slider__thumb-wrapper`, { [`${prefix}--slider__thumb-wrapper--upper`]: twoHandles }]); const lowerThumbWrapperProps = { style: { insetInlineStart: `${this.state.left}%` } }; const upperThumbWrapperProps = { style: { insetInlineStart: `${this.state.leftUpper}%` } }; return /*#__PURE__*/React.createElement("div", { className: cx(`${prefix}--form-item`, className) }, /*#__PURE__*/React.createElement(Text, { as: "label", htmlFor: twoHandles ? undefined : id, className: labelClasses, id: labelId }, labelText), /*#__PURE__*/React.createElement("div", { className: containerClasses }, twoHandles ? /*#__PURE__*/React.createElement("div", { className: lowerInputWrapperClasses }, /*#__PURE__*/React.createElement("input", { type: hideTextInput ? 'hidden' : inputType, id: `${id}-lower-input-for-slider`, name: name, className: lowerInputClasses, value: value, "aria-label": ariaLabelInput, disabled: disabled, required: required, min: min, max: max, step: step, onChange: this.onChange, onBlur: this.onBlur, onKeyUp: this.props.onInputKeyUp, onKeyDown: this.onInputKeyDown, "data-invalid": !isValid && !readOnly ? true : null, "data-handle-position": HandlePosition.LOWER, "aria-invalid": !isValid && !readOnly ? true : undefined, readOnly: readOnly }), !readOnly && !isValid && /*#__PURE__*/React.createElement(WarningFilled, { className: `${prefix}--slider__invalid-icon` }), showWarning && /*#__PURE__*/React.createElement(WarningAltFilled, { className: `${prefix}--slider__invalid-icon ${prefix}--slider__invalid-icon--warning` })) : null, /*#__PURE__*/React.createElement(Text, { className: `${prefix}--slider__range-label` }, formatLabel(min, minLabel)), /*#__PURE__*/React.createElement("div", _extends({ className: sliderClasses, ref: node => { this.element = node; }, onMouseDown: this.onDragStart, onTouchStart: this.onDragStart, onKeyDown: this.onKeyDown, role: "presentation", tabIndex: -1, "data-invalid": (twoHandles ? !isValid || !isValidUpper : !isValid) && !readOnly ? true : null }, other), /*#__PURE__*/React.createElement(ThumbWrapper, _extends({ hasTooltip: hideTextInput, className: lowerThumbWrapperClasses, label: formatLabel(value, undefined), align: "top" }, lowerThumbWrapperProps), /*#__PURE__*/React.createElement("div", { className: lowerThumbClasses, role: "slider", id: twoHandles ? undefined : id, tabIndex: readOnly || disabled ? undefined : 0, "aria-valuetext": formatLabel(value, undefined), "aria-valuemax": twoHandles ? valueUpper : max, "aria-valuemin": min, "aria-valuenow": value, "aria-labelledby": twoHandles ? undefined : labelId, "aria-label": twoHandles ? ariaLabelInput : undefined, ref: this.thumbRef, onFocus: () => this.setState({ activeHandle: HandlePosition.LOWER }) }, twoHandles && !isRtl ? _Fragment || (_Fragment = /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(LowerHandle, { "aria-label": ariaLabelInput }), /*#__PURE__*/React.createElement(LowerHandleFocus, { "aria-label": ariaLabelInput }))) : twoHandles && isRtl ? _Fragment2 || (_Fragment2 = /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(UpperHandle, { "aria-label": ariaLabelInputUpper }), /*#__PURE__*/React.createElement(UpperHandleFocus, { "aria-label": ariaLabelInputUpper }))) : undefined)), twoHandles ? /*#__PURE__*/React.createElement(ThumbWrapper, _extends({ hasTooltip: hideTextInput, className: upperThumbWrapperClasses, label: formatLabel(valueUpper ?? 0, undefined), align: "top" }, upperThumbWrapperProps), /*#__PURE__*/React.createElement("div", { className: upperThumbClasses, role: "slider", tabIndex: readOnly || disabled ? undefined : 0, "aria-valuemax": max, "aria-valuemin": value, "aria-valuenow": valueUpper, "aria-label": ariaLabelInputUpper, ref: this.thumbRefUpper, onFocus: () => this.setState({ activeHandle: HandlePosition.UPPER }) }, twoHandles && !isRtl ? _Fragment3 || (_Fragment3 = /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(UpperHandle, { "aria-label": ariaLabelInputUpper }), /*#__PURE__*/React.createElement(UpperHandleFocus, { "aria-label": ariaLabelInputUpper }))) : twoHandles && isRtl ? _Fragment4 || (_Fragment4 = /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(LowerHandle, { "aria-label": ariaLabelInput }), /*#__PURE__*/React.createElement(LowerHandleFocus, { "aria-label": ariaLabelInput }))) : undefined)) : null, /*#__PURE__*/React.createElement("div", { className: `${prefix}--slider__track`, ref: node => { this.track = node; } }), /*#__PURE__*/React.createElement("div", { className: `${prefix}--slider__filled-track`, ref: this.filledTrackRef })), /*#__PURE__*/React.createElement(Text, { className: `${prefix}--slider__range-label` }, formatLabel(max, maxLabel)), /*#__PURE__*/React.createElement("div", { className: upperInputWrapperClasses }, /*#__PURE__*/React.createElement("input", { type: hideTextInput ? 'hidden' : inputType, id: `${id}-${twoHandles ? 'upper-' : ''}input-for-slider`, name: twoHandles ? nameUpper : name, className: upperInputClasses, value: twoHandles ? valueUpper : value, "aria-labelledby": !ariaLabelInput && !twoHandles ? labelId : undefined, "aria-label": twoHandles ? ariaLabelInputUpper : ariaLabelInput ? ariaLabelInput : undefined, disabled: disabled, required: required, min: min, max: max, step: step, onChange: this.onChange, onBlur: this.onBlur, onKeyDown: this.onInputKeyDown, onKeyUp: this.props.onInputKeyUp, "data-invalid": (twoHandles ? !isValidUpper : !isValid) && !readOnly ? true : null, "data-handle-position": twoHandles ? HandlePosition.UPPER : null, "aria-invalid": (twoHandles ? !isValidUpper : !isValid) && !readOnly ? true : undefined, readOnly: readOnly }), !readOnly && (twoHandles ? !isValidUpper : !isValid) && /*#__PURE__*/React.createElement(WarningFilled, { className: `${prefix}--slider__invalid-icon` }), showWarningUpper && /*#__PURE__*/React.createElement(WarningAltFilled, { className: `${prefix}--slider__invalid-icon ${prefix}--slider__invalid-icon--warning` }))), !readOnly && (!isValid || !isValidUpper) && /*#__PURE__*/React.createElement(Text, { as: "div", className: cx(`${prefix}--slider__validation-msg`, `${prefix}--slider__validation-msg--invalid`, `${prefix}--form-requirement`) }, invalidText), !readOnly && warn && isValid && isValidUpper && /*#__PURE__*/React.createElement(Text, { as: "div", className: cx(`${prefix}--slider__validation-msg`, `${prefix}--form-requirement`) }, warnText), correctedValue && /*#__PURE__*/React.createElement(Text, { as: "div", role: "alert", className: cx(`${prefix}--slider__status-msg`, `${prefix}--form-requirement`) }, t(translationIds.autoCorrectAnnouncement, { correctedValue }))); }); } } _defineProperty(Slider, "contextType", FeatureFlagContext); _defineProperty(Slider, "translationIds", Object.values(translationIds)); Slider.propTypes = { /** * The `ariaLabel` for the `<input>`. */ ariaLabelInput: PropTypes.string, /** * The child nodes. */ children: PropTypes.node, /** * The CSS class name for the slider. */ className: PropTypes.string, /** * `true` to disable this slider. */ disabled: PropTypes.bool, /** * The callback to format the label associated with the minimum/maximum value. */ formatLabel: PropTypes.func, /** * `true` to hide the number input box. */ hideTextInput: PropTypes.bool, /** * The ID of the `<input>`. */ id: PropTypes.string, /** * The `type` attribute of the `<input>`. */ inputType: PropTypes.string, /** * `Specify whether the Slider is currently invalid */ invalid: PropTypes.bool, /** * Provide the text that is displayed when the Slider is in an invalid state */ invalidText: PropTypes.node, /** * The label for the slider. */ labelText: PropTypes.node, /** * Specify whether you want the underlying label to be visually hidden */ hideLabel: PropTypes.bool, /** * `true` to use the light version. */ light: deprecate(PropTypes.bool, 'The `light` prop for `Slider` is no longer needed and has ' + 'been deprecated in v11 in favor of the new `Layer` component. It will be moved in the next major release.'), /** * The maximum value. */ max: PropTypes.number.isRequired, /** * The label associated with the maximum value. */ maxLabel: PropTypes.string, /** * The minimum value. */ min: PropTypes.number.isRequired, /** * The label associated with the minimum value. */ minLabel: PropTypes.string, /** * The `name` attribute of the `<input>`. */ name: PropTypes.string, /** * Provide an optional function to be called when the input element * loses focus */ onBlur: PropTypes.func, /** * The callback to get notified of change in value. */ onChange: PropTypes.func, /** * Provide an optional function to be called when a key is pressed in the number input. */ onInputKeyUp: PropTypes.func, /** * The callback to get notified of value on handle release. */ onRelease: PropTypes.func, /** * Whether the slider should be read-only */ readOnly: PropTypes.bool, /** * `true` to specify if the control is required. */ required: PropTypes.bool, /** * A value determining how much the value should increase/decrease by moving the thumb by mouse. If a value other than 1 is provided and the input is *not* hidden, the new step requirement should be added to a visible label. Values outside the `step` increment will be considered invalid. */ step: PropTypes.number, /** * A value determining how much the value should increase/decrease by Shift+arrow keys, * which will be `(max - min) / stepMultiplier`. */ stepMultiplier: PropTypes.number, /** * Supply a method to translate internal strings with your i18n tool of * choice. Translation keys are available on the `translationIds` field for * this component. */ translateWithId: PropTypes.func, /** * The `ariaLabel` for the upper bound `<input>` when there are two handles. */ unstable_ariaLabelInputUpper: PropTypes.string, /** * The `name` attribute of the upper bound `<input>` when there are two handles. */ unstable_nameUpper: PropTypes.string, /** * The upper bound when there are two handles. */ unstable_valueUpper: PropTypes.number, /** * The value of the slider. When there are two handles, value is the lower * bound. */ value: PropTypes.number.isRequired, /** * `Specify whether the Slider is in a warn state */ warn: PropTypes.bool, /** * Provide the text that is displayed when the Slider is in a warn state */ warnText: PropTypes.node }; export { Slider as default };