Added scrollbars

This commit is contained in:
2026-01-11 23:25:55 +01:00
parent 5d6c02001e
commit d909f2125d
3 changed files with 445 additions and 247 deletions

View File

@@ -1466,3 +1466,280 @@ function updateTabs(controllerId) {
};
})();
/**
* Initialize DataGrid with CSS Grid layout + Custom Scrollbars
*
* Adapted from previous custom scrollbar implementation to work with CSS Grid.
* - Grid handles layout (no height calculations needed)
* - Custom scrollbars for visual consistency and positioning control
* - Vertical scroll: on body container (.dt2-body-container)
* - Horizontal scroll: on table (.dt2-table) to scroll header, body, footer together
*
* @param {string} gridId - The ID of the DataGrid instance
*/
function initDataGridScrollbars(gridId) {
console.debug("initDataGridScrollbars on element " + gridId);
const wrapper = document.getElementById(`tw_${gridId}`);
if (!wrapper) {
console.error(`DataGrid wrapper "tw_${gridId}" not found.`);
return;
}
const verticalScrollbar = wrapper.querySelector(".dt2-scrollbars-vertical");
const verticalWrapper = wrapper.querySelector(".dt2-scrollbars-vertical-wrapper");
const horizontalScrollbar = wrapper.querySelector(".dt2-scrollbars-horizontal");
const horizontalWrapper = wrapper.querySelector(".dt2-scrollbars-horizontal-wrapper");
const bodyContainer = wrapper.querySelector(".dt2-body-container");
const table = wrapper.querySelector(".dt2-table");
if (!verticalScrollbar || !verticalWrapper || !horizontalScrollbar || !horizontalWrapper || !bodyContainer || !table) {
console.error("Essential scrollbar or content elements are missing in the datagrid.");
return;
}
// OPTIMIZATION: Cache element references to avoid repeated querySelector calls
const header = table.querySelector(".dt2-header");
const body = table.querySelector(".dt2-body");
// OPTIMIZATION: RequestAnimationFrame flags to throttle visual updates
let rafScheduledVertical = false;
let rafScheduledHorizontal = false;
const computeScrollbarVisibility = () => {
// Vertical: check if body content exceeds body container height
const isVerticalRequired = bodyContainer.scrollHeight > bodyContainer.clientHeight;
// Horizontal: check if content width exceeds table width (use cached references)
const contentWidth = Math.max(
header ? header.scrollWidth : 0,
body ? body.scrollWidth : 0
);
const isHorizontalRequired = contentWidth > table.clientWidth;
requestAnimationFrame(() => {
verticalWrapper.style.display = isVerticalRequired ? "block" : "none";
horizontalWrapper.style.display = isHorizontalRequired ? "block" : "none";
});
};
const computeScrollbarSize = () => {
// Vertical scrollbar height
const visibleHeight = bodyContainer.clientHeight;
const totalHeight = bodyContainer.scrollHeight;
const wrapperHeight = verticalWrapper.offsetHeight;
let scrollbarHeight = 0;
if (totalHeight > 0) {
scrollbarHeight = (visibleHeight / totalHeight) * wrapperHeight;
}
// 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;
if (totalWidth > 0) {
scrollbarWidth = (visibleWidth / totalWidth) * wrapperWidth;
}
requestAnimationFrame(() => {
verticalScrollbar.style.height = `${scrollbarHeight}px`;
horizontalScrollbar.style.width = `${scrollbarWidth}px`;
});
};
const updateVerticalScrollbarPosition = () => {
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 startYVertical = 0;
let pendingVerticalScroll = null;
verticalScrollbar.addEventListener("mousedown", (e) => {
isDraggingVertical = true;
startYVertical = e.clientY;
document.body.style.userSelect = "none";
verticalScrollbar.classList.add("dt2-dragging");
});
document.addEventListener("mousemove", (e) => {
if (isDraggingVertical) {
const deltaY = e.clientY - startYVertical;
startYVertical = e.clientY;
// OPTIMIZATION: Store the scroll delta, update visual in RAF
if (pendingVerticalScroll === null) {
pendingVerticalScroll = 0;
}
pendingVerticalScroll += deltaY;
if (!rafScheduledVertical) {
rafScheduledVertical = true;
requestAnimationFrame(() => {
rafScheduledVertical = false;
const wrapperHeight = verticalWrapper.offsetHeight;
const scrollbarHeight = verticalScrollbar.offsetHeight;
const maxScrollTop = bodyContainer.scrollHeight - bodyContainer.clientHeight;
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;
});
}
}
});
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) {
rafScheduledHorizontal = true;
requestAnimationFrame(() => {
rafScheduledHorizontal = false;
const wrapperWidth = horizontalWrapper.offsetWidth;
const scrollbarWidth = horizontalScrollbar.offsetWidth;
// 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;
});
}
}
});
document.addEventListener("mouseup", () => {
if (isDraggingHorizontal) {
isDraggingHorizontal = false;
document.body.style.userSelect = "";
horizontalScrollbar.classList.remove("dt2-dragging");
}
});
// Wheel scrolling - OPTIMIZED with RAF throttling
let rafScheduledWheel = false;
let pendingWheelDeltaX = 0;
let pendingWheelDeltaY = 0;
const handleWheelScrolling = (event) => {
// Accumulate wheel deltas
pendingWheelDeltaX += event.deltaX;
pendingWheelDeltaY += event.deltaY;
// Schedule update in next animation frame (throttle)
if (!rafScheduledWheel) {
rafScheduledWheel = true;
requestAnimationFrame(() => {
rafScheduledWheel = false;
// Apply accumulated scroll
bodyContainer.scrollTop += pendingWheelDeltaY;
table.scrollLeft += pendingWheelDeltaX;
// Update scrollbar positions
updateVerticalScrollbarPosition();
updateHorizontalScrollbarPosition();
// Reset pending deltas
pendingWheelDeltaX = 0;
pendingWheelDeltaY = 0;
});
}
event.preventDefault();
};
wrapper.addEventListener("wheel", handleWheelScrolling, { passive: false });
// Initialize scrollbars
computeScrollbarVisibility();
computeScrollbarSize();
updateVerticalScrollbarPosition();
updateHorizontalScrollbarPosition();
// Recompute on window resize
window.addEventListener("resize", () => {
computeScrollbarVisibility();
computeScrollbarSize();
updateVerticalScrollbarPosition();
updateHorizontalScrollbarPosition();
});
console.info(`DataGrid "${gridId}" initialized with custom scrollbars`);
}