122 lines
2.9 KiB
JavaScript
122 lines
2.9 KiB
JavaScript
// saladui/core/click-outside.js
|
|
/**
|
|
* ClickOutsideMonitor utility for SaladUI components
|
|
* Detects clicks outside of specified elements and triggers a callback
|
|
*/
|
|
class ClickOutsideMonitor {
|
|
/**
|
|
* Create a click outside monitor
|
|
*
|
|
* @param {HTMLElement|HTMLElement[]} elements - Element(s) to monitor clicks outside of
|
|
* @param {Function} callback - Function to call when click outside is detected
|
|
* @param {Object} options - Additional options
|
|
*/
|
|
constructor(elements, callback, options = {}) {
|
|
// Normalize elements to an array
|
|
this.elements = Array.isArray(elements) ? elements : [elements];
|
|
this.callback = callback;
|
|
this.options = {
|
|
// Whether to also monitor touchend events (for mobile)
|
|
trackTouch: true,
|
|
// Optional filter function to determine if a click should trigger the callback
|
|
filter: null,
|
|
...options,
|
|
};
|
|
|
|
this.active = false;
|
|
|
|
// Bind methods to maintain correct this context
|
|
this.handleClick = this.handleClick.bind(this);
|
|
this.handleTouchEnd = this.handleTouchEnd.bind(this);
|
|
}
|
|
|
|
/**
|
|
* Start monitoring clicks outside the element(s)
|
|
*/
|
|
start() {
|
|
if (this.active) return;
|
|
|
|
// Add document-level event listeners
|
|
document.addEventListener("click", this.handleClick);
|
|
if (this.options.trackTouch) {
|
|
document.addEventListener("touchend", this.handleTouchEnd);
|
|
}
|
|
|
|
this.active = true;
|
|
}
|
|
|
|
/**
|
|
* Stop monitoring clicks
|
|
*/
|
|
stop() {
|
|
if (!this.active) return;
|
|
|
|
// Remove document-level event listeners
|
|
document.removeEventListener("click", this.handleClick);
|
|
if (this.options.trackTouch) {
|
|
document.removeEventListener("touchend", this.handleTouchEnd);
|
|
}
|
|
|
|
this.active = false;
|
|
}
|
|
|
|
/**
|
|
* Handle click events
|
|
*/
|
|
handleClick(event) {
|
|
this.checkOutsideClick(event);
|
|
}
|
|
|
|
/**
|
|
* Handle touchend events
|
|
*/
|
|
handleTouchEnd(event) {
|
|
this.checkOutsideClick(event);
|
|
}
|
|
|
|
/**
|
|
* Check if click/touch was outside monitored elements
|
|
*/
|
|
checkOutsideClick(event) {
|
|
// Skip if not active or no callback
|
|
if (!this.active || !this.callback) return;
|
|
|
|
// Apply custom filter if provided
|
|
if (this.options.filter && !this.options.filter(event)) {
|
|
return;
|
|
}
|
|
|
|
// Get the event target
|
|
const target = event.target;
|
|
|
|
// Check if click was outside all monitored elements
|
|
const isOutside = !this.elements.some((element) => {
|
|
return element && (element === target || element.contains(target));
|
|
});
|
|
|
|
// If click was outside, call the callback
|
|
if (isOutside) {
|
|
this.callback(event);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update the monitored elements
|
|
*/
|
|
updateElements(elements) {
|
|
this.elements = Array.isArray(elements) ? elements : [elements];
|
|
}
|
|
|
|
/**
|
|
* Clean up all references
|
|
*/
|
|
destroy() {
|
|
this.stop();
|
|
this.elements = null;
|
|
this.callback = null;
|
|
this.options = null;
|
|
}
|
|
}
|
|
|
|
export default ClickOutsideMonitor;
|