UNPKG

@rc-component/segmented

Version:

React segmented controls used in ant.design

243 lines (236 loc) 7.89 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _useMergedState = _interopRequireDefault(require("@rc-component/util/lib/hooks/useMergedState")); var _omit = _interopRequireDefault(require("@rc-component/util/lib/omit")); var _ref = require("@rc-component/util/lib/ref"); var _classnames = _interopRequireDefault(require("classnames")); var React = _interopRequireWildcard(require("react")); var _MotionThumb = _interopRequireDefault(require("./MotionThumb")); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } function getValidTitle(option) { if (typeof option.title !== 'undefined') { return option.title; } // read `label` when title is `undefined` if (typeof option.label !== 'object') { return option.label?.toString(); } } function normalizeOptions(options) { return options.map(option => { if (typeof option === 'object' && option !== null) { const validTitle = getValidTitle(option); return { ...option, title: validTitle }; } return { label: option?.toString(), title: option?.toString(), value: option }; }); } const InternalSegmentedOption = ({ prefixCls, className, style, styles, classNames: segmentedClassNames, data, disabled, checked, label, title, value, name, onChange, onFocus, onBlur, onKeyDown, onKeyUp, onMouseDown, itemRender = node => node }) => { const handleChange = event => { if (disabled) { return; } onChange(event, value); }; const itemContent = /*#__PURE__*/React.createElement("label", { className: (0, _classnames.default)(className, { [`${prefixCls}-item-disabled`]: disabled }), style: style, onMouseDown: onMouseDown }, /*#__PURE__*/React.createElement("input", { name: name, className: `${prefixCls}-item-input`, type: "radio", disabled: disabled, checked: checked, onChange: handleChange, onFocus: onFocus, onBlur: onBlur, onKeyDown: onKeyDown, onKeyUp: onKeyUp }), /*#__PURE__*/React.createElement("div", { className: (0, _classnames.default)(`${prefixCls}-item-label`, segmentedClassNames?.label), title: title, role: "radio", "aria-checked": checked, style: styles?.label }, label)); return itemRender(itemContent, { item: data }); }; const Segmented = /*#__PURE__*/React.forwardRef((props, ref) => { const { prefixCls = 'rc-segmented', direction, vertical, options = [], disabled, defaultValue, value, name, onChange, className = '', style, styles, classNames: segmentedClassNames, motionName = 'thumb-motion', itemRender, ...restProps } = props; const containerRef = React.useRef(null); const mergedRef = React.useMemo(() => (0, _ref.composeRef)(containerRef, ref), [containerRef, ref]); const segmentedOptions = React.useMemo(() => { return normalizeOptions(options); }, [options]); // Note: We should not auto switch value when value not exist in options // which may break single source of truth. const [rawValue, setRawValue] = (0, _useMergedState.default)(segmentedOptions[0]?.value, { value, defaultValue }); // ======================= Change ======================== const [thumbShow, setThumbShow] = React.useState(false); const handleChange = (event, val) => { setRawValue(val); onChange?.(val); }; const divProps = (0, _omit.default)(restProps, ['children']); // ======================= Focus ======================== const [isKeyboard, setIsKeyboard] = React.useState(false); const [isFocused, setIsFocused] = React.useState(false); const handleFocus = () => { setIsFocused(true); }; const handleBlur = () => { setIsFocused(false); }; const handleMouseDown = () => { setIsKeyboard(false); }; // capture keyboard tab interaction for correct focus style const handleKeyUp = event => { if (event.key === 'Tab') { setIsKeyboard(true); } }; // ======================= Keyboard ======================== const onOffset = offset => { const currentIndex = segmentedOptions.findIndex(option => option.value === rawValue); const total = segmentedOptions.length; const nextIndex = (currentIndex + offset + total) % total; const nextOption = segmentedOptions[nextIndex]; if (nextOption) { setRawValue(nextOption.value); onChange?.(nextOption.value); } }; const handleKeyDown = event => { switch (event.key) { case 'ArrowLeft': case 'ArrowUp': onOffset(-1); break; case 'ArrowRight': case 'ArrowDown': onOffset(1); break; } }; const renderOption = segmentedOption => { const { value: optionValue, disabled: optionDisabled } = segmentedOption; return /*#__PURE__*/React.createElement(InternalSegmentedOption, (0, _extends2.default)({}, segmentedOption, { name: name, data: segmentedOption, itemRender: itemRender, key: optionValue, prefixCls: prefixCls, className: (0, _classnames.default)(segmentedOption.className, `${prefixCls}-item`, segmentedClassNames?.item, { [`${prefixCls}-item-selected`]: optionValue === rawValue && !thumbShow, [`${prefixCls}-item-focused`]: isFocused && isKeyboard && optionValue === rawValue }), style: styles?.item, classNames: segmentedClassNames, styles: styles, checked: optionValue === rawValue, onChange: handleChange, onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown, onKeyUp: handleKeyUp, onMouseDown: handleMouseDown, disabled: !!disabled || !!optionDisabled })); }; return /*#__PURE__*/React.createElement("div", (0, _extends2.default)({ role: "radiogroup", "aria-label": "segmented control", tabIndex: disabled ? undefined : 0, style: style }, divProps, { className: (0, _classnames.default)(prefixCls, { [`${prefixCls}-rtl`]: direction === 'rtl', [`${prefixCls}-disabled`]: disabled, [`${prefixCls}-vertical`]: vertical }, className), ref: mergedRef }), /*#__PURE__*/React.createElement("div", { className: `${prefixCls}-group` }, /*#__PURE__*/React.createElement(_MotionThumb.default, { vertical: vertical, prefixCls: prefixCls, value: rawValue, containerRef: containerRef, motionName: `${prefixCls}-${motionName}`, direction: direction, getValueIndex: val => segmentedOptions.findIndex(n => n.value === val), onMotionStart: () => { setThumbShow(true); }, onMotionEnd: () => { setThumbShow(false); } }), segmentedOptions.map(renderOption))); }); if (process.env.NODE_ENV !== 'production') { Segmented.displayName = 'Segmented'; } const TypedSegmented = Segmented; var _default = exports.default = TypedSegmented;