2023-01-27 15:38:31 +01:00
|
|
|
const ignoredClasses = [".flatpickr-calendar", ".spectrum-Popover"]
|
2022-11-25 13:08:34 +01:00
|
|
|
let clickHandlers = []
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle a body click event
|
|
|
|
*/
|
|
|
|
const handleClick = event => {
|
2023-01-18 14:56:53 +01:00
|
|
|
// Ignore click if this is an ignored class
|
2023-03-31 13:00:32 +02:00
|
|
|
if (event.target.closest('[data-ignore-click-outside="true"]')) {
|
|
|
|
return
|
|
|
|
}
|
2022-11-25 13:08:34 +01:00
|
|
|
for (let className of ignoredClasses) {
|
|
|
|
if (event.target.closest(className)) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Process handlers
|
|
|
|
clickHandlers.forEach(handler => {
|
2023-01-18 14:56:53 +01:00
|
|
|
if (handler.element.contains(event.target)) {
|
|
|
|
return
|
2021-03-31 11:59:07 +02:00
|
|
|
}
|
2023-01-18 14:56:53 +01:00
|
|
|
|
|
|
|
// Ignore clicks for modals, unless the handler is registered from a modal
|
2023-01-27 15:38:31 +01:00
|
|
|
const sourceInModal = handler.anchor.closest(".spectrum-Modal") != null
|
2023-01-18 14:56:53 +01:00
|
|
|
const clickInModal = event.target.closest(".spectrum-Modal") != null
|
|
|
|
if (clickInModal && !sourceInModal) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
handler.callback?.(event)
|
2022-11-25 13:08:34 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
document.documentElement.addEventListener("click", handleClick, true)
|
2023-03-31 13:00:32 +02:00
|
|
|
// document.documentElement.addEventListener("contextmenu", handleClick, true)
|
2022-11-25 13:08:34 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds or updates a click handler
|
|
|
|
*/
|
2023-01-27 15:38:31 +01:00
|
|
|
const updateHandler = (id, element, anchor, callback) => {
|
2022-11-25 13:08:34 +01:00
|
|
|
let existingHandler = clickHandlers.find(x => x.id === id)
|
|
|
|
if (!existingHandler) {
|
2023-01-27 15:38:31 +01:00
|
|
|
clickHandlers.push({ id, element, anchor, callback })
|
2022-11-25 13:08:34 +01:00
|
|
|
} else {
|
|
|
|
existingHandler.callback = callback
|
2021-03-31 11:59:07 +02:00
|
|
|
}
|
2022-11-25 13:08:34 +01:00
|
|
|
}
|
2021-03-31 11:59:07 +02:00
|
|
|
|
2022-11-25 13:08:34 +01:00
|
|
|
/**
|
|
|
|
* Removes a click handler
|
|
|
|
*/
|
|
|
|
const removeHandler = id => {
|
|
|
|
clickHandlers = clickHandlers.filter(x => x.id !== id)
|
|
|
|
}
|
2021-03-31 11:59:07 +02:00
|
|
|
|
2022-11-25 13:08:34 +01:00
|
|
|
/**
|
|
|
|
* Svelte action to apply a click outside handler for a certain element
|
2023-01-27 15:38:31 +01:00
|
|
|
* opts.anchor is an optional param specifying the real root source of the
|
|
|
|
* component being observed. This is required for things like popovers, where
|
|
|
|
* the element using the clickoutside action is the popover, but the popover is
|
|
|
|
* rendered at the root of the DOM somewhere, whereas the popover anchor is the
|
|
|
|
* element we actually want to consider when determining the source component.
|
2022-11-25 13:08:34 +01:00
|
|
|
*/
|
2023-01-27 15:38:31 +01:00
|
|
|
export default (element, opts) => {
|
2022-11-25 13:08:34 +01:00
|
|
|
const id = Math.random()
|
2023-01-27 15:38:31 +01:00
|
|
|
const update = newOpts => {
|
|
|
|
const callback = newOpts?.callback || newOpts
|
|
|
|
const anchor = newOpts?.anchor || element
|
|
|
|
updateHandler(id, element, anchor, callback)
|
|
|
|
}
|
|
|
|
update(opts)
|
2021-03-31 11:59:07 +02:00
|
|
|
return {
|
2023-01-27 15:38:31 +01:00
|
|
|
update,
|
2022-11-25 13:08:34 +01:00
|
|
|
destroy: () => removeHandler(id),
|
2021-03-31 11:59:07 +02:00
|
|
|
}
|
|
|
|
}
|