UNPKG

@shopify/polaris

Version:

Shopify’s admin product component library

267 lines (231 loc) • 9.52 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var React = require('react'); var debounce = require('lodash/debounce'); var css = require('../../utilities/css.js'); var useToggle = require('../../utilities/use-toggle.js'); var types = require('../../types.js'); var closestParentMatch = require('../../utilities/closest-parent-match.js'); var scrollIntoView = require('../../utilities/scroll-into-view.js'); var Listbox$1 = require('./Listbox.scss.js'); var hooks$1 = require('../../utilities/combobox/hooks.js'); var selectors = require('./components/Section/selectors.js'); var context = require('../../utilities/listbox/context.js'); var TextOption = require('./components/TextOption/TextOption.js'); var Loading = require('./components/Loading/Loading.js'); var Section = require('./components/Section/Section.js'); var Header = require('./components/Header/Header.js'); var Action = require('./components/Action/Action.js'); var hooks = require('../../utilities/unique-id/hooks.js'); var KeypressListener = require('../KeypressListener/KeypressListener.js'); var VisuallyHidden = require('../VisuallyHidden/VisuallyHidden.js'); var Option = require('./components/Option/Option.js'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var React__default = /*#__PURE__*/_interopDefaultLegacy(React); var debounce__default = /*#__PURE__*/_interopDefaultLegacy(debounce); const scrollable = { props: { 'data-polaris-scrollable': true }, selector: '[data-polaris-scrollable]' }; const LISTBOX_OPTION_SELECTOR = '[data-listbox-option]'; const LISTBOX_OPTION_VALUE_ATTRIBUTE = 'data-listbox-option-value'; const DATA_ATTRIBUTE = 'data-focused'; function Listbox({ children, enableKeyboardControl, accessibilityLabel, onSelect }) { const listboxClassName = css.classNames(Listbox$1['default'].Listbox); const { value: keyboardEventsEnabled, setTrue: enableKeyboardEvents, setFalse: disableKeyboardEvents } = useToggle.useToggle(Boolean(enableKeyboardControl)); const listId = hooks.useUniqueId('Listbox'); const scrollableRef = React.useRef(null); const listboxRef = React.useRef(null); const [loading, setLoading] = React.useState(); const [currentActiveOption, setCurrentActiveOption] = React.useState(); const { setActiveOptionId, setListboxId, listboxId, textFieldLabelId, onOptionSelected, onKeyToBottom, textFieldFocused } = hooks$1.useComboboxListbox(); const inCombobox = Boolean(setActiveOptionId); React.useEffect(() => { if (setListboxId && !listboxId) { setListboxId(listId); } }, [setListboxId, listboxId, listId]); React.useEffect(() => { if (!currentActiveOption || !setActiveOptionId) return; setActiveOptionId(currentActiveOption.domId); }, [currentActiveOption, setActiveOptionId]); // eslint-disable-next-line react-hooks/exhaustive-deps const handleScrollIntoView = React.useCallback(debounce__default['default']((option, first) => { if (scrollableRef.current) { const { element } = option; const focusTarget = first ? closestParentMatch.closestParentMatch(element, selectors.listboxSectionDataSelector.selector) || element : element; scrollIntoView.scrollIntoView(focusTarget, scrollableRef.current); } }, 15), []); const handleChangeActiveOption = React.useCallback(nextOption => { setCurrentActiveOption(currentActiveOption => { if (currentActiveOption) { currentActiveOption.element.removeAttribute(DATA_ATTRIBUTE); } if (nextOption) { nextOption.element.setAttribute(DATA_ATTRIBUTE, 'true'); if (scrollableRef.current) { const first = getNavigableOptions().findIndex(element => element.id === nextOption.element.id) === 0; handleScrollIntoView(nextOption, first); } return nextOption; } else { return undefined; } }); }, [handleScrollIntoView]); React.useEffect(() => { if (listboxRef.current) { scrollableRef.current = listboxRef.current.closest(scrollable.selector); } }, []); React.useEffect(() => { if (enableKeyboardControl && !keyboardEventsEnabled) { enableKeyboardEvents(); } }, [enableKeyboardControl, keyboardEventsEnabled, enableKeyboardEvents]); const onOptionSelect = React.useCallback(option => { handleChangeActiveOption(option); if (onOptionSelected) { onOptionSelected(); } if (onSelect) onSelect(option.value); }, [handleChangeActiveOption, onSelect, onOptionSelected]); const listboxContext = React.useMemo(() => ({ onOptionSelect, setLoading }), [onOptionSelect]); function findNextValidOption(type) { const isUp = type === 'up'; const navItems = getNavigableOptions(); let nextElement = currentActiveOption === null || currentActiveOption === void 0 ? void 0 : currentActiveOption.element; let count = -1; while (count++ < navItems.length) { var _nextElement2; let nextIndex; if (nextElement) { var _nextElement; const currentId = (_nextElement = nextElement) === null || _nextElement === void 0 ? void 0 : _nextElement.id; const currentIndex = navItems.findIndex(currentNavItem => currentNavItem.id === currentId); let increment = isUp ? -1 : 1; if (currentIndex === 0 && isUp) { increment = navItems.length - 1; } else if (currentIndex === navItems.length - 1 && !isUp) { increment = -(navItems.length - 1); } nextIndex = currentIndex + increment; nextElement = navItems[nextIndex]; } else { nextIndex = isUp ? navItems.length - 1 : 0; nextElement = navItems[nextIndex]; } if (((_nextElement2 = nextElement) === null || _nextElement2 === void 0 ? void 0 : _nextElement2.getAttribute('aria-disabled')) === 'true') continue; if (nextIndex === navItems.length - 1 && onKeyToBottom) { onKeyToBottom(); } return nextElement; } return null; } function handleArrow(type, evt) { evt.preventDefault(); const nextValidElement = findNextValidOption(type); if (!nextValidElement) return; const nextOption = { domId: nextValidElement.id, value: nextValidElement.getAttribute(LISTBOX_OPTION_VALUE_ATTRIBUTE) || '', element: nextValidElement, disabled: nextValidElement.getAttribute('aria-disabled') === 'true' }; handleChangeActiveOption(nextOption); } function handleDownArrow(evt) { handleArrow('down', evt); } function handleUpArrow(evt) { handleArrow('up', evt); } function handleEnter(evt) { evt.preventDefault(); evt.stopPropagation(); if (currentActiveOption) { onOptionSelect(currentActiveOption); } } function handleFocus() { if (enableKeyboardControl) return; enableKeyboardEvents(); } function handleBlur(event) { event.stopPropagation(); if (keyboardEventsEnabled) { handleChangeActiveOption(); } if (enableKeyboardControl) return; disableKeyboardEvents(); } const listeners = keyboardEventsEnabled || textFieldFocused ? /*#__PURE__*/React__default['default'].createElement(React__default['default'].Fragment, null, /*#__PURE__*/React__default['default'].createElement(KeypressListener.KeypressListener, { keyEvent: "keydown", keyCode: types.Key.DownArrow, handler: handleDownArrow }), /*#__PURE__*/React__default['default'].createElement(KeypressListener.KeypressListener, { keyEvent: "keydown", keyCode: types.Key.UpArrow, handler: handleUpArrow }), /*#__PURE__*/React__default['default'].createElement(KeypressListener.KeypressListener, { keyEvent: "keydown", keyCode: types.Key.Enter, handler: handleEnter })) : null; return /*#__PURE__*/React__default['default'].createElement(React__default['default'].Fragment, null, listeners, /*#__PURE__*/React__default['default'].createElement(VisuallyHidden.VisuallyHidden, null, /*#__PURE__*/React__default['default'].createElement("div", { "aria-live": "polite" }, loading ? loading : null)), /*#__PURE__*/React__default['default'].createElement(context.ListboxContext.Provider, { value: listboxContext }, /*#__PURE__*/React__default['default'].createElement(context.WithinListboxContext.Provider, { value: true }, children ? /*#__PURE__*/React__default['default'].createElement("ul", { tabIndex: 0, role: "listbox", className: listboxClassName, "aria-label": inCombobox ? undefined : accessibilityLabel, "aria-labelledby": textFieldLabelId, "aria-busy": Boolean(loading), "aria-activedescendant": currentActiveOption && currentActiveOption.domId, id: listId, onFocus: inCombobox ? undefined : handleFocus, onBlur: inCombobox ? undefined : handleBlur, ref: listboxRef }, children) : null))); function getNavigableOptions() { var _listboxRef$current; return [...new Set((_listboxRef$current = listboxRef.current) === null || _listboxRef$current === void 0 ? void 0 : _listboxRef$current.querySelectorAll(LISTBOX_OPTION_SELECTOR))]; } } Listbox.Option = Option.Option; Listbox.TextOption = TextOption.TextOption; Listbox.Loading = Loading.Loading; Listbox.Section = Section.Section; Listbox.Header = Header.Header; Listbox.Action = Action.Action; exports.Listbox = Listbox; exports.scrollable = scrollable;