@linzjs/step-ag-grid 6.1.1 → 7.0.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 (46) hide show
  1. package/dist/index.css +12 -2
  2. package/dist/index.js +378 -203
  3. package/dist/index.js.map +1 -1
  4. package/dist/src/components/GridPopoverHook.d.ts +1 -1
  5. package/dist/src/components/gridForm/GridFormMultiSelect.d.ts +17 -12
  6. package/dist/src/components/gridPopoverEdit/GridPopoutEditMultiSelect.d.ts +1 -1
  7. package/dist/src/contexts/GridSubComponentContext.d.ts +1 -0
  8. package/dist/src/lui/ActionButton.d.ts +4 -2
  9. package/dist/src/lui/FormError.d.ts +2 -1
  10. package/dist/src/lui/TextAreaInput.d.ts +1 -1
  11. package/dist/src/lui/TextInputFormatted.d.ts +1 -1
  12. package/dist/src/react-menu3/utils/utils.d.ts +1 -0
  13. package/dist/src/utils/textMatcher.d.ts +13 -0
  14. package/dist/src/utils/textValidator.d.ts +3 -2
  15. package/dist/src/utils/util.d.ts +2 -1
  16. package/dist/step-ag-grid.esm.js +379 -205
  17. package/dist/step-ag-grid.esm.js.map +1 -1
  18. package/package.json +9 -8
  19. package/src/components/GridPopoverHook.tsx +7 -1
  20. package/src/components/gridForm/GridFormDropDown.tsx +1 -0
  21. package/src/components/gridForm/GridFormMultiSelect.tsx +290 -188
  22. package/src/components/gridForm/GridFormPopoverMenu.tsx +1 -0
  23. package/src/components/gridForm/GridFormSubComponentTextArea.tsx +2 -2
  24. package/src/components/gridForm/GridFormSubComponentTextInput.tsx +3 -5
  25. package/src/components/gridForm/GridFormTextArea.tsx +13 -13
  26. package/src/components/gridForm/GridFormTextInput.tsx +3 -8
  27. package/src/components/gridPopoverEdit/GridPopoutEditMultiSelect.ts +3 -3
  28. package/src/contexts/GridSubComponentContext.ts +2 -0
  29. package/src/lui/ActionButton.scss +10 -0
  30. package/src/lui/ActionButton.tsx +36 -10
  31. package/src/lui/FormError.scss +7 -0
  32. package/src/lui/FormError.tsx +4 -13
  33. package/src/lui/TextAreaInput.tsx +1 -1
  34. package/src/lui/TextInputFormatted.tsx +1 -1
  35. package/src/react-menu3/components/ControlledMenu.tsx +3 -2
  36. package/src/react-menu3/components/MenuList.tsx +2 -12
  37. package/src/react-menu3/hooks/useItems.ts +5 -2
  38. package/src/react-menu3/utils/utils.ts +15 -0
  39. package/src/stories/components/ActionButton.stories.tsx +11 -0
  40. package/src/stories/grid/FormTest.tsx +1 -1
  41. package/src/stories/grid/GridPopoutEditMultiSelect.stories.tsx +93 -17
  42. package/src/styles/Grid.scss +12 -0
  43. package/src/styles/lui-overrides.scss +0 -2
  44. package/src/utils/textMatcher.ts +31 -0
  45. package/src/utils/textValidator.ts +6 -3
  46. package/src/utils/util.ts +6 -7
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@linzjs/step-ag-grid",
3
3
  "repository": "github:linz/step-ag-grid.git",
4
4
  "license": "MIT",
5
- "version": "6.1.1",
5
+ "version": "7.0.1",
6
6
  "keywords": [
7
7
  "aggrid",
8
8
  "ag-grid",
@@ -35,12 +35,13 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "@linzjs/lui": ">=17",
38
- "ag-grid-community": "^27",
39
- "ag-grid-react": "^27",
38
+ "ag-grid-community": ">=27",
39
+ "ag-grid-react": ">=27",
40
40
  "debounce-promise": "^3.1.2",
41
- "lodash-es": "^4.17.21",
42
- "react": "^17",
43
- "react-dom": "^17",
41
+ "lodash-es": ">=4",
42
+ "matcher": "^5.0.0",
43
+ "react": ">=17",
44
+ "react-dom": ">=17",
44
45
  "react-transition-state": "^1.1.5",
45
46
  "uuid": "^9.0.0"
46
47
  },
