@wordpress/components
Version:
UI components for WordPress.
8 lines (7 loc) • 16.6 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../../src/combobox-control/index.tsx"],
"sourcesContent": ["/**\n * External dependencies\n */\nimport clsx from 'clsx';\n\n/**\n * WordPress dependencies\n */\nimport { __, _n, sprintf } from '@wordpress/i18n';\nimport { Component, useState, useMemo, useRef, useEffect } from '@wordpress/element';\nimport { useInstanceId } from '@wordpress/compose';\nimport { speak } from '@wordpress/a11y';\nimport { closeSmall } from '@wordpress/icons';\n\n/**\n * Internal dependencies\n */\nimport { InputWrapperFlex } from './styles';\nimport TokenInput from '../form-token-field/token-input';\nimport SuggestionsList from '../form-token-field/suggestions-list';\nimport BaseControl from '../base-control';\nimport Button from '../button';\nimport { FlexBlock } from '../flex';\nimport withFocusOutside from '../higher-order/with-focus-outside';\nimport { useControlledValue } from '../utils/hooks';\nimport { normalizeTextString } from '../utils/strings';\nimport { useDeprecated36pxDefaultSizeProp } from '../utils/use-deprecated-props';\nimport { withIgnoreIMEEvents } from '../utils/with-ignore-ime-events';\nimport { maybeWarnDeprecated36pxSize } from '../utils/deprecated-36px-size';\nimport Spinner from '../spinner';\nimport { jsx as _jsx, jsxs as _jsxs } from \"react/jsx-runtime\";\nconst noop = () => {};\nconst DetectOutside = withFocusOutside(class DetectOutsideComponent extends Component {\n handleFocusOutside(event) {\n this.props.onFocusOutside(event);\n }\n render() {\n return this.props.children;\n }\n});\nconst getIndexOfMatchingSuggestion = (selectedSuggestion, matchingSuggestions) => selectedSuggestion === null ? -1 : matchingSuggestions.indexOf(selectedSuggestion);\n\n/**\n * `ComboboxControl` is an enhanced version of a [`SelectControl`](../select-control/README.md) with the addition of\n * being able to search for options using a search input.\n *\n * ```jsx\n * import { ComboboxControl } from '@wordpress/components';\n * import { useState } from '@wordpress/element';\n *\n * const options = [\n * \t{\n * \t\tvalue: 'small',\n * \t\tlabel: 'Small',\n * \t},\n * \t{\n * \t\tvalue: 'normal',\n * \t\tlabel: 'Normal',\n * \t\tdisabled: true,\n * \t},\n * \t{\n * \t\tvalue: 'large',\n * \t\tlabel: 'Large',\n * \t\tdisabled: false,\n * \t},\n * ];\n *\n * function MyComboboxControl() {\n * \tconst [ fontSize, setFontSize ] = useState();\n * \tconst [ filteredOptions, setFilteredOptions ] = useState( options );\n * \treturn (\n * \t\t<ComboboxControl\n * \t\t\t__next40pxDefaultSize\n * \t\t\tlabel=\"Font Size\"\n * \t\t\tvalue={ fontSize }\n * \t\t\tonChange={ setFontSize }\n * \t\t\toptions={ filteredOptions }\n * \t\t\tonFilterValueChange={ ( inputValue ) =>\n * \t\t\t\tsetFilteredOptions(\n * \t\t\t\t\toptions.filter( ( option ) =>\n * \t\t\t\t\t\toption.label\n * \t\t\t\t\t\t\t.toLowerCase()\n * \t\t\t\t\t\t\t.startsWith( inputValue.toLowerCase() )\n * \t\t\t\t\t)\n * \t\t\t\t)\n * \t\t\t}\n * \t\t/>\n * \t);\n * }\n * ```\n */\nfunction ComboboxControl(props) {\n const {\n __next40pxDefaultSize = false,\n value: valueProp,\n label,\n options,\n onChange: onChangeProp,\n onFilterValueChange = noop,\n hideLabelFromVision,\n help,\n allowReset = true,\n className,\n isLoading = false,\n messages = {\n selected: __('Item selected.')\n },\n __experimentalRenderItem,\n expandOnFocus = true,\n placeholder\n } = useDeprecated36pxDefaultSizeProp(props);\n const [value, setValue] = useControlledValue({\n value: valueProp,\n onChange: onChangeProp\n });\n const currentOption = options.find(option => option.value === value);\n const currentLabel = currentOption?.label ?? '';\n // Use a custom prefix when generating the `instanceId` to avoid having\n // duplicate input IDs when rendering this component and `FormTokenField`\n // in the same page (see https://github.com/WordPress/gutenberg/issues/42112).\n const instanceId = useInstanceId(ComboboxControl, 'combobox-control');\n const [selectedSuggestion, setSelectedSuggestion] = useState(currentOption || null);\n const [isExpanded, setIsExpanded] = useState(false);\n const [inputHasFocus, setInputHasFocus] = useState(false);\n const [inputValue, setInputValue] = useState('');\n const inputContainer = useRef(null);\n const matchingSuggestions = useMemo(() => {\n const startsWithMatch = [];\n const containsMatch = [];\n const match = normalizeTextString(inputValue);\n options.forEach(option => {\n const index = normalizeTextString(option.label).indexOf(match);\n if (index === 0) {\n startsWithMatch.push(option);\n } else if (index > 0) {\n containsMatch.push(option);\n }\n });\n return startsWithMatch.concat(containsMatch);\n }, [inputValue, options]);\n const onSuggestionSelected = newSelectedSuggestion => {\n if (newSelectedSuggestion.disabled) {\n return;\n }\n setValue(newSelectedSuggestion.value);\n speak(messages.selected, 'assertive');\n setSelectedSuggestion(newSelectedSuggestion);\n setInputValue('');\n setIsExpanded(false);\n };\n const handleArrowNavigation = (offset = 1) => {\n const index = getIndexOfMatchingSuggestion(selectedSuggestion, matchingSuggestions);\n let nextIndex = index + offset;\n if (nextIndex < 0) {\n nextIndex = matchingSuggestions.length - 1;\n } else if (nextIndex >= matchingSuggestions.length) {\n nextIndex = 0;\n }\n setSelectedSuggestion(matchingSuggestions[nextIndex]);\n setIsExpanded(true);\n };\n const onKeyDown = withIgnoreIMEEvents(event => {\n let preventDefault = false;\n if (event.defaultPrevented) {\n return;\n }\n switch (event.code) {\n case 'Enter':\n if (selectedSuggestion) {\n onSuggestionSelected(selectedSuggestion);\n preventDefault = true;\n }\n break;\n case 'ArrowUp':\n handleArrowNavigation(-1);\n preventDefault = true;\n break;\n case 'ArrowDown':\n handleArrowNavigation(1);\n preventDefault = true;\n break;\n case 'Escape':\n setIsExpanded(false);\n setSelectedSuggestion(null);\n preventDefault = true;\n break;\n default:\n break;\n }\n if (preventDefault) {\n event.preventDefault();\n }\n });\n const onBlur = () => {\n setInputHasFocus(false);\n };\n const onFocus = () => {\n setInputHasFocus(true);\n if (expandOnFocus) {\n setIsExpanded(true);\n }\n onFilterValueChange('');\n setInputValue('');\n };\n const onClick = () => {\n setIsExpanded(true);\n };\n const onFocusOutside = () => {\n setIsExpanded(false);\n };\n const onInputChange = event => {\n const text = event.value;\n setInputValue(text);\n onFilterValueChange(text);\n if (inputHasFocus) {\n setIsExpanded(true);\n }\n };\n const handleOnReset = () => {\n setValue(null);\n inputContainer.current?.focus();\n };\n\n // Stop propagation of the keydown event when pressing Enter on the Reset\n // button to prevent calling the onKeydown callback on the container div\n // element which actually sets the selected suggestion.\n const handleResetStopPropagation = event => {\n event.stopPropagation();\n };\n\n // Update current selections when the filter input changes.\n useEffect(() => {\n const hasMatchingSuggestions = matchingSuggestions.length > 0;\n const hasSelectedMatchingSuggestions = getIndexOfMatchingSuggestion(selectedSuggestion, matchingSuggestions) > 0;\n if (hasMatchingSuggestions && !hasSelectedMatchingSuggestions) {\n // If the current selection isn't present in the list of suggestions, then automatically select the first item from the list of suggestions.\n setSelectedSuggestion(matchingSuggestions[0]);\n }\n }, [matchingSuggestions, selectedSuggestion]);\n\n // Announcements.\n useEffect(() => {\n const hasMatchingSuggestions = matchingSuggestions.length > 0;\n if (isExpanded) {\n const message = hasMatchingSuggestions ? sprintf(/* translators: %d: number of results. */\n _n('%d result found, use up and down arrow keys to navigate.', '%d results found, use up and down arrow keys to navigate.', matchingSuggestions.length), matchingSuggestions.length) : __('No results.');\n speak(message, 'polite');\n }\n }, [matchingSuggestions, isExpanded]);\n maybeWarnDeprecated36pxSize({\n componentName: 'ComboboxControl',\n __next40pxDefaultSize,\n size: undefined\n });\n\n // Disable reason: There is no appropriate role which describes the\n // input container intended accessible usability.\n // TODO: Refactor click detection to use blur to stop propagation.\n /* eslint-disable jsx-a11y/no-static-element-interactions */\n return /*#__PURE__*/_jsx(DetectOutside, {\n onFocusOutside: onFocusOutside,\n children: /*#__PURE__*/_jsx(BaseControl, {\n className: clsx(className, 'components-combobox-control'),\n label: label,\n id: `components-form-token-input-${instanceId}`,\n hideLabelFromVision: hideLabelFromVision,\n help: help,\n children: /*#__PURE__*/_jsxs(\"div\", {\n className: \"components-combobox-control__suggestions-container\",\n tabIndex: -1,\n onKeyDown: onKeyDown,\n children: [/*#__PURE__*/_jsxs(InputWrapperFlex, {\n __next40pxDefaultSize: __next40pxDefaultSize,\n children: [/*#__PURE__*/_jsx(FlexBlock, {\n children: /*#__PURE__*/_jsx(TokenInput, {\n className: \"components-combobox-control__input\",\n instanceId: instanceId,\n ref: inputContainer,\n placeholder: placeholder,\n value: isExpanded ? inputValue : currentLabel,\n onFocus: onFocus,\n onBlur: onBlur,\n onClick: onClick,\n isExpanded: isExpanded,\n selectedSuggestionIndex: getIndexOfMatchingSuggestion(selectedSuggestion, matchingSuggestions),\n onChange: onInputChange,\n \"aria-describedby\": help ?\n // TODO: Refactor `TokenInput` to not use hardcoded IDs.\n `components-form-token-input-${instanceId}__help` : undefined\n })\n }), isLoading && /*#__PURE__*/_jsx(Spinner, {}), allowReset && Boolean(value) && !isExpanded && /*#__PURE__*/_jsx(Button, {\n size: \"small\",\n icon: closeSmall,\n onClick: handleOnReset,\n onKeyDown: handleResetStopPropagation,\n label: __('Reset')\n })]\n }), isExpanded && !isLoading && /*#__PURE__*/_jsx(SuggestionsList, {\n instanceId: instanceId\n // The empty string for `value` here is not actually used, but is\n // just a quick way to satisfy the TypeScript requirements of SuggestionsList.\n // See: https://github.com/WordPress/gutenberg/pull/47581/files#r1091089330\n ,\n match: {\n label: inputValue,\n value: ''\n },\n displayTransform: suggestion => suggestion.label,\n suggestions: matchingSuggestions,\n selectedIndex: getIndexOfMatchingSuggestion(selectedSuggestion, matchingSuggestions),\n onHover: setSelectedSuggestion,\n onSelect: onSuggestionSelected,\n scrollIntoView: true,\n __experimentalRenderItem: __experimentalRenderItem\n })]\n })\n })\n });\n /* eslint-enable jsx-a11y/no-static-element-interactions */\n}\nexport default ComboboxControl;"],
"mappings": "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",
"names": ["withFocusOutside", "_jsx", "BaseControl", "clsx", "_jsxs", "TokenInput", "Spinner", "Button", "SuggestionsList"]
}