react-beautiful-dnd
Version:
Beautiful, accessible drag and drop for lists with React.js
89 lines (70 loc) • 2.19 kB
JavaScript
// @flow
import getDragHandleRef from './get-drag-handle-ref';
import type { DraggableId } from '../../../types';
type FocusRetainer = {|
retain: (draggableId: DraggableId) => void,
tryRestoreFocus: (draggableId: DraggableId, draggableRef: HTMLElement) => void,
|}
// our shared state
let retainingFocusFor: ?DraggableId = null;
// If we focus on
const clearRetentionOnFocusChange = (() => {
let isBound: boolean = false;
const bind = () => {
if (isBound) {
return;
}
isBound = true;
// Using capture: true as focus events do not bubble
// Additionally doing this prevents us from intercepting the initial
// focus event as it does not bubble up to this listener
// eslint-disable-next-line no-use-before-define
window.addEventListener('focus', onWindowFocusChange, { capture: true });
};
const unbind = () => {
if (!isBound) {
return;
}
isBound = false;
// eslint-disable-next-line no-use-before-define
window.removeEventListener('focus', onWindowFocusChange, { capture: true });
};
// focusin will fire after the focus event fires on the element
const onWindowFocusChange = () => {
// unbinding self after single use
unbind();
retainingFocusFor = null;
};
const result = () => bind();
result.cancel = () => unbind();
return result;
})();
const retain = (id: DraggableId) => {
retainingFocusFor = id;
clearRetentionOnFocusChange();
};
const tryRestoreFocus = (id: DraggableId, draggableRef: HTMLElement) => {
// Not needing to retain focus
if (!retainingFocusFor) {
return;
}
// Not needing to retain focus for this draggable
if (id !== retainingFocusFor) {
return;
}
// We are about to force focus onto a drag handle
retainingFocusFor = null;
// no need to clear it - we are already clearing it
clearRetentionOnFocusChange.cancel();
const dragHandleRef: ?HTMLElement = getDragHandleRef(draggableRef);
if (!dragHandleRef) {
console.warn('Could not find drag handle in the DOM to focus on it');
return;
}
dragHandleRef.focus();
};
const retainer: FocusRetainer = {
retain,
tryRestoreFocus,
};
export default retainer;