@@ -122,8 +123,8 @@
122
123
  "mkdirp": "^1.0.4",
123
124
  "npm-run-all": "^4.1.5",
124
125
  "postcss": "^8.4.18",
125
- "postcss-scss": "^4.0.5",
126
126
  "postcss-loader": "^7.0.1",
127
+ "postcss-scss": "^4.0.5",
127
128
  "prettier": "^2.7.1",
128
129
  "react-scripts": "^5.0.1",
129
130
  "rollup": "^3.2.5",
@@ -138,8 +139,8 @@
138
139
  "style-loader": "2.0.0",
139
140
  "stylelint": "^14.14.1",
140
141
  "stylelint-config-prettier": "^9.0.3",
141
- "stylelint-config-standard": "^29.0.0",
142
142
  "stylelint-config-recommended-scss": "^8.0.0",
143
+ "stylelint-config-standard": "^29.0.0",
143
144
  "stylelint-prettier": "^2.0.0",
144
145
  "stylelint-scss": "^4.3.0",
145
146
  "tsconfig-paths-webpack-plugin": "^4.0.0",
@@ -8,7 +8,13 @@ import { CloseReason } from "../react-menu3/utils";
8
8
 
9
9
  export interface GridPopoverHookProps<RowType> {
10
10
  className: string | undefined;
11
- invalid?: () => Promise<boolean | string | null> | boolean | string | null;
11
+ invalid?: () =>
12
+ | Promise<JSX.Element | boolean | string | null | undefined>
13
+ | JSX.Element
14
+ | boolean
15
+ | string
16
+ | null
17
+ | undefined;
12
18
  save?: (selectedRows: RowType[]) => Promise<boolean>;
13
19
  }
14
20
 
