Optimized Datagrid scrolling
This commit is contained in:
@@ -185,22 +185,37 @@ function bindTooltipsWithDelegation(elementId) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OPTIMIZATION C: Throttling flag to limit mouseenter processing
|
||||||
|
let tooltipRafScheduled = false;
|
||||||
|
|
||||||
// Add a single mouseenter and mouseleave listener to the parent element
|
// Add a single mouseenter and mouseleave listener to the parent element
|
||||||
element.addEventListener("mouseenter", (event) => {
|
element.addEventListener("mouseenter", (event) => {
|
||||||
//console.debug("Entering element", event.target)
|
// OPTIMIZATION C: Early exit - check mf-no-tooltip FIRST (before any DOM work)
|
||||||
|
if (element.hasAttribute("mf-no-tooltip")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// OPTIMIZATION C: Throttle mouseenter events (max 1 per frame)
|
||||||
|
if (tooltipRafScheduled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const cell = event.target.closest("[data-tooltip]");
|
const cell = event.target.closest("[data-tooltip]");
|
||||||
if (!cell) {
|
if (!cell) {
|
||||||
// console.debug(" No 'data-tooltip' attribute found. Stopping.");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const no_tooltip = element.hasAttribute("mf-no-tooltip");
|
// OPTIMIZATION C: Move ALL DOM reads into RAF to avoid forced synchronous layouts
|
||||||
if (no_tooltip) {
|
tooltipRafScheduled = true;
|
||||||
// console.debug(" Attribute 'mmt-no-tooltip' found. Cancelling.");
|
requestAnimationFrame(() => {
|
||||||
|
tooltipRafScheduled = false;
|
||||||
|
|
||||||
|
// Check again in case tooltip was disabled during RAF delay
|
||||||
|
if (element.hasAttribute("mf-no-tooltip")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// All DOM reads happen here (batched in RAF)
|
||||||
const content = cell.querySelector(".truncate") || cell;
|
const content = cell.querySelector(".truncate") || cell;
|
||||||
const isOverflowing = content.scrollWidth > content.clientWidth;
|
const isOverflowing = content.scrollWidth > content.clientWidth;
|
||||||
const forceShow = cell.classList.contains("mf-tooltip");
|
const forceShow = cell.classList.contains("mf-tooltip");
|
||||||
@@ -220,23 +235,22 @@ function bindTooltipsWithDelegation(elementId) {
|
|||||||
left = window.innerWidth - tooltipRect.width - 5; // Prevent overflow right
|
left = window.innerWidth - tooltipRect.width - 5; // Prevent overflow right
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply styles for tooltip positioning
|
// Apply styles (already in RAF)
|
||||||
requestAnimationFrame(() => {
|
|
||||||
tooltipContainer.textContent = tooltipText;
|
tooltipContainer.textContent = tooltipText;
|
||||||
tooltipContainer.setAttribute("data-visible", "true");
|
tooltipContainer.setAttribute("data-visible", "true");
|
||||||
tooltipContainer.style.top = `${top}px`;
|
tooltipContainer.style.top = `${top}px`;
|
||||||
tooltipContainer.style.left = `${left}px`;
|
tooltipContainer.style.left = `${left}px`;
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}); // OPTIMIZATION C: Removed capture phase (not needed)
|
||||||
}
|
|
||||||
}, true); // Use capture phase for better delegation if needed
|
|
||||||
|
|
||||||
element.addEventListener("mouseleave", (event) => {
|
element.addEventListener("mouseleave", (event) => {
|
||||||
const cell = event.target.closest("[data-tooltip]");
|
const cell = event.target.closest("[data-tooltip]");
|
||||||
if (cell) {
|
if (cell) {
|
||||||
tooltipContainer.setAttribute("data-visible", "false");
|
tooltipContainer.setAttribute("data-visible", "false");
|
||||||
}
|
}
|
||||||
}, true); // Use capture phase for better delegation if needed
|
}); // OPTIMIZATION C: Removed capture phase (not needed)
|
||||||
}
|
}
|
||||||
|
|
||||||
function initLayout(elementId) {
|
function initLayout(elementId) {
|
||||||
@@ -1132,7 +1146,11 @@ function updateTabs(controllerId) {
|
|||||||
* @param {MouseEvent} event - The mouse event
|
* @param {MouseEvent} event - The mouse event
|
||||||
*/
|
*/
|
||||||
function handleGlobalClick(event) {
|
function handleGlobalClick(event) {
|
||||||
console.debug("Global click detected");
|
// DEBUG: Measure click handler performance
|
||||||
|
const clickStart = performance.now();
|
||||||
|
const elementCount = MouseRegistry.elements.size;
|
||||||
|
|
||||||
|
console.warn(`🖱️ Click handler START: processing ${elementCount} registered elements`);
|
||||||
|
|
||||||
// Create a snapshot of current mouse action with modifiers
|
// Create a snapshot of current mouse action with modifiers
|
||||||
const snapshot = createSnapshot(event, 'click');
|
const snapshot = createSnapshot(event, 'click');
|
||||||
@@ -1151,8 +1169,10 @@ function updateTabs(controllerId) {
|
|||||||
const currentMatches = [];
|
const currentMatches = [];
|
||||||
let anyHasLongerSequence = false;
|
let anyHasLongerSequence = false;
|
||||||
let foundAnyMatch = false;
|
let foundAnyMatch = false;
|
||||||
|
let iterationCount = 0;
|
||||||
|
|
||||||
for (const [elementId, data] of MouseRegistry.elements) {
|
for (const [elementId, data] of MouseRegistry.elements) {
|
||||||
|
iterationCount++;
|
||||||
const element = document.getElementById(elementId);
|
const element = document.getElementById(elementId);
|
||||||
if (!element) continue;
|
if (!element) continue;
|
||||||
|
|
||||||
@@ -1246,6 +1266,13 @@ function updateTabs(controllerId) {
|
|||||||
if (MouseRegistry.snapshotHistory.length > 10) {
|
if (MouseRegistry.snapshotHistory.length > 10) {
|
||||||
MouseRegistry.snapshotHistory = [];
|
MouseRegistry.snapshotHistory = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DEBUG: Log click handler performance
|
||||||
|
const clickDuration = performance.now() - clickStart;
|
||||||
|
console.warn(`🖱️ Click handler DONE: ${clickDuration.toFixed(2)}ms (${iterationCount} iterations, ${currentMatches.length} matches)`);
|
||||||
|
if (clickDuration > 100) {
|
||||||
|
console.warn(`⚠️ SLOW CLICK HANDLER: ${clickDuration.toFixed(2)}ms for ${elementCount} elements`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1478,8 +1505,6 @@ function updateTabs(controllerId) {
|
|||||||
* @param {string} gridId - The ID of the DataGrid instance
|
* @param {string} gridId - The ID of the DataGrid instance
|
||||||
*/
|
*/
|
||||||
function initDataGridScrollbars(gridId) {
|
function initDataGridScrollbars(gridId) {
|
||||||
console.debug("initDataGridScrollbars on element " + gridId);
|
|
||||||
|
|
||||||
const wrapper = document.getElementById(`tw_${gridId}`);
|
const wrapper = document.getElementById(`tw_${gridId}`);
|
||||||
|
|
||||||
if (!wrapper) {
|
if (!wrapper) {
|
||||||
@@ -1506,188 +1531,167 @@ function initDataGridScrollbars(gridId) {
|
|||||||
// OPTIMIZATION: RequestAnimationFrame flags to throttle visual updates
|
// OPTIMIZATION: RequestAnimationFrame flags to throttle visual updates
|
||||||
let rafScheduledVertical = false;
|
let rafScheduledVertical = false;
|
||||||
let rafScheduledHorizontal = false;
|
let rafScheduledHorizontal = false;
|
||||||
|
let rafScheduledUpdate = false;
|
||||||
|
|
||||||
const computeScrollbarVisibility = () => {
|
// OPTIMIZATION: Pre-calculated scroll ratios (updated in updateScrollbars)
|
||||||
// Vertical: check if body content exceeds body container height
|
// Allows instant mousedown with zero DOM reads
|
||||||
const isVerticalRequired = bodyContainer.scrollHeight > bodyContainer.clientHeight;
|
let cachedVerticalScrollRatio = 0;
|
||||||
|
let cachedHorizontalScrollRatio = 0;
|
||||||
|
|
||||||
// Horizontal: check if content width exceeds table width (use cached references)
|
// OPTIMIZATION: Cached scroll positions to avoid DOM reads in mousedown
|
||||||
const contentWidth = Math.max(
|
// Initialized once at setup, updated in RAF handlers after each scroll change
|
||||||
header ? header.scrollWidth : 0,
|
let cachedBodyScrollTop = bodyContainer.scrollTop;
|
||||||
body ? body.scrollWidth : 0
|
let cachedTableScrollLeft = table.scrollLeft;
|
||||||
);
|
|
||||||
const isHorizontalRequired = contentWidth > table.clientWidth;
|
/**
|
||||||
|
* OPTIMIZED: Batched update function
|
||||||
|
* Phase 1: Read all DOM properties (no writes)
|
||||||
|
* Phase 2: Calculate all values
|
||||||
|
* Phase 3: Write all DOM properties in single RAF
|
||||||
|
*/
|
||||||
|
const updateScrollbars = () => {
|
||||||
|
if (rafScheduledUpdate) return;
|
||||||
|
|
||||||
|
rafScheduledUpdate = true;
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
verticalWrapper.style.display = isVerticalRequired ? "block" : "none";
|
rafScheduledUpdate = false;
|
||||||
horizontalWrapper.style.display = isHorizontalRequired ? "block" : "none";
|
|
||||||
});
|
// PHASE 1: Read all DOM properties
|
||||||
|
const metrics = {
|
||||||
|
bodyScrollHeight: bodyContainer.scrollHeight,
|
||||||
|
bodyClientHeight: bodyContainer.clientHeight,
|
||||||
|
bodyScrollTop: bodyContainer.scrollTop,
|
||||||
|
tableClientWidth: table.clientWidth,
|
||||||
|
tableScrollLeft: table.scrollLeft,
|
||||||
|
verticalWrapperHeight: verticalWrapper.offsetHeight,
|
||||||
|
horizontalWrapperWidth: horizontalWrapper.offsetWidth,
|
||||||
|
headerScrollWidth: header ? header.scrollWidth : 0,
|
||||||
|
bodyScrollWidth: body ? body.scrollWidth : 0
|
||||||
};
|
};
|
||||||
|
|
||||||
const computeScrollbarSize = () => {
|
// PHASE 2: Calculate all values
|
||||||
// Vertical scrollbar height
|
|
||||||
const visibleHeight = bodyContainer.clientHeight;
|
|
||||||
const totalHeight = bodyContainer.scrollHeight;
|
|
||||||
const wrapperHeight = verticalWrapper.offsetHeight;
|
|
||||||
|
|
||||||
|
const contentWidth = Math.max(metrics.headerScrollWidth, metrics.bodyScrollWidth);
|
||||||
|
|
||||||
|
// Visibility
|
||||||
|
const isVerticalRequired = metrics.bodyScrollHeight > metrics.bodyClientHeight;
|
||||||
|
const isHorizontalRequired = contentWidth > metrics.tableClientWidth;
|
||||||
|
|
||||||
|
// Scrollbar sizes
|
||||||
let scrollbarHeight = 0;
|
let scrollbarHeight = 0;
|
||||||
if (totalHeight > 0) {
|
if (metrics.bodyScrollHeight > 0) {
|
||||||
scrollbarHeight = (visibleHeight / totalHeight) * wrapperHeight;
|
scrollbarHeight = (metrics.bodyClientHeight / metrics.bodyScrollHeight) * metrics.verticalWrapperHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Horizontal scrollbar width (use cached references)
|
|
||||||
const visibleWidth = table.clientWidth;
|
|
||||||
const totalWidth = Math.max(
|
|
||||||
header ? header.scrollWidth : 0,
|
|
||||||
body ? body.scrollWidth : 0
|
|
||||||
);
|
|
||||||
const wrapperWidth = horizontalWrapper.offsetWidth;
|
|
||||||
|
|
||||||
let scrollbarWidth = 0;
|
let scrollbarWidth = 0;
|
||||||
if (totalWidth > 0) {
|
if (contentWidth > 0) {
|
||||||
scrollbarWidth = (visibleWidth / totalWidth) * wrapperWidth;
|
scrollbarWidth = (metrics.tableClientWidth / contentWidth) * metrics.horizontalWrapperWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
// Scrollbar positions
|
||||||
|
const maxScrollTop = metrics.bodyScrollHeight - metrics.bodyClientHeight;
|
||||||
|
let verticalTop = 0;
|
||||||
|
if (maxScrollTop > 0) {
|
||||||
|
const scrollRatio = metrics.verticalWrapperHeight / metrics.bodyScrollHeight;
|
||||||
|
verticalTop = metrics.bodyScrollTop * scrollRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxScrollLeft = contentWidth - metrics.tableClientWidth;
|
||||||
|
let horizontalLeft = 0;
|
||||||
|
if (maxScrollLeft > 0 && contentWidth > 0) {
|
||||||
|
const scrollRatio = metrics.horizontalWrapperWidth / contentWidth;
|
||||||
|
horizontalLeft = metrics.tableScrollLeft * scrollRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
// OPTIMIZATION: Pre-calculate and cache scroll ratios for instant mousedown
|
||||||
|
// Vertical scroll ratio
|
||||||
|
if (maxScrollTop > 0 && scrollbarHeight > 0) {
|
||||||
|
cachedVerticalScrollRatio = maxScrollTop / (metrics.verticalWrapperHeight - scrollbarHeight);
|
||||||
|
} else {
|
||||||
|
cachedVerticalScrollRatio = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Horizontal scroll ratio
|
||||||
|
if (maxScrollLeft > 0 && scrollbarWidth > 0) {
|
||||||
|
cachedHorizontalScrollRatio = maxScrollLeft / (metrics.horizontalWrapperWidth - scrollbarWidth);
|
||||||
|
} else {
|
||||||
|
cachedHorizontalScrollRatio = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PHASE 3: Write all DOM properties (already in RAF)
|
||||||
|
verticalWrapper.style.display = isVerticalRequired ? "block" : "none";
|
||||||
|
horizontalWrapper.style.display = isHorizontalRequired ? "block" : "none";
|
||||||
verticalScrollbar.style.height = `${scrollbarHeight}px`;
|
verticalScrollbar.style.height = `${scrollbarHeight}px`;
|
||||||
horizontalScrollbar.style.width = `${scrollbarWidth}px`;
|
horizontalScrollbar.style.width = `${scrollbarWidth}px`;
|
||||||
|
verticalScrollbar.style.top = `${verticalTop}px`;
|
||||||
|
horizontalScrollbar.style.left = `${horizontalLeft}px`;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateVerticalScrollbarPosition = () => {
|
// Consolidated drag management
|
||||||
const maxScrollTop = bodyContainer.scrollHeight - bodyContainer.clientHeight;
|
|
||||||
const wrapperHeight = verticalWrapper.offsetHeight;
|
|
||||||
|
|
||||||
if (maxScrollTop > 0) {
|
|
||||||
const scrollRatio = wrapperHeight / bodyContainer.scrollHeight;
|
|
||||||
verticalScrollbar.style.top = `${bodyContainer.scrollTop * scrollRatio}px`;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateHorizontalScrollbarPosition = () => {
|
|
||||||
// Use cached references
|
|
||||||
const totalWidth = Math.max(
|
|
||||||
header ? header.scrollWidth : 0,
|
|
||||||
body ? body.scrollWidth : 0
|
|
||||||
);
|
|
||||||
const maxScrollLeft = totalWidth - table.clientWidth;
|
|
||||||
const wrapperWidth = horizontalWrapper.offsetWidth;
|
|
||||||
|
|
||||||
if (maxScrollLeft > 0 && totalWidth > 0) {
|
|
||||||
const scrollRatio = wrapperWidth / totalWidth;
|
|
||||||
horizontalScrollbar.style.left = `${table.scrollLeft * scrollRatio}px`;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Drag management for vertical scrollbar
|
|
||||||
let isDraggingVertical = false;
|
let isDraggingVertical = false;
|
||||||
let startYVertical = 0;
|
let isDraggingHorizontal = false;
|
||||||
let pendingVerticalScroll = null;
|
let dragStartY = 0;
|
||||||
|
let dragStartX = 0;
|
||||||
|
let dragStartScrollTop = 0;
|
||||||
|
let dragStartScrollLeft = 0;
|
||||||
|
|
||||||
|
// Vertical scrollbar mousedown
|
||||||
verticalScrollbar.addEventListener("mousedown", (e) => {
|
verticalScrollbar.addEventListener("mousedown", (e) => {
|
||||||
isDraggingVertical = true;
|
isDraggingVertical = true;
|
||||||
startYVertical = e.clientY;
|
dragStartY = e.clientY;
|
||||||
document.body.style.userSelect = "none";
|
dragStartScrollTop = cachedBodyScrollTop;
|
||||||
verticalScrollbar.classList.add("dt2-dragging");
|
wrapper.setAttribute("mf-no-tooltip", "");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Horizontal scrollbar mousedown
|
||||||
|
horizontalScrollbar.addEventListener("mousedown", (e) => {
|
||||||
|
isDraggingHorizontal = true;
|
||||||
|
dragStartX = e.clientX;
|
||||||
|
dragStartScrollLeft = cachedTableScrollLeft;
|
||||||
|
wrapper.setAttribute("mf-no-tooltip", "");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Consolidated mousemove listener
|
||||||
document.addEventListener("mousemove", (e) => {
|
document.addEventListener("mousemove", (e) => {
|
||||||
if (isDraggingVertical) {
|
if (isDraggingVertical) {
|
||||||
const deltaY = e.clientY - startYVertical;
|
const deltaY = e.clientY - dragStartY;
|
||||||
startYVertical = e.clientY;
|
|
||||||
|
|
||||||
// OPTIMIZATION: Store the scroll delta, update visual in RAF
|
|
||||||
if (pendingVerticalScroll === null) {
|
|
||||||
pendingVerticalScroll = 0;
|
|
||||||
}
|
|
||||||
pendingVerticalScroll += deltaY;
|
|
||||||
|
|
||||||
if (!rafScheduledVertical) {
|
if (!rafScheduledVertical) {
|
||||||
rafScheduledVertical = true;
|
rafScheduledVertical = true;
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
rafScheduledVertical = false;
|
rafScheduledVertical = false;
|
||||||
|
const scrollDelta = deltaY * cachedVerticalScrollRatio;
|
||||||
const wrapperHeight = verticalWrapper.offsetHeight;
|
bodyContainer.scrollTop = dragStartScrollTop + scrollDelta;
|
||||||
const scrollbarHeight = verticalScrollbar.offsetHeight;
|
cachedBodyScrollTop = bodyContainer.scrollTop;
|
||||||
const maxScrollTop = bodyContainer.scrollHeight - bodyContainer.clientHeight;
|
updateScrollbars();
|
||||||
const scrollRatio = maxScrollTop / (wrapperHeight - scrollbarHeight);
|
|
||||||
|
|
||||||
let newTop = parseFloat(verticalScrollbar.style.top || "0") + pendingVerticalScroll;
|
|
||||||
newTop = Math.max(0, Math.min(newTop, wrapperHeight - scrollbarHeight));
|
|
||||||
|
|
||||||
verticalScrollbar.style.top = `${newTop}px`;
|
|
||||||
bodyContainer.scrollTop = newTop * scrollRatio;
|
|
||||||
|
|
||||||
pendingVerticalScroll = null;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
} else if (isDraggingHorizontal) {
|
||||||
});
|
const deltaX = e.clientX - dragStartX;
|
||||||
|
|
||||||
document.addEventListener("mouseup", () => {
|
|
||||||
if (isDraggingVertical) {
|
|
||||||
isDraggingVertical = false;
|
|
||||||
document.body.style.userSelect = "";
|
|
||||||
verticalScrollbar.classList.remove("dt2-dragging");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Drag management for horizontal scrollbar
|
|
||||||
let isDraggingHorizontal = false;
|
|
||||||
let startXHorizontal = 0;
|
|
||||||
let pendingHorizontalScroll = null;
|
|
||||||
|
|
||||||
horizontalScrollbar.addEventListener("mousedown", (e) => {
|
|
||||||
isDraggingHorizontal = true;
|
|
||||||
startXHorizontal = e.clientX;
|
|
||||||
document.body.style.userSelect = "none";
|
|
||||||
horizontalScrollbar.classList.add("dt2-dragging");
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener("mousemove", (e) => {
|
|
||||||
if (isDraggingHorizontal) {
|
|
||||||
const deltaX = e.clientX - startXHorizontal;
|
|
||||||
startXHorizontal = e.clientX;
|
|
||||||
|
|
||||||
// OPTIMIZATION: Store the scroll delta, update visual in RAF
|
|
||||||
if (pendingHorizontalScroll === null) {
|
|
||||||
pendingHorizontalScroll = 0;
|
|
||||||
}
|
|
||||||
pendingHorizontalScroll += deltaX;
|
|
||||||
|
|
||||||
if (!rafScheduledHorizontal) {
|
if (!rafScheduledHorizontal) {
|
||||||
rafScheduledHorizontal = true;
|
rafScheduledHorizontal = true;
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
rafScheduledHorizontal = false;
|
rafScheduledHorizontal = false;
|
||||||
|
const scrollDelta = deltaX * cachedHorizontalScrollRatio;
|
||||||
const wrapperWidth = horizontalWrapper.offsetWidth;
|
table.scrollLeft = dragStartScrollLeft + scrollDelta;
|
||||||
const scrollbarWidth = horizontalScrollbar.offsetWidth;
|
cachedTableScrollLeft = table.scrollLeft;
|
||||||
|
updateScrollbars();
|
||||||
// Use cached references
|
|
||||||
const totalWidth = Math.max(
|
|
||||||
header ? header.scrollWidth : 0,
|
|
||||||
body ? body.scrollWidth : 0
|
|
||||||
);
|
|
||||||
const maxScrollLeft = totalWidth - table.clientWidth;
|
|
||||||
const scrollRatio = maxScrollLeft / (wrapperWidth - scrollbarWidth);
|
|
||||||
|
|
||||||
let newLeft = parseFloat(horizontalScrollbar.style.left || "0") + pendingHorizontalScroll;
|
|
||||||
newLeft = Math.max(0, Math.min(newLeft, wrapperWidth - scrollbarWidth));
|
|
||||||
|
|
||||||
horizontalScrollbar.style.left = `${newLeft}px`;
|
|
||||||
table.scrollLeft = newLeft * scrollRatio;
|
|
||||||
|
|
||||||
pendingHorizontalScroll = null;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Consolidated mouseup listener
|
||||||
document.addEventListener("mouseup", () => {
|
document.addEventListener("mouseup", () => {
|
||||||
if (isDraggingHorizontal) {
|
if (isDraggingVertical) {
|
||||||
|
isDraggingVertical = false;
|
||||||
|
wrapper.removeAttribute("mf-no-tooltip");
|
||||||
|
} else if (isDraggingHorizontal) {
|
||||||
isDraggingHorizontal = false;
|
isDraggingHorizontal = false;
|
||||||
document.body.style.userSelect = "";
|
wrapper.removeAttribute("mf-no-tooltip");
|
||||||
horizontalScrollbar.classList.remove("dt2-dragging");
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1711,13 +1715,16 @@ function initDataGridScrollbars(gridId) {
|
|||||||
bodyContainer.scrollTop += pendingWheelDeltaY;
|
bodyContainer.scrollTop += pendingWheelDeltaY;
|
||||||
table.scrollLeft += pendingWheelDeltaX;
|
table.scrollLeft += pendingWheelDeltaX;
|
||||||
|
|
||||||
// Update scrollbar positions
|
// Update caches with clamped values (read back from DOM in RAF - OK)
|
||||||
updateVerticalScrollbarPosition();
|
cachedBodyScrollTop = bodyContainer.scrollTop;
|
||||||
updateHorizontalScrollbarPosition();
|
cachedTableScrollLeft = table.scrollLeft;
|
||||||
|
|
||||||
// Reset pending deltas
|
// Reset pending deltas
|
||||||
pendingWheelDeltaX = 0;
|
pendingWheelDeltaX = 0;
|
||||||
pendingWheelDeltaY = 0;
|
pendingWheelDeltaY = 0;
|
||||||
|
|
||||||
|
// Update all scrollbars in a single batched operation
|
||||||
|
updateScrollbars();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1726,20 +1733,19 @@ function initDataGridScrollbars(gridId) {
|
|||||||
|
|
||||||
wrapper.addEventListener("wheel", handleWheelScrolling, { passive: false });
|
wrapper.addEventListener("wheel", handleWheelScrolling, { passive: false });
|
||||||
|
|
||||||
// Initialize scrollbars
|
// Initialize scrollbars with single batched update
|
||||||
computeScrollbarVisibility();
|
updateScrollbars();
|
||||||
computeScrollbarSize();
|
|
||||||
updateVerticalScrollbarPosition();
|
|
||||||
updateHorizontalScrollbarPosition();
|
|
||||||
|
|
||||||
// Recompute on window resize
|
// Recompute on window resize with RAF throttling
|
||||||
|
let resizeScheduled = false;
|
||||||
window.addEventListener("resize", () => {
|
window.addEventListener("resize", () => {
|
||||||
computeScrollbarVisibility();
|
if (!resizeScheduled) {
|
||||||
computeScrollbarSize();
|
resizeScheduled = true;
|
||||||
updateVerticalScrollbarPosition();
|
requestAnimationFrame(() => {
|
||||||
updateHorizontalScrollbarPosition();
|
resizeScheduled = false;
|
||||||
|
updateScrollbars();
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.info(`DataGrid "${gridId}" initialized with custom scrollbars`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user