Skip to content

Commit

Permalink
Multiselect working
Browse files Browse the repository at this point in the history
  • Loading branch information
matttdawson committed Jun 23, 2023
1 parent b1c5928 commit fab282f
Show file tree
Hide file tree
Showing 10 changed files with 338 additions and 11 deletions.
19 changes: 11 additions & 8 deletions src/components/Grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { GridOptions } from "ag-grid-community/dist/lib/entities/gridOptions";
import { CellEvent, GridReadyEvent, SelectionChangedEvent } from "ag-grid-community/dist/lib/events";
import { AgGridReact } from "ag-grid-react";
import clsx from "clsx";
import { difference, isEmpty, last, omit, xorBy } from "lodash-es";
import { defer, difference, isEmpty, last, omit, xorBy } from "lodash-es";
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";

import { GridContext } from "../contexts/GridContext";
Expand Down Expand Up @@ -156,13 +156,13 @@ export const Grid = ({
timeoutMs: 1000,
});

const previousGridReady = useRef(gridReady);
/*const previousGridReady = useRef(gridReady);
useEffect(() => {
if (!previousGridReady.current && gridReady) {
previousGridReady.current = true;
setInitialContentSize();
}
}, [gridReady, setInitialContentSize]);
}, [gridReady, setInitialContentSize]);*/

/**
* On data load select the first row of the grid if required.
Expand Down Expand Up @@ -491,11 +491,13 @@ export const Grid = ({
if (!isEmpty(colIdsEdited.current)) {
const skipHeader = sizeColumns === "auto-skip-headers";
if (sizeColumns === "auto" || skipHeader) {
autoSizeColumns({
skipHeader,
userSizedColIds: userSizedColIds.current,
colIds: colIdsEdited.current,
});
defer(() =>
autoSizeColumns({
skipHeader,
userSizedColIds: userSizedColIds.current,
colIds: new Set(colIdsEdited.current),
}),
);
}
colIdsEdited.current.clear();
}
Expand Down Expand Up @@ -565,6 +567,7 @@ export const Grid = ({
onColumnVisible={() => {
setInitialContentSize();
}}
onFirstDataRendered={setInitialContentSize}
onRowDataChanged={onRowDataChanged}
onCellKeyPress={onCellKeyPress}
onCellClicked={onCellClicked}
Expand Down
170 changes: 170 additions & 0 deletions src/components/gridForm/GridFormMultiSelectGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { useCallback, useEffect, useRef, useState } from "react";

import { LuiCheckboxInput } from "@linzjs/lui";

import { useGridPopoverContext } from "../../contexts/GridPopoverContext";
import { MenuItem } from "../../react-menu3";
import { ClickEvent } from "../../react-menu3/types";
import { wait } from "../../utils/util";
import { GridBaseRow } from "../Grid";
import { CellEditorCommon } from "../GridCell";
import { GridIcon } from "../GridIcon";
import { useGridPopoverHook } from "../GridPopoverHook";

export interface MultiSelectGridOption {
value: any;
label?: string;
checked?: boolean | "partial";
canSelectPartial?: boolean;
warning?: string | undefined;
}

export interface GridFormMultiSelectGridSaveProps<RowType extends GridBaseRow> {
selectedRows: RowType[];
addValues: any[];
removeValues: any[];
}

export interface GridFormMultiSelectGridProps<RowType extends GridBaseRow> extends CellEditorCommon {
className?: string | undefined;
noOptionsMessage?: string;
onSave?: (props: GridFormMultiSelectGridSaveProps<RowType>) => Promise<boolean>;
options:
| MultiSelectGridOption[]
| ((selectedRows: RowType[]) => Promise<MultiSelectGridOption[]> | MultiSelectGridOption[]);
invalid?: (props: GridFormMultiSelectGridSaveProps<RowType>) => boolean;
}

export const GridFormMultiSelectGrid = <RowType extends GridBaseRow>(
props: GridFormMultiSelectGridProps<RowType>,
): JSX.Element => {
const { selectedRows } = useGridPopoverContext<RowType>();
const optionsInitialising = useRef(false);

const [initialValues, setInitialValues] = useState<MultiSelectGridOption[]>();
const [options, setOptions] = useState<MultiSelectGridOption[]>();

const genSave = useCallback(
(selectedRows: RowType[]): GridFormMultiSelectGridSaveProps<RowType> => {
if (!options) return { selectedRows, addValues: [], removeValues: [] };

const preChecked = (initialValues ?? []).filter((o) => o.checked).map((o) => o.value);
const postNotChecked = options.filter((o) => o.checked === false).map((o) => o.value);
const removeValues = preChecked.filter((p) => postNotChecked.some((c) => c === p));

const preNotChecked = (initialValues ?? []).filter((o) => o.checked !== true).map((o) => o.value);
const postChecked = options.filter((o) => o.checked === true).map((o) => o.value);
const addValues = preNotChecked.filter((p) => postChecked.some((c) => c === p));

return { selectedRows, addValues, removeValues };
},
[initialValues, options],
);

const save = useCallback(
async (selectedRows: RowType[]): Promise<boolean> => {
if (!options || !props.onSave) return true;

// Any changes to save?
if (JSON.stringify(initialValues) === JSON.stringify(options)) return true;

if (!props.onSave) return true;
await wait(1);
return await props.onSave(genSave(selectedRows));
},
[genSave, initialValues, options, props],
);

const invalid = useCallback(() => {
if (!options) return true;
return props.invalid && props.invalid(genSave(selectedRows));
}, [genSave, options, props, selectedRows]);

const { popoverWrapper } = useGridPopoverHook({
className: props.className,
save,
invalid,
});

// Load up options list if it's async function
useEffect(() => {
if (options || optionsInitialising.current) return;
optionsInitialising.current = true;

(async () => {
const optionsList = typeof props.options === "function" ? await props.options(selectedRows) : props.options;
setInitialValues(JSON.parse(JSON.stringify(optionsList)));
setOptions(optionsList);
optionsInitialising.current = false;
})();
}, [options, props, selectedRows]);

const toggleValue = useCallback(
(item: MultiSelectGridOption) => {
if (options) {
item.checked = item.checked === true && item.canSelectPartial ? "partial" : !item.checked;
setOptions([...options]);
}
},
[options, setOptions],
);

return popoverWrapper(
<>
<div className={"Grid-popoverContainer"}>
<div
style={{
display: "grid",
gridAutoFlow: "column",
gridTemplateRows: "repeat(10, auto)",
maxWidth: "calc(min(100vw,500px))",
overflowX: "auto",
}}
>
{options &&
options.map((o) => (
<>
<MenuItem
onClick={(e: ClickEvent) => {
// Global react-menu MenuItem handler handles tabs
if (e.key !== "Tab" && e.key !== "Enter") {
e.keepOpen = true;
toggleValue(o);
}
}}
>
<LuiCheckboxInput
key={o.label}
isChecked={!!o.checked ?? false}
isIndeterminate={o.checked === "partial"}
value={`${o.value}`}
label={
<>
{o.warning && <GridIcon icon={"ic_warning_outline"} title={o.warning} />}
<span
className={"GridMultiSelectGrid-Label"}
style={{ fontWeight: o.label?.endsWith("0") ? "bold" : "regular" }}
>
{o.label ?? (o.value == null ? `<${o.value}>` : `${o.value}`)}
</span>
</>
}
inputProps={{
onClick: (e) => {
// Click is handled by MenuItem onClick
e.preventDefault();
e.stopPropagation();
},
}}
onChange={() => {
/*Do nothing, change handled by menuItem*/
}}
/>
</MenuItem>
</>
))}
</div>
</div>
</>,
);
};
1 change: 1 addition & 0 deletions src/components/gridForm/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export * from "./GridFormDropDown";
export * from "./GridFormEditBearing";
export * from "./GridFormMessage";
export * from "./GridFormMultiSelect";
export * from "./GridFormMultiSelectGrid";
export * from "./GridFormPopoverMenu";
export * from "./GridFormSubComponentTextArea";
export * from "./GridFormSubComponentTextInput";
Expand Down
17 changes: 17 additions & 0 deletions src/components/gridPopoverEdit/GridPopoutEditMultiSelectGrid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { GridBaseRow } from "../Grid";
import { ColDefT, GenericCellEditorProps, GridCell } from "../GridCell";
import { GridFormMultiSelectGrid, GridFormMultiSelectGridProps } from "../gridForm/GridFormMultiSelectGrid";
import { GenericCellColDef } from "../gridRender/GridRenderGenericCell";

