@wix/design-system
Version:
@wix/design-system
197 lines • 8.96 kB
JavaScript
import React, { useContext, useRef, useCallback, useImperativeHandle, forwardRef, useMemo, useEffect, } from 'react';
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 { mergeRefs } from '../utils/mergeRefs';
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';
import deprecationLog from '../utils/deprecationLog';
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 mergedInputRef = useMemo(() => mergeRefs(localInputRef, inputRef), [inputRef]);
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,
}));
useEffect(() => {
if (typeof tooltipPlacement !== 'undefined') {
deprecationLog('<Input/> - prop "tooltipPlacement" is deprecated and will be removed in next major release, please use "statusMessageTooltipProps" instead.');
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
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: mergedInputRef, ...commonProps })) : (React.createElement("input", { "data-hook": "wsr-input", ref: mergedInputRef, ...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.displayName = 'Input';
export default Object.assign(Input, {
Ticker,
IconAffix,
Affix,
Group,
StatusError: STATUS.ERROR,
StatusWarning: STATUS.WARNING,
StatusLoading: STATUS.LOADING,
});
//# sourceMappingURL=Input.js.map