Outlook_Addin_LLM/node_modules/@fluentui/react-aria/lib/AriaLiveAnnouncer/useDomAnnounce.js

125 lines
4.7 KiB
JavaScript

import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts';
import { createPriorityQueue, useTimeout } from '@fluentui/react-utilities';
import * as React from 'react';
/** The duration the message needs to be in present in DOM for screen readers to register a change and announce */ const MESSAGE_DURATION = 500;
const VISUALLY_HIDDEN_STYLES = {
clip: 'rect(0px, 0px, 0px, 0px)',
height: '1px',
margin: '-1px',
width: '1px',
position: 'absolute',
overflow: 'hidden',
textWrap: 'nowrap'
};
/* INTERNAL: implementation of the announcer using a live region element */ export const useDomAnnounce_unstable = ()=>{
const { targetDocument } = useFluent();
const timeoutRef = React.useRef(undefined);
const [setAnnounceTimeout, clearAnnounceTimeout] = useTimeout();
const elementRef = React.useRef(null);
const order = React.useRef(0);
// investigate alert implementation later
// const [alertList, setAlertList] = React.useState<string[]>([]);
const batchMessages = React.useRef([]);
const [messageQueue] = React.useState(()=>createPriorityQueue((a, b)=>{
if (a.priority !== b.priority) {
return b.priority - a.priority;
}
return a.createdAt - b.createdAt;
}));
const queueMessage = React.useCallback(()=>{
if (timeoutRef.current || !elementRef.current) {
return;
}
const runCycle = ()=>{
if (!elementRef.current) {
return;
}
if (targetDocument && messageQueue.peek()) {
// need a wrapping element for Narrator/Edge, which currently does not pick up text-only live region changes
// consistently
// if this is fixed, we can set textContent to the string directly
const wrappingEl = targetDocument.createElement('span');
wrappingEl.innerText = messageQueue.all().filter((msg)=>msg.message.trim().length > 0).reduce((prevText, currMsg)=>prevText + currMsg.message + '. ', '');
elementRef.current.innerText = '';
elementRef.current.appendChild(wrappingEl);
messageQueue.clear();
batchMessages.current = [];
// begin new cycle to clear (or update) messages
timeoutRef.current = setAnnounceTimeout(()=>{
runCycle();
}, MESSAGE_DURATION);
} else {
elementRef.current.textContent = '';
clearAnnounceTimeout();
timeoutRef.current = undefined;
}
};
runCycle();
}, [
clearAnnounceTimeout,
messageQueue,
setAnnounceTimeout,
targetDocument
]);
const announce = React.useCallback((message, options = {})=>{
const { alert = false, priority = 0, batchId } = options;
// check if message is an alert
if (alert) {
// TODO: alert implementation
// setAlertList([...alertList, message]);
}
const liveMessage = {
message,
createdAt: order.current++,
priority,
batchId
};
// check if batchId exists
if (batchId) {
// update associated msg if it does
const batchMessage = batchMessages.current.find((msg)=>msg.batchId === batchId);
if (batchMessage) {
// replace existing message in queue
messageQueue.remove(batchMessage.message);
// update list of existing batchIds w/ most recent message
batchMessage.message = liveMessage;
} else {
// update list of existing batchIds, add new if doesn't already exist
batchMessages.current = [
...batchMessages.current,
{
batchId,
message: liveMessage
}
];
}
}
// add new message
messageQueue.enqueue(liveMessage);
queueMessage();
}, [
messageQueue,
queueMessage
]);
React.useEffect(()=>{
if (!targetDocument) {
return;
}
const element = targetDocument.createElement('div');
element.setAttribute('aria-live', 'assertive');
Object.assign(element.style, VISUALLY_HIDDEN_STYLES);
targetDocument.body.append(element);
elementRef.current = element;
return ()=>{
element.remove();
elementRef.current = null;
clearAnnounceTimeout();
timeoutRef.current = undefined;
};
}, [
clearAnnounceTimeout,
targetDocument
]);
return announce;
};