@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
91 lines • 4.16 kB
JavaScript
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React, { useEffect, useImperativeHandle, useRef } from 'react';
import clsx from 'clsx';
import { useMergeRefs } from '@awsui/component-toolkit/internal';
import { getBaseProps } from '../internal/base-component';
import { LiveRegionController } from './controller';
import styles from './styles.css.js';
import testUtilStyles from './test-classes/styles.css.js';
export default React.forwardRef(function InternalLiveRegion({ assertive = false, hidden = false, tagName: TagName = 'div', delay, sources, children, __internalRootRef, className, ...restProps }, ref) {
const baseProps = getBaseProps(restProps);
const childrenRef = useRef(null);
const mergedRef = useMergeRefs(childrenRef, __internalRootRef);
useEffect(() => {
// We have to do this because `inert` isn't properly supported until
// React 19 and this seems much more maintainable than version detection.
// `inert` is better than `hidden` because it also blocks pointer and
// focus events as well as hiding the contents from screen readers.
// https://github.com/facebook/react/issues/17157
if (childrenRef.current) {
childrenRef.current.inert = hidden;
}
}, [hidden]);
// Initialize the live region controller inside an effect. We have to do this
// because the controller depends on DOM elements, which aren't available on the
// server.
const liveRegionControllerRef = useRef();
useEffect(() => {
const liveRegionController = new LiveRegionController(assertive ? 'assertive' : 'polite');
liveRegionControllerRef.current = liveRegionController;
return () => {
liveRegionController.destroy();
liveRegionControllerRef.current = undefined;
};
}, [assertive]);
const getContent = () => {
if (sources) {
return getSourceContent(sources);
}
if (childrenRef.current) {
return extractTextContent(childrenRef.current);
}
};
// Call the controller on every render. The controller will deduplicate the
// message against the previous announcement internally.
useEffect(() => {
var _a;
(_a = liveRegionControllerRef.current) === null || _a === void 0 ? void 0 : _a.announce({ message: getContent(), delay });
});
useImperativeHandle(ref, () => ({
reannounce() {
var _a;
(_a = liveRegionControllerRef.current) === null || _a === void 0 ? void 0 : _a.announce({ message: getContent(), delay, forceReannounce: true });
},
}));
return (React.createElement(TagName, { ref: mergedRef, ...baseProps, className: clsx(styles.root, testUtilStyles.root, className), hidden: hidden }, children));
});
const processNode = (childNode) => {
if (childNode.nodeType === Node.TEXT_NODE) {
return childNode.textContent || '';
}
if (childNode.nodeType === Node.ELEMENT_NODE) {
return extractTextContent(childNode);
}
return '';
};
export function extractTextContent(node) {
var _a;
// We use the text content of the node as the announcement text.
// This only extracts text content from the node including all its children which is enough for now.
// To make it more powerful, it is possible to create a more sophisticated extractor with respect to
// ARIA properties to ignore aria-hidden nodes and read ARIA labels from the live content.
if (!node || !((_a = node === null || node === void 0 ? void 0 : node.childNodes) === null || _a === void 0 ? void 0 : _a.length)) {
return '';
}
return Array.from(node.childNodes, processNode).join(' ').replace(/\s+/g, ' ').trim();
}
function getSourceContent(source) {
return source
.map(item => {
if (!item || typeof item === 'string') {
return item;
}
if (item.current) {
return extractTextContent(item.current);
}
})
.filter(Boolean)
.join(' ');
}
//# sourceMappingURL=internal.js.map