UNPKG

stencil-click-outside

Version:

Decorator for StencilJs to run annotated method on click outside of component.

98 lines (97 loc) 3.32 kB
import { Build as BUILD, getElement } from '@stencil/core'; const ClickOutsideOptionsDefaults = { triggerEvents: "click", exclude: "" }; /** * Call this function as soon as the click outside of annotated method's host is done. * @example ``` @ClickOutside() callback() { // this will run when click outside of element (host component) is done. } ``` */ export function ClickOutside(opt = ClickOutsideOptionsDefaults) { return (proto, methodName) => { // this is to resolve the 'compiler optimization issue': // lifecycle events not being called when not explicitly declared in at least one of components from bundle BUILD.connectedCallback = true; BUILD.disconnectedCallback = true; const { connectedCallback, disconnectedCallback } = proto; proto.connectedCallback = function () { const host = getElement(this); const method = this[methodName]; registerClickOutside(this, host, method, opt); return connectedCallback && connectedCallback.call(this); }; proto.disconnectedCallback = function () { const host = getElement(this); const method = this[methodName]; removeClickOutside(this, host, method, opt); return disconnectedCallback && disconnectedCallback.call(this); }; }; } /** * Register callback function for HTMLElement to be executed when user clicks outside of element. * @example ``` <span ref={spanEl => registerClickOutside(this, spanEl, () => this.test())}> Hello, World! </span>; ``` */ export function registerClickOutside(component, element, callback, opt = ClickOutsideOptionsDefaults) { const excludedNodes = getExcludedNodes(opt); getTriggerEvents(opt).forEach(triggerEvent => { window.addEventListener(triggerEvent, (e) => { initClickOutside(e, component, element, callback, excludedNodes); }, false); }); } /** * Remove click outside callback function for HTMLElement. */ export function removeClickOutside(component, element, callback, opt = ClickOutsideOptionsDefaults) { getTriggerEvents(opt).forEach(triggerEvent => { window.removeEventListener(triggerEvent, (e) => { initClickOutside(e, component, element, callback); }, false); }); } function initClickOutside(event, component, element, callback, excludedNodes) { const target = event.target; if (!element.contains(target) && !isExcluded(target, excludedNodes)) { callback.call(component); } } function getTriggerEvents(opt) { if (opt.triggerEvents) { return opt.triggerEvents.split(",").map(e => e.trim()); } return ["click"]; } function getExcludedNodes(opt) { if (opt.exclude) { try { return Array.from(document.querySelectorAll(opt.exclude)); } catch (err) { console.warn(`@ClickOutside: Exclude: '${opt.exclude}' will not be evaluated. Check your exclude selector syntax.`, err); } } return; } function isExcluded(target, excudedNodes) { if (target && excudedNodes) { for (let excludedNode of excudedNodes) { if (excludedNode.contains(target)) { return true; } } } return false; }