UNPKG

react-beautiful-dnd-next

Version:

Beautiful and accessible drag and drop for lists with React

85 lines (67 loc) 2.38 kB
// @flow import { useRef, useEffect } from 'react'; import invariant from 'tiny-invariant'; import { useMemo, useCallback } from 'use-memo-one'; import type { Announce } from '../../types'; import { warning } from '../../dev-warning'; import getBodyElement from '../get-body-element'; // https://allyjs.io/tutorials/hiding-elements.html // Element is visually hidden but is readable by screen readers const visuallyHidden: Object = { position: 'absolute', width: '1px', height: '1px', margin: '-1px', border: '0', padding: '0', overflow: 'hidden', clip: 'rect(0 0 0 0)', // for if 'clip' is ever removed 'clip-path': 'inset(100%)', }; export const getId = (uniqueId: number): string => `react-beautiful-dnd-announcement-${uniqueId}`; export default function useAnnouncer(uniqueId: number): Announce { const id: string = useMemo(() => getId(uniqueId), [uniqueId]); const ref = useRef<?HTMLElement>(null); useEffect(() => { invariant(!ref.current, 'Announcement node already mounted'); const el: HTMLElement = document.createElement('div'); ref.current = el; // identifier el.id = id; // Aria live region // will force itself to be read el.setAttribute('aria-live', 'assertive'); el.setAttribute('role', 'log'); // 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 () => { const toBeRemoved: ?HTMLElement = ref.current; invariant(toBeRemoved, 'Cannot unmount announcement node'); // Remove from body getBodyElement().removeChild(toBeRemoved); ref.current = null; }; }, [id]); const announce: Announce = useCallback((message: string): void => { const el: ?HTMLElement = 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; }