@linzjs/step-ag-grid 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +4 -0
  2. package/dist/index.d.ts +32 -0
  3. package/dist/index.js +89342 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/src/components/ComponentLoadingWrapper.d.ts +9 -0
  6. package/dist/src/components/GenericCellClass.d.ts +2 -0
  7. package/dist/src/components/Grid.d.ts +24 -0
  8. package/dist/src/components/GridCell.d.ts +31 -0
  9. package/dist/src/components/GridIcon.d.ts +6 -0
  10. package/dist/src/components/GridLoadableCell.d.ts +6 -0
  11. package/dist/src/components/GridPopoutHook.d.ts +7 -0
  12. package/dist/src/components/GridSubComponentTextArea.d.ts +5 -0
  13. package/dist/src/components/PostSortRowsHook.d.ts +11 -0
  14. package/dist/src/components/gridForm/GridFormDropDown.d.ts +26 -0
  15. package/dist/src/components/gridForm/GridFormEditBearing.d.ts +9 -0
  16. package/dist/src/components/gridForm/GridFormMessage.d.ts +8 -0
  17. package/dist/src/components/gridForm/GridFormMultiSelect.d.ts +26 -0
  18. package/dist/src/components/gridForm/GridFormPopoutMenu.d.ts +24 -0
  19. package/dist/src/components/gridForm/GridFormTextArea.d.ts +11 -0
  20. package/dist/src/components/gridForm/GridFormTextInput.d.ts +11 -0
  21. package/dist/src/components/gridHeader/GridHeaderSelect.d.ts +8 -0
  22. package/dist/src/components/gridPopoverEdit/GridPopoutEditMultiSelect.d.ts +4 -0
  23. package/dist/src/components/gridPopoverEdit/GridPopoutMenu.d.ts +10 -0
  24. package/dist/src/components/gridPopoverEdit/GridPopoverEditBearing.d.ts +4 -0
  25. package/dist/src/components/gridPopoverEdit/GridPopoverEditDropDown.d.ts +4 -0
  26. package/dist/src/components/gridPopoverEdit/GridPopoverMessage.d.ts +4 -0
  27. package/dist/src/components/gridRender/GridRenderGenericCell.d.ts +15 -0
  28. package/dist/src/components/gridRender/GridRenderPopoutMenuCell.d.ts +3 -0
  29. package/dist/src/contexts/GridContext.d.ts +25 -0
  30. package/dist/src/contexts/GridContextProvider.d.ts +11 -0
  31. package/dist/src/contexts/UpdatingContext.d.ts +6 -0
  32. package/dist/src/contexts/UpdatingContextProvider.d.ts +7 -0
  33. package/dist/src/lui/TextAreaInput.d.ts +11 -0
  34. package/dist/src/lui/TextInputFormatted.d.ts +14 -0
  35. package/dist/src/setupTests.d.ts +1 -0
  36. package/dist/src/stories/components/FormTest.d.ts +12 -0
  37. package/dist/src/stories/components/GridPopoutBearing.stories.d.ts +9 -0
  38. package/dist/src/stories/components/GridPopoutEditDropDown.stories.d.ts +9 -0
  39. package/dist/src/stories/components/GridPopoutEditGeneric.stories.d.ts +9 -0
  40. package/dist/src/stories/components/GridPopoutEditGenericTextArea.stories.d.ts +9 -0
  41. package/dist/src/stories/components/GridPopoutEditMultiSelect.stories.d.ts +9 -0
  42. package/dist/src/stories/components/GridReadOnly.stories.d.ts +9 -0
  43. package/dist/src/utils/bearing.d.ts +5 -0
  44. package/dist/src/utils/util.d.ts +4 -0
  45. package/dist/step-ag-grid.esm.js +89297 -0
  46. package/dist/step-ag-grid.esm.js.map +1 -0
  47. package/package.json +126 -0
  48. package/src/components/ComponentLoadingWrapper.tsx +31 -0
  49. package/src/components/GenericCellClass.tsx +15 -0
  50. package/src/components/Grid.scss +38 -0
  51. package/src/components/Grid.tsx +262 -0
  52. package/src/components/GridCell.tsx +101 -0
  53. package/src/components/GridIcon.tsx +12 -0
  54. package/src/components/GridLoadableCell.tsx +22 -0
  55. package/src/components/GridPopoutHook.tsx +118 -0
  56. package/src/components/GridSubComponentTextArea.tsx +26 -0
  57. package/src/components/GridTheme.scss +94 -0
  58. package/src/components/PostSortRowsHook.ts +131 -0
  59. package/src/components/gridForm/GridFormDropDown.tsx +192 -0
  60. package/src/components/gridForm/GridFormEditBearing.scss +3 -0
  61. package/src/components/gridForm/GridFormEditBearing.tsx +62 -0
  62. package/src/components/gridForm/GridFormMessage.tsx +34 -0
  63. package/src/components/gridForm/GridFormMultiSelect.tsx +180 -0
  64. package/src/components/gridForm/GridFormPopoutMenu.tsx +90 -0
  65. package/src/components/gridForm/GridFormTextArea.tsx +61 -0
  66. package/src/components/gridForm/GridFormTextInput.tsx +66 -0
  67. package/src/components/gridHeader/GridHeaderSelect.tsx +54 -0
  68. package/src/components/gridPopoverEdit/GridPopoutEditMultiSelect.ts +22 -0
  69. package/src/components/gridPopoverEdit/GridPopoutMenu.scss +9 -0
  70. package/src/components/gridPopoverEdit/GridPopoutMenu.tsx +35 -0
  71. package/src/components/gridPopoverEdit/GridPopoverEditBearing.ts +24 -0
  72. package/src/components/gridPopoverEdit/GridPopoverEditDropDown.ts +22 -0
  73. package/src/components/gridPopoverEdit/GridPopoverMessage.ts +22 -0
  74. package/src/components/gridRender/GridRenderGenericCell.scss +15 -0
  75. package/src/components/gridRender/GridRenderGenericCell.tsx +49 -0
  76. package/src/components/gridRender/GridRenderPopoutMenuCell.tsx +22 -0
  77. package/src/contexts/GridContext.tsx +82 -0
  78. package/src/contexts/GridContextProvider.tsx +292 -0
  79. package/src/contexts/UpdatingContext.tsx +16 -0
  80. package/src/contexts/UpdatingContextProvider.tsx +43 -0
  81. package/src/lui/TextAreaInput.tsx +50 -0
  82. package/src/lui/TextInputFormatted.scss +11 -0
  83. package/src/lui/TextInputFormatted.tsx +60 -0
  84. package/src/lui-overrides.scss +163 -0
  85. package/src/react-app-env.d.ts +1 -0
  86. package/src/setupTests.ts +5 -0
  87. package/src/stories/components/FormTest.scss +10 -0
  88. package/src/stories/components/FormTest.tsx +51 -0
  89. package/src/stories/components/GridPopoutBearing.stories.tsx +100 -0
  90. package/src/stories/components/GridPopoutEditDropDown.stories.tsx +174 -0
  91. package/src/stories/components/GridPopoutEditGeneric.stories.tsx +76 -0
  92. package/src/stories/components/GridPopoutEditGenericTextArea.stories.tsx +106 -0
  93. package/src/stories/components/GridPopoutEditMultiSelect.stories.tsx +111 -0
  94. package/src/stories/components/GridReadOnly.stories.tsx +151 -0
  95. package/src/utils/bearing.ts +72 -0
  96. package/src/utils/util.ts +26 -0
