UNPKG

lucid-ui

Version:

A UI component library from Xandr.

228 lines 8.05 kB
import React from 'react'; import PropTypes from 'prop-types'; import _ from 'lodash'; import { lucidClassNames } from '../../util/style-helpers'; import reducers from './TextField.reducers'; import * as KEYCODE from '../../constants/key-code'; const cx = lucidClassNames.bind('&-TextField'); const { bool, string, func, number, object, oneOfType } = PropTypes; /** TODO: Remove the nonPassThroughs when the component is converted to a functional component */ const nonPassThroughs = [ 'style', 'isMultiLine', 'isDisabled', 'rows', 'className', 'onChange', 'onBlur', 'onChangeDebounced', 'onKeyDown', 'onSubmit', 'value', 'debounceLevel', 'lazyLevel', 'initialState', 'callbackId', 'children', ]; class TextField extends React.Component { constructor() { super(...arguments); this.state = { value: this.props.value, isHolding: false, isMounted: false, }; this.textareaElement = React.createRef(); this.inputElement = React.createRef(); this.nativeElement = this.props.isMultiLine ? this.textareaElement : this.inputElement; this.handleChangeDebounced = _.debounce((value, { event, props, }) => { this.props.onChangeDebounced && this.props.onChangeDebounced(value, { event, props }); }, this.props.debounceLevel); this.releaseHold = _.debounce(() => { if (!this.state.isMounted) { return; } this.setState({ isHolding: false }); }, this.props.lazyLevel); this.updateWhenReady = _.debounce((newValue) => { if (!this.state.isMounted) { return; } if (this.state.isHolding) { this.updateWhenReady(newValue); } else if (newValue !== this.state.value) { this.setState({ value: newValue }); } }, this.props.lazyLevel); this.handleChange = (event) => { const { onChange, onChangeDebounced } = this.props; const value = _.get(event, 'target.value', ''); this.setState({ value, isHolding: true }); this.releaseHold(); onChange && onChange(value, { event, props: this.props }); // Also call the debounced handler in case the user wants debounced change // events. if (onChangeDebounced !== _.noop) { event.persist(); // https://facebook.github.io/react/docs/events.html#event-pooling this.handleChangeDebounced(value, { event, props: this.props }); } }; this.handleBlur = (event) => { const { onBlur, onChangeDebounced } = this.props; const value = _.get(event, 'target.value', ''); if (onChangeDebounced !== _.noop) { this.handleChangeDebounced.flush(); } onBlur && onBlur(value, { event, props: this.props }); }; this.handleKeyDown = (event) => { const { props, props: { onSubmit, onKeyDown, onChangeDebounced }, } = this; const value = _.get(event, 'target.value', ''); // If the consumer passed an onKeyDown, we call it if (onKeyDown) { onKeyDown({ event, props }); } if (event.keyCode === KEYCODE.Enter) { if (onChangeDebounced !== _.noop) { this.handleChangeDebounced.flush(); } onSubmit && onSubmit(value, { event, props: this.props }); } }; this.focus = (options) => { /* istanbul ignore next */ this.nativeElement.current.focus(options); }; } UNSAFE_componentWillMount() { this.setState({ isMounted: true }); } componentWillUnmount() { this.setState({ isMounted: false }); } UNSAFE_componentWillReceiveProps(nextProps) { // Allow consumer to optionally control state if (_.has(nextProps, 'value')) { if (this.state.isHolding) { this.updateWhenReady(nextProps.value); } else { this.setState({ value: nextProps.value }); } } } render() { const { className, isDisabled, isMultiLine, rows, style, ...passThroughs } = this.props; const { value } = this.state; const finalProps = { ..._.omit(passThroughs, nonPassThroughs), className: cx('&', { '&-is-disabled': isDisabled, '&-is-multi-line': isMultiLine, '&-is-single-line': !isMultiLine, }, className), disabled: isDisabled, onChange: this.handleChange, onBlur: this.handleBlur, onKeyDown: this.handleKeyDown, style, rows, value, }; return isMultiLine ? (React.createElement("textarea", { ref: this.textareaElement, ...finalProps })) : (React.createElement("input", { type: 'text', ref: this.inputElement, ...finalProps })); } } TextField.displayName = 'TextField'; TextField.peek = { description: `\`TextField\` should cover all your text input needs. It is able to handle single- and multi-line inputs.`, categories: ['controls', 'text'], }; TextField.propTypes = { /** Styles that are passed through to native control. */ style: object, /** Set the TextField to multi line mode. Under the hood this will use a \`textarea\` instead of an \`input\` if set to \`true\`. */ isMultiLine: bool, /** Disables the TextField by greying it out. */ isDisabled: bool, /** Initial number of rows a multi line TextField should have. Ignored when not in multi-line mode. */ rows: number, /** Class names that are appended to the defaults. */ className: string, /** Fires an event every time the user types text into the TextField. Signature: \`(value, { event, props }) => {}\` */ onChange: func, /** Fires an on the \`input\`'s onBlur. Signature: \`(currentValue, { event, props }) => {}\` */ onBlur: func, /** Fires an event, debounced by \`debounceLevel\`, when the user types text into the TextField. Signature: \`(value, { event, props }) => {}\` */ onChangeDebounced: func, /** Fires an event on every keydown Signature: \`({ event, props }) => {}\` */ onKeyDown: func, /** Fires an event when the user hits "enter" from the TextField. You shouldn't use it if you're using \`isMultiLine\`. Signature: \`(value, { event, props }) => {}\` */ onSubmit: func, /** Set the value of the input. */ value: oneOfType([number, string]), /** Number of milliseconds to debounce the \`onChangeDebounced\` callback. Only useful if you provide an \`onChangeDebounced\` handler. */ debounceLevel: number, /** Set the holding time, in milliseconds, that the component will wait if the user is typing and the component gets a new \`value\` prop. Any time the user hits a key, it starts a timer that prevents state changes from flowing in to the component until the timer has elapsed. This was heavily inspired by the [lazy-input](https:/docs.npmjs.com/package/lazy-input) component. */ lazyLevel: number, }; TextField.defaultProps = { style: undefined, isDisabled: false, isMultiLine: false, onBlur: _.noop, onChange: _.noop, onChangeDebounced: _.noop, onSubmit: _.noop, onKeyDown: _.noop, rows: 5, debounceLevel: 500, lazyLevel: 1000, value: '', }; TextField.reducers = reducers; export default TextField; //# sourceMappingURL=TextField.js.map