Added scrollbars
This commit is contained in:
@@ -690,7 +690,7 @@
|
||||
.mf-panel-main {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
overflow: hidden;
|
||||
min-width: 0; /* Important to allow the shrinking of flexbox */
|
||||
}
|
||||
|
||||
@@ -829,169 +829,33 @@
|
||||
/* ********************************************* */
|
||||
/* ************* Datagrid Component ************ */
|
||||
/* ********************************************* */
|
||||
input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.dt2-drag-drop {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
z-index: var(--datagrid-drag-drop-zindex);
|
||||
width: 100px;
|
||||
border: 1px solid var(--color-base-300);
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
box-shadow: 0 0 40px rgba(0, 0, 0, 0.3);
|
||||
background: var(--color-base-100);
|
||||
box-sizing: border-box;
|
||||
overflow-x: auto;
|
||||
pointer-events: none; /* Prevent interfering with mouse events */
|
||||
|
||||
}
|
||||
|
||||
.dt2-main {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dt2-sidebar {
|
||||
opacity: 0; /* Default to invisible */
|
||||
visibility: hidden; /* Prevent interaction when invisible */
|
||||
transition: opacity 0.3s ease, visibility 0s linear 0.3s; /* Delay visibility removal */
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 75%;
|
||||
max-height: 710px;
|
||||
overflow-y: auto;
|
||||
background-color: var(--color-base-100);
|
||||
z-index: var(--datagrid-sidebar-zindex);
|
||||
box-shadow: -5px 0 15px rgba(0, 0, 0, 0.5); /* Stronger shadow */
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.dt2-sidebar.active {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.dt2-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dt2-scrollbars {
|
||||
position: absolute;
|
||||
top: 24px;
|
||||
bottom: 0px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
pointer-events: none; /* Ensures parents don't intercept pointer events */
|
||||
z-index: var(--datagrid-scrollbars-zindex);
|
||||
}
|
||||
|
||||
/* Scrollbar Wrappers common attributes*/
|
||||
.dt2-scrollbars-vertical-wrapper,
|
||||
.dt2-scrollbars-horizontal-wrapper {
|
||||
position: absolute;
|
||||
background-color: var(--color-base-200);
|
||||
opacity: 1;
|
||||
transition: opacity 0.2s ease-in-out; /* Smooth fade in/out */
|
||||
pointer-events: auto; /* Allow interaction */
|
||||
}
|
||||
|
||||
/* Scrollbar Wrappers */
|
||||
.dt2-scrollbars-vertical-wrapper {
|
||||
left: auto;
|
||||
right: 3px;
|
||||
top: 3px;
|
||||
bottom: 3px;
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.dt2-scrollbars-horizontal-wrapper {
|
||||
left: 3px;
|
||||
right: 3px;
|
||||
top: auto;
|
||||
bottom: -12px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
/* Scrollbars */
|
||||
.dt2-scrollbars-vertical,
|
||||
.dt2-scrollbars-horizontal {
|
||||
background-color: var(--color-base-300);
|
||||
border-radius: 3px;
|
||||
pointer-events: auto; /* Allow interaction with the scrollbar */
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
border-radius: 3px; /* Rounded corners */
|
||||
pointer-events: auto; /* Enable interaction */
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
/* Vertical Scrollbar */
|
||||
.dt2-scrollbars-vertical {
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: auto;
|
||||
bottom: auto;
|
||||
width: 100%; /* Fits inside its wrapper */
|
||||
}
|
||||
|
||||
/* Horizontal Scrollbar */
|
||||
.dt2-scrollbars-horizontal {
|
||||
left: auto;
|
||||
right: auto;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
height: 100%; /* Fits inside its wrapper */
|
||||
}
|
||||
|
||||
/* Scrollbar hover effects */
|
||||
.dt2-scrollbars-vertical:hover,
|
||||
.dt2-scrollbars-horizontal:hover,
|
||||
.dt2-scrollbars-vertical.dt2-dragging,
|
||||
.dt2-scrollbars-horizontal.dt2-dragging {
|
||||
background-color: var(--color-base-content);
|
||||
}
|
||||
|
||||
.dt2-table {
|
||||
--color-border: color-mix(in oklab, var(--color-base-content) 20%, #0000);
|
||||
--color-resize: color-mix(in oklab, var(--color-base-content) 50%, #0000);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 10px;
|
||||
/* overflow: hidden; */
|
||||
}
|
||||
|
||||
.dt2-table:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Header and Footer */
|
||||
.dt2-header,
|
||||
.dt2-footer {
|
||||
background-color: var(--color-base-200);
|
||||
border-radius: 10px 10px 0 0;
|
||||
min-width: max-content;
|
||||
min-width: max-content; /* Content width propagates to scrollable parent */
|
||||
}
|
||||
|
||||
/* Body */
|
||||
.dt2-body {
|
||||
overflow: hidden; /* You can change this to auto if horizontal scrolling is required */
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
min-width: max-content;
|
||||
min-width: max-content; /* Content width propagates to scrollable parent */
|
||||
}
|
||||
|
||||
/* Row */
|
||||
.dt2-row {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
/* Cell */
|
||||
.dt2-cell {
|
||||
--color-border: color-mix(in oklab, var(--color-base-content) 20%, #0000);
|
||||
--color-resize: color-mix(in oklab, var(--color-base-content) 50%, #0000);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
@@ -1002,11 +866,12 @@ input:focus {
|
||||
min-width: 100px;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 1;
|
||||
box-sizing: border-box; /* to include the borders in the computations */
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* Cell content types */
|
||||
.dt2-cell-content-text {
|
||||
text-align: inherit;
|
||||
width: 100%;
|
||||
@@ -1016,8 +881,8 @@ input:focus {
|
||||
.dt2-cell-content-checkbox {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center; /* Horizontally center the icon */
|
||||
align-items: center; /* Vertically center the icon */
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dt2-cell-content-number {
|
||||
@@ -1026,36 +891,12 @@ input:focus {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
/* Footer cell */
|
||||
.dt2-footer-cell {
|
||||
cursor: pointer
|
||||
}
|
||||
|
||||
.dt2-footer-menu {
|
||||
position: absolute;
|
||||
display: None;
|
||||
z-index: var(--datagrid-menu-zindex);
|
||||
border: 1px solid oklch(var(--b3));
|
||||
box-sizing: border-box;
|
||||
width: 80px;
|
||||
background-color: var(--color-base-100); /* Add background color */
|
||||
opacity: 1; /* Ensure full opacity */
|
||||
}
|
||||
|
||||
.dt2-footer-menu.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dt2-footer-menu-item {
|
||||
padding: 0 8px;
|
||||
border-radius: 4px;
|
||||
background-color: var(--color-base-100); /* Add background color */
|
||||
}
|
||||
|
||||
.dt2-footer-menu-item:hover {
|
||||
background: color-mix(in oklab, var(--color-base-100, var(--color-base-200)), #000 7%);
|
||||
cursor: pointer
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Resize handle */
|
||||
.dt2-resize-handle {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
@@ -1066,100 +907,149 @@ input:focus {
|
||||
}
|
||||
|
||||
.dt2-resize-handle::after {
|
||||
content: ''; /* This is required */
|
||||
position: absolute; /* Position as needed */
|
||||
content: '';
|
||||
position: absolute;
|
||||
z-index: var(--datagrid-resize-zindex);
|
||||
display: block; /* Makes it a block element */
|
||||
display: block;
|
||||
width: 3px;
|
||||
height: 60%;
|
||||
top: calc(50% - 60% * 0.5);
|
||||
background-color: var(--color-resize);
|
||||
}
|
||||
|
||||
.dt2-header-hidden {
|
||||
width: 5px;
|
||||
background: var(--color-neutral-content);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Hidden column */
|
||||
.dt2-col-hidden {
|
||||
width: 5px;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
/* Highlight */
|
||||
.dt2-highlight-1 {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.dt2-item-handle {
|
||||
background-image: radial-gradient(var(--color-primary-content) 40%, transparent 0);
|
||||
background-repeat: repeat;
|
||||
background-size: 4px 4px;
|
||||
cursor: grab;
|
||||
display: inline-block;
|
||||
height: 16px;
|
||||
margin: auto;
|
||||
/* *********************************************** */
|
||||
/* ******** DataGrid Fixed Header/Footer ******** */
|
||||
/* *********************************************** */
|
||||
|
||||
/*
|
||||
* DataGrid with CSS Grid + Custom Scrollbars
|
||||
* - Wrapper takes 100% of parent height
|
||||
* - Table uses Grid: header (auto) + body (1fr) + footer (auto)
|
||||
* - Native scrollbars hidden, custom scrollbars overlaid
|
||||
* - Vertical scrollbar: right side of entire table
|
||||
* - Horizontal scrollbar: bottom, under footer
|
||||
*/
|
||||
|
||||
/* Main wrapper - takes full parent height, contains table + scrollbars */
|
||||
.dt2-table-wrapper {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
/* **************************************************************************** */
|
||||
/* COLUMNS SETTINGS */
|
||||
/* **************************************************************************** */
|
||||
|
||||
.dt2-cs-header {
|
||||
background-color: var(--color-base-200);
|
||||
min-width: max-content;
|
||||
}
|
||||
|
||||
.dt2-cs-columns {
|
||||
/* Table with Grid layout - horizontal scroll enabled, scrollbars hidden */
|
||||
.dt2-table {
|
||||
height: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 20px 1fr 0.5fr 0.5fr 0.5fr 0.5fr;
|
||||
grid-template-rows: auto 1fr auto; /* header, body, footer */
|
||||
overflow-x: auto; /* Enable horizontal scroll */
|
||||
overflow-y: hidden; /* No vertical scroll on table */
|
||||
scrollbar-width: none; /* Firefox: hide scrollbar */
|
||||
-ms-overflow-style: none; /* IE/Edge: hide scrollbar */
|
||||
}
|
||||
|
||||
.dt2-cs-body input {
|
||||
outline: none;
|
||||
border-color: transparent;
|
||||
box-shadow: none;
|
||||
/* Chrome/Safari: hide scrollbar */
|
||||
.dt2-table::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dt2-cs-body input[type="checkbox"],
|
||||
.dt2-cs-body input.checkbox {
|
||||
outline: initial;
|
||||
border-color: var(--color-border);
|
||||
/* Header - no scroll, takes natural height */
|
||||
.dt2-header-container {
|
||||
overflow: hidden;
|
||||
min-width: max-content; /* Force table to be as wide as content */
|
||||
}
|
||||
|
||||
|
||||
.dt2-cs-cell {
|
||||
padding: 0 6px 0 6px;
|
||||
margin: auto;
|
||||
/* Body - scrollable vertically via JS, scrollbars hidden */
|
||||
.dt2-body-container {
|
||||
overflow: hidden; /* Scrollbars hidden, scroll via JS */
|
||||
min-height: 0; /* Important for Grid to allow shrinking */
|
||||
min-width: max-content; /* Force table to be as wide as content */
|
||||
}
|
||||
|
||||
.dt2-cs-checkbox-cell {
|
||||
margin: auto;
|
||||
/* Footer - no scroll, takes natural height */
|
||||
.dt2-footer-container {
|
||||
overflow: hidden;
|
||||
min-width: max-content; /* Force table to be as wide as content */
|
||||
}
|
||||
|
||||
.dt2-cs-number-cell {
|
||||
padding: 0 6px 0 6px;
|
||||
text-align: right;
|
||||
/* Custom scrollbars container - overlaid on table */
|
||||
.dt2-scrollbars {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
pointer-events: none; /* Let clicks pass through */
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.dt2-cs-select-cell {
|
||||
padding: 0 6px;
|
||||
margin: 3px 0;
|
||||
/* Scrollbar wrappers - clickable/draggable */
|
||||
.dt2-scrollbars-vertical-wrapper,
|
||||
.dt2-scrollbars-horizontal-wrapper {
|
||||
position: absolute;
|
||||
background-color: var(--color-base-200);
|
||||
opacity: 1;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
pointer-events: auto; /* Enable interaction */
|
||||
}
|
||||
|
||||
.dt2-cs-body input:hover {
|
||||
border: 1px solid #ccc; /* Provide a subtle border on focus */
|
||||
/* Vertical scrollbar wrapper - right side, full table height */
|
||||
.dt2-scrollbars-vertical-wrapper {
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
|
||||
.dt2-views-container-select {
|
||||
width: 170px;
|
||||
/* Horizontal scrollbar wrapper - bottom, full width minus vertical scrollbar */
|
||||
.dt2-scrollbars-horizontal-wrapper {
|
||||
left: 0;
|
||||
right: 8px; /* Leave space for vertical scrollbar */
|
||||
bottom: 0;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.dt2-views-container-create {
|
||||
width: 300px;
|
||||
/* Scrollbar thumbs */
|
||||
.dt2-scrollbars-vertical,
|
||||
.dt2-scrollbars-horizontal {
|
||||
background-color: color-mix(in oklab, var(--color-base-content) 20%, #0000);
|
||||
border-radius: 3px;
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
/* Vertical scrollbar thumb */
|
||||
.dt2-scrollbars-vertical {
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Horizontal scrollbar thumb */
|
||||
.dt2-scrollbars-horizontal {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Hover and dragging states */
|
||||
.dt2-scrollbars-vertical:hover,
|
||||
.dt2-scrollbars-horizontal:hover,
|
||||
.dt2-scrollbars-vertical.dt2-dragging,
|
||||
.dt2-scrollbars-horizontal.dt2-dragging {
|
||||
background-color: color-mix(in oklab, var(--color-base-content) 30%, #0000);
|
||||
}
|
||||
|
||||
@@ -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`);
|
||||
}
|
||||
|
||||
|
||||
@@ -281,11 +281,11 @@ class DataGrid(MultipleInstance):
|
||||
def mk_body(self):
|
||||
return Div(
|
||||
*self.mk_body_content_page(0),
|
||||
cls="dt2-body",
|
||||
id=f"tb_{self._id}",
|
||||
cls="dt2-body"
|
||||
)
|
||||
|
||||
def mk_footers(self):
|
||||
return self.mk_headers()
|
||||
return Div(
|
||||
*[Div(
|
||||
*[self.mk_aggregation_cell(col_def, row_index, footer) for col_def in self._state.columns],
|
||||
@@ -299,11 +299,43 @@ class DataGrid(MultipleInstance):
|
||||
|
||||
def mk_table(self):
|
||||
return Div(
|
||||
self.mk_headers(),
|
||||
self.mk_body(),
|
||||
self.mk_footers(),
|
||||
cls="dt2-table",
|
||||
id=f"t_{self._id}",
|
||||
# Grid table with header, body, footer
|
||||
Div(
|
||||
# Header container - no scroll
|
||||
Div(
|
||||
self.mk_headers(),
|
||||
cls="dt2-header-container"
|
||||
),
|
||||
# Body container - scroll via JS, scrollbars hidden
|
||||
Div(
|
||||
self.mk_body(),
|
||||
cls="dt2-body-container",
|
||||
id=f"tb_{self._id}"
|
||||
),
|
||||
# Footer container - no scroll
|
||||
Div(
|
||||
self.mk_footers(),
|
||||
cls="dt2-footer-container"
|
||||
),
|
||||
cls="dt2-table",
|
||||
id=f"t_{self._id}"
|
||||
),
|
||||
# Custom scrollbars overlaid
|
||||
Div(
|
||||
# Vertical scrollbar wrapper (right side)
|
||||
Div(
|
||||
Div(cls="dt2-scrollbars-vertical"),
|
||||
cls="dt2-scrollbars-vertical-wrapper"
|
||||
),
|
||||
# Horizontal scrollbar wrapper (bottom)
|
||||
Div(
|
||||
Div(cls="dt2-scrollbars-horizontal"),
|
||||
cls="dt2-scrollbars-horizontal-wrapper"
|
||||
),
|
||||
cls="dt2-scrollbars"
|
||||
),
|
||||
cls="dt2-table-wrapper",
|
||||
id=f"tw_{self._id}"
|
||||
)
|
||||
|
||||
def mk_aggregation_cell(self, col_def, row_index: int, footer_conf, oob=False):
|
||||
@@ -371,13 +403,12 @@ class DataGrid(MultipleInstance):
|
||||
def render(self):
|
||||
if self._state.ne_df is None:
|
||||
return Div("No data to display !")
|
||||
|
||||
|
||||
return Div(
|
||||
Div(
|
||||
self.mk_table(),
|
||||
# Script(f"bindDatagrid('{self._id}', false);"),
|
||||
),
|
||||
id=self._id
|
||||
self.mk_table(),
|
||||
Script(f"initDataGridScrollbars('{self._id}');"),
|
||||
id=self._id,
|
||||
style="height: 100%;"
|
||||
)
|
||||
|
||||
def __ft__(self):
|
||||
|
||||
Reference in New Issue
Block a user