UNPKG

@wordpress/components

Version:
121 lines (109 loc) 4.12 kB
import { createElement, Fragment } from "@wordpress/element"; /** * External dependencies */ import classnames from 'classnames'; /** * WordPress dependencies */ import { useLayoutEffect, useRef, useEffect, useState } from '@wordpress/element'; import { useAnchor } from '@wordpress/rich-text'; import { useMergeRefs, useRefEffect } from '@wordpress/compose'; /** * Internal dependencies */ import getDefaultUseItems from './get-default-use-items'; import Button from '../button'; import Popover from '../popover'; import { VisuallyHidden } from '../visually-hidden'; import { createPortal } from 'react-dom'; export function getAutoCompleterUI(autocompleter) { const useItems = autocompleter.useItems ? autocompleter.useItems : getDefaultUseItems(autocompleter); function AutocompleterUI(_ref) { let { filterValue, instanceId, listBoxId, className, selectedIndex, onChangeOptions, onSelect, onReset, reset, contentRef } = _ref; const [items] = useItems(filterValue); const popoverAnchor = useAnchor({ editableContentElement: contentRef.current }); const [needsA11yCompat, setNeedsA11yCompat] = useState(false); const popoverRef = useRef(null); const popoverRefs = useMergeRefs([popoverRef, useRefEffect(node => { if (!contentRef.current) return; // If the popover is rendered in a different document than // the content, we need to duplicate the options list in the // content document so that it's available to the screen // readers, which check the DOM ID based aira-* attributes. setNeedsA11yCompat(node.ownerDocument !== contentRef.current.ownerDocument); }, [contentRef])]); useOnClickOutside(popoverRef, reset); useLayoutEffect(() => { onChangeOptions(items); // Temporarily disabling exhaustive-deps to avoid introducing unexpected side effecst. // See https://github.com/WordPress/gutenberg/pull/41820 // eslint-disable-next-line react-hooks/exhaustive-deps }, [items]); if (items.length === 0) { return null; } const ListBox = _ref2 => { let { Component = 'div' } = _ref2; return createElement(Component, { id: listBoxId, role: "listbox", className: "components-autocomplete__results" }, items.map((option, index) => createElement(Button, { key: option.key, id: `components-autocomplete-item-${instanceId}-${option.key}`, role: "option", "aria-selected": index === selectedIndex, disabled: option.isDisabled, className: classnames('components-autocomplete__result', className, { 'is-selected': index === selectedIndex }), onClick: () => onSelect(option) }, option.label))); }; return createElement(Fragment, null, createElement(Popover, { focusOnMount: false, onClose: onReset, placement: "top-start", className: "components-autocomplete__popover", anchor: popoverAnchor, ref: popoverRefs }, createElement(ListBox, null)), contentRef.current && needsA11yCompat && createPortal(createElement(ListBox, { Component: VisuallyHidden }), contentRef.current.ownerDocument.body)); } return AutocompleterUI; } function useOnClickOutside(ref, handler) { useEffect(() => { const listener = event => { // Do nothing if clicking ref's element or descendent elements, or if the ref is not referencing an element if (!ref.current || ref.current.contains(event.target)) { return; } handler(event); }; document.addEventListener('mousedown', listener); document.addEventListener('touchstart', listener); return () => { document.removeEventListener('mousedown', listener); document.removeEventListener('touchstart', listener); }; // Disable reason: `ref` is a ref object and should not be included in a // hook's dependency list. // eslint-disable-next-line react-hooks/exhaustive-deps }, [handler]); } //# sourceMappingURL=autocompleter-ui.js.map