@linzjs/step-ag-grid 8.4.2 → 9.0.0

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 (51) hide show
  1. package/README.md +4 -4
  2. package/dist/index.css +3 -0
  3. package/dist/src/components/gridForm/GridFormMultiSelect.d.ts +5 -4
  4. package/dist/src/lui/TextAreaInput.d.ts +1 -0
  5. package/dist/src/lui/TextInputFormatted.d.ts +1 -0
  6. package/dist/src/react-menu3/components/FocusableItem.d.ts +1 -1
  7. package/dist/src/react-menu3/components/MenuItem.d.ts +3 -1
  8. package/dist/src/react-menu3/components/SubMenu.d.ts +1 -1
  9. package/dist/src/react-menu3/utils/withHovering.d.ts +2 -2
  10. package/dist/src/utils/textMatcher.d.ts +1 -1
  11. package/dist/src/utils/textMatcher.test.d.ts +1 -0
  12. package/dist/step-ag-grid.esm.js +502 -735
  13. package/dist/step-ag-grid.esm.js.map +1 -1
  14. package/package.json +42 -42
  15. package/src/components/Grid.tsx +29 -27
  16. package/src/components/gridForm/GridFormDropDown.tsx +12 -14
  17. package/src/components/gridForm/GridFormMultiSelect.tsx +31 -29
  18. package/src/components/gridForm/GridFormPopoverMenu.tsx +18 -22
  19. package/src/components/gridForm/GridFormSubComponentTextArea.tsx +1 -1
  20. package/src/components/gridForm/GridFormSubComponentTextInput.tsx +1 -1
  21. package/src/components/gridForm/GridFormTextArea.tsx +1 -1
  22. package/src/components/gridForm/GridFormTextInput.tsx +1 -1
  23. package/src/lui/TextAreaInput.tsx +3 -1
  24. package/src/lui/TextInputFormatted.tsx +3 -1
  25. package/src/react-menu3/components/ControlledMenu.tsx +19 -6
  26. package/src/react-menu3/components/MenuItem.tsx +1 -1
  27. package/src/react-menu3/utils/withHovering.tsx +3 -3
  28. package/src/stories/grid/GridKeyboardInteractions.stories.tsx +261 -0
  29. package/src/stories/grid/{GridPopoutBearing.stories.tsx → GridPopoverEditBearing.stories.tsx} +4 -4
  30. package/src/stories/grid/{GridPopoutEditDropDown.stories.tsx → GridPopoverEditDropDown.stories.tsx} +10 -2
  31. package/src/stories/grid/{GridPopoutEditMultiSelect.stories.tsx → GridPopoverEditMultiSelect.stories.tsx} +0 -0
  32. package/src/stories/grid/gridFormInteraction/GridFormDropDownInteraction.stories.tsx +137 -0
  33. package/src/stories/grid/gridFormInteraction/GridFormEditBearingCorrectionInteraction.stories.tsx +92 -0
  34. package/src/stories/grid/gridFormInteraction/GridFormEditBearingInteraction.stories.tsx +91 -0
  35. package/src/stories/grid/gridFormInteraction/GridFormMultiSelectInteraction.stories.tsx +153 -0
  36. package/src/stories/grid/gridFormInteraction/GridFormPopoverMenuInteraction.stories.tsx +171 -0
  37. package/src/stories/grid/gridFormInteraction/GridFormTextAreaInteraction.stories.tsx +78 -0
  38. package/src/stories/grid/gridFormInteraction/GridFormTextInputInteraction.stories.tsx +85 -0
  39. package/src/stories/grid/{gridForm → gridFormStatic}/GridFormDropDown.stories.tsx +1 -1
  40. package/src/stories/grid/{gridForm → gridFormStatic}/GridFormEditBearing.stories.tsx +1 -1
  41. package/src/stories/grid/{gridForm → gridFormStatic}/GridFormEditBearingCorrection.stories.tsx +1 -1
  42. package/src/stories/grid/{gridForm → gridFormStatic}/GridFormMessage.stories.tsx +1 -1
  43. package/src/stories/grid/{gridForm → gridFormStatic}/GridFormMultiSelect.stories.tsx +1 -1
  44. package/src/stories/grid/{gridForm → gridFormStatic}/GridFormPopoverMenu.stories.tsx +1 -1
  45. package/src/stories/grid/{gridForm → gridFormStatic}/GridFormTextArea.stories.tsx +1 -1
  46. package/src/stories/grid/{gridForm → gridFormStatic}/GridFormTextInput.stories.tsx +1 -1
  47. package/src/stories/react-menu/ReactMenu.stories.tsx +66 -3
  48. package/src/styles/Grid.scss +3 -0
  49. package/src/utils/testUtil.ts +1 -0
  50. package/src/utils/textMatcher.test.ts +38 -0
  51. package/src/utils/textMatcher.ts +9 -2
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": "8.4.2",
5
+ "version": "9.0.0",
6
6
  "keywords": [
7
7
  "aggrid",
8
8
  "ag-grid",
@@ -81,76 +81,76 @@
81
81
  ]
