@wordpress/components
Version:
UI components for WordPress.
343 lines (334 loc) • 12.5 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = exports.RangeControl = void 0;
var _clsx = _interopRequireDefault(require("clsx"));
var _i18n = require("@wordpress/i18n");
var _element = require("@wordpress/element");
var _compose = require("@wordpress/compose");
var _baseControl = _interopRequireDefault(require("../base-control"));
var _button = _interopRequireDefault(require("../button"));
var _icon = _interopRequireDefault(require("../icon"));
var _utils = require("../utils");
var _utils2 = require("./utils");
var _math = require("../utils/math");
var _inputRange = _interopRequireDefault(require("./input-range"));
var _rail = _interopRequireDefault(require("./rail"));
var _tooltip = _interopRequireDefault(require("./tooltip"));
var _rangeControlStyles = require("./styles/range-control-styles");
var _space = require("../utils/space");
var _deprecated36pxSize = require("../utils/deprecated-36px-size");
var _jsxRuntime = require("react/jsx-runtime");
/**
* External dependencies
*/
/**
* WordPress dependencies
*/
/**
* Internal dependencies
*/
const noop = () => {};
/**
* Computes the value that `RangeControl` should reset to when pressing
* the reset button.
*/
function computeResetValue({
resetFallbackValue,
initialPosition
}) {
if (resetFallbackValue !== undefined) {
return !Number.isNaN(resetFallbackValue) ? resetFallbackValue : null;
}
if (initialPosition !== undefined) {
return !Number.isNaN(initialPosition) ? initialPosition : null;
}
return null;
}
function UnforwardedRangeControl(props, forwardedRef) {
const {
__nextHasNoMarginBottom = false,
afterIcon,
allowReset = false,
beforeIcon,
className,
color: colorProp = _utils.COLORS.theme.accent,
currentInput,
disabled = false,
help,
hideLabelFromVision = false,
initialPosition,
isShiftStepEnabled = true,
label,
marks = false,
max = 100,
min = 0,
onBlur = noop,
onChange = noop,
onFocus = noop,
onMouseLeave = noop,
onMouseMove = noop,
railColor,
renderTooltipContent = v => v,
resetFallbackValue,
__next40pxDefaultSize = false,
shiftStep = 10,
showTooltip: showTooltipProp,
step = 1,
trackColor,
value: valueProp,
withInputField = true,
__shouldNotWarnDeprecated36pxSize,
...otherProps
} = props;
const [value, setValue] = (0, _utils2.useControlledRangeValue)({
min,
max,
value: valueProp !== null && valueProp !== void 0 ? valueProp : null,
initial: initialPosition
});
const isResetPendent = (0, _element.useRef)(false);
let hasTooltip = showTooltipProp;
let hasInputField = withInputField;
if (step === 'any') {
// The tooltip and number input field are hidden when the step is "any"
// because the decimals get too lengthy to fit well.
hasTooltip = false;
hasInputField = false;
}
const [showTooltip, setShowTooltip] = (0, _element.useState)(hasTooltip);
const [isFocused, setIsFocused] = (0, _element.useState)(false);
const inputRef = (0, _element.useRef)();
const isCurrentlyFocused = inputRef.current?.matches(':focus');
const isThumbFocused = !disabled && isFocused;
const isValueReset = value === null;
const currentValue = value !== undefined ? value : currentInput;
const inputSliderValue = isValueReset ? '' : currentValue;
const rangeFillValue = isValueReset ? (max - min) / 2 + min : value;
const fillValue = isValueReset ? 50 : (value - min) / (max - min) * 100;
const fillValueOffset = `${(0, _math.clamp)(fillValue, 0, 100)}%`;
const classes = (0, _clsx.default)('components-range-control', className);
const wrapperClasses = (0, _clsx.default)('components-range-control__wrapper', !!marks && 'is-marked');
const id = (0, _compose.useInstanceId)(UnforwardedRangeControl, 'inspector-range-control');
const describedBy = !!help ? `${id}__help` : undefined;
const enableTooltip = hasTooltip !== false && Number.isFinite(value);
const handleOnRangeChange = event => {
const nextValue = parseFloat(event.target.value);
setValue(nextValue);
onChange(nextValue);
};
const handleOnChange = next => {
// @ts-expect-error TODO: Investigate if it's problematic for setValue() to
// potentially receive a NaN when next is undefined.
let nextValue = parseFloat(next);
setValue(nextValue);
/*
* Calls onChange only when nextValue is numeric
* otherwise may queue a reset for the blur event.
*/
if (!isNaN(nextValue)) {
if (nextValue < min || nextValue > max) {
nextValue = (0, _utils2.floatClamp)(nextValue, min, max);
}
onChange(nextValue);
isResetPendent.current = false;
} else if (allowReset) {
isResetPendent.current = true;
}
};
const handleOnInputNumberBlur = () => {
if (isResetPendent.current) {
handleOnReset();
isResetPendent.current = false;
}
};
const handleOnReset = () => {
// Reset to `resetFallbackValue` if defined, otherwise set internal value
// to `null` — which, if propagated to the `value` prop, will cause
// the value to be reset to the `initialPosition` prop if defined.
const resetValue = Number.isNaN(resetFallbackValue) ? null : resetFallbackValue !== null && resetFallbackValue !== void 0 ? resetFallbackValue : null;
setValue(resetValue);
/**
* Previously, this callback would always receive undefined as
* an argument. This behavior is unexpected, specifically
* when resetFallbackValue is defined.
*
* The value of undefined is not ideal. Passing it through
* to internal <input /> elements would change it from a
* controlled component to an uncontrolled component.
*
* For now, to minimize unexpected regressions, we're going to
* preserve the undefined callback argument, except when a
* resetFallbackValue is defined.
*/
onChange(resetValue !== null && resetValue !== void 0 ? resetValue : undefined);
};
const handleShowTooltip = () => setShowTooltip(true);
const handleHideTooltip = () => setShowTooltip(false);
const handleOnBlur = event => {
onBlur(event);
setIsFocused(false);
handleHideTooltip();
};
const handleOnFocus = event => {
onFocus(event);
setIsFocused(true);
handleShowTooltip();
};
const offsetStyle = {
[(0, _i18n.isRTL)() ? 'right' : 'left']: fillValueOffset
};
// Add default size deprecation warning.
(0, _deprecated36pxSize.maybeWarnDeprecated36pxSize)({
componentName: 'RangeControl',
__next40pxDefaultSize,
size: undefined,
__shouldNotWarnDeprecated36pxSize
});
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_baseControl.default, {
__nextHasNoMarginBottom: __nextHasNoMarginBottom,
__associatedWPComponentName: "RangeControl",
className: classes,
label: label,
hideLabelFromVision: hideLabelFromVision,
id: `${id}`,
help: help,
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_rangeControlStyles.Root, {
className: "components-range-control__root",
__next40pxDefaultSize: __next40pxDefaultSize,
children: [beforeIcon && /*#__PURE__*/(0, _jsxRuntime.jsx)(_rangeControlStyles.BeforeIconWrapper, {
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_icon.default, {
icon: beforeIcon
})
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_rangeControlStyles.Wrapper, {
__nextHasNoMarginBottom: __nextHasNoMarginBottom,
className: wrapperClasses,
color: colorProp,
marks: !!marks,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_inputRange.default, {
...otherProps,
className: "components-range-control__slider",
describedBy: describedBy,
disabled: disabled,
id: `${id}`,
label: label,
max: max,
min: min,
onBlur: handleOnBlur,
onChange: handleOnRangeChange,
onFocus: handleOnFocus,
onMouseMove: onMouseMove,
onMouseLeave: onMouseLeave,
ref: (0, _compose.useMergeRefs)([inputRef, forwardedRef]),
step: step,
value: inputSliderValue !== null && inputSliderValue !== void 0 ? inputSliderValue : undefined
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_rail.default, {
"aria-hidden": true,
disabled: disabled,
marks: marks,
max: max,
min: min,
railColor: railColor,
step: step,
value: rangeFillValue
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_rangeControlStyles.Track, {
"aria-hidden": true,
className: "components-range-control__track",
disabled: disabled,
style: {
width: fillValueOffset
},
trackColor: trackColor
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_rangeControlStyles.ThumbWrapper, {
className: "components-range-control__thumb-wrapper",
style: offsetStyle,
disabled: disabled,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_rangeControlStyles.Thumb, {
"aria-hidden": true,
isFocused: isThumbFocused,
disabled: disabled
})
}), enableTooltip && /*#__PURE__*/(0, _jsxRuntime.jsx)(_tooltip.default, {
className: "components-range-control__tooltip",
inputRef: inputRef,
tooltipPosition: "bottom",
renderTooltipContent: renderTooltipContent,
show: isCurrentlyFocused || showTooltip,
style: offsetStyle,
value: value
})]
}), afterIcon && /*#__PURE__*/(0, _jsxRuntime.jsx)(_rangeControlStyles.AfterIconWrapper, {
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_icon.default, {
icon: afterIcon
})
}), hasInputField && /*#__PURE__*/(0, _jsxRuntime.jsx)(_rangeControlStyles.InputNumber, {
"aria-label": label,
className: "components-range-control__number",
disabled: disabled,
inputMode: "decimal",
isShiftStepEnabled: isShiftStepEnabled,
max: max,
min: min,
onBlur: handleOnInputNumberBlur,
onChange: handleOnChange,
shiftStep: shiftStep,
size: __next40pxDefaultSize ? '__unstable-large' : 'default',
__unstableInputWidth: __next40pxDefaultSize ? (0, _space.space)(20) : (0, _space.space)(16),
step: step
// @ts-expect-error TODO: Investigate if the `null` value is necessary
,
value: inputSliderValue,
__shouldNotWarnDeprecated36pxSize: true
}), allowReset && /*#__PURE__*/(0, _jsxRuntime.jsx)(_rangeControlStyles.ActionRightWrapper, {
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_button.default, {
className: "components-range-control__reset"
// If the RangeControl itself is disabled, the reset button shouldn't be in the tab sequence.
,
accessibleWhenDisabled: !disabled
// The reset button should be disabled if RangeControl itself is disabled,
// or if the current `value` is equal to the value that would be currently
// assigned when clicking the button.
,
disabled: disabled || value === computeResetValue({
resetFallbackValue,
initialPosition
}),
variant: "secondary",
size: "small",
onClick: handleOnReset,
children: (0, _i18n.__)('Reset')
})
})]
})
});
}
/**
* RangeControls are used to make selections from a range of incremental values.
*
* ```jsx
* import { RangeControl } from '@wordpress/components';
* import { useState } from '@wordpress/element';
*
* const MyRangeControl = () => {
* const [ value, setValue ] = useState();
* return (
* <RangeControl
* __nextHasNoMarginBottom
* __next40pxDefaultSize
* help="Please select how transparent you would like this."
* initialPosition={ 50 }
* label="Opacity"
* max={ 100 }
* min={ 0 }
* value={ value }
* onChange={ setValue }
* />
* );
* };
* ```
*/
const RangeControl = exports.RangeControl = (0, _element.forwardRef)(UnforwardedRangeControl);
var _default = exports.default = RangeControl;
//# sourceMappingURL=index.js.map