Initial Commit
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
// saladui/core/scroll-manager.js
|
||||
/**
|
||||
* ScrollManager utility for SaladUI components
|
||||
* Manages scroll and resize event handlers with optimized performance
|
||||
*/
|
||||
class ScrollManager {
|
||||
/**
|
||||
* Create a scroll manager to handle scroll and resize events
|
||||
*
|
||||
* @param {Function} updateCallback - Function to call when scroll/resize events occur
|
||||
* @param {Object} options - Additional options
|
||||
*/
|
||||
constructor(updateCallback, options = {}) {
|
||||
this.updateCallback = updateCallback;
|
||||
this.options = {
|
||||
// Use requestAnimationFrame for throttling
|
||||
useRAF: true,
|
||||
...options,
|
||||
};
|
||||
|
||||
this.scrollableParents = [];
|
||||
this.active = false;
|
||||
this.resizeObserver = null;
|
||||
this.animationFrameId = null;
|
||||
|
||||
// Bind methods to maintain correct context
|
||||
this.handleScroll = this.handleScroll.bind(this);
|
||||
this.handleResize = this.handleResize.bind(this);
|
||||
this.updatePosition = this.updatePosition.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start tracking scroll and resize events
|
||||
*
|
||||
* @param {HTMLElement} referenceElement - Element to track scrollable parents for
|
||||
* @param {HTMLElement} targetElement - Optional element to observe with ResizeObserver
|
||||
*/
|
||||
start(referenceElement, targetElement = null) {
|
||||
if (this.active) return;
|
||||
|
||||
// Find scrollable parent elements
|
||||
if (referenceElement) {
|
||||
this.scrollableParents = this.findScrollableParents(referenceElement);
|
||||
|
||||
// Add scroll listeners to all scrollable parents
|
||||
this.scrollableParents.forEach((parent) => {
|
||||
parent.addEventListener("scroll", this.handleScroll, { passive: true });
|
||||
});
|
||||
}
|
||||
|
||||
// Add resize listener
|
||||
window.addEventListener("resize", this.handleResize, { passive: true });
|
||||
|
||||
// Set up ResizeObserver for element size changes if available
|
||||
if (targetElement && typeof ResizeObserver !== "undefined") {
|
||||
this.resizeObserver = new ResizeObserver(this.updatePosition);
|
||||
this.resizeObserver.observe(targetElement);
|
||||
|
||||
if (referenceElement && referenceElement !== targetElement) {
|
||||
this.resizeObserver.observe(referenceElement);
|
||||
}
|
||||
}
|
||||
|
||||
this.active = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop tracking scroll and resize events
|
||||
*/
|
||||
stop() {
|
||||
if (!this.active) return;
|
||||
|
||||
// Remove scroll listeners
|
||||
this.scrollableParents.forEach((parent) => {
|
||||
parent.removeEventListener("scroll", this.handleScroll);
|
||||
});
|
||||
|
||||
// Remove resize listener
|
||||
window.removeEventListener("resize", this.handleResize);
|
||||
|
||||
// Disconnect ResizeObserver if present
|
||||
if (this.resizeObserver) {
|
||||
this.resizeObserver.disconnect();
|
||||
this.resizeObserver = null;
|
||||
}
|
||||
|
||||
// Cancel any pending animation frame
|
||||
if (this.animationFrameId !== null) {
|
||||
cancelAnimationFrame(this.animationFrameId);
|
||||
this.animationFrameId = null;
|
||||
}
|
||||
|
||||
this.active = false;
|
||||
this.scrollableParents = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle scroll events with throttling
|
||||
*/
|
||||
handleScroll() {
|
||||
if (this.options.useRAF) {
|
||||
this.throttledUpdate();
|
||||
} else {
|
||||
this.updatePosition();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle resize events with throttling
|
||||
*/
|
||||
handleResize() {
|
||||
if (this.options.useRAF) {
|
||||
this.throttledUpdate();
|
||||
} else {
|
||||
this.updatePosition();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throttle updates using requestAnimationFrame
|
||||
*/
|
||||
throttledUpdate() {
|
||||
if (this.animationFrameId === null) {
|
||||
this.animationFrameId = requestAnimationFrame(() => {
|
||||
this.updatePosition();
|
||||
this.animationFrameId = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the update callback
|
||||
*/
|
||||
updatePosition() {
|
||||
if (this.updateCallback) {
|
||||
this.updateCallback();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all scrollable parent elements
|
||||
*/
|
||||
findScrollableParents(element) {
|
||||
const scrollableParents = [];
|
||||
let currentElement = element;
|
||||
|
||||
while (currentElement && currentElement !== document.body) {
|
||||
const style = window.getComputedStyle(currentElement);
|
||||
if (
|
||||
style.overflow === "auto" ||
|
||||
style.overflow === "scroll" ||
|
||||
style.overflowX === "auto" ||
|
||||
style.overflowX === "scroll" ||
|
||||
style.overflowY === "auto" ||
|
||||
style.overflowY === "scroll"
|
||||
) {
|
||||
scrollableParents.push(currentElement);
|
||||
}
|
||||
currentElement = currentElement.parentElement;
|
||||
}
|
||||
|
||||
// Always include window for global scrolling
|
||||
scrollableParents.push(window);
|
||||
|
||||
return scrollableParents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up all references
|
||||
*/
|
||||
destroy() {
|
||||
this.stop();
|
||||
this.updateCallback = null;
|
||||
this.options = null;
|
||||
}
|
||||
}
|
||||
|
||||
export default ScrollManager;
|
||||
Reference in New Issue
Block a user