82
82
  },
83
83
  "devDependencies": {
84
- "@rollup/plugin-commonjs": "^23.0.2",
85
- "@rollup/plugin-json": "^5.0.1",
84
+ "@rollup/plugin-commonjs": "^24.0.1",
85
+ "@rollup/plugin-json": "^6.0.0",
86
86
  "@rollup/plugin-node-resolve": "^15.0.1",
87
- "@semantic-release/changelog": "^6.0.1",
87
+ "@semantic-release/changelog": "^6.0.2",
88
88
  "@semantic-release/git": "^10.0.1",
89
- "@storybook/addon-actions": "^6.5.13",
90
- "@storybook/addon-essentials": "^6.5.13",
91
- "@storybook/addon-interactions": "^6.5.13",
92
- "@storybook/addon-links": "^6.5.13",
89
+ "@storybook/addon-interactions": "^6.5.16",
90
+ "@storybook/addon-essentials": "^6.5.16",
91
+ "@storybook/builder-webpack5": "^6.5.16",
93
92
  "@storybook/jest": "^0.0.10",
94
- "@storybook/react": "6.5.13",
95
- "@storybook/test-runner": "^0.9.0",
93
+ "@storybook/manager-webpack5": "^6.5.16",
94
+ "@storybook/preset-create-react-app": "^4.1.2",
95
+ "@storybook/react": "^6.5.16",
96
+ "@storybook/test-runner": "^0.9.4",
96
97
  "@storybook/testing-library": "^0.0.13",
97
- "@testing-library/dom": "^8.19.0",
98
+ "@testing-library/dom": "^8.20.0",
98
99
  "@testing-library/jest-dom": "^5.16.5",
99
100
  "@testing-library/react": "^12.1.5",
100
101
  "@testing-library/user-event": "^13.5.0",
101
- "@types/debounce-promise": "^3.1.5",
102
- "@types/jest": "^29.2.2",
102
+ "@types/debounce-promise": "^3.1.6",
103
+ "@types/jest": "^29.4.0",
103
104
  "@types/lodash-es": "^4.17.6",
104
- "@types/node": "^18.11.9",
105
- "@types/react": "^17.0.52",
105
+ "@types/node": "^18.13.0",
106
+ "@types/react": "^17.0.53",
106
107
  "@types/react-dom": "^17.0.18",
107
- "@types/uuid": "^8.3.4",
108
- "@typescript-eslint/parser": "^5.42.0",
109
- "babel-jest": "^29.2.2",
108
+ "@types/uuid": "^9.0.0",
109
+ "@typescript-eslint/parser": "^5.51.0",
110
+ "babel-jest": "^29.4.2",
110
111
  "babel-preset-react-app": "^10.0.1",
111
- "chromatic": "^6.11.4",
112
+ "chromatic": "^6.17.0",
112
113
  "conventional-changelog-conventionalcommits": "^5.0.0",
113
- "eslint": "^8.27.0",
114
- "eslint-config-prettier": "^8.5.0",
114
+ "eslint": "^8.33.0",
115
+ "eslint-config-prettier": "^8.6.0",
115
116
  "eslint-config-react-app": "^7.0.1",
116
- "eslint-plugin-deprecation": "^1.3.2",
117
- "eslint-plugin-import": "^2.26.0",
118
- "eslint-plugin-jest": "^27.1.4",
119
- "eslint-plugin-jsx-a11y": "^6.6.1",
117
+ "eslint-plugin-deprecation": "^1.3.3",
118
+ "eslint-plugin-import": "^2.27.5",
119
+ "eslint-plugin-jest": "^27.2.1",
120
+ "eslint-plugin-jsx-a11y": "^6.7.1",
120
121
  "eslint-plugin-prettier": "^4.2.1",
121
- "eslint-plugin-react": "^7.31.10",
122
+ "eslint-plugin-react": "^7.32.2",
122
123
  "eslint-plugin-react-hooks": "^4.6.0",
123
- "eslint-plugin-testing-library": "^5.9.1",
124
- "jest": "^29.3.1",
124
+ "eslint-plugin-testing-library": "^5.10.1",
125
+ "jest": "^29.4.2",
125
126
  "jest-canvas-mock": "^2.4.0",
126
- "jest-environment-jsdom": "^29.3.1",
127
+ "jest-environment-jsdom": "^29.4.2",
127
128
  "jest-expect-message": "^1.1.3",
128
- "mkdirp": "^1.0.4",
129
+ "mkdirp": "^2.1.3",
129
130
  "npm-run-all": "^4.1.5",
130
- "postcss": "^8.4.18",
131
- "postcss-loader": "^7.0.1",
132
- "postcss-scss": "^4.0.5",
133
- "prettier": "^2.7.1",
131
+ "postcss": "^8.4.21",
132
+ "postcss-loader": "^7.0.2",
133
+ "postcss-scss": "^4.0.6",
134
+ "prettier": "^2.8.4",
134
135
  "react-app-polyfill": "^3.0.0",
135
136
  "react-scripts": "^5.0.1",
136
- "rollup": "^3.2.5",
137
+ "rollup": "^3.14.0",
137
138
  "rollup-plugin-copy": "^3.4.0",
138
139
  "rollup-plugin-peer-deps-external": "^2.2.4",
139
140
  "rollup-plugin-postcss": "^4.0.2",
140
141
  "rollup-plugin-typescript2": "^0.34.1",
141
- "sass": "^1.56.0",
142
- "sass-loader": "10.3.1",
142
+ "sass": "^1.58.0",
143
+ "sass-loader": "^13.2.0",
143
144
  "semantic-release": "^19.0.5",
144
- "storybook-addon-mock": "^2.4.1",
145
- "style-loader": "2.0.0",
146
- "stylelint": "^14.14.1",
147
- "stylelint-config-prettier": "^9.0.3",
145
+ "style-loader": "^3.3.1",
146
+ "stylelint": "^14.16.1",
147
+ "stylelint-config-prettier": "^9.0.4",
148
148
  "stylelint-config-recommended-scss": "^8.0.0",
149
149
  "stylelint-config-standard": "^29.0.0",
150
150
  "stylelint-prettier": "^2.0.0",
151
151
  "stylelint-scss": "^4.3.0",
152
152
  "tsconfig-paths-webpack-plugin": "^4.0.0",
153
- "typescript": "^4.8.4"
153
+ "typescript": "^4.9.5"
154
154
  },