export const GridPopoutEditMultiSelectGrid = <RowType extends GridBaseRow>(
colDef: GenericCellColDef<RowType>,
props: GenericCellEditorProps<GridFormMultiSelectGridProps<RowType>>,
): ColDefT<RowType> =>
GridCell(colDef, {
editor: GridFormMultiSelectGrid,
...props,
editorParams: {
className: "GridMultiSelect-containerMedium",
...(props.editorParams as GridFormMultiSelectGridProps<RowType>),
},
});
1 change: 1 addition & 0 deletions src/components/gridPopoverEdit/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from "./GridPopoutEditMultiSelect";
export * from "./GridPopoutEditMultiSelectGrid";
export * from "./GridPopoverMenu";
export * from "./GridPopoverEditBearing";
export * from "./GridPopoverEditBearing";
Expand Down
8 changes: 6 additions & 2 deletions src/react-menu3/components/MenuList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,15 @@ export const MenuList = ({
break;

case Keys.LEFT:
if (!focusSideways(elementTarget, -1)) return;
if (!focusSideways(elementTarget, -1)) {
return; // Unhandled
}
break;

case Keys.RIGHT:
if (!focusSideways(elementTarget, 1)) return;
if (!focusSideways(elementTarget, 1)) {
return; // Unhandled
}
break;

// prevent browser from scrolling the page when SPACE is pressed
Expand Down
4 changes: 3 additions & 1 deletion src/react-menu3/hooks/useItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ export const useItems = (menuRef: MutableRefObject<any>, focusRef: MutableRefObj
sortItems();
index = nextIndex;
newItem = items[index];
defer(() => (newItem as HTMLElement).scrollIntoView({ behavior: "smooth", inline: "nearest" }));
defer(() =>
(newItem as HTMLElement).scrollIntoView({ behavior: "smooth", block: "nearest", inline: "center" }),
);
break;

case HoverActionTypes.INCREASE:
Expand Down
Loading

0 comments on commit fab282f

Please sign in to comment.