UNPKG

@dotconnor/grommet

Version:

focus on the essential experience

402 lines (344 loc) 16.5 kB
"use strict"; exports.__esModule = true; exports.TextInput = void 0; var _react = _interopRequireWildcard(require("react")); var _styledComponents = _interopRequireWildcard(require("styled-components")); var _defaultProps = require("../../default-props"); var _Box = require("../Box"); var _Button = require("../Button"); var _Drop = require("../Drop"); var _InfiniteScroll = require("../InfiniteScroll"); var _Keyboard = require("../Keyboard"); var _FormContext = require("../Form/FormContext"); var _contexts = require("../../contexts"); var _utils = require("../../utils"); var _StyledTextInput = require("./StyledTextInput"); function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; } function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } var renderLabel = function renderLabel(suggestion) { if (suggestion && typeof suggestion === 'object') { return suggestion.label || suggestion.value; } return suggestion; }; var stringLabel = function stringLabel(suggestion) { if (suggestion && typeof suggestion === 'object') { if (suggestion.label && typeof suggestion.label === 'string') { return suggestion.label; } return suggestion.value; } return suggestion; }; var ContainerBox = (0, _styledComponents["default"])(_Box.Box).withConfig({ displayName: "TextInput__ContainerBox", componentId: "sc-1ai0c08-0" })(["", ";@media screen and (-ms-high-contrast:active),(-ms-high-contrast:none){width:100%;}"], function (props) { return props.dropHeight ? (0, _utils.sizeStyle)('max-height', props.dropHeight, props.theme) : 'max-height: inherit;'; }); var defaultDropAlign = { top: 'bottom', left: 'left' }; var defaultMessages = { enterSelect: '(Press Enter to Select)', suggestionsCount: 'suggestions available', suggestionsExist: 'This input has suggestions use arrow keys to navigate', suggestionIsOpen: 'Suggestions drop is open, continue to use arrow keys to navigate' }; var TextInput = /*#__PURE__*/(0, _react.forwardRef)(function (_ref, ref) { var a11yTitle = _ref.a11yTitle, defaultValue = _ref.defaultValue, _ref$dropAlign = _ref.dropAlign, dropAlign = _ref$dropAlign === void 0 ? defaultDropAlign : _ref$dropAlign, dropHeight = _ref.dropHeight, dropTarget = _ref.dropTarget, dropProps = _ref.dropProps, icon = _ref.icon, id = _ref.id, _ref$messages = _ref.messages, messages = _ref$messages === void 0 ? defaultMessages : _ref$messages, name = _ref.name, _onBlur = _ref.onBlur, onChange = _ref.onChange, _onFocus = _ref.onFocus, onKeyDown = _ref.onKeyDown, onSelect = _ref.onSelect, onSuggestionSelect = _ref.onSuggestionSelect, onSuggestionsClose = _ref.onSuggestionsClose, onSuggestionsOpen = _ref.onSuggestionsOpen, placeholder = _ref.placeholder, plain = _ref.plain, readOnly = _ref.readOnly, reverse = _ref.reverse, suggestions = _ref.suggestions, textAlign = _ref.textAlign, valueProp = _ref.value, rest = _objectWithoutPropertiesLoose(_ref, ["a11yTitle", "defaultValue", "dropAlign", "dropHeight", "dropTarget", "dropProps", "icon", "id", "messages", "name", "onBlur", "onChange", "onFocus", "onKeyDown", "onSelect", "onSuggestionSelect", "onSuggestionsClose", "onSuggestionsOpen", "placeholder", "plain", "readOnly", "reverse", "suggestions", "textAlign", "value"]); var theme = (0, _react.useContext)(_styledComponents.ThemeContext) || _defaultProps.defaultProps.theme; var announce = (0, _react.useContext)(_contexts.AnnounceContext); var formContext = (0, _react.useContext)(_FormContext.FormContext); var inputRef = (0, _utils.useForwardedRef)(ref); var dropRef = (0, _react.useRef)(); var suggestionsRef = (0, _react.useRef)(); var suggestionRefs = {}; // if this is a readOnly property, don't set a name with the form context // this allows Select to control the form context for the name. var _formContext$useFormI = formContext.useFormInput(readOnly ? undefined : name, valueProp), value = _formContext$useFormI[0], setValue = _formContext$useFormI[1]; var _useState = (0, _react.useState)(), focus = _useState[0], setFocus = _useState[1]; var _useState2 = (0, _react.useState)(), showDrop = _useState2[0], setShowDrop = _useState2[1]; var handleSuggestionSelect = (0, _react.useMemo)(function () { return onSelect && !onSuggestionSelect ? onSelect : onSuggestionSelect; }, [onSelect, onSuggestionSelect]); var handleTextSelect = (0, _react.useMemo)(function () { return onSelect && onSuggestionSelect ? onSelect : undefined; }, [onSelect, onSuggestionSelect]); // if we have no suggestions, close drop if it's open (0, _react.useEffect)(function () { if (showDrop && (!suggestions || !suggestions.length)) { setShowDrop(false); if (onSuggestionsClose) onSuggestionsClose(); } }, [onSuggestionsClose, showDrop, suggestions]); // If we have suggestions and focus, open drop if it's closed. // This can occur when suggestions are tied to the value. // We don't want focus or showDrop in the dependencies because we // don't want to open the drop just because Esc close it. /* eslint-disable react-hooks/exhaustive-deps */ (0, _react.useEffect)(function () { if (focus && !showDrop && suggestions && suggestions.length) { setShowDrop(true); if (onSuggestionsOpen) onSuggestionsOpen(); } }, [onSuggestionsOpen, suggestions]); /* eslint-enable react-hooks/exhaustive-deps */ var _useState3 = (0, _react.useState)(-1), activeSuggestionIndex = _useState3[0], setActiveSuggestionIndex = _useState3[1]; // reset activeSuggestionIndex when the drop is closed (0, _react.useEffect)(function () { if (activeSuggestionIndex !== -1 && !showDrop) { setActiveSuggestionIndex(-1); } }, [activeSuggestionIndex, showDrop]); // announce active suggestion (0, _react.useEffect)(function () { if (activeSuggestionIndex >= 0) { var label = stringLabel(suggestions[activeSuggestionIndex]); announce(label + " " + messages.enterSelect); } }, [activeSuggestionIndex, announce, messages, suggestions]); var _useState4 = (0, _react.useState)(-1), selectedSuggestionIndex = _useState4[0], setSelectedSuggestionIndex = _useState4[1]; // set selectedSuggestionIndex based on value and current suggestions (0, _react.useEffect)(function () { if (suggestions) { var suggestionValues = suggestions.map(function (suggestion) { return typeof suggestion === 'object' ? suggestion.value : suggestion; }); setSelectedSuggestionIndex(suggestionValues.indexOf(value)); } else setSelectedSuggestionIndex(-1); }, [suggestions, value]); // make sure activeSuggestion remains visible in scroll (0, _react.useEffect)(function () { var buttonNode = suggestionRefs[activeSuggestionIndex]; var optionsNode = suggestionsRef.current; if (buttonNode && (0, _utils.isNodeAfterScroll)(buttonNode, optionsNode) && optionsNode.scrollTo) { optionsNode.scrollTo(0, buttonNode.offsetTop - (optionsNode.getBoundingClientRect().height - buttonNode.getBoundingClientRect().height)); } if (buttonNode && (0, _utils.isNodeBeforeScroll)(buttonNode, optionsNode) && optionsNode.scrollTo) { optionsNode.scrollTo(0, buttonNode.offsetTop); } }, [activeSuggestionIndex, suggestionRefs]); var openDrop = (0, _react.useCallback)(function () { setShowDrop(true); announce(messages.suggestionIsOpen); announce(suggestions.length + " " + messages.suggestionsCount); if (onSuggestionsOpen) onSuggestionsOpen(); }, [announce, messages.suggestionsCount, messages.suggestionIsOpen, onSuggestionsOpen, suggestions]); var closeDrop = (0, _react.useCallback)(function () { setShowDrop(false); if (messages.onSuggestionsClose) onSuggestionsClose(); if (onSuggestionsClose) onSuggestionsClose(); }, [messages.onSuggestionsClose, onSuggestionsClose]); var onNextSuggestion = function onNextSuggestion(event) { event.preventDefault(); var nextActiveIndex = Math.min(activeSuggestionIndex + 1, suggestions.length - 1); setActiveSuggestionIndex(nextActiveIndex); }; var onPreviousSuggestion = function onPreviousSuggestion(event) { event.preventDefault(); var nextActiveIndex = Math.max(activeSuggestionIndex - 1, 0); setActiveSuggestionIndex(nextActiveIndex); }; var showStyledPlaceholder = placeholder && typeof placeholder !== 'string' && !(inputRef.current && inputRef.current.value); var drop; var extraProps = { onSelect: handleTextSelect }; if (showDrop) { drop = /*#__PURE__*/ // keyboard access needed here in case user clicks // and drags on scroll bar and focus shifts to drop _react["default"].createElement(_Keyboard.Keyboard, { onDown: function onDown(event) { return onNextSuggestion(event); }, onUp: function onUp(event) { return onPreviousSuggestion(event); }, onEnter: function onEnter(event) { // we stole the focus, give it back inputRef.current.focus(); closeDrop(); if (handleSuggestionSelect) { var adjustedEvent = event; adjustedEvent.suggestion = suggestions[activeSuggestionIndex]; handleSuggestionSelect(adjustedEvent); } setValue(suggestions[activeSuggestionIndex]); } }, /*#__PURE__*/_react["default"].createElement(_Drop.Drop, _extends({ ref: dropRef, id: id ? "text-input-drop__" + id : undefined, align: dropAlign, responsive: false, target: dropTarget || inputRef.current, onClickOutside: closeDrop, onEsc: closeDrop }, dropProps), /*#__PURE__*/_react["default"].createElement(ContainerBox, { ref: suggestionsRef, overflow: "auto", dropHeight: dropHeight }, /*#__PURE__*/_react["default"].createElement(_StyledTextInput.StyledSuggestions, null, /*#__PURE__*/_react["default"].createElement(_InfiniteScroll.InfiniteScroll, { items: suggestions, step: theme.select.step }, function (suggestion, index, itemRef) { // Determine whether the label is done as a child or // as an option Button kind property. var renderedLabel = renderLabel(suggestion); var child; if (typeof renderedLabel !== 'string') // must be an element rendered by suggestions.label child = renderedLabel;else if (!theme.button.option) // don't have theme support, need to layout here child = /*#__PURE__*/_react["default"].createElement(_Box.Box, { align: "start", pad: "small" }, renderedLabel); // if we have a child, turn on plain, and hoverIndicator return /*#__PURE__*/_react["default"].createElement("li", { key: stringLabel(suggestion) + "-" + index, ref: itemRef }, /*#__PURE__*/_react["default"].createElement(_Button.Button, { active: activeSuggestionIndex === index || selectedSuggestionIndex === index, ref: function ref(r) { suggestionRefs[index] = r; }, fill: true, plain: !child ? undefined : true, align: "start", kind: !child ? 'option' : undefined, hoverIndicator: !child ? undefined : 'background', label: !child ? renderedLabel : undefined, onClick: function onClick(event) { // we stole the focus, give it back inputRef.current.focus(); closeDrop(); if (handleSuggestionSelect) { event.persist(); var adjustedEvent = event; adjustedEvent.suggestion = suggestion; adjustedEvent.target = inputRef.current; handleSuggestionSelect(adjustedEvent); } setValue(suggestion); }, onMouseOver: function onMouseOver() { return setActiveSuggestionIndex(index); }, onFocus: function onFocus() { return setActiveSuggestionIndex(index); } }, child)); }))))); } return /*#__PURE__*/_react["default"].createElement(_StyledTextInput.StyledTextInputContainer, { plain: plain }, showStyledPlaceholder && /*#__PURE__*/_react["default"].createElement(_StyledTextInput.StyledPlaceholder, null, placeholder), icon && /*#__PURE__*/_react["default"].createElement(_StyledTextInput.StyledIcon, { reverse: reverse, theme: theme }, icon), /*#__PURE__*/_react["default"].createElement(_Keyboard.Keyboard, { onEnter: function onEnter(event) { closeDrop(); if (activeSuggestionIndex >= 0 && handleSuggestionSelect) { // prevent submitting forms when choosing a suggestion event.preventDefault(); event.persist(); var adjustedEvent = event; adjustedEvent.suggestion = suggestions[activeSuggestionIndex]; adjustedEvent.target = inputRef.current; handleSuggestionSelect(adjustedEvent); } }, onEsc: showDrop ? function (event) { closeDrop(); // we have to stop both synthetic events and native events // drop and layer should not close by pressing esc on this // input event.stopPropagation(); event.nativeEvent.stopImmediatePropagation(); } : undefined, onTab: showDrop ? closeDrop : undefined, onUp: showDrop && suggestions && suggestions.length > 0 && activeSuggestionIndex ? function (event) { onPreviousSuggestion(event); } : undefined, onDown: suggestions && suggestions.length > 0 ? function (event) { if (!showDrop) { openDrop(); } else { onNextSuggestion(event); } } : undefined, onKeyDown: onKeyDown }, /*#__PURE__*/_react["default"].createElement(_StyledTextInput.StyledTextInput, _extends({ "aria-label": a11yTitle, ref: inputRef, id: id, name: name, autoComplete: "off", plain: plain, placeholder: typeof placeholder === 'string' ? placeholder : undefined, icon: icon, reverse: reverse, focus: focus, textAlign: textAlign }, rest, extraProps, { defaultValue: renderLabel(defaultValue), value: renderLabel(value), readOnly: readOnly, onFocus: function onFocus(event) { setFocus(true); if (suggestions && suggestions.length > 0) { announce(messages.suggestionsExist); openDrop(); } if (_onFocus) _onFocus(event); }, onBlur: function onBlur(event) { setFocus(false); if (_onBlur) _onBlur(event); }, onChange: readOnly ? undefined : function (event) { setValue(event.target.value); if (onChange) onChange(event); } }))), drop); }); TextInput.displayName = 'TextInput'; var TextInputDoc; if (process.env.NODE_ENV !== 'production') { // eslint-disable-next-line global-require TextInputDoc = require('./doc').doc(TextInput); } var TextInputWrapper = TextInputDoc || TextInput; exports.TextInput = TextInputWrapper;