UNPKG

@awsui/components-react

Version:

On July 19th, 2022, we launched [Cloudscape Design System](https://cloudscape.design). Cloudscape is an evolution of AWS-UI. It consists of user interface guidelines, front-end components, design resources, and development tools for building intuitive, en

123 lines • 7.26 kB
import { __rest } from "tslib"; // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react'; import clsx from 'clsx'; import { warnOnce } from '@awsui/component-toolkit/internal'; import { getBaseProps } from '../internal/base-component'; import { getAllFocusables } from '../internal/components/focus-lock/utils'; import { SingleTabStopNavigationProvider, } from '../internal/context/single-tab-stop-navigation-context'; import { hasModifierKeys } from '../internal/events'; import { useMergeRefs } from '../internal/hooks/use-merge-refs'; import { KeyCode } from '../internal/keycode'; import { circleIndex } from '../internal/utils/circle-index'; import handleKey from '../internal/utils/handle-key'; import ItemElement from './item-element.js'; import styles from './styles.css.js'; import testUtilStyles from './test-classes/styles.css.js'; const InternalButtonGroup = forwardRef((_a, ref) => { var { items = [], onItemClick, onFilesChange, ariaLabel, dropdownExpandToViewport, __internalRootRef = null } = _a, props = __rest(_a, ["items", "onItemClick", "onFilesChange", "ariaLabel", "dropdownExpandToViewport", "__internalRootRef"]); const baseProps = getBaseProps(props); const focusedIdRef = useRef(null); const navigationAPI = useRef(null); const containerObjectRef = useRef(null); const containerRef = useMergeRefs(containerObjectRef, __internalRootRef); const itemsRef = useRef({}); const [tooltip, setTooltip] = useState(null); useImperativeHandle(ref, () => ({ focus: id => { var _a; (_a = itemsRef.current[id]) === null || _a === void 0 ? void 0 : _a.focus(); }, })); function getNextFocusTarget() { var _a, _b; if (containerObjectRef.current) { const buttons = Array.from(containerObjectRef.current.querySelectorAll(`.${testUtilStyles.item}`)); const activeButtons = buttons.filter(button => !button.disabled); return (_b = (_a = activeButtons.find(button => button.dataset.itemid === focusedIdRef.current)) !== null && _a !== void 0 ? _a : activeButtons[0]) !== null && _b !== void 0 ? _b : null; } return null; } function onUnregisterActive(focusableElement) { var _a; // Only refocus when the node is actually removed (no such ID anymore). const target = (_a = navigationAPI.current) === null || _a === void 0 ? void 0 : _a.getFocusTarget(); if (target && target.dataset.itemid !== focusableElement.dataset.itemid) { target.focus(); } } useEffect(() => { var _a; (_a = navigationAPI.current) === null || _a === void 0 ? void 0 : _a.updateFocusTarget(); }); function onFocus(event) { var _a; if (event.target instanceof HTMLElement && event.target.dataset.itemid) { focusedIdRef.current = event.target.dataset.itemid; } (_a = navigationAPI.current) === null || _a === void 0 ? void 0 : _a.updateFocusTarget(); } function onBlur() { var _a; (_a = navigationAPI.current) === null || _a === void 0 ? void 0 : _a.updateFocusTarget(); } function onKeyDown(event) { var _a; const focusTarget = (_a = navigationAPI.current) === null || _a === void 0 ? void 0 : _a.getFocusTarget(); const specialKeys = [KeyCode.right, KeyCode.left, KeyCode.end, KeyCode.home, KeyCode.pageUp, KeyCode.pageDown]; if (hasModifierKeys(event) || specialKeys.indexOf(event.keyCode) === -1) { return; } if (!containerObjectRef.current || !focusTarget) { return; } // Ignore navigation when the focused element is not an item. if (document.activeElement && !document.activeElement.matches(`.${testUtilStyles.item}`)) { return; } event.preventDefault(); const focusables = getFocusablesFrom(containerObjectRef.current); const activeIndex = focusables.indexOf(focusTarget); handleKey(event, { onHome: () => focusElement(focusables[0]), onEnd: () => focusElement(focusables[focusables.length - 1]), onInlineStart: () => focusElement(focusables[circleIndex(activeIndex - 1, [0, focusables.length - 1])]), onInlineEnd: () => focusElement(focusables[circleIndex(activeIndex + 1, [0, focusables.length - 1])]), }); } function focusElement(element) { element.focus(); } // List all non-disabled and registered focusables: those are eligible for keyboard navigation. function getFocusablesFrom(target) { function isElementRegistered(element) { var _a, _b; return (_b = (_a = navigationAPI.current) === null || _a === void 0 ? void 0 : _a.isRegistered(element)) !== null && _b !== void 0 ? _b : false; } function isElementDisabled(element) { if (element instanceof HTMLButtonElement) { return element.disabled; } return false; } return getAllFocusables(target).filter(el => isElementRegistered(el) && !isElementDisabled(el)); } return (React.createElement("div", Object.assign({}, baseProps, { className: clsx(styles.root, testUtilStyles['button-group'], baseProps.className), ref: containerRef, role: "toolbar", "aria-label": ariaLabel, onFocus: onFocus, onBlur: onBlur, onKeyDown: onKeyDown }), React.createElement(SingleTabStopNavigationProvider, { ref: navigationAPI, navigationActive: true, getNextFocusTarget: getNextFocusTarget, onUnregisterActive: onUnregisterActive }, items.map((itemOrGroup, index) => { var _a; const itemContent = (item) => (React.createElement(ItemElement, { key: item.id, item: item, dropdownExpandToViewport: dropdownExpandToViewport, tooltip: tooltip, setTooltip: setTooltip, onItemClick: onItemClick, onFilesChange: onFilesChange, ref: element => (itemsRef.current[item.id] = element) })); const isGroupBefore = ((_a = items[index - 1]) === null || _a === void 0 ? void 0 : _a.type) === 'group'; const currentItem = items[index]; const isGroupNow = (currentItem === null || currentItem === void 0 ? void 0 : currentItem.type) === 'group'; const shouldAddDivider = isGroupBefore || (!isGroupBefore && isGroupNow && index !== 0); if (isGroupNow && currentItem.items.length === 0) { warnOnce('ButtonGroup', 'Empty group detected. Empty groups are not allowed.'); } return (React.createElement(React.Fragment, { key: itemOrGroup.type === 'group' ? index : itemOrGroup.id }, shouldAddDivider && React.createElement("div", { className: styles.divider }), itemOrGroup.type === 'group' ? (React.createElement("div", { key: index, role: "group", "aria-label": itemOrGroup.text, className: styles.group }, itemOrGroup.items.map(item => itemContent(item)))) : (itemContent(itemOrGroup)))); })))); }); export default InternalButtonGroup; //# sourceMappingURL=internal.js.map