UNPKG

@hello-pangea/dnd

Version:

Beautiful and accessible drag and drop for lists with React

80 lines (64 loc) 2.51 kB
import { useRef, useEffect } from 'react'; import { useMemo, useCallback } from 'use-memo-one'; import type { Announce, ContextId } from '../../types'; import { warning } from '../../dev-warning'; import getBodyElement from '../get-body-element'; import visuallyHidden from '../visually-hidden-style'; export const getId = (contextId: ContextId): string => `rfd-announcement-${contextId}`; export default function useAnnouncer(contextId: ContextId): Announce { const id: string = useMemo(() => getId(contextId), [contextId]); const ref = useRef<HTMLElement | null>(null); useEffect( function setup() { const el: HTMLElement = document.createElement('div'); // storing reference for usage in announce ref.current = el; // identifier el.id = id; // Aria live region // will force itself to be read el.setAttribute('aria-live', 'assertive'); // must read the whole thing every time el.setAttribute('aria-atomic', 'true'); // hide the element visually Object.assign(el.style, visuallyHidden); // Add to body getBodyElement().appendChild(el); return function cleanup() { // Not clearing the ref as it might be used by announce before the timeout expires // unmounting after a timeout to let any announcements // during a mount be published setTimeout(function remove() { // checking if element exists as the body might have been changed by things like 'turbolinks' const body: HTMLBodyElement = getBodyElement(); if (body.contains(el)) { body.removeChild(el); } // if el was the current ref - clear it so that // we can get a warning if announce is called if (el === ref.current) { ref.current = null; } }); }; }, [id], ); const announce: Announce = useCallback((message: string): void => { const el: HTMLElement | null = ref.current; if (el) { el.textContent = message; return; } warning(` A screen reader message was trying to be announced but it was unable to do so. This can occur if you unmount your <DragDropContext /> in your onDragEnd. Consider calling provided.announce() before the unmount so that the instruction will not be lost for users relying on a screen reader. Message not passed to screen reader: "${message}" `); }, []); return announce; }