@linzjs/step-ag-grid 3.0.1 → 4.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 (37) hide show
  1. package/dist/index.js +390 -320
  2. package/dist/index.js.map +1 -1
  3. package/dist/src/components/GridPopoverHook.d.ts +13 -1
  4. package/dist/src/components/gridForm/GridFormSubComponentTextArea.d.ts +11 -0
  5. package/dist/src/components/gridForm/GridFormSubComponentTextInput.d.ts +8 -8
  6. package/dist/src/components/gridForm/GridFormTextArea.d.ts +3 -4
  7. package/dist/src/components/gridForm/GridFormTextInput.d.ts +3 -4
  8. package/dist/src/index.d.ts +6 -3
  9. package/dist/src/lui/TextAreaInput.d.ts +4 -5
  10. package/dist/src/lui/TextInputFormatted.d.ts +4 -10
  11. package/dist/src/utils/textValidator.d.ts +6 -0
  12. package/dist/step-ag-grid.esm.js +387 -321
  13. package/dist/step-ag-grid.esm.js.map +1 -1
  14. package/package.json +1 -1
  15. package/src/components/Grid.tsx +2 -2
  16. package/src/components/GridCell.tsx +5 -0
  17. package/src/components/GridPopoverHook.tsx +79 -1
  18. package/src/components/gridForm/GridFormDropDown.tsx +63 -56
  19. package/src/components/gridForm/GridFormEditBearing.tsx +7 -8
  20. package/src/components/gridForm/GridFormMultiSelect.tsx +6 -10
  21. package/src/components/gridForm/GridFormPopoverMenu.tsx +7 -10
  22. package/src/components/gridForm/GridFormSubComponentTextArea.tsx +58 -0
  23. package/src/components/gridForm/GridFormSubComponentTextInput.tsx +48 -55
  24. package/src/components/gridForm/GridFormTextArea.tsx +10 -18
  25. package/src/components/gridForm/GridFormTextInput.tsx +12 -25
  26. package/src/contexts/GridPopoverContextProvider.tsx +11 -4
  27. package/src/index.ts +6 -3
  28. package/src/lui/TextAreaInput.tsx +34 -18
  29. package/src/lui/TextInputFormatted.tsx +19 -35
  30. package/src/react-menu3/components/MenuItem.tsx +5 -2
  31. package/src/stories/grid/FormTest.tsx +16 -3
  32. package/src/stories/grid/GridPopoutEditDropDown.stories.tsx +20 -17
  33. package/src/stories/grid/GridPopoutEditMultiSelect.stories.tsx +2 -2
  34. package/src/stories/grid/GridReadOnly.stories.tsx +15 -3
  35. package/src/utils/textValidator.ts +24 -0
  36. package/dist/src/components/GridSubComponentTextArea.d.ts +0 -10
  37. package/src/components/GridSubComponentTextArea.tsx +0 -62
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": "3.0.1",
5
+ "version": "4.0.0",
6
6
  "keywords": [
7
7
  "aggrid",
8
8
  "ag-grid",
@@ -202,7 +202,7 @@ export const Grid = (params: GridProps): JSX.Element => {
202
202
  [startCellEditing],
203
203
  );
204
204
 
205
- const onCellKeyDown = useCallback(
205
+ const onCellKeyPress = useCallback(
206
206
  (e: CellEvent) => {
207
207
  if ((e.event as KeyboardEvent).key === "Enter") startCellEditing(e);
208
208
  },
@@ -254,7 +254,7 @@ export const Grid = (params: GridProps): JSX.Element => {
254
254
  onFirstDataRendered={sizeColumnsToFit}
255
255
  onGridSizeChanged={sizeColumnsToFit}
256
256
  suppressClickEdit={true}
257
- onCellKeyDown={onCellKeyDown}
257
+ onCellKeyPress={onCellKeyPress}
258
258
  onCellClicked={onCellClicked}
259
259
  onCellDoubleClicked={onCellDoubleClick}
260
260
  onCellEditingStarted={refreshSelectedRows}
@@ -68,6 +68,11 @@ export const GridCell = <RowType extends GridBaseRow, Props extends CellEditorCo
68
68
  editable: props.editable ?? true,
69
69
  cellEditor: GenericCellEditorComponentWrapper(custom),
70
70
  }),
71
+ suppressKeyboardEvent: (e) => {
72
+ // It's important that aggrid doesn't trigger edit on enter
73
+ // as the incorrect selected rows will be returned
74
+ return e.event.key === "Enter";
75
+ },
71
76
  ...(custom?.editorParams && {
72
77
  cellEditorParams: { ...custom.editorParams, multiEdit: custom.multiEdit },
73
78
  }),
@@ -1,4 +1,4 @@
1
- import { useCallback, useContext, useEffect, useRef, useState } from "react";
1
+ import { KeyboardEvent, KeyboardEventHandler, useCallback, useContext, useEffect, useRef, useState } from "react";
2
2
  import { GridContext } from "../contexts/GridContext";
3
3
  import { GridBaseRow } from "./Grid";
4
4
  import { ControlledMenu } from "../react-menu3";
@@ -30,6 +30,79 @@ export const useGridPopoverHook = <RowType extends GridBaseRow>(props: GridPopov
30
30
  [props.save, stopEditing, updateValue],
31
31
  );
32
32
 
33
+ const onlyInputKeyboardEventHandlers: {
34
+ onKeyUp?: KeyboardEventHandler<HTMLElement> | undefined;
35
+ onKeyDown?: KeyboardEventHandler<HTMLElement> | undefined;
36
+ } = {
37
+ onKeyUp: (e: KeyboardEvent) => {
38
+ const isTextArea = (e.currentTarget as any).type === "textarea";
39
+ if (e.key === "Enter" && !isTextArea) {
40
+ e.preventDefault();
41
+ e.stopPropagation();
42
+ triggerSave().then();
43
+ } else if (e.key === "Tab") {
44
+ e.preventDefault();
45
+ e.stopPropagation();
46
+ }
47
+ },
48
+ onKeyDown: (e: KeyboardEvent) => {
49
+ const isTextArea = (e.currentTarget as any).type === "textarea";
50
+ if (e.key === "Enter" && !isTextArea) {
51
+ e.preventDefault();
52
+ e.stopPropagation();
53
+ } else if (e.key === "Tab") {
54
+ e.preventDefault();
55
+ e.stopPropagation();
56
+ !e.shiftKey && triggerSave().then();
57
+ }
58
+ },
59
+ };
60
+ const firstInputKeyboardEventHandlers: {
61
+ onKeyUp?: KeyboardEventHandler<HTMLElement> | undefined;
62
+ onKeyDown?: KeyboardEventHandler<HTMLElement> | undefined;
63
+ } = {
64
+ onKeyUp: (e: KeyboardEvent) => {
65
+ if (e.key === "Tab" && e.shiftKey) {
66
+ e.preventDefault();
67
+ e.stopPropagation();
68
+ }
69
+ },
70
+ onKeyDown: (e: KeyboardEvent) => {
71
+ if (e.key === "Tab" && e.shiftKey) {
72
+ e.preventDefault();
73
+ e.stopPropagation();
74
+ }
75
+ },
76
+ };
77
+
78
+ const lastInputKeyboardEventHandlers: {
79
+ onKeyUp?: KeyboardEventHandler<HTMLElement> | undefined;
80
+ onKeyDown?: KeyboardEventHandler<HTMLElement> | undefined;
81
+ } = {
82
+ onKeyUp: (e: KeyboardEvent) => {
83
+ const isTextArea = (e.currentTarget as any).type === "textarea";
84
+ if (e.key === "Enter" && !isTextArea) {
85
+ e.preventDefault();
86
+ e.stopPropagation();
87
+ triggerSave().then();
88
+ } else if (e.key === "Tab" && !e.shiftKey) {
89
+ e.preventDefault();
90
+ e.stopPropagation();
91
+ }
92
+ },
93
+ onKeyDown: (e: KeyboardEvent) => {
94
+ const isTextArea = (e.currentTarget as any).type === "textarea";
95
+ if (e.key === "Enter" && !isTextArea) {
96
+ e.preventDefault();
97
+ e.stopPropagation();
98
+ } else if (e.key === "Tab" && !e.shiftKey) {
99
+ e.preventDefault();
100
+ e.stopPropagation();
101
+ triggerSave().then();
102
+ }
103
+ },
104
+ };
105
+
33
106
  const popoverWrapper = useCallback(
34
107
  (children: JSX.Element) => {
35
108
  return (
@@ -43,6 +116,8 @@ export const useGridPopoverHook = <RowType extends GridBaseRow>(props: GridPopov
43
116
  saveButtonRef={saveButtonRef}
44
117
  menuClassName={"step-ag-grid-react-menu"}
45
118
  onClose={(event: MenuCloseEvent) => {
119
+ // Prevent menu from closing when modals are invoked
120
+ if (event.reason === "blur") return;
46
121
  triggerSave(event.reason).then();
47
122
  }}
48
123
  viewScroll={"auto"}
@@ -76,5 +151,8 @@ export const useGridPopoverHook = <RowType extends GridBaseRow>(props: GridPopov
76
151
  return {
77
152
  popoverWrapper,
78
153
  triggerSave,
154
+ onlyInputKeyboardEventHandlers,
155
+ firstInputKeyboardEventHandlers,
156
+ lastInputKeyboardEventHandlers,
79
157
  };
80
158
  };
@@ -10,6 +10,8 @@ import debounce from "debounce-promise";
10
10
  import { CellEditorCommon } from "../GridCell";
11
11
  import { useGridPopoverHook } from "../GridPopoverHook";
12
12
  import { useGridPopoverContext } from "../../contexts/GridPopoverContext";
13
+ import { GridSubComponentContext } from "contexts/GridSubComponentContext";
14
+ import { ClickEvent, MenuInstance } from "../../react-menu3/types";
13
15
 
14
16
  export interface GridPopoutEditDropDownSelectedItem<RowType, ValueType> {
15
17
  // Note the row that was clicked on will be first
@@ -69,11 +71,15 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
69
71
  const [filteredValues, setFilteredValues] = useState<any[]>([]);
70
72
  const optionsInitialising = useRef(false);
71
73
  const [options, setOptions] = useState<FinalSelectOption<ValueType>[] | null>(null);
72
- const [subComponentValues, setSubComponentValues] = useState<{ optionValue: any; subComponentValue: any }[]>([]);
74
+ const subComponentIsValid = useRef(false);
75
+ const [subSelectedValue, setSubSelectedValue] = useState<any>();
76
+ const [selectedSubComponent, setSelectedSubComponent] = useState<any>();
73
77
 
74
78
  const selectItemHandler = useCallback(
75
- async (value: ValueType, subComponentValue?: ValueType): Promise<boolean> =>
76
- updateValue(async (selectedRows) => {
79
+ async (value: ValueType, subComponentValue?: ValueType): Promise<boolean> => {
80
+ if (subComponentValue !== undefined && !subComponentIsValid.current) return false;
81
+
82
+ return updateValue(async (selectedRows) => {
77
83
  const hasChanged = selectedRows.some((row) => row[field as keyof RowType] !== value);
78
84
  if (hasChanged) {
79
85
  if (props.onSelectedItem) {
@@ -83,7 +89,8 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
83
89
  }
84
90
  }
85
91
  return true;
86
- }),
92
+ });
93
+ },
87
94
  [field, props, updateValue],
88
95
  );
89
96
 
@@ -185,7 +192,16 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
185
192
  },
186
193
  [filteredValues, options, selectItemHandler, selectFilterHandler, stopEditing, filter, props],
187
194
  );
188
- const { popoverWrapper } = useGridPopoverHook({ className: props.className });
195
+
196
+ const save = useCallback(async () => {
197
+ // Handler for sub-selected value
198
+ if (!selectedSubComponent) return true;
199
+ if (!subComponentIsValid.current) return false;
200
+ await selectItemHandler(selectedSubComponent.value as ValueType, subSelectedValue);
201
+ return true;
202
+ }, [selectItemHandler, selectedSubComponent, subSelectedValue]);
203
+
204
+ const { popoverWrapper } = useGridPopoverHook({ className: props.className, save });
189
205
  return popoverWrapper(
190
206
  <>
191
207
  {props.filtered && (
@@ -223,59 +239,50 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
223
239
  <MenuHeader key={`$$header_${index}`}>{item.label}</MenuHeader>
224
240
  ) : filteredValues.includes(item.value) ? null : (
225
241
  <div key={`menu-wrapper-${index}`}>
226
- {!item.subComponent ? (
227
- <MenuItem
228
- key={`${fieldToString(field)}-${index}`}
229
- disabled={!!item.disabled}
230
- title={item.disabled && typeof item.disabled !== "boolean" ? item.disabled : ""}
231
- value={item.value}
232
- onClick={() => {
242
+ <MenuItem
243
+ key={`${fieldToString(field)}-${index}`}
244
+ disabled={!!item.disabled}
245
+ title={item.disabled && typeof item.disabled !== "boolean" ? item.disabled : ""}
246
+ value={item.value}
247
+ onClick={(e: ClickEvent) => {
248
+ if (item.subComponent) {
249
+ if (selectedSubComponent === item) {
250
+ setSelectedSubComponent(null);
251
+ subComponentIsValid.current = true;
252
+ } else {
253
+ setSelectedSubComponent(item);
254
+ }
255
+ e.keepOpen = true;
256
+ } else {
233
257
  selectItemHandler(item.value as ValueType).then();
234
- }}
235
- >
236
- {item.label ?? (item.value == null ? `<${item.value}>` : `${item.value}`)}
237
- </MenuItem>
238
- ) : (
239
- <FocusableItem className={"LuiDeprecatedForms"} key={`${fieldToString(field)}-${index}_subcomponent`}>
240
- {(ref: any) =>
241
- item.subComponent && (
242
- <item.subComponent
243
- setValue={(value: any) => {
244
- const localSubComponentValues = [...subComponentValues];
245
- const subComponentValueIndex = localSubComponentValues.findIndex(
246
- ({ optionValue }) => optionValue === item.value,
247
- );
248
- if (subComponentValueIndex !== -1) {
249
- localSubComponentValues[subComponentValueIndex].subComponentValue = value;
250
- } else {
251
- localSubComponentValues.push({
252
- subComponentValue: value,
253
- optionValue: item.value,
254
- });
255
- }
256
- setSubComponentValues(localSubComponentValues);
257
- }}
258
- keyDown={(key: string, event: KeyboardEvent<HTMLInputElement>) => {
259
- const subComponentItem = subComponentValues.find(
260
- ({ optionValue }) => optionValue === item.value,
261
- );
262
- if ((key === "Enter" || key === "Tab") && subComponentItem) {
263
- event.preventDefault();
264
- event.stopPropagation();
265
- return selectItemHandler(
266
- item.value as ValueType,
267
- subComponentItem.subComponentValue,
268
- ).then(() => {
269
- ref.closeMenu();
270
- return true;
271
- });
272
- }
273
- return false;
274
- }}
275
- key={`${fieldToString(field)}-${index}_subcomponent_inner`}
276
- />
277
- )
278
258
  }
259
+ }}
260
+ >
261
+ {item.label ?? (item.value == null ? `<${item.value}>` : `${item.value}`)}
262
+ </MenuItem>
263
+
264
+ {item.subComponent && selectedSubComponent === item && (
265
+ <FocusableItem className={"LuiDeprecatedForms"} key={`${item.label}_subcomponent`}>
266
+ {(ref: MenuInstance) => (
267
+ <GridSubComponentContext.Provider
268
+ value={{
269
+ value: subSelectedValue,
270
+ setValue: (value: any) => {
271
+ setSubSelectedValue(value);
272
+ },
273
+ setValid: (valid: boolean) => {
274
+ subComponentIsValid.current = valid;
275
+ },
276
+ triggerSave: async () => {
277
+ ref.closeMenu();
278
+ },
279
+ }}
280
+ >
281
+ {item.subComponent && (
282
+ <item.subComponent key={`${fieldToString(field)}-${index}_subcomponent_inner`} />
283
+ )}
284
+ </GridSubComponentContext.Provider>
285
+ )}
279
286
  </FocusableItem>
280
287
  )}
281
288
  </div>
@@ -43,7 +43,10 @@ export const GridFormEditBearing = <RowType extends GridBaseRow>(props: GridForm
43
43
  },
44
44
  [field, initialValue, props, value],
45
45
  );
46
- const { triggerSave, popoverWrapper } = useGridPopoverHook({ className: props.className, save });
46
+ const { popoverWrapper, onlyInputKeyboardEventHandlers } = useGridPopoverHook({
47
+ className: props.className,
48
+ save,
49
+ });
47
50
 
48
51
  return popoverWrapper(
49
52
  <div className={"GridFormEditBearing-input Grid-popoverContainer"}>
@@ -52,13 +55,9 @@ export const GridFormEditBearing = <RowType extends GridBaseRow>(props: GridForm
52
55
  onChange={(e) => {
53
56
  setValue(e.target.value.trim());
54
57
  }}
55
- inputProps={{
56
- autoFocus: true,
57
- placeholder: props.placeHolder,
58
- disabled: false,
59
- maxLength: 16,
60
- onKeyDown: async (e) => e.key === "Enter" && triggerSave().then(),
61
- }}
58
+ autoFocus={true}
59
+ placeholder={props.placeHolder}
60
+ {...onlyInputKeyboardEventHandlers}
62
61
  formatted={bearingStringValidator(value, props.range) ? "?" : convertDDToDMS(bearingNumberParser(value))}
63
62
  error={bearingStringValidator(value, props.range)}
64
63
  />
@@ -1,7 +1,7 @@
1
1
  import "../../styles/GridFormMultiSelect.scss";
2
2
 
3
3
  import { FocusableItem, MenuDivider, MenuItem } from "../../react-menu3";
4
- import { KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from "react";
4
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
5
5
  import { GridBaseRow } from "../Grid";
6
6
  import { ComponentLoadingWrapper } from "../ComponentLoadingWrapper";
7
7
  import { delay, fromPairs, isEqual, omit, pick, toPairs } from "lodash-es";
@@ -149,7 +149,10 @@ export const GridFormMultiSelect = <RowType extends GridBaseRow, ValueType>(
149
149
  [selectedValues],
150
150
  );
151
151
 
152
- const { popoverWrapper, triggerSave } = useGridPopoverHook({ className: props.className, save });
152
+ const { popoverWrapper, lastInputKeyboardEventHandlers, triggerSave } = useGridPopoverHook({
153
+ className: props.className,
154
+ save,
155
+ });
153
156
  return popoverWrapper(
154
157
  <ComponentLoadingWrapper loading={!options} className={"GridFormMultiSelect-container"}>
155
158
  <>
@@ -186,14 +189,7 @@ export const GridFormMultiSelect = <RowType extends GridBaseRow, ValueType>(
186
189
  e.keepOpen = true;
187
190
  toggleValue(item);
188
191
  }}
189
- onKeyDown={async (e: KeyboardEvent) => {
190
- if (e.key === "Enter") triggerSave().then();
191
- else if (e.key === " ") {
192
- toggleValue(item);
193
- e.preventDefault();
194
- e.stopPropagation();
195
- }
196
- }}
192
+ {...lastInputKeyboardEventHandlers}
197
193
  >
198
194
  <LuiCheckboxInput
199
195
  isChecked={`${item.value}` in selectedValues}
@@ -1,5 +1,5 @@
1
1
  import { GridBaseRow } from "../Grid";
2
- import { useCallback, useEffect, useRef, useState } from "react";
2
+ import { Fragment, useCallback, useEffect, useRef, useState } from "react";
3
3
  import { ComponentLoadingWrapper } from "../ComponentLoadingWrapper";
4
4
  import { FocusableItem, MenuDivider, MenuItem } from "../../react-menu3";
5
5
  import { useGridPopoverHook } from "../GridPopoverHook";
@@ -99,8 +99,8 @@ export const GridFormPopoverMenu = <RowType extends GridBaseRow>(props: GridForm
99
99
  setSubComponentSelected(subComponentSelected === item ? null : item);
100
100
  e.keepOpen = true;
101
101
  } else {
102
- setSubComponentSelected(null);
103
102
  actionClick(item).then();
103
+ e.keepOpen = true;
104
104
  }
105
105
  },
106
106
  [actionClick, subComponentSelected],
@@ -117,17 +117,14 @@ export const GridFormPopoverMenu = <RowType extends GridBaseRow>(props: GridForm
117
117
  if (!actionProcessing.current && subComponentSelected) {
118
118
  if (!subComponentIsValid.current) return false;
119
119
  await actionClick(subComponentSelected);
120
+ return false;
120
121
  }
122
+ // Otherwise assume it's a cancel, either way we close the menu
121
123
  return true;
122
124
  }, [actionClick, subComponentSelected]);
123
125
 
124
126
  const { popoverWrapper, triggerSave } = useGridPopoverHook({ className: props.className, save });
125
127
 
126
- const localTriggerSave = async (reason?: string) => {
127
- if (!subComponentIsValid.current) return;
128
- return triggerSave(reason);
129
- };
130
-
131
128
  return popoverWrapper(
132
129
  <ComponentLoadingWrapper loading={!filteredOptions} className={"GridFormPopupMenu"}>
133
130
  <>
@@ -136,7 +133,7 @@ export const GridFormPopoverMenu = <RowType extends GridBaseRow>(props: GridForm
136
133
  <MenuDivider key={`$$divider_${index}`} />
137
134
  ) : (
138
135
  !item.hidden && (
139
- <>
136
+ <Fragment key={`${item.label}`}>
140
137
  <MenuItem
141
138
  key={`${item.label}`}
142
139
  onClick={(e: ClickEvent) => onMenuItemClick(e, item)}
@@ -158,7 +155,7 @@ export const GridFormPopoverMenu = <RowType extends GridBaseRow>(props: GridForm
158
155
  setValid: (valid: boolean) => {
159
156
  subComponentIsValid.current = valid;
160
157
  },
161
- triggerSave: localTriggerSave,
158
+ triggerSave,
162
159
  }}
163
160
  >
164
161
  <item.subComponent />
@@ -167,7 +164,7 @@ export const GridFormPopoverMenu = <RowType extends GridBaseRow>(props: GridForm
167
164
  }
168
165
  </FocusableItem>
169
166
  )}
170
- </>
167
+ </Fragment>
171
168
  )
172
169
  ),
173
170
  )}
@@ -0,0 +1,58 @@
1
+ import { useCallback, useContext, useEffect } from "react";
2
+ import { GridSubComponentContext } from "../../contexts/GridSubComponentContext";
3
+ import { CellEditorCommon } from "../GridCell";
4
+ import clsx from "clsx";
5
+ import { TextAreaInput } from "../../lui/TextAreaInput";
6
+ import { TextInputValidator, TextInputValidatorProps } from "../../utils/textValidator";
7
+
8
+ export interface GridSubComponentTextAreaProps extends TextInputValidatorProps, CellEditorCommon {
9
+ placeholder?: string;
10
+ width?: string | number;
11
+ defaultValue: string;
12
+ className?: string;
13
+ helpText?: string;
14
+ }
15
+
16
+ export const GridFormSubComponentTextArea = (props: GridSubComponentTextAreaProps): JSX.Element => {
17
+ const { value, setValue, setValid, triggerSave } = useContext(GridSubComponentContext);
18
+
19
+ const helpText = props.helpText ?? "Press tab to save";
20
+
21
+ // If is not initialised yet as it's just been created then set the default value
22
+ useEffect(() => {
23
+ if (value == null) setValue(props.defaultValue);
24
+ }, [props.defaultValue, setValue, value]);
25
+
26
+ const invalid = useCallback(() => TextInputValidator(props, value), [props, value]);
27
+
28
+ useEffect(() => {
29
+ setValid(value != null && invalid() == null);
30
+ }, [setValid, invalid, value]);
31
+
32
+ return (
33
+ <div className={clsx("FreeTextInput LuiDeprecatedForms", props.className)}>
34
+ <TextAreaInput
35
+ className={"free-text-input"}
36
+ value={value}
37
+ onChange={(e) => setValue(e.target.value)}
38
+ error={invalid()}
39
+ helpText={helpText}
40
+ autoFocus={true}
41
+ placeholder={props.placeholder}
42
+ onKeyDown={(e) => {
43
+ if (e.key === "Tab") {
44
+ e.preventDefault();
45
+ e.stopPropagation();
46
+ }
47
+ }}
48
+ onKeyUp={(e) => {
49
+ if (e.key === "Tab") {
50
+ !e.shiftKey && triggerSave().then();
51
+ e.preventDefault();
52
+ e.stopPropagation();
53
+ }
54
+ }}
55
+ />
56
+ </div>
57
+ );
58
+ };
@@ -1,67 +1,60 @@
1
- import "../../styles/GridFormSubComponentTextInput.scss";
2
-
3
- import { useState, KeyboardEvent } from "react";
1
+ import { useCallback, useContext, useEffect } from "react";
2
+ import { GridSubComponentContext } from "../../contexts/GridSubComponentContext";
4
3
  import { TextInputFormatted } from "../../lui/TextInputFormatted";
4
+ import { TextInputValidator, TextInputValidatorProps } from "../../utils/textValidator";
5
+ import { CellEditorCommon } from "../GridCell";
5
6
 
6
- export interface GridFormSubComponentTextInput {
7
- setValue: (value: string) => void;
8
- keyDown: (key: string, event: KeyboardEvent<HTMLInputElement>) => void;
7
+ export interface GridFormSubComponentTextInputProps extends TextInputValidatorProps, CellEditorCommon {
9
8
  placeholder?: string;
10
- className?: string;
11
- customHelpText?: string;
9
+ width?: string | number;
10
+ defaultValue: string;
11
+ helpText?: string;
12
12
  }
13
13
 
14
- export const GridFormSubComponentTextInput = ({
15
- keyDown,
16
- placeholder,
17
- setValue,
18
- className,
19
- customHelpText,
20
- }: GridFormSubComponentTextInput) => {
21
- const placeholderText = placeholder || "Other...";
22
- const helpText = customHelpText || "Press enter or tab to save";
14
+ export const GridFormSubComponentTextInput = (props: GridFormSubComponentTextInputProps): JSX.Element => {
15
+ const { value, setValue, setValid, triggerSave } = useContext(GridSubComponentContext);
16
+
17
+ const helpText = props.helpText ?? "Press enter or tab to save";
18
+
19
+ // If is not initialised yet as it's just been created then set the default value
20
+ useEffect(() => {
21
+ if (value == null) setValue(props.defaultValue);
22
+ }, [props.defaultValue, setValue, value]);
23
+
24
+ const invalid = useCallback(() => TextInputValidator(props, value), [props, value]);
25
+
26
+ useEffect(() => {
27
+ setValid(value != null && invalid() == null);
28
+ }, [setValid, invalid, value]);
23
29
 
24
- const inputClass = className || "GridFormSubComponentTextInput-full-width-input";
25
- const [inputValue, setInputValue] = useState("");
26
30
  return (
27
- <div
31
+ <TextInputFormatted
32
+ value={value}
33
+ error={invalid()}
34
+ onChange={(e) => setValue(e.target.value)}
35
+ helpText={helpText}
36
+ autoFocus={true}
37
+ onKeyDown={(e) => {
38
+ if (e.key === "Tab" || e.key === "Enter") {
39
+ e.preventDefault();
40
+ e.stopPropagation();
41
+ }
42
+ }}
43
+ onKeyUp={(e) => {
44
+ if (e.key === "Tab") {
45
+ !e.shiftKey && triggerSave().then();
46
+ e.preventDefault();
47
+ e.stopPropagation();
48
+ } else if (e.key === "Enter") {
49
+ triggerSave().then();
50
+ e.preventDefault();
51
+ e.stopPropagation();
52
+ }
53
+ }}
54
+ placeholder={props.placeholder}
28
55
  style={{
29
- display: "flex",
30
- flexDirection: "column",
31
56
  width: "100%",
32
- padding: 0,
33
57
  }}
34
- >
35
- <TextInputFormatted
36
- className={inputClass}
37
- value={inputValue}
38
- onChange={(e) => {
39
- const value = e.target.value;
40
- setValue(value);
41
- setInputValue(value);
42
- }}
43
- inputProps={{
44
- onKeyDown: (e) => {
45
- return keyDown(e.key, e);
46
- },
47
- placeholder: placeholderText,
48
- onMouseEnter: (e) => {
49
- if (document.activeElement != e.currentTarget) {
50
- e.currentTarget.focus();
51
- }
52
- },
53
- style: {
54
- width: "100%",
55
- },
56
- }}
57
- />
58
- <span
59
- style={{
60
- fontSize: "0.7rem",
61
- }}
62
- >
63
- {helpText}
64
- </span>
65
- </div>
58
+ />
66
59
  );
67
60
  };
@@ -4,32 +4,22 @@ import { useGridPopoverHook } from "../GridPopoverHook";
4
4
  import { GridBaseRow } from "../Grid";
5
5
  import { CellEditorCommon } from "../GridCell";
6
6
  import { useGridPopoverContext } from "../../contexts/GridPopoverContext";
7
+ import { TextInputValidator, TextInputValidatorProps } from "../../utils/textValidator";
7
8
 
8
- export interface GridFormTextAreaProps<RowType extends GridBaseRow> extends CellEditorCommon {
9
+ export interface GridFormTextAreaProps<RowType extends GridBaseRow> extends TextInputValidatorProps, CellEditorCommon {
9
10
  placeholder?: string;
10
- required?: boolean;
11
- maxLength?: number;
12
11
  width?: string | number;
13
- validate?: (value: string) => string | null;
14
12
  onSave?: (selectedRows: RowType[], value: string) => Promise<boolean>;
13
+ helpText?: string;
15
14
  }
16
15
 
17
16
  export const GridFormTextArea = <RowType extends GridBaseRow>(props: GridFormTextAreaProps<RowType>) => {
18
17
  const { field, value: initialVale } = useGridPopoverContext<RowType>();
19
18
  const [value, setValue] = useState(initialVale != null ? `${initialVale}` : "");
20
19
 
21
- const invalid = useCallback(() => {
22
- if (props.required && value.length == 0) {
23
- return `Some text is required`;
24
- }
25
- if (props.maxLength && value.length > props.maxLength) {
26
- return `Text must be no longer than ${props.maxLength} characters`;
27
- }
28
- if (props.validate) {
29
- return props.validate(value);
30
- }
31
- return null;
32
- }, [props, value]);
20
+ const helpText = props.helpText ?? "Press tab to save";
21
+
22
+ const invalid = useCallback(() => TextInputValidator(props, value), [props, value]);
33
23
 
34
24
  const save = useCallback(
35
25
  async (selectedRows: RowType[]): Promise<boolean> => {
@@ -52,14 +42,16 @@ export const GridFormTextArea = <RowType extends GridBaseRow>(props: GridFormTex
52
42
  },
53
43
  [invalid, initialVale, value, props, field],
54
44
  );
55
- const { popoverWrapper } = useGridPopoverHook({ className: props.className, save });
45
+ const { popoverWrapper, lastInputKeyboardEventHandlers } = useGridPopoverHook({ className: props.className, save });
56
46
  return popoverWrapper(
57
47
  <div style={{ display: "flex", flexDirection: "row", width: props.width ?? 240 }}>
58
48
  <TextAreaInput
59
49
  value={value}
60
50
  onChange={(e) => setValue(e.target.value)}
61
51
  error={invalid()}
62
- inputProps={{ placeholder: props.placeholder }}
52
+ placeholder={props.placeholder}
53
+ helpText={helpText}
54
+ {...lastInputKeyboardEventHandlers}
63
55
  />
64
56
  </div>,
65
57
  );