index.mjs 3.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. // Utilities
  2. import { attachedRoot } from "../../util/index.mjs"; // Types
  3. function defaultConditional() {
  4. return true;
  5. }
  6. function checkEvent(e, el, binding) {
  7. // The include element callbacks below can be expensive
  8. // so we should avoid calling them when we're not active.
  9. // Explicitly check for false to allow fallback compatibility
  10. // with non-toggleable components
  11. if (!e || checkIsActive(e, binding) === false) return false;
  12. // If we're clicking inside the shadowroot, then the app root doesn't get the same
  13. // level of introspection as to _what_ we're clicking. We want to check to see if
  14. // our target is the shadowroot parent container, and if it is, ignore.
  15. const root = attachedRoot(el);
  16. if (typeof ShadowRoot !== 'undefined' && root instanceof ShadowRoot && root.host === e.target) return false;
  17. // Check if additional elements were passed to be included in check
  18. // (click must be outside all included elements, if any)
  19. const elements = (typeof binding.value === 'object' && binding.value.include || (() => []))();
  20. // Add the root element for the component this directive was defined on
  21. elements.push(el);
  22. // Check if it's a click outside our elements, and then if our callback returns true.
  23. // Non-toggleable components should take action in their callback and return falsy.
  24. // Toggleable can return true if it wants to deactivate.
  25. // Note that, because we're in the capture phase, this callback will occur before
  26. // the bubbling click event on any outside elements.
  27. return !elements.some(el => el?.contains(e.target));
  28. }
  29. function checkIsActive(e, binding) {
  30. const isActive = typeof binding.value === 'object' && binding.value.closeConditional || defaultConditional;
  31. return isActive(e);
  32. }
  33. function directive(e, el, binding) {
  34. const handler = typeof binding.value === 'function' ? binding.value : binding.value.handler;
  35. // Clicks in the Shadow DOM change their target while using setTimeout, so the original target is saved here
  36. e.shadowTarget = e.target;
  37. el._clickOutside.lastMousedownWasOutside && checkEvent(e, el, binding) && setTimeout(() => {
  38. checkIsActive(e, binding) && handler && handler(e);
  39. }, 0);
  40. }
  41. function handleShadow(el, callback) {
  42. const root = attachedRoot(el);
  43. callback(document);
  44. if (typeof ShadowRoot !== 'undefined' && root instanceof ShadowRoot) {
  45. callback(root);
  46. }
  47. }
  48. export const ClickOutside = {
  49. // [data-app] may not be found
  50. // if using bind, inserted makes
  51. // sure that the root element is
  52. // available, iOS does not support
  53. // clicks on body
  54. mounted(el, binding) {
  55. const onClick = e => directive(e, el, binding);
  56. const onMousedown = e => {
  57. el._clickOutside.lastMousedownWasOutside = checkEvent(e, el, binding);
  58. };
  59. handleShadow(el, app => {
  60. app.addEventListener('click', onClick, true);
  61. app.addEventListener('mousedown', onMousedown, true);
  62. });
  63. if (!el._clickOutside) {
  64. el._clickOutside = {
  65. lastMousedownWasOutside: false
  66. };
  67. }
  68. el._clickOutside[binding.instance.$.uid] = {
  69. onClick,
  70. onMousedown
  71. };
  72. },
  73. beforeUnmount(el, binding) {
  74. if (!el._clickOutside) return;
  75. handleShadow(el, app => {
  76. if (!app || !el._clickOutside?.[binding.instance.$.uid]) return;
  77. const {
  78. onClick,
  79. onMousedown
  80. } = el._clickOutside[binding.instance.$.uid];
  81. app.removeEventListener('click', onClick, true);
  82. app.removeEventListener('mousedown', onMousedown, true);
  83. });
  84. delete el._clickOutside[binding.instance.$.uid];
  85. }
  86. };
  87. export default ClickOutside;
  88. //# sourceMappingURL=index.mjs.map