import isHtmlElement from '../is-type-of-element/is-html-element'; export type InteractiveTagNames = typeof interactiveTagNames; export type InteractiveTagName = InteractiveTagNames[number]; export const interactiveTagNames = [ 'input', 'button', 'textarea', 'select', 'option', 'optgroup', 'video', 'audio', ] as const; function isAnInteractiveElement( parent: Element, current?: Element | null, ): 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 = ( interactiveTagNames as ReadonlyArray ).includes(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 | null = 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 function isEventInInteractiveElement( draggable: Element, event: Event, ): boolean { const target = event.target; if (!isHtmlElement(target)) { return false; } return isAnInteractiveElement(draggable, target); }