@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
JavaScript
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