diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue index 93b16583f5..8ef8edf4b1 100644 --- a/apps/desktop/src/components/grid/DataGrid.vue +++ b/apps/desktop/src/components/grid/DataGrid.vue @@ -3485,10 +3485,17 @@ let scrollingTimer = 0; const isScrolling = ref(false); let infiniteScrollPositions = new WeakMap(); +function updateDomGridVisibleItemsDuringScroll(scroller: HTMLElement) { + if (useCanvasGridRows.value || scroller !== gridScrollerElement()) return; + (scrollerRef.value as { updateVisibleItems?: (itemsChanged: boolean, checkPositionDiff?: boolean) => void } | null)?.updateVisibleItems?.(false, true); +} + function markGridScrolling() { if (!isScrolling.value) isScrolling.value = true; clearTimeout(scrollingTimer); scrollingTimer = window.setTimeout(() => { + const scroller = gridScrollerElement(); + if (scroller) updateDomGridVisibleItemsDuringScroll(scroller); isScrolling.value = false; }, 120); } @@ -3506,12 +3513,25 @@ function maybeCheckInfiniteScroll(scroller: HTMLElement) { } } +function clampGridScrollerBounds(scroller: HTMLElement) { + const maxTop = Math.max(0, scroller.scrollHeight - scroller.clientHeight); + const maxLeft = Math.max(0, scroller.scrollWidth - scroller.clientWidth); + const nextTop = Math.max(0, Math.min(maxTop, scroller.scrollTop)); + const nextLeft = Math.max(0, Math.min(maxLeft, scroller.scrollLeft)); + if (nextTop !== scroller.scrollTop) scroller.scrollTop = nextTop; + if (nextLeft !== scroller.scrollLeft) scroller.scrollLeft = nextLeft; +} + function onScrollerScroll(e: Event) { - syncHeaderScroll(e); const target = e.target; if (target instanceof HTMLElement) { + clampGridScrollerBounds(target); + updateDomGridVisibleItemsDuringScroll(target); + syncHeaderScroll(e); recordScrollPosition({ top: target.scrollTop, left: target.scrollLeft }); maybeCheckInfiniteScroll(target); + } else { + syncHeaderScroll(e); } markGridScrolling(); } @@ -5715,6 +5735,58 @@ function rowCellsUseSelectionVisual(rowId: number): boolean { return hasRowSelection.value && isRowSelected(rowId) && !hasCellSelection.value; } +function dataGridRowStyle(item: RowItem): CSSProperties { + const dark = isDark.value || (typeof document !== "undefined" && document.documentElement.classList.contains("dark")); + const rowBg = item.isDeleted + ? dark + ? "rgb(55, 31, 32)" + : "rgb(255, 244, 244)" + : item.isNew + ? dark + ? "rgb(51, 51, 55)" + : "rgb(243, 243, 243)" + : item.isDraft + ? dark + ? "rgb(51, 51, 55)" + : "rgb(243, 243, 243)" + : item.displayIndex % 2 === 1 + ? dark + ? "rgb(32, 32, 34)" + : "rgb(248, 248, 248)" + : dark + ? "rgb(19, 20, 22)" + : "rgb(255, 255, 255)"; + const rowNumberBg = + item.status === "new" + ? dark + ? "rgb(33, 45, 40)" + : "rgb(219, 244, 233)" + : item.status === "edited" + ? dark + ? "rgb(48, 41, 28)" + : "rgb(253, 241, 219)" + : item.status === "deleted" + ? dark + ? "rgb(55, 31, 32)" + : "rgb(255, 244, 244)" + : isRowActive(item.displayIndex) && !item.isDeleted + ? dark + ? "rgb(66, 67, 70)" + : "rgb(226, 226, 226)" + : dark + ? "rgb(35, 37, 42)" + : "rgb(255, 255, 255)"; + return { + "--data-grid-cell-bg": rowBg, + "--data-grid-row-number-bg": rowNumberBg, + "--data-grid-cell-selected-bg": dark ? "rgb(66, 67, 70)" : "rgb(226, 226, 226)", + "--data-grid-cell-selected-dirty-bg": dark ? "rgb(94, 75, 26)" : "rgb(244, 229, 186)", + "--data-grid-cell-selected-border": dark ? "rgb(170, 170, 175)" : "rgb(90, 90, 90)", + "--data-grid-row-number-active-bg": dark ? "rgb(66, 67, 70)" : "rgb(232, 232, 232)", + "--data-grid-row-number-selected-bg": dark ? "rgb(66, 67, 70)" : "rgb(226, 226, 226)", + } as CSSProperties; +} + const canvasRef = ref(); const canvasOverlayRef = ref(); const canvasViewportWidth = ref(0); @@ -5967,6 +6039,32 @@ function onCanvasWheel(event: WheelEvent) { onCanvasScroll({ target: scroller } as unknown as Event); } +function onDomGridWheel(event: WheelEvent) { + if (event.ctrlKey || event.metaKey) return; + const scroller = event.currentTarget instanceof HTMLElement ? event.currentTarget : gridScrollerElement(); + if (!scroller) return; + + const verticalDelta = canvasWheelDeltaToPixels(event.deltaY, event.deltaMode, scroller.clientHeight); + const horizontalDelta = canvasWheelDeltaToPixels(event.deltaX, event.deltaMode, scroller.clientWidth); + const shiftedHorizontalDelta = event.shiftKey && Math.abs(verticalDelta) > Math.abs(horizontalDelta) ? verticalDelta : 0; + const effectiveVerticalDelta = shiftedHorizontalDelta === 0 ? verticalDelta : 0; + const effectiveHorizontalDelta = horizontalDelta + shiftedHorizontalDelta; + if (effectiveVerticalDelta === 0 && effectiveHorizontalDelta === 0) return; + + const maxTop = Math.max(0, scroller.scrollHeight - scroller.clientHeight); + const maxLeft = Math.max(0, scroller.scrollWidth - scroller.clientWidth); + const nextTop = Math.max(0, Math.min(maxTop, scroller.scrollTop + effectiveVerticalDelta)); + const nextLeft = Math.max(0, Math.min(maxLeft, scroller.scrollLeft + effectiveHorizontalDelta)); + // Let an outer scroll container handle wheel input once the grid reaches its boundary. + if (nextTop === scroller.scrollTop && nextLeft === scroller.scrollLeft) return; + event.preventDefault(); + event.stopPropagation(); + + scroller.scrollTop = nextTop; + scroller.scrollLeft = nextLeft; + onScrollerScroll({ target: scroller } as unknown as Event); +} + function onCanvasMouseMove(event: MouseEvent) { if (columnHeaderPointerInteractionActive()) { if (canvasRef.value) canvasRef.value.style.cursor = "default"; @@ -8775,7 +8873,7 @@ const gridContextMenuItems = computed(() => {