Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,11 @@ $root: ".widget-datagrid";
display: grid !important;
min-width: fit-content;
margin-bottom: 0;
&.infinite-loading {
// in order to restrict the scroll to row area
// we need to prevent table itself to expanding beyond available position
min-width: 0;
}
}
}

Expand Down Expand Up @@ -530,24 +535,57 @@ $root: ".widget-datagrid";
margin: var(--spacing-small, 8px) 0;
}

.infinite-loading.widget-datagrid-grid-body {
// when virtual scroll is enabled we make area that holds rows scrollable
// (while the area that holds column headers still stays in place)
overflow-y: auto;
.infinite-loading {
.widget-datagrid-grid-head {
width: calc(var(--widgets-grid-width) - var(--widgets-grid-scrollbar-size));
overflow-x: hidden;
}
.widget-datagrid-grid-head[data-scrolled-y="true"] {
box-shadow: 0 5px 5px -5px gray;
}

.widget-datagrid-grid-body {
width: var(--widgets-grid-width);
overflow-y: auto;
max-height: var(--widgets-grid-body-height);
}

.widget-datagrid-grid-head[data-scrolled-x="true"]:after {
content: "";
position: absolute;
left: 0px;
width: 10px;
box-shadow: inset 5px 0 5px -5px gray;
top: 0;
bottom: 0;
}
}

.widget-datagrid-grid-head,
.widget-datagrid-grid-head {
display: grid;

// this head is not part of the grid, so it has dedicated column template --widgets-grid-template-columns-head
// but it might not be available at the initial render, so we use template from the grid --widgets-grid-template-columns
// using template from the grid might to misalignment from the grid itself,
// but in practice:
// - grid has no data at that moment, so misalignment is not visible.
// - as soon as the grid itself gets rendered --widgets-grid-template-columns-head gets calculated
// and everything looks like it should.
grid-template-columns: var(--widgets-grid-template-columns-head, var(--widgets-grid-template-columns));
}
.widget-datagrid-grid-body {
// this element has to position their children (columns or headers)
// as grid and have those aligned with the parent grid
display: grid;
// this property makes sure we align our own grid columns
// to the columns defined in the global grid
grid-template-columns: subgrid;
grid-template-columns: var(--widgets-grid-template-columns);
}

// ensure that we cover all columns of original top level grid
// so our own columns get aligned with the parent
.grid-mock-header {
grid-template-columns: subgrid;
grid-column: 1 / -1;
display: grid;
}

:where(#{$root}-paging-bottom, #{$root}-padding-top) {
Expand Down
4 changes: 4 additions & 0 deletions packages/pluggableWidgets/datagrid-web/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

### Changed

- We improved virtual scrolling behavior when horizontal scrolling is present due to grid size.

### Added

- We added a new property for export to excel. The new property allows to set the cell export type and also the format for type number and date.
Expand Down
10 changes: 8 additions & 2 deletions packages/pluggableWidgets/datagrid-web/src/components/Grid.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import classNames from "classnames";
import { observer } from "mobx-react-lite";
import { PropsWithChildren, ReactElement } from "react";
import { useDatagridConfig, useGridStyle } from "../model/hooks/injection-hooks";
import { useDatagridConfig, useGridSizeStore, useGridStyle } from "../model/hooks/injection-hooks";

export const Grid = observer(function Grid(props: PropsWithChildren): ReactElement {
const config = useDatagridConfig();
const gridSizeStore = useGridSizeStore();

const style = useGridStyle().get();
return (
<div
aria-multiselectable={config.multiselectable}
className={"widget-datagrid-grid table"}
className={classNames("widget-datagrid-grid table", {
"infinite-loading": gridSizeStore.hasVirtualScrolling
})}
role="grid"
style={style}
ref={gridSizeStore.gridContainerRef}
>
{props.children}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import classNames from "classnames";
import { observer } from "mobx-react-lite";
import { Fragment, PropsWithChildren, ReactElement, ReactNode } from "react";
import {
useDatagridConfig,
useGridSizeStore,
useItemCount,
useLoaderViewModel,
usePaginationService,
Expand All @@ -14,14 +14,14 @@ import { SpinnerLoader } from "./loader/SpinnerLoader";

export const GridBody = observer(function GridBody(props: PropsWithChildren): ReactElement {
const { children } = props;
const { bodySize, containerRef, isInfinite, handleScroll } = useBodyScroll();
const gridSizeStore = useGridSizeStore();
const { handleScroll } = useBodyScroll();

return (
<div
className={classNames("widget-datagrid-grid-body table-content", { "infinite-loading": isInfinite })}
style={isInfinite && bodySize > 0 ? { maxHeight: `${bodySize}px` } : {}}
className={"widget-datagrid-grid-body table-content"}
role="rowgroup"
ref={containerRef}
ref={gridSizeStore.gridBodyRef}
onScroll={handleScroll}
>
<ContentGuard>{children}</ContentGuard>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ReactElement, useState } from "react";
import { useColumnsStore, useDatagridConfig } from "../model/hooks/injection-hooks";
import { useColumnsStore, useDatagridConfig, useGridSizeStore } from "../model/hooks/injection-hooks";
import { ColumnId } from "../typings/GridColumn";
import { CheckboxColumnHeader } from "./CheckboxColumnHeader";
import { ColumnProvider } from "./ColumnProvider";
Expand All @@ -11,6 +11,7 @@ import { HeaderSkeletonLoader } from "./loader/HeaderSkeletonLoader";
export function GridHeader(): ReactElement {
const { columnsHidable, id: gridId } = useDatagridConfig();
const columnsStore = useColumnsStore();
const gridSizeStore = useGridSizeStore();
const columns = columnsStore.visibleColumns;
const [dragOver, setDragOver] = useState<[ColumnId, "before" | "after"] | undefined>(undefined);
const [isDragging, setIsDragging] = useState<[ColumnId | undefined, ColumnId, ColumnId | undefined] | undefined>();
Expand All @@ -20,7 +21,7 @@ export function GridHeader(): ReactElement {
}

return (
<div className="widget-datagrid-grid-head" role="rowgroup">
<div className="widget-datagrid-grid-head" role="rowgroup" ref={gridSizeStore.gridHeaderRef}>
<div key="headers_row" className="tr" role="row">
<CheckboxColumnHeader key="headers_column_select_all" />
{columns.map(column => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ export function Header(props: HeaderProps): ReactElement {
role="columnheader"
style={!canSort ? { cursor: "unset" } : undefined}
title={caption}
ref={ref => column.setHeaderElementRef(ref)}
data-column-id={column.columnId}
onDrop={draggableProps.onDrop}
onDragEnter={draggableProps.onDragEnter}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { ReactNode, useCallback, useEffect, useRef } from "react";
import { useColumnsStore, useDatagridConfig, useGridSizeStore } from "../model/hooks/injection-hooks";

function getColumnSizes(container: HTMLDivElement | null): Map<string, number> {
const sizes = new Map<string, number>();
if (container) {
container.querySelectorAll<HTMLDivElement>("[data-column-id]").forEach(c => {
const columnId = c.dataset.columnId;
if (!columnId) {
console.debug("getColumnSizes: can't find id on:", c);
return;
}
sizes.set(columnId, c.offsetWidth);
});
}

return sizes;
}

export function MockHeader(): ReactNode {
const columnsStore = useColumnsStore();
const config = useDatagridConfig();
const gridSizeStore = useGridSizeStore();
const headerRef = useRef<HTMLDivElement | null>(null);
const resizeCallback = useCallback<ResizeObserverCallback>(() => {
gridSizeStore.updateColumnSizes(getColumnSizes(headerRef.current).values().toArray());
}, [headerRef, gridSizeStore]);

useEffect(() => {
const observer = new ResizeObserver(resizeCallback);

if (headerRef.current) {
observer.observe(headerRef.current);
}
return () => {
observer.disconnect();
};
}, [resizeCallback, headerRef]);

return (
<div className={"grid-mock-header"} aria-hidden ref={headerRef}>
{config.checkboxColumnEnabled && <div data-column-id="checkboxes" key={"checkboxes"}></div>}
{columnsStore.visibleColumns.map(c => (
<div
data-column-id={c.columnId}
key={c.columnId}
// we set header ref here instead of the real header
// as this mock header is aligned with CSS grid, so it is more reliable
// the real header is aligned programmatically based on this header
ref={ref => c.setHeaderElementRef(ref)}
></div>
))}
{config.selectorColumnEnabled && <div data-column-id="selector" key={"selector"}></div>}
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { WidgetFooter } from "./WidgetFooter";
import { WidgetHeader } from "./WidgetHeader";
import { WidgetRoot } from "./WidgetRoot";
import { WidgetTopBar } from "./WidgetTopBar";
import { MockHeader } from "./MockHeader";

export function Widget(props: { onExportCancel?: () => void }): ReactElement {
return (
Expand All @@ -25,6 +26,7 @@ export function Widget(props: { onExportCancel?: () => void }): ReactElement {
<SelectAllBar />
<RefreshStatus />
<GridBody>
<MockHeader />
<RowsRenderer />
<EmptyPlaceholder />
</GridBody>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ exports[`Grid renders without crashing 1`] = `
<div
class="widget-datagrid-grid table"
role="grid"
style="grid-template-columns: 1fr 1fr 54px;"
style="--widgets-grid-template-columns: 1fr 1fr 54px;"
>
Test
</div>
Expand All @@ -17,7 +17,7 @@ exports[`Grid renders without selector column 1`] = `
<div
class="widget-datagrid-grid table"
role="grid"
style="grid-template-columns: 1fr 1fr;"
style="--widgets-grid-template-columns: 1fr 1fr;"
>
Test
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export class ColumnStore implements GridColumn {
private baseInfo: BaseColumnInfo;
private parentStore: IColumnParentStore;

// this holds size of the column that it had prior to resizing started
// this is needed to prevent personalization being saved while resizing
private frozenSize: number | undefined;

// dynamic props from PW API
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { DerivedLoaderController } from "../services/DerivedLoaderController";
import { PaginationController } from "../services/PaginationController";
import { SelectionGate } from "../services/SelectionGate.service";
import { CORE_TOKENS as CORE, DG_TOKENS as DG, SA_TOKENS } from "../tokens";
import { GridSizeStore } from "../stores/GridSize.store";

// base
injected(ColumnGroupStore, CORE.setupService, CORE.mainGate, CORE.config, DG.filterHost);
Expand All @@ -40,6 +41,7 @@ injected(DatasourceService, CORE.setupService, DG.queryGate, DG.refreshInterval.
injected(PaginationController, CORE.setupService, DG.paginationConfig, DG.query);
injected(GridBasicData, CORE.mainGate);
injected(WidgetRootViewModel, CORE.mainGate, CORE.config, DG.exportProgressService, SA_TOKENS.selectionDialogVM);
injected(GridSizeStore, DG.paginationService);

// loader
injected(DerivedLoaderController, DG.query, DG.exportProgressService, CORE.columnsStore, DG.loaderConfig);
Expand All @@ -58,7 +60,7 @@ injected(GridPersonalizationStore, CORE.setupService, CORE.mainGate, CORE.column
// selection
injected(SelectionGate, CORE.mainGate);
injected(createSelectionHelper, CORE.setupService, DG.selectionGate, CORE.config.optional);
injected(gridStyleAtom, CORE.columnsStore, CORE.config);
injected(gridStyleAtom, CORE.columnsStore, CORE.config, DG.gridSizeStore);
injected(rowClassProvider, CORE.mainGate);

// row-interaction
Expand Down Expand Up @@ -98,6 +100,8 @@ export class DatagridContainer extends Container {
this.bind(DG.query).toInstance(DatasourceService).inSingletonScope();
// Pagination service
this.bind(DG.paginationService).toInstance(PaginationController).inSingletonScope();
// Grid sizing and scrolling store
this.bind(DG.gridSizeStore).toInstance(GridSizeStore).inSingletonScope();
// Datasource params service
this.bind(DG.paramsService).toInstance(DatasourceParamsController).inSingletonScope();
// FilterAPI
Expand Down Expand Up @@ -229,5 +233,7 @@ export class DatagridContainer extends Container {

// Hydrate filters from props
this.get(DG.combinedFilter).hydrate(props.datasource.filter);

this.get(DG.gridSizeStore);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const [useExportProgressService] = createInjectionHooks(DG.exportProgress
export const [useLoaderViewModel] = createInjectionHooks(DG.loaderVM);
export const [useMainGate] = createInjectionHooks(CORE.mainGate);
export const [usePaginationService] = createInjectionHooks(DG.paginationService);
export const [useGridSizeStore] = createInjectionHooks(DG.gridSizeStore);
export const [useSelectionHelper] = createInjectionHooks(DG.selectionHelper);
export const [useGridStyle] = createInjectionHooks(DG.gridColumnsStyle);
export const [useQueryService] = createInjectionHooks(DG.query);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,12 @@
import { useInfiniteControl } from "@mendix/widget-plugin-grid/components/InfiniteBody";
import { RefObject, UIEventHandler, useCallback } from "react";
import { usePaginationService } from "./injection-hooks";
import { UIEventHandler } from "react";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: this might be removed but, OK :D

import { useInfiniteControl } from "./useInfiniteControl";

export function useBodyScroll(): {
handleScroll: UIEventHandler<HTMLDivElement> | undefined;
bodySize: number;
containerRef: RefObject<HTMLDivElement | null>;
isInfinite: boolean;
} {
const paging = usePaginationService();
const setPage = useCallback((cb: (n: number) => number) => paging.setPage(cb), [paging]);

const isInfinite = paging.pagination === "virtualScrolling";
const [trackScrolling, bodySize, containerRef] = useInfiniteControl({
hasMoreItems: paging.hasMoreItems,
isInfinite,
setPage
});
const [trackBodyScrolling] = useInfiniteControl();

return {
handleScroll: isInfinite ? trackScrolling : undefined,
bodySize,
containerRef,
isInfinite
handleScroll: trackBodyScrolling
};
}
Loading
Loading