diff --git a/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorPreview.tsx b/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorPreview.tsx index 1b113163aa..2c8fcbe728 100644 --- a/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorPreview.tsx +++ b/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorPreview.tsx @@ -12,6 +12,7 @@ import { ColumnPreview } from "./helpers/ColumnPreview"; import { useSelectActionHelper } from "./helpers/SelectActionHelper"; import { useFocusTargetController } from "@mendix/widget-plugin-grid/keyboard-navigation/useFocusTargetController"; import "./ui/DatagridPreview.scss"; +import { HelpersProvider } from "./helpers/helpers-context"; // Fix type definition for Selectable // TODO: Open PR to fix in appdev. @@ -80,67 +81,73 @@ export function preview(props: DatagridPreviewProps): ReactElement { const eventsController = { getProps: () => Object.create({}) }; return ( - ReactElement) => ( - - {renderWrapper(null)} - - ), - [EmptyPlaceholder] - )} - exporting={false} - filterRenderer={useCallback( - (renderWrapper, columnIndex) => { - const column = props.columns.at(columnIndex); - return column?.filter ? ( - + + ReactElement) => ( + {renderWrapper(null)} - - ) : ( - renderWrapper(null) - ); - }, - [props.columns] - )} - headerContent={ - -
- - } - hasMoreItems={false} - headerWrapperRenderer={selectableWrapperRenderer(previewColumns)} - numberOfItems={props.pageSize ?? numberOfItems} - page={0} - paginationType={props.pagination} - pageSize={props.pageSize ?? numberOfItems} - showPagingButtons={props.showPagingButtons} - loadMoreButtonCaption={props.loadMoreButtonCaption} - paging={props.pagination === "buttons"} - pagingPosition={props.pagingPosition} - preview - processedRows={0} - styles={parseStyle(props.style)} - selectionStatus={"none"} - id={gridId} - gridInteractive={!!(props.itemSelection !== "None" || props.onClick)} - selectActionHelper={selectActionHelper} - cellEventsController={eventsController} - checkboxEventsController={eventsController} - focusController={focusController} - /> + + ), + [EmptyPlaceholder] + )} + exporting={false} + filterRenderer={useCallback( + (renderWrapper, columnIndex) => { + const column = props.columns.at(columnIndex); + return column?.filter ? ( + + {renderWrapper(null)} + + ) : ( + renderWrapper(null) + ); + }, + [props.columns] + )} + headerContent={ + +
+ + } + hasMoreItems={false} + headerWrapperRenderer={selectableWrapperRenderer(previewColumns)} + numberOfItems={props.pageSize ?? numberOfItems} + page={0} + paginationType={props.pagination} + pageSize={props.pageSize ?? numberOfItems} + showPagingButtons={props.showPagingButtons} + loadMoreButtonCaption={props.loadMoreButtonCaption} + paging={props.pagination === "buttons"} + pagingPosition={props.pagingPosition} + preview + processedRows={0} + styles={parseStyle(props.style)} + id={gridId} + gridInteractive={!!(props.itemSelection !== "None" || props.onClick)} + /> + ); } diff --git a/packages/pluggableWidgets/datagrid-web/src/Datagrid.tsx b/packages/pluggableWidgets/datagrid-web/src/Datagrid.tsx index 72406f07e8..46df0c3ca6 100644 --- a/packages/pluggableWidgets/datagrid-web/src/Datagrid.tsx +++ b/packages/pluggableWidgets/datagrid-web/src/Datagrid.tsx @@ -1,24 +1,19 @@ -import { useSelectionHelper } from "@mendix/widget-plugin-grid/selection"; +import { useOnResetFiltersEvent } from "@mendix/widget-plugin-external-events/hooks"; import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid"; +import { observer } from "mobx-react-lite"; import { ReactElement, ReactNode, createElement, useCallback, useEffect, useMemo } from "react"; import { DatagridContainerProps } from "../typings/DatagridProps"; import { Cell } from "./components/Cell"; import { Widget } from "./components/Widget"; import { WidgetHeaderContext } from "./components/WidgetHeaderContext"; -import "./ui/Datagrid.scss"; -import { useShowPagination } from "./utils/useShowPagination"; -import { useSelectActionHelper } from "./helpers/SelectActionHelper"; -import { useClickActionHelper } from "@mendix/widget-plugin-grid/helpers/ClickActionHelper"; -import { useCellEventsController } from "./features/row-interaction/CellEventsController"; -import { useCheckboxEventsController } from "./features/row-interaction/CheckboxEventsController"; -import { useFocusTargetController } from "@mendix/widget-plugin-grid/keyboard-navigation/useFocusTargetController"; -import { useOnResetFiltersEvent } from "@mendix/widget-plugin-external-events/hooks"; +import { ProgressStore } from "./features/data-export/ProgressStore"; +import { useDataExport } from "./features/data-export/useDataExport"; import { IColumnGroupStore } from "./helpers/state/ColumnGroupStore"; -import { observer } from "mobx-react-lite"; import { RootGridStore } from "./helpers/state/RootGridStore"; import { useRootStore } from "./helpers/state/useRootStore"; -import { useDataExport } from "./features/data-export/useDataExport"; -import { ProgressStore } from "./features/data-export/ProgressStore"; +import "./ui/Datagrid.scss"; +import { useShowPagination } from "./utils/useShowPagination"; +import { HelpersProvider, useProvideGridHelpers } from "./helpers/helpers-context"; interface Props extends DatagridContainerProps { columnsStore: IColumnGroupStore; @@ -64,96 +59,73 @@ const Container = observer((props: Props): ReactElement => { [props.datasource, props.pageSize, isInfiniteLoad, currentPage] ); - const selectionHelper = useSelectionHelper(props.itemSelection, props.datasource, props.onSelectionChange); - - const selectActionHelper = useSelectActionHelper(props, selectionHelper); - - const clickActionHelper = useClickActionHelper({ - onClickTrigger: props.onClickTrigger, - onClick: props.onClick - }); useOnResetFiltersEvent(props.rootStore.staticInfo.name, props.rootStore.staticInfo.filtersChannelName); - const visibleColumnsCount = selectActionHelper.showCheckboxColumn - ? columnsStore.visibleColumns.length + 1 - : columnsStore.visibleColumns.length; - - const focusController = useFocusTargetController({ - rows: items.length, - columns: visibleColumnsCount, - pageSize: props.pageSize - }); - - const cellEventsController = useCellEventsController(selectActionHelper, clickActionHelper, focusController); - - const checkboxEventsController = useCheckboxEventsController(selectActionHelper, focusController); + const helpers = useProvideGridHelpers(props); return ( - ReactElement) => - props.showEmptyPlaceholder === "custom" ? renderWrapper(props.emptyPlaceholder) :
, - [props.emptyPlaceholder, props.showEmptyPlaceholder] - )} - filterRenderer={useCallback( - (renderWrapper, columnIndex) => { - const columnFilter = columnsStore.columnFilters[columnIndex]; - return renderWrapper(columnFilter.renderFilterWidgets()); - }, - [columnsStore.columnFilters] - )} - headerTitle={props.filterSectionTitle?.value} - headerContent={ - props.filtersPlaceholder && ( - - {props.filtersPlaceholder} - - ) - } - hasMoreItems={props.datasource.hasMoreItems ?? false} - headerWrapperRenderer={useCallback((_columnIndex: number, header: ReactElement) => header, [])} - id={useMemo(() => `DataGrid${generateUUID()}`, [])} - numberOfItems={props.datasource.totalCount} - onExportCancel={abortExport} - page={currentPage} - pageSize={props.pageSize} - paginationType={props.pagination} - loadMoreButtonCaption={props.loadMoreButtonCaption?.value} - paging={useShowPagination({ - pagination: props.pagination, - showPagingButtons: props.showPagingButtons, - totalCount: props.datasource.totalCount, - limit: props.datasource.limit - })} - pagingPosition={props.pagingPosition} - showPagingButtons={props.showPagingButtons} - rowClass={useCallback((value: any) => props.rowClass?.get(value)?.value ?? "", [props.rowClass])} - gridInteractive={!!(props.itemSelection || props.onClick)} - setPage={setPage} - styles={props.style} - selectionStatus={selectionHelper?.type === "Multi" ? selectionHelper.selectionStatus : "unknown"} - exporting={exportProgress.exporting} - processedRows={exportProgress.loaded} - exportDialogLabel={props.exportDialogLabel?.value} - cancelExportLabel={props.cancelExportLabel?.value} - selectRowLabel={props.selectRowLabel?.value} - visibleColumns={columnsStore.visibleColumns} - availableColumns={columnsStore.availableColumns} - columnsCreateSizeSnapshot={() => columnsStore.createSizeSnapshot()} - columnsSwap={(moved, [target, placement]) => columnsStore.swapColumns(moved, [target, placement])} - selectActionHelper={selectActionHelper} - cellEventsController={cellEventsController} - checkboxEventsController={checkboxEventsController} - focusController={focusController} - /> + + ReactElement) => + props.showEmptyPlaceholder === "custom" ? renderWrapper(props.emptyPlaceholder) :
, + [props.emptyPlaceholder, props.showEmptyPlaceholder] + )} + filterRenderer={useCallback( + (renderWrapper, columnIndex) => { + const columnFilter = columnsStore.columnFilters[columnIndex]; + return renderWrapper(columnFilter.renderFilterWidgets()); + }, + [columnsStore.columnFilters] + )} + headerTitle={props.filterSectionTitle?.value} + headerContent={ + props.filtersPlaceholder && ( + + {props.filtersPlaceholder} + + ) + } + hasMoreItems={props.datasource.hasMoreItems ?? false} + headerWrapperRenderer={useCallback((_columnIndex: number, header: ReactElement) => header, [])} + id={useMemo(() => `DataGrid${generateUUID()}`, [])} + numberOfItems={props.datasource.totalCount} + onExportCancel={abortExport} + page={currentPage} + pageSize={props.pageSize} + paginationType={props.pagination} + loadMoreButtonCaption={props.loadMoreButtonCaption?.value} + paging={useShowPagination({ + pagination: props.pagination, + showPagingButtons: props.showPagingButtons, + totalCount: props.datasource.totalCount, + limit: props.datasource.limit + })} + pagingPosition={props.pagingPosition} + showPagingButtons={props.showPagingButtons} + rowClass={useCallback((value: any) => props.rowClass?.get(value)?.value ?? "", [props.rowClass])} + gridInteractive={!!(props.itemSelection || props.onClick)} + setPage={setPage} + styles={props.style} + exporting={exportProgress.exporting} + processedRows={exportProgress.loaded} + exportDialogLabel={props.exportDialogLabel?.value} + cancelExportLabel={props.cancelExportLabel?.value} + selectRowLabel={props.selectRowLabel?.value} + visibleColumns={columnsStore.visibleColumns} + availableColumns={columnsStore.availableColumns} + columnsCreateSizeSnapshot={() => columnsStore.createSizeSnapshot()} + columnsSwap={(moved, [target, placement]) => columnsStore.swapColumns(moved, [target, placement])} + /> + ); }); diff --git a/packages/pluggableWidgets/datagrid-web/src/__tests__/perf.spec.tsx b/packages/pluggableWidgets/datagrid-web/src/__tests__/perf.spec.tsx index 32e5bcd814..1e508281fb 100644 --- a/packages/pluggableWidgets/datagrid-web/src/__tests__/perf.spec.tsx +++ b/packages/pluggableWidgets/datagrid-web/src/__tests__/perf.spec.tsx @@ -58,7 +58,7 @@ describe("Datagrid", () => { }); window.IntersectionObserver = mockIntersectionObserver; }); - it("should not take more then 9s to hide 3 column", async () => { + it("should not take eternity to hide 3 column", async () => { const props: DatagridContainerProps = { advanced: false, name: "datagrid", diff --git a/packages/pluggableWidgets/datagrid-web/src/components/Cell.tsx b/packages/pluggableWidgets/datagrid-web/src/components/Cell.tsx index 18505de4e1..6db8875b8e 100644 --- a/packages/pluggableWidgets/datagrid-web/src/components/Cell.tsx +++ b/packages/pluggableWidgets/datagrid-web/src/components/Cell.tsx @@ -1,19 +1,15 @@ -import { createElement, ReactElement, useMemo } from "react"; -import { observer } from "mobx-react-lite"; -import { computed } from "mobx"; +import { createElement, ReactElement, useMemo, memo } from "react"; import { GridColumn } from "../typings/GridColumn"; import { CellComponentProps } from "../typings/CellComponent"; import { CellElement } from "./CellElement"; import { useFocusTargetProps } from "@mendix/widget-plugin-grid/keyboard-navigation/useFocusTargetProps"; -// eslint-disable-next-line prefer-arrow-callback -const component = observer(function Cell(props: CellComponentProps): ReactElement { +function CellNode(props: CellComponentProps): ReactElement { const keyNavProps = useFocusTargetProps({ columnIndex: props.columnIndex ?? -1, rowIndex: props.rowIndex }); const handlers = useMemo(() => props.eventsController.getProps(props.item), [props.item, props.eventsController]); - const children = computed(() => props.column.renderCellContent(props.item)).get(); return ( ): onKeyUp={handlers.onKeyUp} onFocus={handlers.onFocus} > - {children} + {props.column.renderCellContent(props.item)} ); -}); +} -// Override NamedExoticComponent type -export const Cell = component as (props: CellComponentProps) => ReactElement; +export const Cell = memo(CellNode) as (props: CellComponentProps) => ReactElement; diff --git a/packages/pluggableWidgets/datagrid-web/src/components/CheckboxCell.tsx b/packages/pluggableWidgets/datagrid-web/src/components/CheckboxCell.tsx index f60ea106d2..77d9979fcf 100644 --- a/packages/pluggableWidgets/datagrid-web/src/components/CheckboxCell.tsx +++ b/packages/pluggableWidgets/datagrid-web/src/components/CheckboxCell.tsx @@ -2,23 +2,32 @@ import { createElement, ReactElement } from "react"; import { ObjectItem } from "mendix"; import { useFocusTargetProps } from "@mendix/widget-plugin-grid/keyboard-navigation/useFocusTargetProps"; import { CellElement, CellElementProps } from "./CellElement"; -import { useWidgetProps } from "../helpers/useWidgetProps"; +import { useHelpersContext } from "../helpers/helpers-context"; export type CheckboxCellProps = CellElementProps & { rowIndex: number; lastRow?: boolean; item: ObjectItem; + interactive: boolean; + selectRowLabel?: string; }; -export function CheckboxCell({ item, rowIndex, lastRow, ...rest }: CheckboxCellProps): ReactElement { +export function CheckboxCell({ + item, + rowIndex, + lastRow, + interactive, + selectRowLabel, + ...rest +}: CheckboxCellProps): ReactElement { + const { selectActionHelper, checkboxEventsController } = useHelpersContext(); const keyNavProps = useFocusTargetProps({ columnIndex: 0, rowIndex }); - const { selectActionHelper, checkboxEventsController, selectRowLabel, gridInteractive } = useWidgetProps(); return ( - + ; diff --git a/packages/pluggableWidgets/datagrid-web/src/components/Row.tsx b/packages/pluggableWidgets/datagrid-web/src/components/Row.tsx index 78cf469d52..d74e471e5d 100644 --- a/packages/pluggableWidgets/datagrid-web/src/components/Row.tsx +++ b/packages/pluggableWidgets/datagrid-web/src/components/Row.tsx @@ -1,11 +1,11 @@ import classNames from "classnames"; import { ObjectItem } from "mendix"; import { ReactElement, createElement } from "react"; -import { CellComponent, EventsController } from "../typings/CellComponent"; +import { useHelpersContext } from "../helpers/helpers-context"; +import { CellComponent } from "../typings/CellComponent"; import { GridColumn } from "../typings/GridColumn"; -import { SelectorCell } from "./SelectorCell"; import { CheckboxCell } from "./CheckboxCell"; -import { SelectActionHelper } from "../helpers/SelectActionHelper"; +import { SelectorCell } from "./SelectorCell"; export interface RowProps { className?: string; @@ -15,17 +15,15 @@ export interface RowProps { index: number; showSelectorCell?: boolean; selectableWrapper?: (column: number, children: React.ReactElement) => React.ReactElement; - selectActionHelper: SelectActionHelper; preview: boolean; totalRows: number; - clickable: boolean; - eventsController: EventsController; + interactive: boolean; } export function Row(props: RowProps): ReactElement { - const { CellComponent: Cell, selectActionHelper, preview, totalRows, eventsController } = props; - const selected = selectActionHelper.isSelected(props.item); - const ariaSelected = selectActionHelper.selectionType === "None" ? undefined : selected; + const { cellEventsController } = useHelpersContext(); + const { CellComponent: Cell, preview, totalRows } = props; + const [showCheckbox, selected, ariaSelected] = useSelectionFlags(props.item); const borderTop = props.index === 0; return ( @@ -34,13 +32,14 @@ export function Row(props: RowProps): ReactElement { role="row" aria-selected={ariaSelected} > - {selectActionHelper.showCheckboxColumn && ( + {showCheckbox && ( )} {props.columns.map((column, baseIndex) => { @@ -49,11 +48,11 @@ export function Row(props: RowProps): ReactElement { key={`row_${props.item.id}_col_${column.columnId}`} column={column} rowIndex={props.index} - columnIndex={selectActionHelper.showCheckboxColumn ? baseIndex + 1 : baseIndex} + columnIndex={showCheckbox ? baseIndex + 1 : baseIndex} item={props.item} - clickable={props.clickable} + clickable={props.interactive} preview={preview} - eventsController={eventsController} + eventsController={cellEventsController} /> ); @@ -63,10 +62,19 @@ export function Row(props: RowProps): ReactElement { )}
); } + +const useSelectionFlags = ( + item: ObjectItem +): [showCheckbox: boolean, selected: boolean, ariaSelected: boolean | undefined] => { + const { selectActionHelper } = useHelpersContext(); + const selected = selectActionHelper.isSelected(item); + const ariaSelected = selectActionHelper.selectionType === "None" ? undefined : selected; + return [selectActionHelper.showCheckboxColumn, selected, ariaSelected]; +}; diff --git a/packages/pluggableWidgets/datagrid-web/src/components/RowsRenderer.tsx b/packages/pluggableWidgets/datagrid-web/src/components/RowsRenderer.tsx index 32a3a07d08..be72bb3802 100644 --- a/packages/pluggableWidgets/datagrid-web/src/components/RowsRenderer.tsx +++ b/packages/pluggableWidgets/datagrid-web/src/components/RowsRenderer.tsx @@ -1,37 +1,32 @@ +import { KeyNavProvider } from "@mendix/widget-plugin-grid/keyboard-navigation/context"; import { ObjectItem } from "mendix"; import { createElement } from "react"; -import { CellComponent, EventsController } from "../typings/CellComponent"; +import { useHelpersContext } from "../helpers/helpers-context"; +import { CellComponent } from "../typings/CellComponent"; import { GridColumn } from "../typings/GridColumn"; -import { KeyNavProvider } from "@mendix/widget-plugin-grid/keyboard-navigation/context"; -import { FocusTargetController } from "@mendix/widget-plugin-grid/keyboard-navigation/FocusTargetController"; import { Row } from "./Row"; -import { SelectActionHelper } from "../helpers/SelectActionHelper"; interface RowsRendererProps { Cell: CellComponent; columns: GridColumn[]; columnsHidable: boolean; - eventsController: EventsController; - focusController: FocusTargetController; interactive: boolean; preview: boolean; rowClass?: (item: ObjectItem) => string; rows: ObjectItem[]; selectableWrapper?: (column: number, children: React.ReactElement) => React.ReactElement; - selectActionHelper: SelectActionHelper; } -export function RowsRenderer(props: RowsRendererProps): React.ReactElement { +// eslint-disable-next-line prefer-arrow-callback +export const RowsRenderer = function RowsRenderer(props: RowsRendererProps): React.ReactElement { return ( - + {props.rows.map((item, rowIndex) => { return ( ); -} +}; diff --git a/packages/pluggableWidgets/datagrid-web/src/components/Widget.tsx b/packages/pluggableWidgets/datagrid-web/src/components/Widget.tsx index 5bc14ed567..af78390c4f 100644 --- a/packages/pluggableWidgets/datagrid-web/src/components/Widget.tsx +++ b/packages/pluggableWidgets/datagrid-web/src/components/Widget.tsx @@ -1,11 +1,9 @@ import { Pagination } from "@mendix/widget-plugin-grid/components/Pagination"; -import { SelectionStatus } from "@mendix/widget-plugin-grid/selection"; import classNames from "classnames"; import { ListActionValue, ObjectItem } from "mendix"; import { CSSProperties, ReactElement, ReactNode, createElement, useCallback, useState, Fragment } from "react"; import { PagingPositionEnum, PaginationEnum, ShowPagingButtonsEnum } from "../../typings/DatagridProps"; -import { WidgetPropsProvider } from "../helpers/useWidgetProps"; -import { CellComponent, EventsController } from "../typings/CellComponent"; +import { CellComponent } from "../typings/CellComponent"; import { ColumnId, GridColumn } from "../typings/GridColumn"; import { CheckboxColumnHeader } from "./CheckboxColumnHeader"; import { ColumnResizer } from "./ColumnResizer"; @@ -19,10 +17,9 @@ import { WidgetHeader } from "./WidgetHeader"; import { WidgetRoot } from "./WidgetRoot"; import { WidgetTopBar } from "./WidgetTopBar"; import { ExportWidget } from "./ExportWidget"; -import { SelectActionHelper } from "../helpers/SelectActionHelper"; -import { FocusTargetController } from "@mendix/widget-plugin-grid/keyboard-navigation/FocusTargetController"; import { observer } from "mobx-react-lite"; import { RowsRenderer } from "./RowsRenderer"; +import { useHelpersContext } from "../helpers/helpers-context"; export interface WidgetProps { CellComponent: CellComponent; @@ -57,18 +54,11 @@ export interface WidgetProps number) => void; styles?: CSSProperties; rowAction?: ListActionValue; - selectionStatus: SelectionStatus; showSelectAllToggle?: boolean; exportDialogLabel?: string; cancelExportLabel?: string; selectRowLabel?: string; - // Helpers - cellEventsController: EventsController; - checkboxEventsController: EventsController; - selectActionHelper: SelectActionHelper; - focusController: FocusTargetController; - visibleColumns: GridColumn[]; availableColumns: GridColumn[]; @@ -77,33 +67,23 @@ export interface WidgetProps(props: WidgetProps): ReactElement => { - const { className, exporting, numberOfItems, onExportCancel, selectActionHelper } = props; - - const selectionEnabled = selectActionHelper.selectionType !== "None"; + const { className, exporting, numberOfItems, onExportCancel } = props; return ( - - -
- {exporting && ( - - )} - - + +
+ {exporting && ( + + )} + ); }); @@ -132,11 +112,12 @@ const Main = observer((props: WidgetProps): ReactElemen paging, pagingPosition, preview, - selectActionHelper, setPage, visibleColumns } = props; + const { selectActionHelper } = useHelpersContext(); + const isInfinite = !paging; const [isDragging, setIsDragging] = useState<[ColumnId | undefined, ColumnId, ColumnId | undefined] | undefined>(); const [dragOver, setDragOver] = useState<[ColumnId, "before" | "after"] | undefined>(undefined); @@ -236,9 +217,6 @@ const Main = observer((props: WidgetProps): ReactElemen rows={rows} rowClass={props.rowClass} selectableWrapper={props.headerWrapperRenderer} - selectActionHelper={selectActionHelper} - focusController={props.focusController} - eventsController={props.cellEventsController} /> {(rows.length === 0 || preview) && emptyPlaceholderRenderer && diff --git a/packages/pluggableWidgets/datagrid-web/src/components/WidgetHeaderContext.tsx b/packages/pluggableWidgets/datagrid-web/src/components/WidgetHeaderContext.tsx index c963a6cc39..dc64e1f8a5 100644 --- a/packages/pluggableWidgets/datagrid-web/src/components/WidgetHeaderContext.tsx +++ b/packages/pluggableWidgets/datagrid-web/src/components/WidgetHeaderContext.tsx @@ -6,11 +6,11 @@ import { useCreateSelectionContextValue } from "@mendix/widget-plugin-grid/selection"; import { createElement, memo, ReactElement, ReactNode } from "react"; +import { useHelpersContext } from "../helpers/helpers-context"; interface WidgetHeaderContextProps { children?: ReactNode; filtersStore: HeaderFiltersStore; - selectionHelper?: SelectionHelper; } const SelectionContext = getGlobalSelectionContext(); @@ -26,9 +26,10 @@ function SelectionStatusProvider(props: { selectionHelper?: SelectionHelper; chi } function HeaderContainer(props: WidgetHeaderContextProps): ReactElement { + const { selectionHelper } = useHelpersContext(); return ( - {props.children} + {props.children} ); } diff --git a/packages/pluggableWidgets/datagrid-web/src/components/WidgetRoot.tsx b/packages/pluggableWidgets/datagrid-web/src/components/WidgetRoot.tsx index 9341f2d143..ea86f13889 100644 --- a/packages/pluggableWidgets/datagrid-web/src/components/WidgetRoot.tsx +++ b/packages/pluggableWidgets/datagrid-web/src/components/WidgetRoot.tsx @@ -1,19 +1,17 @@ import classNames from "classnames"; import { ReactElement, createElement, useRef, useMemo } from "react"; -import { SelectionMethod } from "../helpers/SelectActionHelper"; +import { useHelpersContext } from "../helpers/helpers-context"; type P = JSX.IntrinsicElements["div"]; export interface WidgetRootProps extends P { className?: string; - selection?: boolean; - selectionMethod: SelectionMethod; exporting?: boolean; } export function WidgetRoot(props: WidgetRootProps): ReactElement { + const { className, exporting, children, ...rest } = props; const ref = useRef(null); - const { className, selectionMethod, selection, exporting, children, ...rest } = props; const style = useMemo(() => { const s = { ...props.style }; if (exporting && ref.current) { @@ -21,6 +19,9 @@ export function WidgetRoot(props: WidgetRootProps): ReactElement { } return s; }, [props.style, exporting]); + const { selectActionHelper } = useHelpersContext(); + const selectionMethod = selectActionHelper.selectionMethod; + const selectionEnabled = selectActionHelper.selectionType !== "None"; return (
{children} diff --git a/packages/pluggableWidgets/datagrid-web/src/components/__tests__/Table.spec.tsx b/packages/pluggableWidgets/datagrid-web/src/components/__tests__/Table.spec.tsx index 15edc5fdba..ecdb7b66d4 100644 --- a/packages/pluggableWidgets/datagrid-web/src/components/__tests__/Table.spec.tsx +++ b/packages/pluggableWidgets/datagrid-web/src/components/__tests__/Table.spec.tsx @@ -1,6 +1,6 @@ import "@testing-library/jest-dom"; import { ClickActionHelper } from "@mendix/widget-plugin-grid/helpers/ClickActionHelper"; -import { MultiSelectionStatus, useSelectionHelper } from "@mendix/widget-plugin-grid/selection"; +import { MultiSelectionStatus, SelectionHelper, useSelectionHelper } from "@mendix/widget-plugin-grid/selection"; import { SelectionMultiValueBuilder, list, listWidget, objectItems } from "@mendix/widget-plugin-test-utils"; import { cleanup, getAllByRole, getByRole, queryByRole, render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; @@ -13,9 +13,10 @@ import { } from "../../features/row-interaction/CheckboxEventsController"; import { SelectActionHelper, useSelectActionHelper } from "../../helpers/SelectActionHelper"; import { GridColumn } from "../../typings/GridColumn"; -import { column, mockGridColumn, mockWidgetProps } from "../../utils/test-utils"; +import { column, mockGridColumn, mockProvideHelpers, mockWidgetProps } from "../../utils/test-utils"; import { Widget, WidgetProps } from "../Widget"; import { ItemSelectionMethodEnum } from "typings/DatagridProps"; +import { HelpersProvider } from "../../helpers/helpers-context"; // you can also pass the mock implementation // to jest.fn as an argument @@ -30,44 +31,54 @@ window.IntersectionObserver = jest.fn(() => ({ })); describe("Table", () => { + function renderWidget( + props: WidgetProps, + helpers = mockProvideHelpers() + ): ReturnType { + return render( + + + + ); + } it("renders the structure correctly", () => { - const component = render(); + const component = renderWidget(mockWidgetProps()); expect(component.asFragment()).toMatchSnapshot(); }); it("renders the structure correctly with sorting", () => { - const component = render(); + const component = renderWidget({ ...mockWidgetProps(), columnsSortable: true }); expect(component.asFragment()).toMatchSnapshot(); }); it("renders the structure correctly with resizing", () => { - const component = render(); + const component = renderWidget({ ...mockWidgetProps(), columnsResizable: true }); expect(component.asFragment()).toMatchSnapshot(); }); it("renders the structure correctly with dragging", () => { - const component = render(); + const component = renderWidget({ ...mockWidgetProps(), columnsDraggable: true }); expect(component.asFragment()).toMatchSnapshot(); }); it("renders the structure correctly with filtering", () => { - const component = render(); + const component = renderWidget({ ...mockWidgetProps(), columnsFilterable: true }); expect(component.asFragment()).toMatchSnapshot(); }); it("renders the structure correctly with hiding", () => { - const component = render(); + const component = renderWidget({ ...mockWidgetProps(), columnsHidable: true }); expect(component.asFragment()).toMatchSnapshot(); }); it("renders the structure correctly with paging", () => { - const component = render(); + const component = renderWidget({ ...mockWidgetProps(), paging: true }); expect(component.asFragment()).toMatchSnapshot(); }); @@ -78,15 +89,16 @@ describe("Table", () => { props.columnsFilterable = true; props.visibleColumns = columns; props.availableColumns = columns; - const component = render(); + const component = renderWidget(props); expect(component.asFragment()).toMatchSnapshot(); }); it("renders the structure correctly with empty placeholder", () => { - const component = render( - renderWrapper(
)} /> - ); + const component = renderWidget({ + ...mockWidgetProps(), + emptyPlaceholderRenderer: renderWrapper => renderWrapper(
) + }); expect(component.asFragment()).toMatchSnapshot(); }); @@ -103,13 +115,13 @@ describe("Table", () => { props.visibleColumns = columns; props.availableColumns = columns; - const component = render(); + const component = renderWidget(props); expect(component.asFragment()).toMatchSnapshot(); }); it("renders the structure correctly with dynamic row class", () => { - const component = render( "myclass"} />); + const component = renderWidget({ ...mockWidgetProps(), rowClass: () => "myclass" }); expect(component.asFragment()).toMatchSnapshot(); }); @@ -121,63 +133,59 @@ describe("Table", () => { props.visibleColumns = columns; props.availableColumns = columns; - const component = render(); + const component = renderWidget(props); expect(component.asFragment()).toMatchSnapshot(); }); it("renders the structure correctly with header wrapper", () => { - const component = render( - ( -
- {header} -
- )} - /> + const props = mockWidgetProps(); + props.headerWrapperRenderer = (index, header) => ( +
+ {header} +
); + const component = renderWidget(props); expect(component.asFragment()).toMatchSnapshot(); }); it("renders the structure correctly with header filters and a11y", () => { - const component = render( - - -
- } - headerTitle="filter title" - /> + const props = mockWidgetProps(); + props.headerContent = ( +
+ +
); + props.headerTitle = "filter title"; + const component = renderWidget(props); expect(component.asFragment()).toMatchSnapshot(); }); describe("with selection method checkbox", () => { let props: ReturnType; + let helpers: ReturnType; beforeEach(() => { props = mockWidgetProps(); - props.selectActionHelper = new SelectActionHelper("Single", undefined, "checkbox", false, 5, "clear"); + helpers = mockProvideHelpers(); + helpers.selectActionHelper = new SelectActionHelper("Single", undefined, "checkbox", false, 5, "clear"); props.gridInteractive = true; props.paging = true; props.data = objectItems(3); }); it("render method class", () => { - const { container } = render(); + const { container } = renderWidget(props, helpers); expect(container.firstChild).toHaveClass("widget-datagrid-selection-method-checkbox"); }); it("render an extra column and add class to each selected row", () => { - props.selectActionHelper.isSelected = () => true; + helpers.selectActionHelper.isSelected = () => true; - const { asFragment } = render(); + const { asFragment } = renderWidget(props, helpers); expect(asFragment()).toMatchSnapshot(); }); @@ -185,40 +193,45 @@ describe("Table", () => { it("render correct number of checked checkboxes", () => { const [a, b, c, d, e, f] = (props.data = objectItems(6)); let selection: ObjectItem[] = []; - props.selectActionHelper.isSelected = item => selection.includes(item); + helpers.selectActionHelper.isSelected = item => selection.includes(item); // eslint-disable-next-line @typescript-eslint/explicit-function-return-type const getChecked = () => screen.getAllByRole("checkbox").filter(elt => elt.checked); - const { rerender } = render(); + const Wrapped = (props: WidgetProps): JSX.Element => ( + + + + ); + const { rerender } = render(); expect(getChecked()).toHaveLength(0); selection = [a, b, c]; - rerender(); + rerender(); expect(getChecked()).toHaveLength(3); selection = [c]; - rerender(); + rerender(); expect(getChecked()).toHaveLength(1); selection = [d, e]; - rerender(); + rerender(); expect(getChecked()).toHaveLength(2); selection = [f, e, d, a]; - rerender(); + rerender(); expect(getChecked()).toHaveLength(4); }); it("call onSelect when checkbox is clicked", async () => { const items = props.data; const onSelect = jest.fn(); - props.selectActionHelper.onSelect = onSelect; - props.checkboxEventsController = new CheckboxEventsController( + helpers.selectActionHelper.onSelect = onSelect; + helpers.checkboxEventsController = new CheckboxEventsController( item => ({ item, - selectionMethod: props.selectActionHelper.selectionMethod, + selectionMethod: helpers.selectActionHelper.selectionMethod, selectionType: "Single", selectionMode: "clear", pageSize: props.pageSize @@ -229,7 +242,7 @@ describe("Table", () => { jest.fn() ); - render(); + renderWidget(props, helpers); const checkbox1 = screen.getAllByRole("checkbox")[0]; const checkbox3 = screen.getAllByRole("checkbox")[2]; @@ -256,8 +269,9 @@ describe("Table", () => { const props = mockWidgetProps(); props.data = objectItems(5); props.paging = true; - props.selectActionHelper = new SelectActionHelper("Multi", undefined, "checkbox", false, 5, "clear"); - render(); + const helpers = mockProvideHelpers(); + helpers.selectActionHelper = new SelectActionHelper("Multi", undefined, "checkbox", false, 5, "clear"); + renderWidget(props, helpers); const colheader = screen.getAllByRole("columnheader")[0]; expect(queryByRole(colheader, "checkbox")).toBeNull(); @@ -266,12 +280,17 @@ describe("Table", () => { describe("with multi selection helper", () => { it("render header checkbox if helper is given and checkbox state depends on the helper status", () => { const props = mockWidgetProps(); + const helpers = mockProvideHelpers(); props.data = objectItems(5); props.paging = true; - props.selectActionHelper = new SelectActionHelper("Multi", undefined, "checkbox", true, 5, "clear"); const renderWithStatus = (status: MultiSelectionStatus): ReturnType => { - return render(); + helpers.selectActionHelper = new SelectActionHelper("Multi", undefined, "checkbox", true, 5, "clear"); + helpers.selectionHelper = jest.mocked({ + type: "Multi", + selectionStatus: status + } as any); + return renderWidget(props, helpers); }; renderWithStatus("none"); @@ -288,9 +307,10 @@ describe("Table", () => { it("not render header checkbox if method is rowClick", () => { const props = mockWidgetProps(); - props.selectActionHelper = new SelectActionHelper("Multi", undefined, "rowClick", false, 5, "clear"); + const helpers = mockProvideHelpers(); + helpers.selectActionHelper = new SelectActionHelper("Multi", undefined, "rowClick", false, 5, "clear"); - render(); + renderWidget(props, helpers); const colheader = screen.getAllByRole("columnheader")[0]; expect(queryByRole(colheader, "checkbox")).toBeNull(); @@ -298,43 +318,45 @@ describe("Table", () => { it("call onSelectAll when header checkbox is clicked", async () => { const props = mockWidgetProps(); - props.selectActionHelper = new SelectActionHelper("Multi", undefined, "checkbox", true, 5, "clear"); - props.selectActionHelper.onSelectAll = jest.fn(); - props.selectionStatus = "none"; + const helpers = mockProvideHelpers(); + helpers.selectActionHelper = new SelectActionHelper("Multi", undefined, "checkbox", true, 5, "clear"); + helpers.selectActionHelper.onSelectAll = jest.fn(); + helpers.selectionHelper = jest.mocked({ type: "Multi", selectionStatus: "none" } as any); - render(); + renderWidget(props, helpers); const checkbox = screen.getAllByRole("checkbox")[0]; await userEvent.click(checkbox); - expect(props.selectActionHelper.onSelectAll).toHaveBeenCalledTimes(1); + expect(helpers.selectActionHelper.onSelectAll).toHaveBeenCalledTimes(1); await userEvent.click(checkbox); - expect(props.selectActionHelper.onSelectAll).toHaveBeenCalledTimes(2); + expect(helpers.selectActionHelper.onSelectAll).toHaveBeenCalledTimes(2); }); }); describe("with selection method rowClick", () => { let props: ReturnType; + let helpers: ReturnType; beforeEach(() => { props = mockWidgetProps(); - props.selectActionHelper = new SelectActionHelper("Single", undefined, "rowClick", true, 5, "clear"); + helpers = mockProvideHelpers(); + helpers.selectActionHelper = new SelectActionHelper("Single", undefined, "rowClick", true, 5, "clear"); props.gridInteractive = true; props.paging = true; props.data = objectItems(3); }); it("render method class", () => { - const { container } = render(); - + const { container } = renderWidget(props, helpers); expect(container.firstChild).toHaveClass("widget-datagrid-selection-method-click"); }); it("add class to each selected cell", () => { - props.selectActionHelper.isSelected = () => true; + helpers.selectActionHelper.isSelected = () => true; - const { asFragment } = render(); + const { asFragment } = renderWidget(props, helpers); expect(asFragment()).toMatchSnapshot(); }); @@ -345,11 +367,11 @@ describe("Table", () => { const columns = [column("Column A"), column("Column B")].map((col, index) => mockGridColumn(col, index)); props.visibleColumns = columns; props.availableColumns = columns; - props.cellEventsController = new CellEventsController( + helpers.cellEventsController = new CellEventsController( item => ({ item, - selectionType: props.selectActionHelper.selectionType, - selectionMethod: props.selectActionHelper.selectionMethod, + selectionType: helpers.selectActionHelper.selectionType, + selectionMethod: helpers.selectActionHelper.selectionMethod, selectionMode: "clear", clickTrigger: "none", pageSize: props.pageSize @@ -361,7 +383,7 @@ describe("Table", () => { jest.fn() ); - render(); + renderWidget(props, helpers); const rows = screen.getAllByRole("row").slice(1); expect(rows).toHaveLength(3); @@ -413,8 +435,9 @@ describe("Table", () => { }: WidgetProps & { selectionMethod: ItemSelectionMethodEnum; }): ReactElement { - const helper = useSelectionHelper(selection, ds, undefined); - const selectHelper = useSelectActionHelper( + const { focusController } = mockProvideHelpers(); + const selectionHelper = useSelectionHelper(selection, ds, undefined); + const selectActionHelper = useSelectActionHelper( { itemSelection: selection, itemSelectionMethod: selectionMethod, @@ -422,24 +445,29 @@ describe("Table", () => { showSelectAllToggle: false, pageSize: 5 }, - helper + selectionHelper ); const cellEventsController = useCellEventsController( - selectHelper, + selectActionHelper, new ClickActionHelper("single", null), - props.focusController + focusController ); - const checkboxEventsController = useCheckboxEventsController(selectHelper, props.focusController); + const checkboxEventsController = useCheckboxEventsController(selectActionHelper, focusController); + + const helpers = Object.freeze({ + cellEventsController, + checkboxEventsController, + focusController, + selectActionHelper, + selectionHelper, + preview: false + }); return ( - + + + ); } @@ -618,7 +646,7 @@ describe("Table", () => { const user = userEvent.setup(); - render(); + renderWidget({ ...props, data: items }); const [input] = screen.getAllByRole("textbox"); await user.click(input); diff --git a/packages/pluggableWidgets/datagrid-web/src/helpers/helpers-context.ts b/packages/pluggableWidgets/datagrid-web/src/helpers/helpers-context.ts new file mode 100644 index 0000000000..122926d93e --- /dev/null +++ b/packages/pluggableWidgets/datagrid-web/src/helpers/helpers-context.ts @@ -0,0 +1,104 @@ +import { useClickActionHelper } from "@mendix/widget-plugin-grid/helpers/ClickActionHelper"; +import { + LayoutProps, + useFocusTargetController +} from "@mendix/widget-plugin-grid/keyboard-navigation/useFocusTargetController"; +import { useSelectionHelper } from "@mendix/widget-plugin-grid/selection"; +import { createContext, useContext, useMemo, useRef } from "react"; +import { DatagridContainerProps } from "../../typings/DatagridProps"; +import { useCellEventsController } from "../features/row-interaction/CellEventsController"; +import { useCheckboxEventsController } from "../features/row-interaction/CheckboxEventsController"; +import { GridHelpers } from "../typings/GridHelpers"; +import { useSelectActionHelper } from "./SelectActionHelper"; +import { IColumnGroupStore } from "./state/ColumnGroupStore"; + +const context = createContext(null); + +export const HelpersProvider = context.Provider; + +interface Props + extends Pick< + DatagridContainerProps, + | "itemSelection" + | "datasource" + | "onSelectionChange" + | "itemSelectionMethod" + | "showSelectAllToggle" + | "pageSize" + | "itemSelectionMode" + | "onClickTrigger" + | "onClick" + > { + columnsStore: IColumnGroupStore; +} + +export function useProvideGridHelpers(props: Props): GridHelpers { + const selectionHelper = useSelectionHelper(props.itemSelection, props.datasource, props.onSelectionChange); + + const selectActionHelper = useSelectActionHelper(props, selectionHelper); + + const clickActionHelper = useClickActionHelper({ + onClickTrigger: props.onClickTrigger, + onClick: props.onClick + }); + + const layout = getLayoutProps(props, selectActionHelper.showCheckboxColumn); + const focusController = useFocusTargetController(layout); + + const cellEventsController = useCellEventsController(selectActionHelper, clickActionHelper, focusController); + + const checkboxEventsController = useCheckboxEventsController(selectActionHelper, focusController); + + const helpers = useMemo( + () => + Object.freeze({ + cellEventsController, + checkboxEventsController, + focusController, + selectActionHelper, + selectionHelper, + preview: false + }), + [cellEventsController, checkboxEventsController, focusController, selectActionHelper, selectionHelper] + ); + + return useChangeGuard(helpers); +} + +function getLayoutProps(props: Props, withCheckbox: boolean): LayoutProps { + const visibleColumnsCount = withCheckbox + ? props.columnsStore.visibleColumns.length + 1 + : props.columnsStore.visibleColumns.length; + + return { + rows: props.datasource.items ? props.datasource.items.length : 0, + columns: visibleColumnsCount, + pageSize: props.pageSize + }; +} + +function useChangeGuard(deps: GridHelpers): GridHelpers { + const ctx = useRef(deps); + const prevDeps = ctx.current; + + if (!Object.is(prevDeps, deps)) { + console.error("GridHelpers object should not change between renders."); + } + + for (const key of Object.keys(deps) as Array) { + if (Object.is(prevDeps[key], deps[key])) { + continue; + } + console.error(`GridHelpers.${key} should not change between renders.`); + } + + return (ctx.current = deps); +} + +export function useHelpersContext(): GridHelpers { + const ctx = useContext(context); + if (ctx === null) { + throw new Error("useHelpersContext must be used within a GridHelpersProvider"); + } + return ctx; +} diff --git a/packages/pluggableWidgets/datagrid-web/src/helpers/useSelectionStatus.ts b/packages/pluggableWidgets/datagrid-web/src/helpers/useSelectionStatus.ts new file mode 100644 index 0000000000..5a8579f56b --- /dev/null +++ b/packages/pluggableWidgets/datagrid-web/src/helpers/useSelectionStatus.ts @@ -0,0 +1,13 @@ +import { SelectionStatus } from "@mendix/widget-plugin-grid/selection"; +import { useHelpersContext } from "./helpers-context"; + +export function useSelectionStatus(): SelectionStatus { + const { selectionHelper, preview } = useHelpersContext(); + let selectionStatus: SelectionStatus = "unknown"; + if (preview) { + selectionStatus = "none"; + } else if (selectionHelper?.type === "Multi") { + selectionStatus = selectionHelper.selectionStatus; + } + return selectionStatus; +} diff --git a/packages/pluggableWidgets/datagrid-web/src/helpers/useWidgetProps.ts b/packages/pluggableWidgets/datagrid-web/src/helpers/useWidgetProps.ts deleted file mode 100644 index 15f75cdc97..0000000000 --- a/packages/pluggableWidgets/datagrid-web/src/helpers/useWidgetProps.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ObjectItem } from "mendix"; -import { createContext, Provider, useContext } from "react"; -import { GridColumn } from "../typings/GridColumn"; -import { WidgetProps } from "../components/Widget"; - -const NO_PROPS_VALUE = Symbol("NO_PROPS_VALUE"); - -type Props = WidgetProps; -type ContextValue = typeof NO_PROPS_VALUE | Props; - -const context = createContext(NO_PROPS_VALUE); - -export const WidgetPropsProvider: Provider = context.Provider; - -export function useWidgetProps(): Props { - const value = useContext(context); - - if (value === NO_PROPS_VALUE) { - throw new Error("useTableProps: failed to get value from props provider."); - } - - return value; -} diff --git a/packages/pluggableWidgets/datagrid-web/src/typings/CellComponent.ts b/packages/pluggableWidgets/datagrid-web/src/typings/CellComponent.ts index bc0f756450..77f25f1816 100644 --- a/packages/pluggableWidgets/datagrid-web/src/typings/CellComponent.ts +++ b/packages/pluggableWidgets/datagrid-web/src/typings/CellComponent.ts @@ -1,5 +1,5 @@ import { ObjectItem } from "mendix"; -import { ReactElement, ReactNode } from "react"; +import { ReactElement } from "react"; import { GridColumn } from "./GridColumn"; import { ElementProps } from "@mendix/widget-plugin-grid/event-switch/base"; @@ -8,7 +8,6 @@ export interface EventsController { } export interface CellComponentProps { - children?: ReactNode; className?: string; column: C; item: ObjectItem; diff --git a/packages/pluggableWidgets/datagrid-web/src/typings/GridHelpers.ts b/packages/pluggableWidgets/datagrid-web/src/typings/GridHelpers.ts new file mode 100644 index 0000000000..3762f51d06 --- /dev/null +++ b/packages/pluggableWidgets/datagrid-web/src/typings/GridHelpers.ts @@ -0,0 +1,13 @@ +import { EventsController } from "./CellComponent"; +import { FocusTargetController } from "@mendix/widget-plugin-grid/keyboard-navigation/FocusTargetController"; +import { SelectActionHelper } from "../helpers/SelectActionHelper"; +import { SelectionHelper } from "@mendix/widget-plugin-grid/selection"; + +export type GridHelpers = { + readonly cellEventsController: EventsController; + readonly checkboxEventsController: EventsController; + readonly focusController: FocusTargetController; + readonly selectActionHelper: SelectActionHelper; + readonly selectionHelper: SelectionHelper | undefined; + readonly preview: boolean; +}; diff --git a/packages/pluggableWidgets/datagrid-web/src/utils/test-utils.tsx b/packages/pluggableWidgets/datagrid-web/src/utils/test-utils.tsx index 24bdc8dd3a..6f6e27e52e 100644 --- a/packages/pluggableWidgets/datagrid-web/src/utils/test-utils.tsx +++ b/packages/pluggableWidgets/datagrid-web/src/utils/test-utils.tsx @@ -11,6 +11,8 @@ import { PositionController } from "@mendix/widget-plugin-grid/keyboard-navigati import { VirtualGridLayout } from "@mendix/widget-plugin-grid/keyboard-navigation/VirtualGridLayout"; import { ColumnStore } from "../helpers/state/column/ColumnStore"; import { IColumnParentStore } from "../helpers/state/ColumnGroupStore"; +import { GridHelpers } from "../typings/GridHelpers"; +import { Writable } from "@mendix/widget-plugin-test-utils/dist/builders/type-utils"; export const column = (header = "Test", patch?: (col: ColumnsType) => void): ColumnsType => { const c: ColumnsType = { @@ -67,6 +69,17 @@ export function mockGridColumn(c: ColumnsType, index: number): GridColumn { return new ColumnStore(index, c, parentStore); } +export function mockProvideHelpers(): Writable { + return { + focusController: new FocusTargetController(new PositionController(), new VirtualGridLayout(1, 1, 10)), + selectActionHelper: mockSelectionProps(), + cellEventsController: { getProps: () => Object.create({}) }, + checkboxEventsController: { getProps: () => Object.create({}) }, + selectionHelper: undefined, + preview: false + }; +} + export function mockWidgetProps(): WidgetProps { const id = "dg1"; const columnsProp = [column("Test")]; @@ -97,16 +110,8 @@ export function mockWidgetProps(): WidgetProps { availableColumns: columns, columnsSwap: jest.fn(), columnsCreateSizeSnapshot: jest.fn(), - selectionStatus: "unknown", setPage: jest.fn(), processedRows: 0, - gridInteractive: false, - selectActionHelper: mockSelectionProps(), - cellEventsController: { getProps: () => Object.create({}) }, - checkboxEventsController: { getProps: () => Object.create({}) }, - focusController: new FocusTargetController( - new PositionController(), - new VirtualGridLayout(1, columns.length, 10) - ) + gridInteractive: false }; } diff --git a/packages/shared/widget-plugin-grid/src/keyboard-navigation/FocusTargetController.ts b/packages/shared/widget-plugin-grid/src/keyboard-navigation/FocusTargetController.ts index 922a45bcfb..9471ecd578 100644 --- a/packages/shared/widget-plugin-grid/src/keyboard-navigation/FocusTargetController.ts +++ b/packages/shared/widget-plugin-grid/src/keyboard-navigation/FocusTargetController.ts @@ -8,13 +8,13 @@ export type FocusTargetUpdateEvent = { lastPos: PositionString; targetPos: Posit export type Listener = (event: FocusTargetUpdateEvent) => void; export class FocusTargetController { - private _listeners: Listener[]; + private _listeners: Set; private _focusTarget: PositionString; private _positionController: PositionController; private _layout: VirtualGridLayout; constructor(pos: PositionController, layout: VirtualGridLayout) { - this._listeners = []; + this._listeners = new Set(); this._focusTarget = this._getInitPosition(); this._positionController = pos; this._layout = layout; @@ -25,11 +25,11 @@ export class FocusTargetController { } addListener(listener: Listener): void { - this._listeners.push(listener); + this._listeners.add(listener); } removeListener(listener: Listener): void { - this._listeners.splice(this._listeners.indexOf(listener), 1); + this._listeners.delete(listener); } updateGridLayout(layout: VirtualGridLayout): void { diff --git a/packages/shared/widget-plugin-test-utils/src/main.ts b/packages/shared/widget-plugin-test-utils/src/main.ts index 7fb3cb67d4..9dfd990834 100644 --- a/packages/shared/widget-plugin-test-utils/src/main.ts +++ b/packages/shared/widget-plugin-test-utils/src/main.ts @@ -1,3 +1,4 @@ +export type { Writable } from "./builders/type-utils.js"; export * from "./builders/EditableValueBuilder.js"; export * from "./builders/ReferenceValueBuilder.js"; export * from "./builders/ReferenceSetValueBuilder.js";