remirror/remirror

View on GitHub
packages/a11y-status/src/a11y-status.ts

Summary

Maintainability
A
0 mins
Test Coverage
A
94%
import { debounce } from 'throttle-debounce';

// Code taken from https://github.com/downshift-js/downshift/blob/master/src/set-a11y-status.js#L1

let statusDiv: HTMLDivElement | undefined;

/**
 * Get the status node or create it if it does not already exist.
 *
 * @param doc - document passed by the user.
 * @returns the singleton status node.
 */
const getStatusDiv = (doc = document) => {
  if (statusDiv) {
    return statusDiv;
  }

  statusDiv = doc.createElement('div');
  statusDiv.setAttribute('id', 'a11y-status-message');
  statusDiv.setAttribute('role', 'status');
  statusDiv.setAttribute('aria-live', 'polite');
  statusDiv.setAttribute('aria-relevant', 'additions text');
  Object.assign(statusDiv.style, {
    border: '0',
    clip: 'rect(0 0 0 0)',
    height: '1px',
    margin: '-1px',
    overflow: 'hidden',
    padding: '0',
    position: 'absolute',
    width: '1px',
  });

  doc.body.append(statusDiv);
  return statusDiv;
};

/**
 * Remove the current live status when no changes occur within a 500ms period.
 */
const cleanupStatus = debounce(500, () => {
  getStatusDiv().textContent = '';
});

/**
 * Set the status of the created live region so that screen readers
 * announce the changes for a11y.
 *
 * @remarks
 *
 * The status is using a singleton html div. This means that the same invisible
 * live status element is used for the lifetime of your code.
 *
 * @param status the status message
 * @param doc document passed by the user.
 */
export const setStatus = (status: string, doc?: Document): void => {
  const div = getStatusDiv(doc);

  if (!status) {
    return;
  }

  div.textContent = status;
  cleanupStatus();
};