react-beautiful-dnd-next
Version:
Beautiful and accessible drag and drop for lists with React
85 lines (67 loc) • 2.38 kB
JavaScript
// @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;
}