@@ -285,6 +285,7 @@ export const GridFormDropDown = <RowType extends GridBaseRow>(props: GridFormPop
285
285
  {(ref: MenuInstance) => (
286
286
  <GridSubComponentContext.Provider
287
287
  value={{
288
+ context: { options },
288
289
  data,
289
290
  value: subSelectedValue,
290
291
  setValue: (value: any) => {
@@ -1,10 +1,20 @@
1
1
  import "../../styles/GridFormMultiSelect.scss";
2
2
 
3
- import { FocusableItem, MenuDivider, MenuItem } from "../../react-menu3";
4
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
+ import { FocusableItem, MenuDivider, MenuHeader, MenuItem } from "../../react-menu3";
4
+ import {
5
+ useCallback,
6
+ useEffect,
7
+ useMemo,
8
+ useRef,
9
+ useState,
10
+ KeyboardEvent,
11
+ SetStateAction,
12
+ Dispatch,
13
+ Fragment,
14
+ } from "react";
5
15
  import { GridBaseRow } from "../Grid";
6
16
  import { ComponentLoadingWrapper } from "../ComponentLoadingWrapper";
7
- import { delay, fromPairs, isEqual, omit, pick, toPairs } from "lodash-es";
17
+ import { groupBy, isEmpty, pick, toPairs } from "lodash-es";
8
18
  import { LuiCheckboxInput } from "@linzjs/lui";
9
19
  import { useGridPopoverHook } from "../GridPopoverHook";
10
20
  import { MenuSeparatorString } from "./GridFormDropDown";
@@ -12,20 +22,24 @@ import { CellEditorCommon } from "../GridCell";
12
22
  import { ClickEvent } from "../../react-menu3/types";
13
23
  import { GridSubComponentContext } from "contexts/GridSubComponentContext";
14
24
  import { useGridPopoverContext } from "../../contexts/GridPopoverContext";
25
+ import { FormError } from "../../lui/FormError";
26
+ import { textMatch } from "../../utils/textMatcher";
15
27
 
16
- interface MultiFinalSelectOption<ValueType> {
17
- value: ValueType;
18
- label?: JSX.Element | string;
28
+ export interface MultiSelectOption {
29
+ value: any;
30
+ label?: string;
19
31
  subComponent?: (props: any) => JSX.Element;
32
+ subValue?: any;
33
+ filter?: string;
34
+ checked?: boolean;
20
35
  }
21
36
 
22
- export type MultiSelectOption<ValueType> = ValueType | MultiFinalSelectOption<ValueType>;
23
-
24
- export interface SelectedOptionResult<ValueType> extends MultiFinalSelectOption<ValueType> {
25
- subValue?: any;
37
+ export interface GridFormMultiSelectGroup {
38
+ header: string;
39
+ filter?: string;
26
40
  }
27
41
 
28
- export interface GridFormMultiSelectProps<RowType extends GridBaseRow, ValueType> extends CellEditorCommon {
42
+ export interface GridFormMultiSelectProps<RowType extends GridBaseRow> extends CellEditorCommon {
29
43
  className?:
30
44
  | "GridMultiSelect-containerSmall"
31
45
  | "GridMultiSelect-containerMedium"
@@ -35,218 +49,306 @@ export interface GridFormMultiSelectProps<RowType extends GridBaseRow, ValueType
35
49
  | undefined;
36
50
  filtered?: boolean;
37
51
  filterPlaceholder?: string;
38
- onSave?: (selectedRows: RowType[], selectedOptions: SelectedOptionResult<ValueType>[]) => Promise<boolean>;
39
- options:
40
- | MultiSelectOption<ValueType>[]
41
- | ((selectedRows: RowType[]) => Promise<MultiSelectOption<ValueType>[]> | MultiSelectOption<ValueType>[]);
42
- initialSelectedValues?: (selectedRows: RowType[]) => any[] | Record<string, null | any>;
52
+ filterHelpText?: string | ((filter: string, options: MultiSelectOption[]) => string | undefined);
53
+ onSelectFilter?: (filter: string, options: MultiSelectOption[]) => void;
54
+ onSave?: (selectedRows: RowType[], selectedOptions: MultiSelectOption[]) => Promise<boolean>;
55
+ headers?: GridFormMultiSelectGroup[];
56
+ options: MultiSelectOption[] | ((selectedRows: RowType[]) => Promise<MultiSelectOption[]> | MultiSelectOption[]);
57
+ invalid?: (selectedRows: RowType[], selectedOptions: MultiSelectOption[]) => boolean;
43
58
  }
44
59
 
45
- export const GridFormMultiSelect = <RowType extends GridBaseRow, ValueType>(
46
- props: GridFormMultiSelectProps<RowType, ValueType>,
47
- ) => {
60
+ export const GridFormMultiSelect = <RowType extends GridBaseRow>(props: GridFormMultiSelectProps<RowType>) => {
48
61
  const { selectedRows, data } = useGridPopoverContext<RowType>();
49
62
 
50
- const initialiseValues = useMemo(() => {
51
- const r = props.initialSelectedValues && props.initialSelectedValues(selectedRows);
52
- // convert array of strings to object<value,null>
53
- return Array.isArray(r) ? fromPairs(r.map((v) => [v, null])) : r;
54
- }, [props, selectedRows]);
55
-
56
- const subComponentIsValid = useRef<Record<string, boolean>>({});
57
- const [selectedValues, setSelectedValues] = useState<Record<string, null | any>>(() => initialiseValues ?? {});
63
+ const subComponentIsValidRef = useRef<Record<string, boolean>>({});
64
+ const optionsInitialising = useRef(false);
58
65
 
59
66
  const [filter, setFilter] = useState("");
60
- const [filteredValues, setFilteredValues] = useState<any[]>([]);
61
- const optionsInitialising = useRef(false);
62
- const [options, setOptions] = useState<MultiFinalSelectOption<ValueType>[]>();
67
+ const [initialValues, setInitialValues] = useState("");
68
+ const [options, setOptions] = useState<MultiSelectOption[]>();
63
69
 
64
70
  const invalid = useCallback(() => {
65
- const validations = pick(subComponentIsValid.current, Object.keys(selectedValues));
66
- return Object.values(validations).some((v) => !v);
67
- }, [selectedValues]);
71
+ if (!options) return true;
72
+ const selectedValues = options.filter((o) => o.checked).map((o) => o.value);
73
+ const subValidations = pick(subComponentIsValidRef.current, selectedValues);
74
+ if (Object.values(subValidations).some((v) => !v)) return true;
75
+ return (
76
+ props.invalid &&
77
+ props.invalid(
78
+ selectedRows,
79
+ options.filter((o) => o.checked),
80
+ )
81
+ );
82
+ }, [options, props, selectedRows]);
68
83
 
69
84
  const save = useCallback(
70
85
  async (selectedRows: RowType[]): Promise<boolean> => {
71
- if (props.onSave) {
72
- if (!options) {
73
- console.error("options not initialised");
74
- return false;
75
- }
86
+ if (!options || !props.onSave) return true;
76
87
 
77
- const validations = pick(subComponentIsValid.current, Object.keys(selectedValues));
78
- const notValid = Object.values(validations).some((v) => !v);
79
- if (notValid) return false;
88
+ // Any changes to save?
89
+ if (initialValues === JSON.stringify(options)) return true;
80
90
 
81
- const menuOptionSubValueResult = toPairs(selectedValues).map(([value, subValue]) => {
82
- const o = {
83
- ...options.find((o) => o.value == value),
84
- } as SelectedOptionResult<ValueType>;
85
- o.subValue = subValue;
86
- return o;
87
- });
88
-
89
- if (isEqual(initialiseValues, selectedValues)) {
90
- // No changes to save
91
- return true;
92
- }
93
-
94
- return await props.onSave(selectedRows, menuOptionSubValueResult);
95
- }
96
- return true;
91
+ return props.onSave(
92
+ selectedRows,
93
+ options.filter((o) => o.checked),
94
+ );
97
95
  },
98
- [initialiseValues, options, props, selectedValues],
96
+ [initialValues, options, props],
99
97
  );
100
98
 
101
99
  // Load up options list if it's async function
102
100
  useEffect(() => {
103
101
  if (options || optionsInitialising.current) return;
104
102
  optionsInitialising.current = true;
105
- let optionsConf = props.options ?? [];
106
103
 
107
104
  (async () => {
108
- if (typeof optionsConf == "function") {
109
- optionsConf = await optionsConf(selectedRows);
110
- }
111
-
112
- const optionsList = optionsConf?.map((item) => {
113
- if (item == null || typeof item === "string" || typeof item === "number") {
114
- item = { value: item as ValueType, label: item } as MultiFinalSelectOption<ValueType>;
115
- }
116
- return item;
117
- }) as any as MultiFinalSelectOption<ValueType>[];
118
-
119
- if (props.filtered) {
120
- // This is needed otherwise when filter input is rendered and sets autofocus
121
- // the mouse up of the double click edit triggers the cell to cancel editing
122
- delay(() => setOptions(optionsList), 100);
123
- } else {
124
- setOptions(optionsList);
125
- }
105
+ const optionsList = typeof props.options === "function" ? await props.options(selectedRows) : props.options;
106
+ setInitialValues(JSON.stringify(optionsList));
107
+ setOptions(optionsList);
126
108
  optionsInitialising.current = false;
127
109
  })();
128
- }, [props.filtered, props.options, options, selectedRows]);
129
-
130
- useEffect(() => {
131
- if (!props.filtered || options == null) return;
132
- setFilteredValues(
133
- options
134
- .map((option) => {
135
- if (option.label != null && typeof option.label !== "string") {
136
- console.error("Cannot filter non-string labels", option);
137
- return undefined;
138
- }
139
- const str = (option.label as string) || "";
140
- return str.toLowerCase().indexOf(filter.trim()) === -1 ? option.value : undefined;
141
- })
142
- .filter((r) => r !== undefined),
143
- );
144
- }, [props.filtered, filter, options]);
110
+ }, [options, props, selectedRows]);
145
111
 
146
- const toggleValue = useCallback(
147
- (item: MultiFinalSelectOption<ValueType>) => {
148
- if (`${item.value}` in selectedValues) {
149
- setSelectedValues(omit(selectedValues, [`${item.value}`]));
150
- } else {
151
- setSelectedValues({ ...selectedValues, [`${item.value}`]: null });
152
- }
153
- },
154
- [selectedValues],
112
+ /**
113
+ * Groups options into their header groups
114
+ */
115
+ const headerGroups = useMemo(
116
+ () =>
117
+ options &&
118
+ groupBy(
119
+ options.filter((o) => textMatch(o.label, filter) && o.value),
120
+ "filter",
121
+ ),
122
+ [filter, options],
155
123
  );
156
124
 
125
+ const headers: GridFormMultiSelectGroup[] = useMemo(() => props.headers ?? [{ header: "" }], [props.headers]);
126
+
157
127
  const { popoverWrapper, triggerSave } = useGridPopoverHook({
158
128
  className: props.className,
159
129
  invalid,
160
130
  save,
161
131
  });
132
+
162
133
  return popoverWrapper(
163
134
  <ComponentLoadingWrapper loading={!options} className={"GridFormMultiSelect-container"}>
164
- <>
165
- {options && props.filtered && (
166
- <>
167
- <FocusableItem className={"filter-item"} key={"filter"}>
168
- {({ ref }: any) => (
169
- <div style={{ display: "flex", width: "100%" }} className={"GridFormMultiSelect-filter"}>
170
- <input
171
- autoFocus
172
- className={"free-text-input"}
173
- style={{ border: "0px" }}
174
- ref={ref}
175
- type="text"
176
- placeholder={props.filterPlaceholder ?? "Placeholder"}
177
- data-testid={"filteredMenu-free-text-input"}
178
- defaultValue={""}
179
- onChange={(e) => setFilter(e.target.value.toLowerCase())}
180
- />
181
- </div>
182
- )}
183
- </FocusableItem>
184
- <MenuDivider key={`$$divider_filter`} />
185
- </>
186
- )}
187
- <div className={"GridFormMultiSelect-options"}>
188
- {options?.map((item, index) =>
189
- item.value === MenuSeparatorString ? (
190
- <MenuDivider key={`$$divider_${index}`} />
191
- ) : filteredValues.includes(item.value) ? null : (
192
- <div key={`${index}`}>
193
- <MenuItem
194
- onClick={(e: ClickEvent) => {
195
- // Global react-menu MenuItem handler handles tabs
196
- if (e.key !== "Tab") {
197
- e.keepOpen = true;
198
- toggleValue(item);
199
- }
200
- }}
201
- >
202
- <LuiCheckboxInput
203
- isChecked={`${item.value}` in selectedValues}
204
- value={`${item.value}`}
205
- label={item.label ?? (item.value == null ? `<${item.value}>` : `${item.value}`)}
206
- inputProps={{
207
- onClick: (e) => {
208
- e.preventDefault();
209
- e.stopPropagation();
210
- return false;
211
- },
212
- }}
213
- onChange={() => {
214
- /*Do nothing, change handled by menuItem*/
215
- }}
216
- />
217
- </MenuItem>
218
-
219
- {`${item.value}` in selectedValues && item.subComponent && (
220
- <FocusableItem className={"LuiDeprecatedForms"} key={`${item.value}_subcomponent`}>
221
- {(_: any) =>
222
- item.subComponent && (
223
- <GridSubComponentContext.Provider
224
- value={{
225
- data,
226
- value: selectedValues[`${item.value}`],
227
- setValue: (value: any) => {
228
- setSelectedValues({
229
- ...selectedValues,
230
- [`${item.value}`]: value,
231
- });
232
- },
233
- setValid: (valid: boolean) => {
234
- subComponentIsValid.current[`${item.value}`] = valid;
235
- },
236
- triggerSave,
237
- }}
238
- >
239
- <item.subComponent />
240
- </GridSubComponentContext.Provider>
241
- )
242
- }
243
- </FocusableItem>
244
- )}
245
- </div>
246
- ),
135
+ {options && (
136
+ <>
137
+ {props.filtered && (
138
+ <FilterInput
139
+ {...{ headerGroups, options, setOptions, filter, setFilter }}
140
+ filterHelpText={props.filterHelpText}
141
+ onSelectFilter={props.onSelectFilter}
142
+ filterPlaceholder={props.filterPlaceholder}
143
+ />
247
144
  )}
248
- </div>
249
- </>
145
+
146
+ {headerGroups && (
147
+ <div className={"GridFormMultiSelect-options"}>
148
+ {headers.map((header) => {
149
+ const subOptions = headerGroups[`${header.filter}`];
150
+ return (
151
+ !isEmpty(subOptions) && (
152
+ <>
153
+ {header.header && <MenuHeader>{header.header}</MenuHeader>}
154
+ {subOptions.map((item, index) =>
155
+ item.value === MenuSeparatorString ? (
156
+ <MenuDivider key={`div_${index}`} />
157
+ ) : (
158
+ <Fragment key={`val_${item.value}`}>
159
+ <MenuRadioItem item={item} options={options} setOptions={setOptions} />
160
+
161
+ {item.checked && item.subComponent && (
162
+ <MenuSubComponent
163
+ {...{ item, options, setOptions, data, triggerSave }}
164
+ subComponentIsValid={subComponentIsValidRef.current}
165
+ />
166
+ )}
167
+ </Fragment>
168
+ ),
169
+ )}
170
+ </>
171
+ )
172
+ );
173
+ })}
174
+ </div>
175
+ )}
176
+ </>
177
+ )}
250
178
  </ComponentLoadingWrapper>,
251
179
  );
252
180
  };
181
+
182
+ const FilterInput = (props: {
183
+ options: MultiSelectOption[];
184
+ setOptions: (options: MultiSelectOption[]) => void;
185
+ onSelectFilter?: (filter: string, options: MultiSelectOption[]) => void;
186
+ filter: string;
187
+ setFilter: Dispatch<SetStateAction<string>>;
188
+ headerGroups: Record<string, MultiSelectOption[]> | undefined;
189
+ filterPlaceholder?: string;
190
+ filterHelpText?: string | ((filter: string, options: MultiSelectOption[]) => string | undefined);
191
+ }) => {
192
+ const { options, setOptions, onSelectFilter, filter, setFilter, headerGroups, filterPlaceholder, filterHelpText } =
193
+ props;
194
+
195
+ const toggleSelectAllVisible = useCallback(() => {
196
+ if (!options || !headerGroups) return;
197
+
198
+ if (isEmpty(filter.trim())) {
199
+ // Toggle off if any items are checked otherwise on
200
+ const anyChecked = options.some((o) => o.checked);
201
+ options.forEach((o) => (o.checked = !anyChecked));
202
+ } else {
203
+ // Toggle on if any filtered items are checked otherwise off
204
+ const anyChecked = Object.values(headerGroups).some((headerOptions) =>
205
+ headerOptions.some((o) => o.checked === false),
206
+ );
207
+ Object.values(headerGroups).forEach((headerOptions) => {
208
+ headerOptions.forEach((o) => {
209
+ if (o.checked !== undefined) o.checked = anyChecked;
210
+ });
211
+ });
212
+ }
213
+ setOptions([...options]);
214
+ }, [filter, headerGroups, options, setOptions]);
215
+
216
+ const addCustomFilterValue = useCallback(() => {
217
+ if (!options || !onSelectFilter) return;
218
+
219
+ const preFilterOptions = JSON.stringify(options);
220
+ onSelectFilter(filter.trim(), options);
221
+ // Detect if options list changed and update
222
+ if (preFilterOptions === JSON.stringify(options)) return;
223
+
224
+ setOptions([...options]);
225
+ setFilter("");
226
+ }, [filter, onSelectFilter, options, setFilter, setOptions]);
227
+
228
+ const handleKeyUp = useCallback(
229
+ (e: KeyboardEvent) => {
230
+ if (e.key === "Enter") {
231
+ e.stopPropagation();
232
+ e.preventDefault();
233
+
234
+ if (e.ctrlKey) toggleSelectAllVisible();
235
+ else if (onSelectFilter) addCustomFilterValue();
236
+ }
237
+ },
238
+ [addCustomFilterValue, onSelectFilter, toggleSelectAllVisible],
239
+ );
240
+
241
+ return (
242
+ <>
243
+ <FocusableItem className={"filter-item"} key={"filter"}>
244
+ {(_: any) => (
245
+ <div style={{ width: "100%" }} className={"GridFormMultiSelect-filter"}>
246
+ <input
247
+ className={"LuiTextInput-input"}
248
+ type="text"
249
+ placeholder={filterPlaceholder ?? "Placeholder"}
250
+ data-testid={"filteredMenu-free-text-input"}
251
+ value={filter}
252
+ data-disableenterautosave={true}
253
+ data-allowtabtoSave={true}
254
+ onChange={(e) => setFilter(e.target.value)}
255
+ onKeyUp={handleKeyUp}
256
+ />
257
+ {filterHelpText && (
258
+ <FormError
259
+ error={null}
260
+ helpText={
261
+ typeof filterHelpText === "function" ? filterHelpText(filter.trim(), options) : filterHelpText
262
+ }
263
+ />
264
+ )}
265
+ </div>
266
+ )}
267
+ </FocusableItem>
268
+ <MenuDivider key={`$$divider_filter`} />
269
+ {headerGroups && !toPairs(headerGroups).some(([_, options]) => !isEmpty(options)) && (
270
+ <div className={"szh-menu__item"}>[No items match the filter]</div>
271
+ )}
272
+ </>
273
+ );
274
+ };
275
+
276
+ const MenuRadioItem = (props: {
277
+ item: MultiSelectOption;
278
+ options: MultiSelectOption[];
279
+ setOptions: (options: MultiSelectOption[]) => void;
280
+ }) => {
281
+ const { item, options, setOptions } = props;
282
+
283
+ const toggleValue = useCallback(
284
+ (item: MultiSelectOption) => {
285
+ item.checked = !item.checked;
286
+ setOptions([...options]);
287
+ },
288
+ [options, setOptions],
289
+ );
290
+
291
+ return (
292
+ <MenuItem
293
+ onClick={(e: ClickEvent) => {
294
+ // Global react-menu MenuItem handler handles tabs
295
+ if (e.key !== "Tab") {
296
+ e.keepOpen = true;
297
+ toggleValue(item);
298
+ }
299
+ }}
300
+ >
301
+ <LuiCheckboxInput
302
+ isChecked={item.checked ?? false}
303
+ value={`${item.value}`}
304
+ label={item.label ?? (item.value == null ? `<${item.value}>` : `${item.value}`)}
305
+ inputProps={{
306
+ onClick: (e) => {
307
+ // Click is handled by MenuItem onClick
308
+ e.preventDefault();
309
+ e.stopPropagation();
310
+ },
311
+ }}
312
+ onChange={() => {
313
+ /*Do nothing, change handled by menuItem*/
314
+ }}
315
+ />
316
+ </MenuItem>
317
+ );
318
+ };
319
+
320
+ const MenuSubComponent = (props: {
321
+ data: any;
322
+ item: MultiSelectOption;
323
+ options: MultiSelectOption[];
324
+ setOptions: (options: MultiSelectOption[]) => void;
325
+ subComponentIsValid: Record<string, boolean>;
326
+ triggerSave: () => Promise<void>;
327
+ }) => {
328
+ const { data, item, options, setOptions, subComponentIsValid, triggerSave } = props;
329
+ return (
330
+ <FocusableItem className={"LuiDeprecatedForms"} key={`${item.value}_subcomponent`}>
331
+ {(_: any) =>
332
+ item.subComponent && (
333
+ <GridSubComponentContext.Provider
334
+ value={{
335
+ context: { options },
336
+ data,
337
+ value: item.subValue,
338
+ setValue: (value: any) => {
339
+ item.subValue = value;
340
+ setOptions([...options]);
341
+ },
342
+ setValid: (valid: boolean) => {
343
+ subComponentIsValid[`${item.value}`] = valid;
344
+ },
345
+ triggerSave,
346
+ }}
347
+ >
348
+ <item.subComponent />
349
+ </GridSubComponentContext.Provider>
350
+ )
351
+ }
352
+ </FocusableItem>
353
+ );
354
+ };
@@ -137,6 +137,7 @@ export const GridFormPopoverMenu = <RowType extends GridBaseRow>(props: GridForm
137
137
  item.subComponent && (
138
138
  <GridSubComponentContext.Provider
139
139
  value={{
140
+ context: {},
140
141
  data,
141
142
  value: subSelectedValue,
142
143
  setValue: (value: any) => {
@@ -19,7 +19,7 @@ export interface GridSubComponentTextAreaProps<RowType extends GridBaseRow>
19
19
  export const GridFormSubComponentTextArea = <RowType extends GridBaseRow>(
20
20
  props: GridSubComponentTextAreaProps<RowType>,
21
21
  ): JSX.Element => {
22
- const { value, data, setValue, setValid } = useContext(GridSubComponentContext);
22
+ const { value, data, setValue, setValid, context } = useContext(GridSubComponentContext);
23
23
 
24
24
  const helpText = props.helpText ?? "Press tab to save";
25
25
 
@@ -28,7 +28,7 @@ export const GridFormSubComponentTextArea = <RowType extends GridBaseRow>(
28
28
  if (value == null) setValue(props.defaultValue);
29
29
  }, [props.defaultValue, setValue, value]);
30
30
 
31
- const invalid = useCallback(() => TextInputValidator(props, value, data), [data, props, value]);
31
+ const invalid = useCallback(() => TextInputValidator(props, value, data, context), [data, props, value, context]);
32
32
 
33
33
  useEffect(() => {
34
34
  setValid(value != null && invalid() == null);
@@ -17,7 +17,7 @@ export interface GridFormSubComponentTextInputProps<RowType extends GridBaseRow>
17
17
  export const GridFormSubComponentTextInput = <RowType extends GridBaseRow>(
18
18
  props: GridFormSubComponentTextInputProps<RowType>,
19
19
  ): JSX.Element => {
20
- const { value, setValue, setValid, data } = useContext(GridSubComponentContext);
20
+ const { value, setValue, setValid, data, context } = useContext(GridSubComponentContext);
21
21
 
22
22
  const helpText = props.helpText ?? "Press enter or tab to save";
23
23
 
@@ -26,7 +26,7 @@ export const GridFormSubComponentTextInput = <RowType extends GridBaseRow>(
26
26
  if (value == null) setValue(props.defaultValue);
27
27
  }, [props.defaultValue, setValue, value]);
28
28
 
29
- const invalid = useCallback(() => TextInputValidator(props, value, data), [data, props, value]);
29
+ const invalid = useCallback(() => TextInputValidator(props, value, data, context), [context, data, props, value]);
30
30
 
31
31
  useEffect(() => {
32
32
  setValid(value != null && invalid() == null);
@@ -40,9 +40,7 @@ export const GridFormSubComponentTextInput = <RowType extends GridBaseRow>(
40
40
  helpText={helpText}
41
41
  autoFocus={true}
42
42
  placeholder={props.placeholder}
43
- style={{
44
- width: "100%",
45
- }}
43
+ style={{ width: "100%" }}
46
44
  />
47
45
  );
48
46
  };