121 lines
4.1 KiB
JavaScript
121 lines
4.1 KiB
JavaScript
// MgGridInfoPanel - Sticky scroll handling
|
|
// Makes the InfoPanel sticky to viewport when scrolling
|
|
|
|
window.MgGridInfoPanel = {
|
|
observers: new Map(),
|
|
|
|
// Initialize sticky behavior for an InfoPanel element
|
|
initSticky: function (element, topOffset) {
|
|
if (!element) return;
|
|
|
|
const elementId = element.id || this.generateId(element);
|
|
|
|
// Clean up existing observer if any
|
|
this.disposeSticky(element);
|
|
|
|
// Store the initial position of the element (relative to document)
|
|
const rect = element.getBoundingClientRect();
|
|
const initialTop = rect.top + window.scrollY;
|
|
|
|
// Calculate and set initial state
|
|
this.updatePosition(element, initialTop);
|
|
|
|
// Handler to update position on scroll and resize
|
|
const updateHandler = () => {
|
|
this.updatePosition(element, initialTop);
|
|
};
|
|
|
|
// Add event listeners - use passive to not block scrolling
|
|
window.addEventListener('resize', updateHandler, { passive: true });
|
|
window.addEventListener('scroll', updateHandler, { passive: true });
|
|
|
|
// Store cleanup info
|
|
this.observers.set(elementId, {
|
|
element: element,
|
|
updateHandler: updateHandler,
|
|
initialTop: initialTop
|
|
});
|
|
|
|
return true;
|
|
},
|
|
|
|
// Dispose sticky behavior
|
|
disposeSticky: function (element) {
|
|
if (!element) return;
|
|
|
|
const elementId = element.id || this.findElementId(element);
|
|
const observer = this.observers.get(elementId);
|
|
|
|
if (observer) {
|
|
window.removeEventListener('resize', observer.updateHandler);
|
|
window.removeEventListener('scroll', observer.updateHandler);
|
|
|
|
// Reset styles
|
|
element.style.height = '';
|
|
element.style.maxHeight = '';
|
|
element.style.transform = '';
|
|
|
|
this.observers.delete(elementId);
|
|
}
|
|
},
|
|
|
|
// Update panel position and height based on scroll
|
|
updatePosition: function (element, initialTop) {
|
|
if (!element) return;
|
|
|
|
const scrollY = window.scrollY;
|
|
const viewportHeight = window.innerHeight;
|
|
const bottomPadding = 30; // 30px from bottom
|
|
|
|
// Calculate how much we've scrolled past the initial position
|
|
const scrolledPast = Math.max(0, scrollY - initialTop);
|
|
|
|
// Get the splitter pane to know our container limits
|
|
const paneContainer = element.closest('.dxbl-splitter-pane');
|
|
let maxScrollOffset = Infinity;
|
|
|
|
if (paneContainer) {
|
|
// Don't scroll past the bottom of the pane
|
|
const paneHeight = paneContainer.offsetHeight;
|
|
const elementHeight = element.offsetHeight;
|
|
maxScrollOffset = Math.max(0, paneHeight - elementHeight);
|
|
}
|
|
|
|
// Clamp the scroll offset
|
|
const translateY = Math.min(scrolledPast, maxScrollOffset);
|
|
|
|
// Apply transform to make it "sticky"
|
|
element.style.transform = `translateY(${translateY}px)`;
|
|
|
|
// Calculate height: from current visual position to viewport bottom
|
|
const rect = element.getBoundingClientRect();
|
|
const visualTop = rect.top; // This already accounts for transform
|
|
|
|
// Height from current visual top to viewport bottom minus padding
|
|
const availableHeight = viewportHeight - visualTop - bottomPadding;
|
|
|
|
// Clamp height
|
|
const finalHeight = Math.max(200, Math.min(availableHeight, viewportHeight - bottomPadding));
|
|
|
|
element.style.height = finalHeight + 'px';
|
|
element.style.maxHeight = finalHeight + 'px';
|
|
},
|
|
|
|
// Generate a unique ID for the element
|
|
generateId: function (element) {
|
|
const id = 'mg-info-panel-' + Math.random().toString(36).substr(2, 9);
|
|
element.id = id;
|
|
return id;
|
|
},
|
|
|
|
// Find element ID from stored observers
|
|
findElementId: function (element) {
|
|
for (const [id, observer] of this.observers.entries()) {
|
|
if (observer.element === element) {
|
|
return id;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
};
|