UNPKG

@orfeas126/box-ui-elements

Version:
270 lines (265 loc) 9.1 kB
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } import * as React from 'react'; import { CompositeDecorator, EditorState } from 'draft-js'; import noop from 'lodash/noop'; import DraftJSMentionSelectorCore from './DraftJSMentionSelectorCore'; import DraftMentionItem from './DraftMentionItem'; import FormInput from '../form/FormInput'; import * as messages from '../input-messages'; /** * Scans a Draft ContentBlock for entity ranges, so they can be annotated * @see docs at {@link https://draftjs.org/docs/advanced-topics-decorators.html#compositedecorator} * @param {ContentBlock} contentBlock * @param {function} callback * @param {ContentState} contentState */ const mentionStrategy = (contentBlock, callback, contentState) => { contentBlock.findEntityRanges(character => { const entityKey = character.getEntity(); const ret = entityKey !== null && contentState.getEntity(entityKey).getType() === 'MENTION'; return ret; }, callback); }; class DraftJSMentionSelector extends React.Component { constructor(props) { super(props); /** * Event handler called on blur. Triggers validation * @param {SyntheticFocusEvent} event The event object * @returns {void} */ _defineProperty(this, "handleBlur", event => { if (this.props.validateOnBlur && this.containerEl && event.relatedTarget instanceof Node && !this.containerEl.contains(event.relatedTarget)) { this.checkValidity(); } }); _defineProperty(this, "handleFocus", event => { const { onFocus } = this.props; if (onFocus) { onFocus(event); } }); /** * Updates editorState, rechecks validity * @param {EditorState} nextEditorState The new editor state to set in the state * @returns {void} */ _defineProperty(this, "handleChange", nextEditorState => { const { internalEditorState } = this.state; const { onChange } = this.props; onChange(nextEditorState); if (internalEditorState) { this.setState({ internalEditorState: nextEditorState }); } }); _defineProperty(this, "handleValidityStateUpdateHandler", () => { const { isTouched } = this.state; if (!isTouched) { return; } const error = this.getErrorFromValidityState(); this.setState({ error }); }); _defineProperty(this, "checkValidity", () => { this.handleValidityStateUpdateHandler(); }); const mentionDecorator = new CompositeDecorator([{ strategy: mentionStrategy, component: DraftMentionItem }]); // @NOTE: // This component might be either own its EditorState (in which case it lives in `this.state.internalEditorState`) // or be a controlled component whose EditorState is passed in via the `editorState` prop. // If `props.editorState` is set, `internalEditorState` is `null`, // otherwise we initialize it here this.state = { contacts: [], isTouched: false, internalEditorState: props.editorState ? null : EditorState.createEmpty(mentionDecorator), error: null }; } static getDerivedStateFromProps(nextProps) { const { contacts } = nextProps; return contacts ? { contacts } : null; } componentDidUpdate(prevProps, prevState) { const { internalEditorState: prevInternalEditorState } = prevState; const { internalEditorState } = this.state; const { editorState: prevEditorStateFromProps } = prevProps; const { editorState } = this.props; // Determine whether we're working with the internal editor state or // external editor state passed in from props const prevEditorState = prevInternalEditorState || prevEditorStateFromProps; const currentEditorState = internalEditorState || editorState; // Only handle isTouched state transitions and check validity if the // editorState references are different. This is to avoid getting stuck // in an infinite loop of checking validity because checkValidity always // calls setState({ error }) if (prevEditorState && currentEditorState && prevEditorState !== currentEditorState) { const newState = this.getDerivedStateFromEditorState(currentEditorState, prevEditorState); if (newState) { this.setState(newState, this.checkValidityIfAllowed); } else { this.checkValidityIfAllowed(); } } } getDerivedStateFromEditorState(currentEditorState, previousEditorState) { const isPreviousEditorStateEmpty = this.isEditorStateEmpty(previousEditorState); const isCurrentEditorStateEmpty = this.isEditorStateEmpty(currentEditorState); const isNewEditorState = isCurrentEditorStateEmpty && !isPreviousEditorStateEmpty; const isEditorStateDirty = isPreviousEditorStateEmpty && !isCurrentEditorStateEmpty; let newState = null; // Detect case where controlled EditorState is created anew and empty. // If next editorState is empty and the current editorState is not empty // that means it is a new empty state and this component should not be marked dirty if (isNewEditorState) { newState = { isTouched: false, error: null }; } else if (isEditorStateDirty) { // Detect case where controlled EditorState has been made dirty // If the current editorState is empty and the next editorState is not // empty then this is the first interaction so mark this component dirty newState = { isTouched: true }; } return newState; } checkValidityIfAllowed() { const { validateOnBlur } = this.props; if (!validateOnBlur) { this.checkValidity(); } } isEditorStateEmpty(editorState) { const text = editorState.getCurrentContent().getPlainText().trim(); const lastChangeType = editorState.getLastChangeType(); return text.length === 0 && lastChangeType === null; } /** * @returns {string} */ getErrorFromValidityState() { const { editorState: externalEditorState, isRequired, maxLength, minLength } = this.props; const { internalEditorState } = this.state; // manually check for content length if isRequired is true const editorState = internalEditorState || externalEditorState; const { length } = editorState.getCurrentContent().getPlainText().trim(); if (isRequired && !length) { return messages.valueMissing(); } if (typeof minLength !== 'undefined' && length < minLength) { return messages.tooShort(minLength); } if (typeof maxLength !== 'undefined' && length > maxLength) { return messages.tooLong(maxLength); } return null; } render() { const { className = '', contactsLoaded, editorState: externalEditorState, hideLabel, isDisabled, isRequired, label, description, mentionTriggers, name, onMention, placeholder, selectorRow, startMentionMessage, onReturn } = this.props; const { contacts, internalEditorState, error } = this.state; const { handleBlur, handleChange, handleFocus } = this; const editorState = internalEditorState || externalEditorState; return /*#__PURE__*/React.createElement("div", { ref: containerEl => { this.containerEl = containerEl; }, className: className }, /*#__PURE__*/React.createElement(FormInput, { name: name, onValidityStateUpdate: this.handleValidityStateUpdateHandler }, /*#__PURE__*/React.createElement(DraftJSMentionSelectorCore, { contacts: contacts, contactsLoaded: contactsLoaded, editorState: editorState, error: error, hideLabel: hideLabel, isDisabled: isDisabled, isRequired: isRequired, label: label, description: description, mentionTriggers: mentionTriggers, onBlur: handleBlur, onChange: handleChange, onFocus: handleFocus, onMention: onMention, onReturn: onReturn, placeholder: placeholder, selectorRow: selectorRow, startMentionMessage: startMentionMessage }))); } } _defineProperty(DraftJSMentionSelector, "defaultProps", { isRequired: false, onChange: noop, validateOnBlur: true }); export default DraftJSMentionSelector; //# sourceMappingURL=DraftJSMentionSelector.js.map