155
155
  "babel": {
156
156
  "presets": [
@@ -340,33 +340,35 @@ export const Grid = (params: GridProps): JSX.Element => {
340
340
  />
341
341
  </div>
342
342
  )}
343
- <AgGridReact
344
- animateRows={params.animateRows}
345
- rowClassRules={params.rowClassRules}
346
- defaultColDef={params.defaultColDef}
347
- getRowId={(params) => `${params.data.id}`}
348
- suppressRowClickSelection={true}
349
- rowSelection={params.rowSelection ?? "multiple"}
350
- suppressBrowserResizeObserver={true}
351
- colResizeDefault={"shift"}
352
- onFirstDataRendered={sizeColumnsToFit}
353
- onGridSizeChanged={sizeColumnsToFit}
354
- suppressClickEdit={true}
355
- onCellKeyPress={onCellKeyPress}
356
- onCellClicked={onCellClicked}
357
- onCellDoubleClicked={onCellDoubleClick}
358
- onCellEditingStarted={refreshSelectedRows}
359
- domLayout={params.domLayout}
360
- columnDefs={columnDefs}
361
- rowData={params.rowData}
362
- noRowsOverlayComponent={noRowsOverlayComponent}
363
- onGridReady={onGridReady}
364
- onSortChanged={ensureSelectedRowIsVisible}
365
- postSortRows={params.postSortRows ?? postSortRows}
366
- onSelectionChanged={synchroniseExternalStateToGridSelection}
367
- onColumnMoved={params.onColumnMoved}
368
- alwaysShowVerticalScroll={params.alwaysShowVerticalScroll}
369
- />
343
+ <div style={{ flex: 1 }}>
344
+ <AgGridReact
345
+ animateRows={params.animateRows}
346
+ rowClassRules={params.rowClassRules}
347
+ defaultColDef={params.defaultColDef}
348
+ getRowId={(params) => `${params.data.id}`}
349
+ suppressRowClickSelection={true}
350
+ rowSelection={params.rowSelection ?? "multiple"}
351
+ suppressBrowserResizeObserver={true}
352
+ colResizeDefault={"shift"}
353
+ onFirstDataRendered={sizeColumnsToFit}
354
+ onGridSizeChanged={sizeColumnsToFit}
355
+ suppressClickEdit={true}
356
+ onCellKeyPress={onCellKeyPress}
357
+ onCellClicked={onCellClicked}
358
+ onCellDoubleClicked={onCellDoubleClick}
359
+ onCellEditingStarted={refreshSelectedRows}
360
+ domLayout={params.domLayout}
361
+ columnDefs={columnDefs}
362
+ rowData={params.rowData}
363
+ noRowsOverlayComponent={noRowsOverlayComponent}
364
+ onGridReady={onGridReady}
365
+ onSortChanged={ensureSelectedRowIsVisible}
366
+ postSortRows={params.postSortRows ?? postSortRows}
367
+ onSelectionChanged={synchroniseExternalStateToGridSelection}
368
+ onColumnMoved={params.onColumnMoved}
369
+ alwaysShowVerticalScroll={params.alwaysShowVerticalScroll}
370
+ />
371
+ </div>
370
372
  </div>
371
373
  );
