UNPKG

@wix/design-system

Version:

@wix/design-system

259 lines 10.6 kB
import React, { useContext, useRef, useCallback, useImperativeHandle, forwardRef, useEffect, } from 'react'; import PropTypes from 'prop-types'; import { st, classes } from './Input.st.css.js'; import { InputContext } from './InputContext'; import { STATUS } from '../StatusIndicator/StatusIndicator.constants'; import { DATA_ATTR } from './Input.constants'; import Ticker from './components/Ticker'; import IconAffix from './components/IconAffix'; import Affix from './components/Affix'; import Group from './components/Group'; import { WixStyleReactMaskingContext } from '../WixStyleReactMaskingProvider/context'; import { WixStyleReactDefaultsOverrideContext } from '../WixStyleReactDefaultsOverrideProvider'; import { StatusContext, getAriaAttributesFromContext, getStatusFromContext, } from '../FormField/StatusContext'; import { useAutoFocusAndSelect } from './hooks/useAutoFocusAndSelect'; import { transformAriaKebabCase } from './utils/transformAriaKebabCase'; import { Suffix } from './components/Suffix/Suffix'; export const Input = forwardRef((props, ref) => { const { input: inputPropsDefaults } = useContext(WixStyleReactDefaultsOverrideContext); const { role, autoSelect = true, textOverflow = 'clip', size = inputPropsDefaults.size, hideStatusSuffix = false, clearButton = false, focusOnClearClick = true, border = 'standard', maxLength = 524288, autoFocus, onFocus, onBlur, value, customInput, min, max, step, inputRef, id, name, disabled, defaultValue, onChange, type, onKeyDown, onEnterPressed, onEscapePressed, onPaste, onCopy, placeholder, tabIndex, onInputClicked, onKeyUp, readOnly, disableEditing, required, autocomplete, onCompositionChange, pattern, prefix, suffix, className, status, statusMessage, onClear, menuArrow, rtl, dataHook, forceFocus, noRightBorderRadius, noLeftBorderRadius, forceHover, tooltipPlacement, clearButtonTooltipContent, clearButtonTooltipProps, clearButtonAriaLabel, statusMessageTooltipProps, ...rest } = props; const isComposing = useRef(false); const localInputRef = useRef(null); const { maskingClassNames } = useContext(WixStyleReactMaskingContext); const statusContext = useContext(StatusContext); const { _onFocus, _onBlur, isFocused } = useAutoFocusAndSelect({ localInputRef, onFocus, onBlur, autoFocus, autoSelect, value, }); useImperativeHandle(ref, () => ({ /** * Sets focus on the input element */ focus: (options = {}) => localInputRef.current?.focus(options), /** * Removes focus from the input element */ blur: () => localInputRef.current?.blur(), /** * Selects the text in the input element */ select: () => localInputRef.current?.select(), /** * Sets the start and end positions of the current text selection in the input element */ setSelectionRange: (start, end) => localInputRef.current?.setSelectionRange(start, end), /** * Clears the input value */ clear: (event) => { // If the value is undefined, we are dealing with an uncontrolled input if (value === undefined && localInputRef.current) { localInputRef.current.value = ''; } onClear?.(event); }, /** * Gets the input DOM element. */ input: localInputRef.current, })); /** * Legacy reason to still support inputRef prop as prop. */ useEffect(() => { if (inputRef && localInputRef.current) { inputRef(localInputRef.current); } }, [inputRef]); const isValid = useCallback((val) => { if (type === 'number') { /* * Limit our number input to contain only: * - \d - digits * - . - a dot * - , - a comma * - \- - a hyphen minus * - + - a plus sign */ return /^[\d.,\-+]*$/.test(val); } return true; }, [type]); const _onChange = useCallback((event) => { if (isValid(event.target.value)) { onChange?.(event); } }, [isValid, onChange]); const _onKeyPress = useCallback((event) => { if (!isValid(event.currentTarget.value + event.key)) { event.preventDefault(); } }, [isValid]); const _onWheel = useCallback(() => { if (type === 'number') { localInputRef.current?.blur(); } }, [type]); const _onKeyDown = useCallback((event) => { if (isComposing.current) { return; } // On key event onKeyDown?.(event); // Enter if (event.key === 'Enter' || event.keyCode === 13) { onEnterPressed?.(event); } // Escape if (event.key === 'Escape' || event.keyCode === 27) { onEscapePressed?.(event); } }, [isComposing, onEnterPressed, onEscapePressed, onKeyDown]); const _onClick = useCallback((event) => onInputClicked?.(event), [onInputClicked]); const _onCompositionChange = useCallback((composing) => { isComposing.current = composing; onCompositionChange?.(composing); }, [onCompositionChange]); const commonProps = { role, id, min, max, step, 'data-mask': !!maskingClassNames, className: st(classes.input, {}, maskingClassNames), style: { textOverflow }, name, disabled, defaultValue, value, onFocus: _onFocus, onBlur: _onBlur, onChange: _onChange, onKeyPress: _onKeyPress, onWheel: _onWheel, onKeyDown: _onKeyDown, onClick: _onClick, onPaste, onCopy, placeholder, tabIndex, onKeyUp, readOnly: readOnly || disableEditing, type, required, autoComplete: autocomplete, onCompositionStart: () => _onCompositionChange(true), onCompositionEnd: () => _onCompositionChange(false), pattern, maxLength, ...getAriaAttributesFromContext(statusContext), ...transformAriaKebabCase(rest), }; const CustomInputComponent = customInput; const inputElement = customInput ? (React.createElement(CustomInputComponent, { "data-hook": "wsr-custom-input", ref: localInputRef, ...commonProps })) : (React.createElement("input", { "data-hook": "wsr-input", ref: localInputRef, ...commonProps })); const dataAttributes = { [DATA_ATTR.ROOT]: true, [DATA_ATTR.SIZE]: size, [DATA_ATTR.STATUS]: getStatusFromContext(statusContext, status), [DATA_ATTR.PREFIX]: !!prefix, [DATA_ATTR.DISABLED]: disabled, [DATA_ATTR.HOVER]: forceHover, [DATA_ATTR.FOCUS]: forceFocus || isFocused, [DATA_ATTR.LEFTBORDERRADIUS]: noLeftBorderRadius, [DATA_ATTR.RIGHTBORDERRADIUS]: noRightBorderRadius, }; return (React.createElement("div", { className: st(classes.root, { size, hasFocus: forceFocus || isFocused, status: getStatusFromContext(statusContext, status), forceHover, readOnly, disabled, border, noRightBorderRadius, noLeftBorderRadius, }, className), dir: rtl ? 'rtl' : undefined, "data-hook": dataHook, ...dataAttributes }, React.createElement("div", { className: classes.wrapper }, prefix && (React.createElement(InputContext.Provider, { value: { ...props, size, inPrefix: true } }, prefix)), inputElement, React.createElement(InputContext.Provider, { value: { ...props, size, inSuffix: true } }, React.createElement(Suffix, { statusMessage: statusMessage, status: status, disabled: disabled, menuArrow: menuArrow, suffix: suffix, tooltipPlacement: tooltipPlacement, clearButtonTooltipContent: clearButtonTooltipContent, clearButtonTooltipProps: clearButtonTooltipProps, clearButtonAriaLabel: clearButtonAriaLabel, statusMessageTooltipProps: statusMessageTooltipProps, hideStatusSuffix: hideStatusSuffix, onClear: onClear, clearButton: clearButton, value: value, size: size, onInputClicked: onInputClicked, focusOnClearClick: focusOnClearClick, inputElementRef: localInputRef }))))); }); Input.propTypes = { role: PropTypes.any, dataHook: PropTypes.any, className: PropTypes.any, id: PropTypes.any, ariaControls: PropTypes.any, ariaDescribedby: PropTypes.any, ariaLabel: PropTypes.any, autoFocus: PropTypes.any, autocomplete: PropTypes.any, defaultValue: PropTypes.any, disabled: PropTypes.any, status: PropTypes.any, statusMessage: PropTypes.any, hideStatusSuffix: PropTypes.any, forceFocus: PropTypes.any, forceHover: PropTypes.any, maxLength: PropTypes.any, menuArrow: PropTypes.any, clearButton: PropTypes.any, name: PropTypes.any, border: PropTypes.any, noLeftBorderRadius: PropTypes.any, noRightBorderRadius: PropTypes.any, onBlur: PropTypes.any, onChange: PropTypes.any, onClear: PropTypes.any, onCompositionChange: PropTypes.any, onEnterPressed: PropTypes.any, onEscapePressed: PropTypes.any, onFocus: PropTypes.any, onInputClicked: PropTypes.any, onKeyDown: PropTypes.any, onKeyUp: PropTypes.any, onPaste: PropTypes.any, onCopy: PropTypes.any, placeholder: PropTypes.any, prefix: PropTypes.any, readOnly: PropTypes.any, disableEditing: PropTypes.any, size: PropTypes.any, suffix: PropTypes.any, tabIndex: PropTypes.any, textOverflow: PropTypes.any, /** * @deprecated use statusMessageTooltipProps instead */ tooltipPlacement: PropTypes.any, type: PropTypes.any, value: PropTypes.any, required: PropTypes.any, min: PropTypes.any, max: PropTypes.any, step: PropTypes.any, customInput: PropTypes.any, pattern: PropTypes.any, focusOnClearClick: PropTypes.any, clearButtonTooltipContent: PropTypes.any, clearButtonTooltipProps: PropTypes.any, clearButtonAriaLabel: PropTypes.any, statusMessageTooltipProps: PropTypes.any, }; Input.displayName = 'Input'; export default Object.assign(Input, { Ticker, IconAffix, Affix, Group, StatusError: STATUS.ERROR, StatusWarning: STATUS.WARNING, StatusLoading: STATUS.LOADING, }); //# sourceMappingURL=Input.js.map