diff --git a/src/myfasthtml/assets/myfasthtml.css b/src/myfasthtml/assets/myfasthtml.css
index 3b54707..f40ab05 100644
--- a/src/myfasthtml/assets/myfasthtml.css
+++ b/src/myfasthtml/assets/myfasthtml.css
@@ -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);
}
diff --git a/src/myfasthtml/assets/myfasthtml.js b/src/myfasthtml/assets/myfasthtml.js
index c44cf70..f0a8c0a 100644
--- a/src/myfasthtml/assets/myfasthtml.js
+++ b/src/myfasthtml/assets/myfasthtml.js
@@ -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`);
+}
+
diff --git a/src/myfasthtml/controls/DataGrid.py b/src/myfasthtml/controls/DataGrid.py
index 6dcc053..755bab2 100644
--- a/src/myfasthtml/controls/DataGrid.py
+++ b/src/myfasthtml/controls/DataGrid.py
@@ -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):