372
374
  };
@@ -8,9 +8,10 @@ import { CellEditorCommon } from "../GridCell";
8
8
  import { useGridPopoverHook } from "../GridPopoverHook";
9
9
  import { useGridPopoverContext } from "../../contexts/GridPopoverContext";
10
10
  import { GridSubComponentContext } from "../../contexts/GridSubComponentContext";
11
- import { ClickEvent, MenuInstance } from "../../react-menu3/types";
11
+ import { ClickEvent } from "../../react-menu3/types";
12
12
  import { FormError } from "../../lui/FormError";
13
13
  import { isNotEmpty } from "../../utils/util";
14
+ import { textMatch } from "../../utils/textMatcher";
14
15
 
15
16
  export interface GridPopoutEditDropDownSelectedItem<RowType> {
16
17
  // Note the row that was clicked on will be first
@@ -136,8 +137,7 @@ export const GridFormDropDown = <RowType extends GridBaseRow>(props: GridFormDro
136
137
  console.error("Cannot filter non-string labels", option);
137
138
  return undefined;
138
139
  }
139
- const str = (option.label as string) || "";
140
- return str.toLowerCase().indexOf(filter.toLowerCase()) !== -1 ? option : undefined;
140
+ return textMatch((option.label as string) || "", filter) ? option : undefined;
141
141
  })
