Added scrollbars
This commit is contained in:
@@ -690,7 +690,7 @@
|
|||||||
.mf-panel-main {
|
.mf-panel-main {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: auto;
|
overflow: hidden;
|
||||||
min-width: 0; /* Important to allow the shrinking of flexbox */
|
min-width: 0; /* Important to allow the shrinking of flexbox */
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -829,169 +829,33 @@
|
|||||||
/* ********************************************* */
|
/* ********************************************* */
|
||||||
/* ************* Datagrid Component ************ */
|
/* ************* 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-header,
|
||||||
.dt2-footer {
|
.dt2-footer {
|
||||||
background-color: var(--color-base-200);
|
background-color: var(--color-base-200);
|
||||||
border-radius: 10px 10px 0 0;
|
border-radius: 10px 10px 0 0;
|
||||||
min-width: max-content;
|
min-width: max-content; /* Content width propagates to scrollable parent */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Body */
|
||||||
.dt2-body {
|
.dt2-body {
|
||||||
overflow: hidden; /* You can change this to auto if horizontal scrolling is required */
|
overflow: hidden;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
min-width: max-content;
|
min-width: max-content; /* Content width propagates to scrollable parent */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Row */
|
||||||
.dt2-row {
|
.dt2-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 22px;
|
height: 22px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Cell */
|
||||||
.dt2-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;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
@@ -1002,11 +866,12 @@ input:focus {
|
|||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
flex-shrink: 1;
|
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);
|
border-bottom: 1px solid var(--color-border);
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Cell content types */
|
||||||
.dt2-cell-content-text {
|
.dt2-cell-content-text {
|
||||||
text-align: inherit;
|
text-align: inherit;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -1016,8 +881,8 @@ input:focus {
|
|||||||
.dt2-cell-content-checkbox {
|
.dt2-cell-content-checkbox {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
justify-content: center; /* Horizontally center the icon */
|
justify-content: center;
|
||||||
align-items: center; /* Vertically center the icon */
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dt2-cell-content-number {
|
.dt2-cell-content-number {
|
||||||
@@ -1026,36 +891,12 @@ input:focus {
|
|||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Footer cell */
|
||||||
.dt2-footer-cell {
|
.dt2-footer-cell {
|
||||||
cursor: pointer
|
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Resize handle */
|
||||||
.dt2-resize-handle {
|
.dt2-resize-handle {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
@@ -1066,100 +907,149 @@ input:focus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dt2-resize-handle::after {
|
.dt2-resize-handle::after {
|
||||||
content: ''; /* This is required */
|
content: '';
|
||||||
position: absolute; /* Position as needed */
|
position: absolute;
|
||||||
z-index: var(--datagrid-resize-zindex);
|
z-index: var(--datagrid-resize-zindex);
|
||||||
display: block; /* Makes it a block element */
|
display: block;
|
||||||
width: 3px;
|
width: 3px;
|
||||||
height: 60%;
|
height: 60%;
|
||||||
top: calc(50% - 60% * 0.5);
|
top: calc(50% - 60% * 0.5);
|
||||||
background-color: var(--color-resize);
|
background-color: var(--color-resize);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dt2-header-hidden {
|
/* Hidden column */
|
||||||
width: 5px;
|
|
||||||
background: var(--color-neutral-content);
|
|
||||||
border-bottom: 1px solid var(--color-border);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dt2-col-hidden {
|
.dt2-col-hidden {
|
||||||
width: 5px;
|
width: 5px;
|
||||||
border-bottom: 1px solid var(--color-border);
|
border-bottom: 1px solid var(--color-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Highlight */
|
||||||
.dt2-highlight-1 {
|
.dt2-highlight-1 {
|
||||||
color: var(--color-accent);
|
color: var(--color-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dt2-item-handle {
|
/* *********************************************** */
|
||||||
background-image: radial-gradient(var(--color-primary-content) 40%, transparent 0);
|
/* ******** DataGrid Fixed Header/Footer ******** */
|
||||||
background-repeat: repeat;
|
/* *********************************************** */
|
||||||
background-size: 4px 4px;
|
|
||||||
cursor: grab;
|
/*
|
||||||
display: inline-block;
|
* DataGrid with CSS Grid + Custom Scrollbars
|
||||||
height: 16px;
|
* - Wrapper takes 100% of parent height
|
||||||
margin: auto;
|
* - 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;
|
position: relative;
|
||||||
top: 1px;
|
|
||||||
width: 12px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* **************************************************************************** */
|
/* Table with Grid layout - horizontal scroll enabled, scrollbars hidden */
|
||||||
/* COLUMNS SETTINGS */
|
.dt2-table {
|
||||||
/* **************************************************************************** */
|
height: 100%;
|
||||||
|
|
||||||
.dt2-cs-header {
|
|
||||||
background-color: var(--color-base-200);
|
|
||||||
min-width: max-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dt2-cs-columns {
|
|
||||||
display: grid;
|
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 {
|
/* Chrome/Safari: hide scrollbar */
|
||||||
outline: none;
|
.dt2-table::-webkit-scrollbar {
|
||||||
border-color: transparent;
|
display: none;
|
||||||
box-shadow: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dt2-cs-body input[type="checkbox"],
|
/* Header - no scroll, takes natural height */
|
||||||
.dt2-cs-body input.checkbox {
|
.dt2-header-container {
|
||||||
outline: initial;
|
overflow: hidden;
|
||||||
border-color: var(--color-border);
|
min-width: max-content; /* Force table to be as wide as content */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Body - scrollable vertically via JS, scrollbars hidden */
|
||||||
.dt2-cs-cell {
|
.dt2-body-container {
|
||||||
padding: 0 6px 0 6px;
|
overflow: hidden; /* Scrollbars hidden, scroll via JS */
|
||||||
margin: auto;
|
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 {
|
/* Footer - no scroll, takes natural height */
|
||||||
margin: auto;
|
.dt2-footer-container {
|
||||||
|
overflow: hidden;
|
||||||
|
min-width: max-content; /* Force table to be as wide as content */
|
||||||
}
|
}
|
||||||
|
|
||||||
.dt2-cs-number-cell {
|
/* Custom scrollbars container - overlaid on table */
|
||||||
padding: 0 6px 0 6px;
|
.dt2-scrollbars {
|
||||||
text-align: right;
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
pointer-events: none; /* Let clicks pass through */
|
||||||
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dt2-cs-select-cell {
|
/* Scrollbar wrappers - clickable/draggable */
|
||||||
padding: 0 6px;
|
.dt2-scrollbars-vertical-wrapper,
|
||||||
margin: 3px 0;
|
.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 {
|
/* Vertical scrollbar wrapper - right side, full table height */
|
||||||
border: 1px solid #ccc; /* Provide a subtle border on focus */
|
.dt2-scrollbars-vertical-wrapper {
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Horizontal scrollbar wrapper - bottom, full width minus vertical scrollbar */
|
||||||
.dt2-views-container-select {
|
.dt2-scrollbars-horizontal-wrapper {
|
||||||
width: 170px;
|
left: 0;
|
||||||
|
right: 8px; /* Leave space for vertical scrollbar */
|
||||||
|
bottom: 0;
|
||||||
|
height: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dt2-views-container-create {
|
/* Scrollbar thumbs */
|
||||||
width: 300px;
|
.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):
|
def mk_body(self):
|
||||||
return Div(
|
return Div(
|
||||||
*self.mk_body_content_page(0),
|
*self.mk_body_content_page(0),
|
||||||
cls="dt2-body",
|
cls="dt2-body"
|
||||||
id=f"tb_{self._id}",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def mk_footers(self):
|
def mk_footers(self):
|
||||||
|
return self.mk_headers()
|
||||||
return Div(
|
return Div(
|
||||||
*[Div(
|
*[Div(
|
||||||
*[self.mk_aggregation_cell(col_def, row_index, footer) for col_def in self._state.columns],
|
*[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):
|
def mk_table(self):
|
||||||
return Div(
|
return Div(
|
||||||
self.mk_headers(),
|
# Grid table with header, body, footer
|
||||||
self.mk_body(),
|
Div(
|
||||||
self.mk_footers(),
|
# Header container - no scroll
|
||||||
cls="dt2-table",
|
Div(
|
||||||
id=f"t_{self._id}",
|
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):
|
def mk_aggregation_cell(self, col_def, row_index: int, footer_conf, oob=False):
|
||||||
@@ -371,13 +403,12 @@ class DataGrid(MultipleInstance):
|
|||||||
def render(self):
|
def render(self):
|
||||||
if self._state.ne_df is None:
|
if self._state.ne_df is None:
|
||||||
return Div("No data to display !")
|
return Div("No data to display !")
|
||||||
|
|
||||||
return Div(
|
return Div(
|
||||||
Div(
|
self.mk_table(),
|
||||||
self.mk_table(),
|
Script(f"initDataGridScrollbars('{self._id}');"),
|
||||||
# Script(f"bindDatagrid('{self._id}', false);"),
|
id=self._id,
|
||||||
),
|
style="height: 100%;"
|
||||||
id=self._id
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def __ft__(self):
|
def __ft__(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user