@@ -0,0 +1,180 @@
1
+ import "@szhsin/react-menu/dist/index.css";
2
+
3
+ import { MenuItem, MenuDivider, FocusableItem } from "@szhsin/react-menu";
4
+ import { useCallback, useEffect, useRef, useState } from "react";
5
+ import { GridBaseRow } from "../Grid";
6
+ import { ComponentLoadingWrapper } from "../ComponentLoadingWrapper";
7
+ import { delay } from "lodash-es";
8
+ import { LuiCheckboxInput } from "@linzjs/lui";
9
+ import { GenericCellEditorParams, GridFormProps } from "../GridCell";
10
+ import { useGridPopoutHook } from "../GridPopoutHook";
11
+
12
+ interface FinalSelectOption<ValueType> {
13
+ value: ValueType;
14
+ label?: JSX.Element | string;
15
+ subComponent?: (props: any, ref: any) => any;
16
+ }
17
+
18
+ export const MenuSeparatorString = "_____MENU_SEPARATOR_____";
19
+ export const MenuSeparator = Object.freeze({ value: MenuSeparatorString });
20
+
21
+ export type SelectOption<ValueType> = ValueType | FinalSelectOption<ValueType>;
22
+
23
+ export interface MultiSelectResult<RowType> {
24
+ selectedRows: RowType[];
25
+ values: Record<string, any>;
26
+ }
27
+
28
+ export interface GridFormMultiSelectProps<RowType extends GridBaseRow, ValueType>
29
+ extends GenericCellEditorParams<RowType> {
30
+ filtered?: boolean;
31
+ filterPlaceholder?: string;
32
+ onSave?: (props: MultiSelectResult<RowType>) => Promise<boolean>;
33
+ options:
34
+ | SelectOption<ValueType>[]
35
+ | ((selectedRows: RowType[]) => Promise<SelectOption<ValueType>[]> | SelectOption<ValueType>[]);
36
+ }
37
+
38
+ export const GridFormMultiSelect = <RowType extends GridBaseRow, ValueType>(props: GridFormProps<RowType>) => {
39
+ const formProps = props.formProps as GridFormMultiSelectProps<RowType, ValueType>;
40
+
41
+ const [filter, setFilter] = useState("");
42
+ const [filteredValues, setFilteredValues] = useState<any[]>([]);
43
+ const optionsInitialising = useRef(false);
44
+ const [options, setOptions] = useState<FinalSelectOption<ValueType>[]>();
45
+ const subSelectedValues = useRef<Record<string, any>>({});
46
+ const [selectedValues, setSelectedValues] = useState<any[]>([]);
47
+
48
+ const save = useCallback(
49
+ async (selectedRows: RowType[]): Promise<boolean> => {
50
+ const values: Record<string, any> = {};
51
+ selectedValues.forEach((value) => {
52
+ values[value] = subSelectedValues.current[value] ?? true;
53
+ });
54
+ if (formProps.onSave) {
55
+ return await formProps.onSave({ selectedRows, values: selectedValues });
56
+ }
57
+ return true;
58
+ },
59
+ [formProps, selectedValues],
60
+ );
61
+ const { popoutWrapper } = useGridPopoutHook(props, save);
62
+
63
+ // Load up options list if it's async function
64
+ useEffect(() => {
65
+ if (options || optionsInitialising.current) return;
66
+ optionsInitialising.current = true;
67
+ let optionsConf = formProps.options ?? [];
68
+
69
+ (async () => {
70
+ if (typeof optionsConf == "function") {
71
+ optionsConf = await optionsConf(props.selectedRows);
72
+ }
73
+
74
+ const optionsList = optionsConf?.map((item) => {
75
+ if (item == null || typeof item == "string" || typeof item == "number") {
76
+ item = { value: item as ValueType, label: item } as FinalSelectOption<ValueType>;
77
+ }
78
+ return item;
79
+ }) as any as FinalSelectOption<ValueType>[];
80
+
81
+ if (formProps.filtered) {
82
+ // This is needed otherwise when filter input is rendered and sets autofocus
83
+ // the mouse up of the double click edit triggers the cell to cancel editing
84
+ delay(() => setOptions(optionsList), 100);
85
+ } else {
86
+ setOptions(optionsList);
87
+ }
88
+ optionsInitialising.current = false;
89
+ })();
90
+ }, [formProps.filtered, formProps.options, options, props.selectedRows]);
91
+
92
+ useEffect(() => {
93
+ if (!formProps.filtered || options == null) return;
94
+ setFilteredValues(
95
+ options
96
+ .map((option) => {
97
+ if (option.label != null && typeof option.label !== "string") {
98
+ console.error("Cannot filter non-string labels", option);
99
+ return undefined;
100
+ }
101
+ const str = (option.label as string) || "";
102
+ return str.toLowerCase().indexOf(filter) === -1 ? option.value : undefined;
103
+ })
104
+ .filter((r) => r !== undefined),
105
+ );
106
+ }, [formProps.filtered, filter, options]);
107
+
108
+ return popoutWrapper(
109
+ <ComponentLoadingWrapper loading={!options}>
110
+ <div className={"Grid-popoverContainerList"}>
111
+ {options && formProps.filtered && (
112
+ <>
113
+ <FocusableItem className={"filter-item"}>
114
+ {({ ref }: any) => (
115
+ <div style={{ display: "flex", width: "100%" }}>
116
+ <input
117
+ autoFocus
118
+ className={"free-text-input"}
119
+ style={{ border: "0px" }}
120
+ ref={ref}
121
+ type="text"
122
+ placeholder={formProps.filterPlaceholder ?? "Placeholder"}
123
+ data-testid={"filteredMenu-free-text-input"}
124
+ defaultValue={""}
125
+ onChange={(e) => setFilter(e.target.value.toLowerCase())}
126
+ />
127
+ </div>
128
+ )}
129
+ </FocusableItem>
130
+ <MenuDivider key={`$$divider_filter`} />
131
+ </>
132
+ )}
133
+ {options?.map((item, index) =>
134
+ item.value === MenuSeparatorString ? (
135
+ <MenuDivider key={`$$divider_${index}`} />
136
+ ) : filteredValues.includes(item.value) ? null : (
137
+ <>
138
+ <MenuItem
139
+ key={`${item.value}`}
140
+ onClick={(e) => {
141
+ e.keepOpen = true;
142
+ // onSelectMenuOption(itemIndex, e.value);
143
+ return false;
144
+ }}
145
+ >
146
+ <LuiCheckboxInput
147
+ isChecked={selectedValues.includes(item.value)}
148
+ value={`${item.value}`}
149
+ label={item.label ?? (item.value == null ? `<${item.value}>` : `${item.value}`)}
150
+ onChange={(e) => {
151
+ if (e.target.checked) {
152
+ setSelectedValues([...selectedValues, item.value as string]);
153
+ } else {
154
+ setSelectedValues(selectedValues.filter((value) => value != item.value));
155
+ }
156
+ }}
157
+ />
158
+ </MenuItem>
159
+ {selectedValues.includes(item.value) && item.subComponent && (
160
+ <FocusableItem className={"LuiDeprecatedForms"} key={`${item.value}_subcomponent`}>
161
+ {(ref) =>
162
+ item.subComponent &&
163
+ item.subComponent(
164
+ {
165
+ setValue: (value: any) => {
166
+ subSelectedValues.current[item.value as string] = value;
167
+ },
168
+ },
169
+ ref,
170
+ )
171
+ }
172
+ </FocusableItem>
173
+ )}
174
+ </>
175
+ ),
176
+ )}
177
+ </div>
178
+ </ComponentLoadingWrapper>,
179
+ );
180
+ };
@@ -0,0 +1,90 @@
1
+ import { GridBaseRow } from "../Grid";
2
+ import { useCallback, useContext, useEffect, useRef, useState } from "react";
3
+ import { GridContext } from "../../contexts/GridContext";
4
+ import { ComponentLoadingWrapper } from "../ComponentLoadingWrapper";
5
+ import { MenuDivider, MenuItem } from "@szhsin/react-menu";
6
+ import { GenericCellEditorParams, GridFormProps } from "../GridCell";
7
+ import { useGridPopoutHook } from "../GridPopoutHook";
8
+
9
+ export interface GridFormPopoutMenuProps<RowType extends GridBaseRow> extends GenericCellEditorParams<RowType> {
10
+ options: (selectedRows: RowType[]) => Promise<MenuOption<RowType>[]>;
11
+ }
12
+
13
+ /** Menu configuration types **/
14
+ export const MenuSeparator = Object.freeze({ __isMenuSeparator__: true });
15
+
16
+ interface MenuSeparatorType {
17
+ __isMenuSeparator__: boolean;
18
+ }
19
+
20
+ export interface MenuOption<RowType> {
21
+ label: JSX.Element | string | MenuSeparatorType;
22
+ action: (selectedRows: RowType[]) => Promise<boolean>;
23
+ multiEdit: boolean;
24
+ }
25
+
26
+ /**
27
+ * NOTE: If the popout menu doesn't appear on single click when also selecting row it's because
28
+ * you need a useMemo around your columnDefs
29
+ */
30
+ export const GridFormPopoutMenu = <RowType extends GridBaseRow>(props: GridFormProps<RowType>) => {
31
+ const formProps = props.formProps as GridFormPopoutMenuProps<RowType>;
32
+
33
+ const { updatingCells } = useContext(GridContext);
34
+ const optionsInitialising = useRef(false);
35
+ const [options, setOptions] = useState<MenuOption<RowType>[]>();
36
+
37
+ // Load up options list if it's async function
38
+ useEffect(() => {
39
+ if (options || optionsInitialising.current) return;
40
+ optionsInitialising.current = true;
41
+ const optionsConf = formProps.options ?? [];
42
+
43
+ (async () => {
44
+ if (typeof optionsConf == "function") {
45
+ setOptions(await optionsConf(props.selectedRows));
46
+ } else {
47
+ setOptions(optionsConf);
48
+ }
49
+
50
+ optionsInitialising.current = false;
51
+ })();
52
+ }, [options, formProps.options, props.selectedRows]);
53
+
54
+ const actionClick = useCallback(
55
+ async (menuOption: MenuOption<any>) => {
56
+ return await updatingCells({ selectedRows: props.selectedRows, field: props.field }, async (selectedRows) => {
57
+ await menuOption.action(selectedRows);
58
+ return true;
59
+ });
60
+ },
61
+ [props.field, props.selectedRows, updatingCells],
62
+ );
63
+
64
+ const selectedRowCount = props.selectedRows.length;
65
+
66
+ const filteredOptions = options?.filter((menuOption) => {
67
+ return menuOption.label === MenuSeparator || selectedRowCount === 1 || menuOption.multiEdit;
68
+ });
69
+
70
+ const { popoutWrapper } = useGridPopoutHook(props);
71
+ return popoutWrapper(
72
+ <ComponentLoadingWrapper loading={!filteredOptions}>
73
+ <div className={"Grid-popoverContainerList"}>
74
+ {options?.map((item, index) =>
75
+ item.label === MenuSeparator ? (
76
+ <MenuDivider key={`$$divider_${index}`} />
77
+ ) : (
78
+ <MenuItem
79
+ key={`${item.label}`}
80
+ onClick={() => actionClick(item)}
81
+ disabled={!filteredOptions?.includes(item)}
82
+ >
83
+ {item.label as JSX.Element | string}
84
+ </MenuItem>
85
+ ),
86
+ )}
87
+ </div>
88
+ </ComponentLoadingWrapper>,
89
+ );
90
+ };
@@ -0,0 +1,61 @@
1
+ import { useCallback, useState } from "react";
2
+ import { GenericCellEditorParams, GridFormProps } from "../GridCell";
3
+ import { TextAreaInput } from "../../lui/TextAreaInput";
4
+ import { useGridPopoutHook } from "../GridPopoutHook";
5
+ import { GridBaseRow } from "../Grid";
6
+
7
+ export interface GridFormTextAreaProps<RowType extends GridBaseRow> extends GenericCellEditorParams<RowType> {
8
+ placeholder?: string;
9
+ required?: boolean;
10
+ maxlength?: number;
11
+ width?: string | number;
12
+ onSave?: (selectedRows: RowType[], value: string) => Promise<boolean>;
13
+ }
14
+
15
+ export const GridFormTextArea = <RowType extends GridBaseRow>(props: GridFormProps<RowType>) => {
16
+ const formProps = props.formProps as GridFormTextAreaProps<RowType>;
17
+ const [value, setValue] = useState(props.value ?? "");
18
+
19
+ const invalid = useCallback(() => {
20
+ if (formProps.required && value.length == 0) {
21
+ return `Some text is required`;
22
+ }
23
+ if (formProps.maxlength && value.length > formProps.maxlength) {
24
+ return `Text must be no longer than ${formProps.maxlength} characters`;
25
+ }
26
+ return null;
27
+ }, [formProps.maxlength, formProps.required, value.length]);
28
+
29
+ const save = useCallback(
30
+ async (selectedRows: any[]): Promise<boolean> => {
31
+ if (invalid()) return false;
32
+
33
+ if (props.value === (value ?? "")) return true;
34
+
35
+ if (formProps.onSave) {
36
+ return await formProps.onSave(selectedRows, value);
37
+ }
38
+
39
+ const field = props.field;
40
+ if (field == null) {
41
+ console.error("ColDef has no field set");
42
+ return false;
43
+ }
44
+ selectedRows.forEach((row) => (row[field] = value));
45
+ return true;
46
+ },
47
+ [formProps, invalid, props.field, props.value, value],
48
+ );
49
+ const { popoutWrapper } = useGridPopoutHook(props, save);
50
+
51
+ return popoutWrapper(
52
+ <div style={{ display: "flex", flexDirection: "row", width: formProps.width ?? 240 }} className={"FormTest"}>
53
+ <TextAreaInput
54
+ value={value}
55
+ onChange={(e) => setValue(e.target.value)}
56
+ error={invalid()}
57
+ inputProps={{ placeholder: formProps.placeholder }}
58
+ />
59
+ </div>,
60
+ );
61
+ };
@@ -0,0 +1,66 @@
1
+ import { useCallback, useState } from "react";
2
+ import { GenericCellEditorParams, GridFormProps } from "../GridCell";
3
+ import { TextInputFormatted } from "../../lui/TextInputFormatted";
4
+ import { useGridPopoutHook } from "../GridPopoutHook";
5
+ import { GridBaseRow } from "../Grid";
6
+
7
+ export interface GridFormTextInputProps<RowType extends GridBaseRow> extends GenericCellEditorParams<RowType> {
8
+ placeholder?: string;
9
+ required?: boolean;
10
+ maxlength?: number;
11
+ width?: string | number;
12
+ onSave?: (selectedRows: RowType[], value: string) => Promise<boolean>;
13
+ }
14
+
15
+ export const GridFormTextInput = <RowType extends GridBaseRow>(props: GridFormProps<RowType>) => {
16
+ const formProps = props.formProps as GridFormTextInputProps<RowType>;
17
+ const [value, setValue] = useState(props.value ?? "");
18
+
19
+ const invalid = useCallback(() => {
20
+ if (formProps.required && value.length == 0) {
21
+ return `Some text is required`;
22
+ }
23
+ if (formProps.maxlength && value.length > formProps.maxlength) {
24
+ return `Text must be no longer than ${formProps.maxlength} characters`;
25
+ }
26
+ return null;
27
+ }, [formProps.maxlength, formProps.required, value.length]);
28
+
29
+ const save = useCallback(
30
+ async (selectedRows: any[]): Promise<boolean> => {
31
+ if (invalid()) return false;
32
+
33
+ if (props.value === (value ?? "")) return true;
34
+
35
+ if (formProps.onSave) {
36
+ return await formProps.onSave(selectedRows, value);
37
+ }
38
+
39
+ const field = props.field;
40
+ if (field == null) {
41
+ console.error("ColDef has no field set");
42
+ return false;
43
+ }
44
+ selectedRows.forEach((row) => (row[field] = value));
45
+ return true;
46
+ },
47
+ [invalid, props.value, props.field, value, formProps],
48
+ );
49
+ const { popoutWrapper, triggerSave } = useGridPopoutHook(props, save);
50
+
51
+ return popoutWrapper(
52
+ <div style={{ display: "flex", flexDirection: "row", width: formProps.width ?? 240 }} className={"FormTest"}>
53
+ <TextInputFormatted
54
+ value={value}
55
+ onChange={(e) => setValue(e.target.value)}
56
+ error={invalid()}
57
+ formatted={""}
58
+ inputProps={{
59
+ style: { width: "100%" },
60
+ placeholder: formProps.placeholder,
61
+ onKeyDown: async (e) => e.key === "Enter" && triggerSave().then(),
62
+ }}
63
+ />
64
+ </div>,
65
+ );
66
+ };
@@ -0,0 +1,54 @@
1
+ import clsx from "clsx";
2
+ import { IHeaderParams } from "ag-grid-community";
3
+ import { useCallback, useEffect, useState } from "react";
4
+
5
+ /**
6
+ * AgGrid's existing select header doesn't work the way we want.
7
+ * If you have partial select then clicking the header checkbox will select all,
8
+ * but we want to deselect all on partial select.
9
+ */
10
+ export const GridHeaderSelect = ({ api }: IHeaderParams) => {
11
+ // This is used to force an update on selection change
12
+ const [updateCounter, setUpdateCounter] = useState(0);
13
+ const selectedNodeCount = api.getSelectedRows().length;
14
+
15
+ const clickHandler = useCallback(() => {
16
+ setUpdateCounter(updateCounter + 1);
17
+ }, [updateCounter]);
18
+
19
+ useEffect(() => {
20
+ api.addEventListener("selectionChanged", clickHandler);
21
+ return () => api.removeEventListener("selectionChanged", clickHandler);
22
+ }, [api, clickHandler]);
23
+
24
+ const handleMultiSelect = () => {
25
+ if (selectedNodeCount == 0) {
26
+ api.selectAllFiltered();
27
+ } else {
28
+ api.deselectAll();
29
+ }
30
+ };
31
+
32
+ const totalNodeCount = api.getModel().getRowCount();
33
+ const partialSelect = selectedNodeCount != 0 && selectedNodeCount != totalNodeCount;
34
+ const allSelected = selectedNodeCount != 0 && selectedNodeCount == totalNodeCount;
35
+
36
+ return (
37
+ <div
38
+ className={clsx(
39
+ "ag-wrapper ag-input-wrapper ag-checkbox-input-wrapper",
40
+ partialSelect && "ag-indeterminate",
41
+ allSelected && "ag-checked",
42
+ )}
43
+ onClick={handleMultiSelect}
44
+ >
45
+ <input
46
+ type="checkbox"
47
+ className={"ag-checkbox-input-wrapper"}
48
+ onChange={() => {
49
+ /* do nothing */
50
+ }}
51
+ />
52
+ </div>
53
+ );
54
+ };
@@ -0,0 +1,22 @@
1
+ import { GenericMultiEditCellClass } from "../GenericCellClass";
2
+ import { GenericCellColDef, GridRendererGenericCell } from "../gridRender/GridRenderGenericCell";
3
+ import { GridCell } from "../GridCell";
4
+ import { GridBaseRow } from "../Grid";
5
+ import { GridFormMultiSelect, GridFormMultiSelectProps } from "../gridForm/GridFormMultiSelect";
6
+
7
+ export const GridPopoutEditMultiSelect = <RowType extends GridBaseRow, ValueType>(
8
+ colDef: GenericCellColDef<RowType, GridFormMultiSelectProps<RowType, ValueType>>,
9
+ ) =>
10
+ GridCell<RowType, GridFormMultiSelectProps<RowType, ValueType>>({
11
+ initialWidth: 65,
12
+ maxWidth: 150,
13
+ cellRenderer: GridRendererGenericCell,
14
+ cellClass: colDef.cellEditor?.multiEdit ? GenericMultiEditCellClass : undefined,
15
+ ...colDef,
16
+ ...(colDef?.cellEditorParams && {
17
+ cellEditorParams: {
18
+ ...colDef.cellEditorParams,
19
+ form: GridFormMultiSelect,
20
+ },
21
+ }),
22
+ });
@@ -0,0 +1,9 @@
1
+ @use 'node_modules/@linzjs/lui/dist/scss/Core' as lui;
2
+
3
+ .GridPopoutMenu-burger svg {
4
+ fill: lui.$sea;
5
+ }
6
+
7
+ .GridPopoutMenu-burgerDisabled svg {
8
+ fill: lui.$disabled-color;
9
+ }
@@ -0,0 +1,35 @@
1
+ import "./GridPopoutMenu.scss";
2
+ import "@szhsin/react-menu/dist/index.css";
3
+
4
+ import { ColDef } from "ag-grid-community";
5
+ import { GenericMultiEditCellClass } from "../GenericCellClass";
6
+ import { GridBaseRow } from "../Grid";
7
+ import { GridCell } from "../GridCell";
8
+ import { GridFormPopoutMenu, GridFormPopoutMenuProps } from "../gridForm/GridFormPopoutMenu";
9
+ import { GridRenderPopoutMenuCell } from "../gridRender/GridRenderPopoutMenuCell";
10
+ import { GenericCellColDef } from "../gridRender/GridRenderGenericCell";
11
+
12
+ /**
13
+ * Popout burger menu
14
+ */
15
+ export const GridPopoutMenu = <RowType extends GridBaseRow>(
16
+ colDef: GenericCellColDef<RowType, GridFormPopoutMenuProps<RowType>>,
17
+ ): ColDef =>
18
+ GridCell<RowType, GridFormPopoutMenuProps<RowType>>({
19
+ maxWidth: 64,
20
+ editable: colDef.editable != null ? colDef.editable : true,
21
+ cellRenderer: GridRenderPopoutMenuCell,
22
+ cellClass: colDef?.cellEditorParams?.multiEdit !== false ? GenericMultiEditCellClass : undefined,
23
+ ...colDef,
24
+ cellRendererParams: {
25
+ // Menus open on single click, this parameter is picked up in Grid.tsx
26
+ singleClickEdit: true,
27
+ },
28
+ ...(colDef?.cellEditorParams && {
29
+ cellEditorParams: {
30
+ multiEdit: true,
31
+ ...colDef.cellEditorParams,
32
+ form: GridFormPopoutMenu,
33
+ },
34
+ }),
35
+ });
@@ -0,0 +1,24 @@
1
+ import { GenericMultiEditCellClass } from "../GenericCellClass";
2
+ import { GenericCellColDef, GridRendererGenericCell } from "../gridRender/GridRenderGenericCell";
3
+ import { bearingValueFormatter } from "../../utils/bearing";
4
+ import { GridCell } from "../GridCell";
5
+ import { GridFormEditBearing, GridFormEditBearingProps } from "../gridForm/GridFormEditBearing";
6
+ import { GridBaseRow } from "../Grid";
7
+
8
+ export const GridPopoverEditBearing = <RowType extends GridBaseRow>(
9
+ colDef: GenericCellColDef<RowType, GridFormEditBearingProps<RowType>>,
10
+ ) =>
11
+ GridCell<RowType, GridFormEditBearingProps<RowType>>({
12
+ initialWidth: 65,
13
+ maxWidth: 150,
14
+ valueFormatter: bearingValueFormatter,
15
+ cellRenderer: GridRendererGenericCell,
16
+ cellClass: colDef.cellEditorParams?.multiEdit ? GenericMultiEditCellClass : undefined,
17
+ ...colDef,
18
+ ...(colDef?.cellEditorParams && {
19
+ cellEditorParams: {
20
+ ...colDef.cellEditorParams,
21
+ form: GridFormEditBearing,
22
+ },
23
+ }),
24
+ });
@@ -0,0 +1,22 @@
1
+ import { GenericMultiEditCellClass } from "../GenericCellClass";
2
+ import { GenericCellColDef, GridRendererGenericCell } from "../gridRender/GridRenderGenericCell";
3
+ import { GridCell } from "../GridCell";
4
+ import { GridBaseRow } from "../Grid";
5
+ import { GridFormDropDown, GridFormPopoutDropDownProps } from "../gridForm/GridFormDropDown";
6
+
7
+ export const GridPopoverEditDropDown = <RowType extends GridBaseRow, ValueType>(
8
+ colDef: GenericCellColDef<RowType, GridFormPopoutDropDownProps<RowType, ValueType>>,
9
+ ) =>
10
+ GridCell<RowType, GridFormPopoutDropDownProps<RowType, ValueType>>({
11
+ initialWidth: 65,
12
+ maxWidth: 150,
13
+ cellRenderer: GridRendererGenericCell,
14
+ cellClass: colDef.cellEditor?.multiEdit ? GenericMultiEditCellClass : undefined,
15
+ ...colDef,
16
+ ...(colDef?.cellEditorParams && {
17
+ cellEditorParams: {
18
+ ...colDef.cellEditorParams,
19
+ form: GridFormDropDown,
20
+ },
21
+ }),
22
+ });
@@ -0,0 +1,22 @@
1
+ import { GridCell } from "../GridCell";
2
+ import { GridFormMessage, GridFormMessageProps } from "../gridForm/GridFormMessage";
3
+ import { GridBaseRow } from "../Grid";
4
+ import { GenericCellColDef } from "../gridRender/GridRenderGenericCell";
5
+
6
+ export const GridPopoverMessage = <RowType extends GridBaseRow>(
7
+ colDef: GenericCellColDef<RowType, GridFormMessageProps<RowType>>,
8
+ ) => {
9
+ return GridCell<RowType, GridFormMessageProps<RowType>>({
10
+ maxWidth: 140,
11
+ ...colDef,
12
+ cellRendererParams: colDef.cellRendererParams ?? {
13
+ singleClickEdit: true,
14
+ },
15
+ ...(colDef?.cellEditorParams && {
16
+ cellEditorParams: {
17
+ ...colDef.cellEditorParams,
18
+ form: GridFormMessage,
19
+ },
20
+ }),
21
+ });
22
+ };
@@ -0,0 +1,15 @@
1
+ @use 'node_modules/@linzjs/lui/dist/scss/Core' as lui;
2
+
3
+ .AgGridGenericCellRenderer-icon {
4
+ margin-right: 4px;
5
+ }
6
+
7
+ .AgGridGenericCellRenderer-ic_infoIcon {
8
+ @extend .AgGridGenericCellRenderer-icon;
9
+ fill: lui.$info;
10
+ }
11
+
12
+ .AgGridGenericCellRenderer-ic_warningIcon {
13
+ @extend .AgGridGenericCellRenderer-icon;
14
+ fill: lui.$warning;
15
+ }
@@ -0,0 +1,49 @@
1
+ import "./GridRenderGenericCell.scss";
2
+
3
+ import { useContext } from "react";
4
+ import { UpdatingContext } from "../../contexts/UpdatingContext";
5
+ import { GridLoadableCell } from "../GridLoadableCell";
6
+ import { GridIcon } from "../GridIcon";
7
+ import { ColDef, ICellRendererParams } from "ag-grid-community";
8
+ import { ValueFormatterParams } from "ag-grid-community/dist/lib/entities/colDef";
9
+ import { GenericCellEditorParams } from "../GridCell";
10
+ import { GridBaseRow } from "../Grid";
11
+
12
+ export interface GenericCellColDef<RowType extends GridBaseRow, FormProps extends Record<string, any>> extends ColDef {
13
+ cellRendererParams?: GenericCellRendererParams;
14
+ cellEditorParams?: GenericCellEditorParams<RowType> & FormProps;
15
+ }
16
+
17
+ export interface GenericCellRendererParams {
18
+ singleClickEdit?: boolean;
19
+ warning?: (props: ICellRendererParams) => string | boolean | undefined;
20
+ info?: (props: ICellRendererParams) => string | boolean | undefined;
21
+ }
22
+
23
+ export const GridRendererGenericCell = (props: ICellRendererParams): JSX.Element => {
24
+ const { checkUpdating } = useContext(UpdatingContext);
25
+
26
+ const cellRendererParams = props.colDef?.cellRendererParams as GenericCellRendererParams | undefined;
27
+ const warningFn = cellRendererParams?.warning;
28
+ const warningText = warningFn ? warningFn(props) : undefined;
29
+ const infoFn = cellRendererParams?.info;
30
+ const infoText = infoFn ? infoFn(props) : undefined;
31
+
32
+ const defaultFormatter = (props: ValueFormatterParams): string => props.value;
33
+ const formatter = props.colDef?.valueFormatter ?? defaultFormatter;
34
+ if (typeof formatter === "string") {
35
+ console.error("valueFormatter must be a function");
36
+ return <span>valueFormatter must be a function</span>;
37
+ }
38
+ const formatted = formatter(props as ValueFormatterParams);
39
+
40
+ return (
41
+ <GridLoadableCell isLoading={checkUpdating(props.colDef?.field ?? props.colDef?.colId ?? "", props.data.id)}>
42
+ <>
43
+ {typeof warningText === "string" && <GridIcon icon={"ic_warning"} title={warningText} />}
44
+ {typeof infoText === "string" && <GridIcon icon={"ic_info"} title={infoText} />}
45
+ <span title={formatted}>{formatted}</span>
46
+ </>
47
+ </GridLoadableCell>
48
+ );
49
+ };