react-beautiful-dnd-next
Version:
Beautiful and accessible drag and drop for lists with React
73 lines (60 loc) • 2.12 kB
JavaScript
// @flow
import isElement from '../../is-type-of-element/is-element';
export type TagNameMap = {
[tagName: string]: true,
};
export const interactiveTagNames: TagNameMap = {
input: true,
button: true,
textarea: true,
select: true,
option: true,
optgroup: true,
video: true,
audio: true,
};
const isAnInteractiveElement = (
parent: Element,
current: ?Element,
): boolean => {
if (current == null) {
return false;
}
// Most interactive elements cannot have children. However, some can such as 'button'.
// See 'Permitted content' on https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button
// Rather than having two different functions we can consolidate our checks into this single
// function to keep things simple.
// There is no harm checking if the parent has an interactive tag name even if it cannot have
// any children. We need to perform this loop anyway to check for the contenteditable attribute
const hasAnInteractiveTag: boolean = Boolean(
interactiveTagNames[current.tagName.toLowerCase()],
);
if (hasAnInteractiveTag) {
return true;
}
// contenteditable="true" or contenteditable="" are valid ways
// of creating a contenteditable container
// https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/contenteditable
const attribute: ?string = current.getAttribute('contenteditable');
if (attribute === 'true' || attribute === '') {
return true;
}
// nothing more can be done and no results found
if (current === parent) {
return false;
}
// recursion to check parent
return isAnInteractiveElement(parent, current.parentElement);
};
export default (event: Event, canDragInteractiveElements: boolean): boolean => {
// Allowing drag with all element types
if (canDragInteractiveElements) {
return true;
}
const { target, currentTarget } = event;
// Technically target and currentTarget are EventTarget's and do not have to be elements
if (!isElement(target) || !isElement(currentTarget)) {
return true;
}
return !isAnInteractiveElement(currentTarget, target);
};