142
142
  .filter((r) => r !== undefined),
143
143
  );
@@ -270,19 +270,17 @@ export const GridFormDropDown = <RowType extends GridBaseRow>(props: GridFormDro
270
270
  title={item.disabled && typeof item.disabled !== "boolean" ? item.disabled : ""}
271
271
  value={item.value}
272
272
  onFocus={() => {
273
- setSelectedItem(item);
274
- if (item.subComponent) {
275
- subComponentIsValid.current = true;
276
- subComponentInitialValue.current = null;
277
- } else {
273
+ if (selectedItem !== item) {
274
+ setSelectedItem(item);
278
275
  setSubSelectedValue(null);
279
276
  subComponentIsValid.current = true;
277
+ if (item.subComponent) {
278
+ subComponentInitialValue.current = null;
279
+ }
280
280
  }
281
281
  }}
282
282
  onClick={(e: ClickEvent) => {
283
- if (item.subComponent) {
284
- e.keepOpen = true;
285
- }
283
+ e.keepOpen = !!item.subComponent;
286
284
  }}
287
285
  >
288
286
  {item.label ?? (item.value == null ? `<${item.value}>` : `${item.value}`)}
@@ -291,7 +289,7 @@ export const GridFormDropDown = <RowType extends GridBaseRow>(props: GridFormDro
291
289
 
292
290
  {item.subComponent && selectedItem === item && (
293
291
  <FocusableItem className={"LuiDeprecatedForms"} key={`${item.label}_subcomponent`}>
294
- {(ref: MenuInstance) => (
292
+ {() => (
295
293
  <GridSubComponentContext.Provider
296
294
  value={{
297
295
  context: { options },
@@ -308,13 +306,13 @@ export const GridFormDropDown = <RowType extends GridBaseRow>(props: GridFormDro
308
306
  subComponentIsValid.current = valid;
309
307
  },
310
308
  triggerSave: async () => {
311
- ref.closeMenu();
309
+ // empty
312
310
  },
313
311
  }}
314
312
  >
315
313
  {item.subComponent && (
316
314
  <div className={"subComponent"}>
317
- <item.subComponent key={`${fieldToString(field)}-${index}_subcomponent_inner`} />
315
+ <item.subComponent />
318
316
  </div>
319
317
  )}
320
318
  </GridSubComponentContext.Provider>
@@ -41,6 +41,11 @@ export interface GridFormMultiSelectGroup {
41
41
  filter?: string;
42
42
  }
43
43
 
44
+ export interface GridFormMultiSelectSaveProps<RowType extends GridBaseRow> {
45
+ selectedRows: RowType[];
46
+ selectedOptions: MultiSelectOption[];
47
+ }
48
+
44
49
  export interface GridFormMultiSelectProps<RowType extends GridBaseRow> extends CellEditorCommon {
45
50
  className?:
46
51
  | "GridMultiSelect-containerSmall"
@@ -54,7 +59,7 @@ export interface GridFormMultiSelectProps<RowType extends GridBaseRow> extends C
54
59
  filterHelpText?: string | ((filter: string, options: MultiSelectOption[]) => string | undefined);
55
60
  noOptionsMessage?: string;
56
61
  onSelectFilter?: (props: { filter: string; options: MultiSelectOption[] }) => void;
57
- onSave?: (props: { selectedRows: RowType[]; selectedOptions: MultiSelectOption[] }) => Promise<boolean>;
62
+ onSave?: (props: GridFormMultiSelectSaveProps<RowType>) => Promise<boolean>;
58
63
  headers?: GridFormMultiSelectGroup[];
59
64
  options: MultiSelectOption[] | ((selectedRows: RowType[]) => Promise<MultiSelectOption[]> | MultiSelectOption[]);
60
65
  invalid?: (selectedRows: RowType[], selectedOptions: MultiSelectOption[]) => boolean;
@@ -164,11 +169,12 @@ export const GridFormMultiSelect = <RowType extends GridBaseRow>(props: GridForm
164
169
  filterPlaceholder={props.filterPlaceholder}
165
170
  />
166
171
  )}
167
- {headerGroups && (isEmpty(headerGroups) || !toPairs(headerGroups).some(([_, options]) => !isEmpty(options))) && (
168
- <MenuItem key={"noOptions"} className={"GridMultiSelect-noOptions"} disabled={true}>
169
- {props.noOptionsMessage ?? "No Options"}
170
- </MenuItem>
171
- )}
172
+ {headerGroups &&
173
+ (isEmpty(headerGroups) || !toPairs(headerGroups).some(([_, options]) => !isEmpty(options))) && (
174
+ <MenuItem key={"noOptions"} className={"GridMultiSelect-noOptions"} disabled={true}>
175
+ {props.noOptionsMessage ?? "No Options"}
176
+ </MenuItem>
177
+ )}
172
178
  {headerGroups && !isEmpty(headerGroups) && (
173
179
  <div className={"GridFormMultiSelect-options"}>
174
180
  {headers.map((header, index) => {
@@ -397,29 +403,25 @@ const MenuSubComponent = (props: {
397
403
  const { data, item, options, setOptions, subComponentIsValid, triggerSave } = props;
398
404
  return (
399
405
  <FocusableItem className={"LuiDeprecatedForms"} key={`${item.value}_subcomponent`}>
400
- {(_: any) =>
401
- item.subComponent && (
402
- <GridSubComponentContext.Provider
403
- value={{
404
- context: { options },
405
- data,
406
- value: item.subValue,
407
- setValue: (value: any) => {
408
- item.subValue = value;
409
- setOptions([...options]);
410
- },
411
- setValid: (valid: boolean) => {
412
- subComponentIsValid[`${item.value}`] = valid;
413
- },
414
- triggerSave,
415
- }}
416
- >
417
- <div className={"subComponent"}>
418
- <item.subComponent />
419
- </div>
420
- </GridSubComponentContext.Provider>
421
- )
422
- }
406
+ {() => (
407
+ <GridSubComponentContext.Provider
408
+ value={{
409
+ context: { options },
410
+ data,
411
+ value: item.subValue,
412
+ setValue: (value: any) => {
413
+ item.subValue = value;
414
+ setOptions([...options]);
415
+ },
416
+ setValid: (valid: boolean) => {
417
+ subComponentIsValid[`${item.value}`] = valid;
418
+ },
419
+ triggerSave,
420
+ }}
421
+ >
422
+ <div className={"subComponent"}>{item.subComponent && <item.subComponent />}</div>
423
+ </GridSubComponentContext.Provider>
424
+ )}
423
425
  </FocusableItem>
424
426
  );
425
427
  };
@@ -143,28 +143,24 @@ export const GridFormPopoverMenu = <RowType extends GridBaseRow>(props: GridForm
143
143
  </MenuItem>
144
144
  {item.subComponent && subComponentSelected === item && (
145
145
  <FocusableItem className={"LuiDeprecatedForms"} key={`${item.label}_subcomponent`}>
146
- {(_: any) =>
147
- item.subComponent && (
148
- <GridSubComponentContext.Provider
149
- value={{
150
- context: {},
151
- data,
152
- value: subSelectedValue,
153
- setValue: (value: any) => {
154
- setSubSelectedValue(value);
155
- },
156
- setValid: (valid: boolean) => {
157
- subComponentIsValid.current = valid;
158
- },
159
- triggerSave,
160
- }}
161
- >
162
- <div className={"subComponent"}>
163
- <item.subComponent />
164
- </div>
165
- </GridSubComponentContext.Provider>
166
- )
167
- }
146
+ {() => (
147
+ <GridSubComponentContext.Provider
148
+ value={{
149
+ context: {},
150
+ data,
151
+ value: subSelectedValue,
152
+ setValue: (value: any) => {
153
+ setSubSelectedValue(value);
154
+ },
155
+ setValid: (valid: boolean) => {
156
+ subComponentIsValid.current = valid;
157
+ },
158
+ triggerSave,
159
+ }}
160
+ >
161
+ <div className={"subComponent"}>{item.subComponent && <item.subComponent />}</div>
162
+ </GridSubComponentContext.Provider>
163
+ )}
168
164
  </FocusableItem>
169
165
  )}
170
166
  </Fragment>
@@ -42,8 +42,8 @@ export const GridFormSubComponentTextArea = <RowType extends GridBaseRow>(
42
42
  onChange={(e) => setValue(e.target.value)}
43
43
  error={invalid()}
44
44
  helpText={helpText}
45
- autoFocus={true}
46
45
  placeholder={props.placeholder}
46
+ allowTabToSave={true}
47
47
  />
48
48
  </div>
49
49
  );
@@ -38,9 +38,9 @@ export const GridFormSubComponentTextInput = <RowType extends GridBaseRow>(
38
38
  error={invalid()}
39
39
  onChange={(e) => setValue(e.target.value)}
40
40
  helpText={helpText}
41
- autoFocus={true}
42
41
  placeholder={props.placeholder}
43
42
  style={{ width: "100%" }}
43
+ allowTabToSave={true}
44
44
  />
45
45
  );
46
46
  };
@@ -55,7 +55,7 @@ export const GridFormTextArea = <RowType extends GridBaseRow>(props: GridFormTex
55
55
  value={value}
56
56
  onChange={(e) => setValue(e.target.value)}
57
57
  error={invalid()}
58
- placeholder={props.placeholder}
58
+ placeholder={props.placeholder ?? "Type here"}
59
59
  helpText={helpText}
60
60
  />
61
61
  </div>,
@@ -57,7 +57,7 @@ export const GridFormTextInput = <RowType extends GridBaseRow>(props: GridFormTe
57
57
  error={invalid()}
58
58
  formatted={props.units}
59
59
  style={{ width: props.width ?? 240 }}
60
- placeholder={props.placeholder}
60
+ placeholder={props.placeholder ?? "Type here"}
61
61
  helpText={helpText}
62
62
  />
63
63
  </div>,
@@ -18,6 +18,7 @@ export interface LuiTextAreaInputProps extends InputHTMLAttributes<HTMLTextAreaE
18
18
  mandatory?: boolean;
19
19
  helpText?: string;
20
20
  error?: JSX.Element | string | boolean | null;
21
+ allowTabToSave?: boolean;
21
22
  }
22
23
 
23
24
  export const TextAreaInput = (props: LuiTextAreaInputProps) => {
@@ -39,7 +40,7 @@ export const TextAreaInput = (props: LuiTextAreaInputProps) => {
39
40
  {/* wrapper div used for error styling */}
40
41
  <textarea
41
42
  rows={5}
42
- {...omit(props, ["error", "value", "helpText", "formatted", "className"])}
43
+ {...omit(props, ["error", "value", "helpText", "formatted", "className", "allowTabToSave"])}
43
44
  id={id}
44
45
  value={props.value ?? ""}
45
46
  spellCheck={true}
@@ -50,6 +51,7 @@ export const TextAreaInput = (props: LuiTextAreaInputProps) => {
50
51
  }
51
52
  props.onMouseEnter && props.onMouseEnter(e);
52
53
  }}
54
+ data-allowtabtosave={props.allowTabToSave}
53
55
  >
54
56
  {props.value}
55
57
  </textarea>
@@ -14,6 +14,7 @@ export interface LuiTextInputProps extends DetailedHTMLProps<InputHTMLAttributes
14
14
  helpText?: string;
15
15
  error?: JSX.Element | string | boolean | null;
16
16
  formatted?: string;
17
+ allowTabToSave?: boolean;
17
18
  }
18
19
 
19
20
  export const TextInputFormatted = (props: LuiTextInputProps): JSX.Element => {
@@ -25,12 +26,13 @@ export const TextInputFormatted = (props: LuiTextInputProps): JSX.Element => {
25
26
  type={"text"}
26
27
  spellCheck={true}
27
28
  defaultValue={props.value}
28
- {...omit(props, ["error", "value", "helpText", "formatted", "className"])}
29
+ {...omit(props, ["error", "value", "helpText", "formatted", "className", "allowTabToSave"])}
29
30
  className={"LuiTextInput-input"}
30
31
  onMouseEnter={(e) => {
31
32
  e.currentTarget.focus();
32
33
  props.onMouseEnter && props.onMouseEnter(e);
33
34
  }}
35
+ data-allowtabtosave={props.allowTabToSave}
34
36
  />
35
37
  <span className={"LuiTextInput-formatted"}>{props.formatted}</span>
36
38
  </span>
@@ -127,6 +127,25 @@ export const ControlledMenuFr = (
127
127
  return;
128
128
  }
129
129
 
130
+ const invokeSave = (reason: string) => {
131
+ if (!saveButtonRef?.current) return;
132
+ saveButtonRef.current?.setAttribute("data-reason", reason);
133
+ saveButtonRef?.current?.click();
134
+ };
135
+
136
+ const allowTabToSave = activeElement.getAttribute("data-allowtabtosave") == "true";
137
+ if (allowTabToSave) {
138
+ if (isDown) {
139
+ ev.preventDefault();
140
+ ev.stopPropagation();
141
+ lastTabDownEl.current = activeElement;
142
+ } else {
143
+ lastTabDownEl.current == activeElement &&
144
+ invokeSave(ev.shiftKey ? CloseReason.TAB_BACKWARD : CloseReason.TAB_FORWARD);
145
+ }
146
+ return;
147
+ }
148
+
130
149
  const inputElsIterator = thisDocument.querySelectorAll<HTMLElement>(".szh-menu--state-open input,textarea");
131
150
  let inputEls: HTMLElement[] = [];
132
151
  inputElsIterator.forEach((el) => inputEls.push(el));
@@ -138,12 +157,6 @@ export const ControlledMenuFr = (
138
157
 
139
158
  const isTextArea = activeElement.nodeName === "TEXTAREA";
140
159
  const suppressEnterAutoSave = activeElement.getAttribute("data-disableenterautosave") == "true" || isTextArea;
141
- const allowTabToSave = activeElement.getAttribute("data-allowtabtosave") == "true";
142
- const invokeSave = (reason: string) => {
143
- if (!saveButtonRef?.current) return;
144
- saveButtonRef.current?.setAttribute("data-reason", reason);
145
- saveButtonRef?.current?.click();
146
- };
147
160
 
148
161
  switch (activeElement.nodeName) {
149
162
  case "TEXTAREA":
@@ -128,7 +128,7 @@ const MenuItemFr = ({
128
128
  };
129
129
 
130
130
  /**
131
- * Keyboard events are triggered on up, otherwise sub-components get spaces and enters typed in them
131
+ * Keyboard events are triggered on up, otherwise subcomponents get spaces and enters typed in them
132
132
  */
133
133
  const handleKeyUp = (e: KeyboardEvent) => {
134
134
  if (!isHovering) return;
@@ -1,4 +1,4 @@
1
- import { memo, forwardRef, useContext, useRef, MutableRefObject } from "react";
1
+ import { memo, forwardRef, useContext, useRef, MutableRefObject, PropsWithRef } from "react";
2
2
  import { HoverItemContext } from "../contexts/HoverItemContext";
3
3
 
4
4
  export interface withHoveringResultProps {
@@ -7,9 +7,9 @@ export interface withHoveringResultProps {
7
7
  menuItemRef?: MutableRefObject<any>;
8
8
  }
9
9
 
10
- export const withHovering = (name: string, WrappedComponent: (...args: any) => JSX.Element): any => {
10
+ export const withHovering = <T,>(name: string, WrappedComponent: (props: T) => JSX.Element) => {
11
11
  const Component = memo(WrappedComponent);
12
- const WithHovering = forwardRef<any>((props: any, ref) => {
12
+ const WithHovering = forwardRef((props: PropsWithRef<T>, ref) => {
13
13
  const menuItemRef = useRef<any>(null);
14
14
  return (
15
15
  <Component