From d909f2125dba72f82c11dfe467f3cddb2a022e04 Mon Sep 17 00:00:00 2001 From: Kodjo Sossouvi Date: Sun, 11 Jan 2026 23:25:55 +0100 Subject: [PATCH] Added scrollbars --- src/myfasthtml/assets/myfasthtml.css | 358 ++++++++++----------------- src/myfasthtml/assets/myfasthtml.js | 277 +++++++++++++++++++++ src/myfasthtml/controls/DataGrid.py | 57 ++++- 3 files changed, 445 insertions(+), 247 deletions(-) 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):