AyCode.Blazor/AyCode.Blazor.Components/wwwroot/js/mgGridInfoPanel.js

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;
}
};