@linzjs/step-ag-grid 2.5.0 → 3.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 (39) hide show
  1. package/dist/index.js +361 -282
  2. package/dist/index.js.map +1 -1
  3. package/dist/src/components/GridCell.d.ts +4 -7
  4. package/dist/src/components/gridForm/GridFormDropDown.d.ts +1 -1
  5. package/dist/src/components/gridForm/GridFormEditBearing.d.ts +1 -1
  6. package/dist/src/components/gridForm/GridFormMessage.d.ts +3 -3
  7. package/dist/src/components/gridForm/GridFormMultiSelect.d.ts +2 -2
  8. package/dist/src/components/gridForm/GridFormPopoverMenu.d.ts +8 -3
  9. package/dist/src/components/gridForm/GridFormSubComponentTextInput.d.ts +1 -1
  10. package/dist/src/components/gridForm/GridFormTextArea.d.ts +1 -1
  11. package/dist/src/components/gridForm/GridFormTextInput.d.ts +1 -1
  12. package/dist/src/contexts/GridPopoverContext.d.ts +10 -6
  13. package/dist/src/contexts/GridPopoverContextProvider.d.ts +3 -1
  14. package/dist/src/lui/TextInputFormatted.d.ts +0 -1
  15. package/dist/step-ag-grid.esm.js +362 -283
  16. package/dist/step-ag-grid.esm.js.map +1 -1
  17. package/package.json +1 -1
  18. package/src/components/GridCell.tsx +9 -33
  19. package/src/components/GridPopoverHook.tsx +4 -8
  20. package/src/components/GridSubComponentTextArea.tsx +0 -6
  21. package/src/components/gridForm/GridFormDropDown.tsx +35 -37
  22. package/src/components/gridForm/GridFormEditBearing.tsx +12 -8
  23. package/src/components/gridForm/GridFormMessage.tsx +8 -6
  24. package/src/components/gridForm/GridFormMultiSelect.tsx +56 -48
  25. package/src/components/gridForm/GridFormPopoverMenu.tsx +112 -29
  26. package/src/components/gridForm/GridFormSubComponentTextInput.tsx +2 -1
  27. package/src/components/gridForm/GridFormTextArea.tsx +11 -9
  28. package/src/components/gridForm/GridFormTextInput.tsx +13 -16
  29. package/src/contexts/GridPopoverContext.tsx +18 -9
  30. package/src/contexts/GridPopoverContextProvider.tsx +24 -27
  31. package/src/lui/TextAreaInput.tsx +13 -1
  32. package/src/lui/TextInputFormatted.tsx +3 -2
  33. package/src/react-menu3/components/MenuList.tsx +42 -10
  34. package/src/react-menu3/contexts/SettingsContext.ts +1 -1
  35. package/src/stories/grid/FormTest.tsx +17 -15
  36. package/src/stories/grid/GridPopoutEditDropDown.stories.tsx +3 -1
  37. package/src/stories/grid/GridPopoutEditGenericTextArea.stories.tsx +0 -1
  38. package/src/stories/grid/GridPopoutEditMultiSelect.stories.tsx +3 -3
  39. package/src/stories/grid/GridReadOnly.stories.tsx +15 -5
@@ -1,13 +1,16 @@
1
1
  import { GridBaseRow } from "../Grid";
2
- import { useCallback, useContext, useEffect, useRef, useState } from "react";
3
- import { GridContext } from "../../contexts/GridContext";
2
+ import { useCallback, useEffect, useRef, useState } from "react";
4
3
  import { ComponentLoadingWrapper } from "../ComponentLoadingWrapper";
5
- import { MenuDivider, MenuItem } from "../../react-menu3";
4
+ import { FocusableItem, MenuDivider, MenuItem } from "../../react-menu3";
6
5
  import { useGridPopoverHook } from "../GridPopoverHook";
7
- import { CellEditorCommon, CellParams } from "../GridCell";
6
+ import { CellEditorCommon } from "../GridCell";
7
+ import { GridSubComponentContext } from "../../contexts/GridSubComponentContext";
8
+ import { ClickEvent } from "../../react-menu3/types";
9
+ import { useGridPopoverContext } from "../../contexts/GridPopoverContext";
8
10
 
9
11
  export interface GridFormPopoutMenuProps<RowType extends GridBaseRow> extends CellEditorCommon {
10
12
  options: (selectedRows: RowType[]) => Promise<MenuOption<RowType>[]>;
13
+ defaultAction?: (selectedRows: RowType[], menuOption: SelectedMenuOptionResult<RowType>) => Promise<void>;
11
14
  }
12
15
 
13
16
  /** Menu configuration types **/
@@ -17,24 +20,43 @@ interface MenuSeparatorType {
17
20
  __isMenuSeparator__: boolean;
18
21
  }
19
22
 
20
- export interface MenuOption<RowType> {
23
+ export interface SelectedMenuOptionResult<RowType extends GridBaseRow> extends MenuOption<RowType> {
24
+ subValue: any;
25
+ }
26
+
27
+ export interface MenuOption<RowType extends GridBaseRow> {
21
28
  label: JSX.Element | string | MenuSeparatorType;
22
- action?: (selectedRows: RowType[]) => Promise<boolean>;
29
+ action?: (selectedRows: RowType[], menuOption: SelectedMenuOptionResult<RowType>) => Promise<void>;
23
30
  disabled?: string | boolean;
24
31
  supportsMultiEdit: boolean;
25
32
  hidden?: boolean;
33
+ subComponent?: (props: any) => JSX.Element;
26
34
  }
27
35
 
28
36
  /**
29
37
  * NOTE: If the popout menu doesn't appear on single click when also selecting row it's because
30
38
  * you need a useMemo around your columnDefs
31
39
  */
32
- export const GridFormPopoverMenu = <RowType extends GridBaseRow>(_props: GridFormPopoutMenuProps<RowType>) => {
33
- const props = _props as GridFormPopoutMenuProps<RowType> & CellParams<RowType>;
34
- const { updatingCells } = useContext(GridContext);
40
+ export const GridFormPopoverMenu = <RowType extends GridBaseRow>(props: GridFormPopoutMenuProps<RowType>) => {
41
+ const { selectedRows, updateValue } = useGridPopoverContext<RowType>();
42
+
35
43
  const optionsInitialising = useRef(false);
36
44
  const [options, setOptions] = useState<MenuOption<RowType>[]>();
37
45
 
46
+ // Save triggers during async action processing which triggers another action(), this ref blocks that
47
+ const actionProcessing = useRef(false);
48
+ const [subComponentSelected, setSubComponentSelected] = useState<MenuOption<RowType> | null>(null);
49
+ const subComponentIsValid = useRef(false);
50
+ const [subSelectedValue, setSubSelectedValue] = useState<any>();
51
+
52
+ const defaultAction = useCallback(
53
+ async (selectedRows: RowType[], menuOption: SelectedMenuOptionResult<RowType>) => {
54
+ if (props.defaultAction) await props.defaultAction(selectedRows, menuOption);
55
+ else console.error(`No action specified for ${menuOption.label} menu options`);
56
+ },
57
+ [props],
58
+ );
59
+
38
60
  // Load up options list if it's async function
39
61
  useEffect(() => {
40
62
  if (options || optionsInitialising.current) return;
@@ -42,33 +64,70 @@ export const GridFormPopoverMenu = <RowType extends GridBaseRow>(_props: GridFor
42
64
  const optionsConf = props.options ?? [];
43
65
 
44
66
  (async () => {
45
- if (typeof optionsConf == "function") {
46
- setOptions(await optionsConf(props.selectedRows));
47
- } else {
48
- setOptions(optionsConf);
67
+ const newOptions = typeof optionsConf == "function" ? await optionsConf(selectedRows) : optionsConf;
68
+ setOptions(newOptions);
69
+ if (!props.defaultAction) {
70
+ const anyOptionsAreMissingAction = newOptions.some((option) => !option.action);
71
+ if (anyOptionsAreMissingAction) {
72
+ console.error("There's no default action handler and some Menu options are missing an action handler", {
73
+ invalidMenuOptions: newOptions.filter((option) => !option.action),
74
+ });
75
+ }
49
76
  }
50
-
51
77
  optionsInitialising.current = false;
52
78
  })();
53
- }, [options, props.options, props.selectedRows]);
79
+ }, [options, props.defaultAction, props.options, selectedRows]);
54
80
 
55
81
  const actionClick = useCallback(
56
- async (menuOption: MenuOption<any>) => {
57
- return await updatingCells({ selectedRows: props.selectedRows, field: props.field }, async (selectedRows) => {
58
- menuOption.action && (await menuOption.action(selectedRows));
82
+ async (menuOption: MenuOption<RowType>) => {
83
+ actionProcessing.current = true;
84
+ return updateValue(async () => {
85
+ const result = { ...menuOption, subValue: subSelectedValue };
86
+ await (menuOption.action ?? defaultAction)(selectedRows, result);
87
+ actionProcessing.current = false;
59
88
  return true;
60
89
  });
61
90
  },
62
- [props.field, props.selectedRows, updatingCells],
91
+ [defaultAction, selectedRows, subSelectedValue, updateValue],
63
92
  );
64
93
 
65
- const selectedRowCount = props.selectedRows.length;
94
+ const onMenuItemClick = useCallback(
95
+ (e: ClickEvent, item: MenuOption<RowType>) => {
96
+ if (item.subComponent) {
97
+ subComponentIsValid.current = false;
98
+ setSubSelectedValue(null);
99
+ setSubComponentSelected(subComponentSelected === item ? null : item);
100
+ e.keepOpen = true;
101
+ } else {
102
+ setSubComponentSelected(null);
103
+ actionClick(item).then();
104
+ }
105
+ },
106
+ [actionClick, subComponentSelected],
107
+ );
108
+
109
+ const selectedRowCount = selectedRows.length;
66
110
 
67
111
  const filteredOptions = options?.filter((menuOption) => {
68
112
  return menuOption.label === PopoutMenuSeparator || selectedRowCount === 1 || menuOption.supportsMultiEdit;
69
113
  });
70
114
 
71
- const { popoverWrapper } = useGridPopoverHook({ className: props.className });
115
+ const save = useCallback(async () => {
116
+ // if a subcomponent is open we assume that it's meant to be saved.
117
+ if (!actionProcessing.current && subComponentSelected) {
118
+ if (!subComponentIsValid.current) return false;
119
+ await actionClick(subComponentSelected);
120
+ }
121
+ return true;
122
+ }, [actionClick, subComponentSelected]);
123
+
124
+ const { popoverWrapper, triggerSave } = useGridPopoverHook({ className: props.className, save });
125
+
126
+ const localTriggerSave = async (reason?: string) => {
127
+ if (!subComponentIsValid.current) return;
128
+ return triggerSave(reason);
129
+ };
130
+
72
131
  return popoverWrapper(
73
132
  <ComponentLoadingWrapper loading={!filteredOptions} className={"GridFormPopupMenu"}>
74
133
  <>
@@ -77,14 +136,38 @@ export const GridFormPopoverMenu = <RowType extends GridBaseRow>(_props: GridFor
77
136
  <MenuDivider key={`$$divider_${index}`} />
78
137
  ) : (
79
138
  !item.hidden && (
80
- <MenuItem
81
- key={`${item.label}`}
82
- onClick={() => actionClick(item)}
83
- disabled={!!item.disabled || !filteredOptions?.includes(item)}
84
- title={item.disabled && typeof item.disabled !== "boolean" ? item.disabled : ""}
85
- >
86
- {item.label as JSX.Element | string}
87
- </MenuItem>
139
+ <>
140
+ <MenuItem
141
+ key={`${item.label}`}
142
+ onClick={(e: ClickEvent) => onMenuItemClick(e, item)}
143
+ disabled={!!item.disabled || !filteredOptions?.includes(item)}
144
+ title={item.disabled && typeof item.disabled !== "boolean" ? item.disabled : ""}
145
+ >
146
+ {item.label as JSX.Element | string}
147
+ </MenuItem>
148
+ {item.subComponent && subComponentSelected === item && (
149
+ <FocusableItem className={"LuiDeprecatedForms"} key={`${item.label}_subcomponent`}>
150
+ {(_: any) =>
151
+ item.subComponent && (
152
+ <GridSubComponentContext.Provider
153
+ value={{
154
+ value: subSelectedValue,
155
+ setValue: (value: any) => {
156
+ setSubSelectedValue(value);
157
+ },
158
+ setValid: (valid: boolean) => {
159
+ subComponentIsValid.current = valid;
160
+ },
161
+ triggerSave: localTriggerSave,
162
+ }}
163
+ >
164
+ <item.subComponent />
165
+ </GridSubComponentContext.Provider>
166
+ )
167
+ }
168
+ </FocusableItem>
169
+ )}
170
+ </>
88
171
  )
89
172
  ),
90
173
  )}
@@ -1,6 +1,7 @@
1
+ import "../../styles/GridFormSubComponentTextInput.scss";
2
+
1
3
  import { useState, KeyboardEvent } from "react";
2
4
  import { TextInputFormatted } from "../../lui/TextInputFormatted";
3
- import "../../styles/GridFormSubComponentTextInput.scss";
4
5
 
5
6
  export interface GridFormSubComponentTextInput {
6
7
  setValue: (value: string) => void;
@@ -2,7 +2,8 @@ import { useCallback, useState } from "react";
2
2
  import { TextAreaInput } from "../../lui/TextAreaInput";
3
3
  import { useGridPopoverHook } from "../GridPopoverHook";
4
4
  import { GridBaseRow } from "../Grid";
5
- import { CellEditorCommon, CellParams } from "../GridCell";
5
+ import { CellEditorCommon } from "../GridCell";
6
+ import { useGridPopoverContext } from "../../contexts/GridPopoverContext";
6
7
 
7
8
  export interface GridFormTextAreaProps<RowType extends GridBaseRow> extends CellEditorCommon {
8
9
  placeholder?: string;
@@ -13,9 +14,9 @@ export interface GridFormTextAreaProps<RowType extends GridBaseRow> extends Cell
13
14
  onSave?: (selectedRows: RowType[], value: string) => Promise<boolean>;
14
15
  }
15
16
 
16
- export const GridFormTextArea = <RowType extends GridBaseRow>(_props: GridFormTextAreaProps<RowType>) => {
17
- const props = _props as GridFormTextAreaProps<RowType> & CellParams<RowType>;
18
- const [value, setValue] = useState(props.value ?? "");
17
+ export const GridFormTextArea = <RowType extends GridBaseRow>(props: GridFormTextAreaProps<RowType>) => {
18
+ const { field, value: initialVale } = useGridPopoverContext<RowType>();
19
+ const [value, setValue] = useState(initialVale != null ? `${initialVale}` : "");
19
20
 
20
21
  const invalid = useCallback(() => {
21
22
  if (props.required && value.length == 0) {
@@ -31,24 +32,25 @@ export const GridFormTextArea = <RowType extends GridBaseRow>(_props: GridFormTe
31
32
  }, [props, value]);
32
33
 
33
34
  const save = useCallback(
34
- async (selectedRows: any[]): Promise<boolean> => {
35
+ async (selectedRows: RowType[]): Promise<boolean> => {
35
36
  if (invalid()) return false;
36
37
 
37
- if (props.value === (value ?? "")) return true;
38
+ if (initialVale === (value ?? "")) return true;
38
39
 
39
40
  if (props.onSave) {
40
41
  return await props.onSave(selectedRows, value);
41
42
  }
42
43
 
43
- const field = props.field;
44
44
  if (field == null) {
45
45
  console.error("ColDef has no field set");
46
46
  return false;
47
47
  }
48
- selectedRows.forEach((row) => (row[field] = value));
48
+ selectedRows.forEach((row) => {
49
+ row[field] = value as any;
50
+ });
49
51
  return true;
50
52
  },
51
- [props, invalid, value],
53
+ [invalid, initialVale, value, props, field],
52
54
  );
53
55
  const { popoverWrapper } = useGridPopoverHook({ className: props.className, save });
54
56
  return popoverWrapper(
@@ -2,7 +2,8 @@ import { useCallback, useState } from "react";
2
2
  import { TextInputFormatted } from "../../lui/TextInputFormatted";
3
3
  import { useGridPopoverHook } from "../GridPopoverHook";
4
4
  import { GridBaseRow } from "../Grid";
5
- import { CellEditorCommon, CellParams } from "../GridCell";
5
+ import { CellEditorCommon } from "../GridCell";
6
+ import { useGridPopoverContext } from "../../contexts/GridPopoverContext";
6
7
 
7
8
  export interface GridFormTextInputProps<RowType extends GridBaseRow> extends CellEditorCommon {
8
9
  placeholder?: string;
@@ -15,9 +16,10 @@ export interface GridFormTextInputProps<RowType extends GridBaseRow> extends Cel
15
16
  onSave?: (selectedRows: RowType[], value: string) => Promise<boolean>;
16
17
  }
17
18
 
18
- export const GridFormTextInput = <RowType extends GridBaseRow>(_props: GridFormTextInputProps<RowType>) => {
19
- const props = _props as GridFormTextInputProps<RowType> & CellParams<RowType>;
20
- const initValue = props.value == null ? "" : `${props.value}`;
19
+ export const GridFormTextInput = <RowType extends GridBaseRow>(props: GridFormTextInputProps<RowType>) => {
20
+ const { field, data, value: initialVale } = useGridPopoverContext<RowType>();
21
+
22
+ const initValue = initialVale == null ? "" : `${initialVale}`;
21
23
  const [value, setValue] = useState(initValue);
22
24
 
23
25
  const invalid = useCallback(() => {
@@ -29,13 +31,13 @@ export const GridFormTextInput = <RowType extends GridBaseRow>(_props: GridFormT
29
31
  return `Text must be no longer than ${props.maxLength} characters`;
30
32
  }
31
33
  if (props.validate) {
32
- return props.validate(trimmedValue, props.data);
34
+ return props.validate(trimmedValue, data);
33
35
  }
34
36
  return null;
35
- }, [props, value]);
37
+ }, [data, props, value]);
36
38
 
37
39
  const save = useCallback(
38
- async (selectedRows: any[]): Promise<boolean> => {
40
+ async (selectedRows: RowType[]): Promise<boolean> => {
39
41
  if (invalid()) return false;
40
42
  const trimmedValue = value.trim();
41
43
  if (initValue === trimmedValue) return true;
@@ -44,15 +46,16 @@ export const GridFormTextInput = <RowType extends GridBaseRow>(_props: GridFormT
44
46
  return await props.onSave(selectedRows, trimmedValue);
45
47
  }
46
48
 
47
- const field = props.field;
48
49
  if (field == null) {
49
50
  console.error("ColDef has no field set");
50
51
  return false;
51
52
  }
52
- selectedRows.forEach((row) => (row[field] = trimmedValue));
53
+ selectedRows.forEach((row) => {
54
+ row[field] = trimmedValue as any;
55
+ });
53
56
  return true;
54
57
  },
55
- [invalid, value, initValue, props],
58
+ [invalid, value, initValue, props, field],
56
59
  );
57
60
  const { popoverWrapper, triggerSave } = useGridPopoverHook({ className: props.className, save });
58
61
 
@@ -63,12 +66,6 @@ export const GridFormTextInput = <RowType extends GridBaseRow>(_props: GridFormT
63
66
  onChange={(e) => setValue(e.target.value)}
64
67
  error={invalid()}
65
68
  formatted={props.units}
66
- onMouseEnter={(e) => {
67
- if (document.activeElement != e.currentTarget) {
68
- e.currentTarget.focus();
69
- e.currentTarget.selectionStart = e.currentTarget.value.length;
70
- }
71
- }}
72
69
  inputProps={{
73
70
  style: { width: "100%" },
74
71
  placeholder: props.placeholder,
@@ -1,5 +1,5 @@
1
- import { createContext, RefObject } from "react";
2
- import { ICellEditorParams } from "ag-grid-community";
1
+ import { createContext, RefObject, useContext } from "react";
2
+ import { GridBaseRow } from "../components/Grid";
3
3
 
4
4
  export interface PropsType {
5
5
  value: any;
@@ -9,18 +9,27 @@ export interface PropsType {
9
9
  updateValue: (saveFn: (selectedRows: any[]) => Promise<boolean>) => Promise<boolean>;
10
10
  }
11
11
 
12
- export type GridPopoverContextType = {
12
+ export interface GridPopoverContextType<RowType extends GridBaseRow> {
13
13
  anchorRef: RefObject<Element>;
14
14
  saving: boolean;
15
15
  setSaving: (saving: boolean) => void;
16
- setProps: (props: ICellEditorParams, multiEdit: boolean) => void;
17
- propsRef: RefObject<PropsType>;
18
- };
16
+ field: keyof RowType;
17
+ value: any;
18
+ data: RowType;
19
+ selectedRows: RowType[];
20
+ updateValue: (saveFn: (selectedRows: any[]) => Promise<boolean>) => Promise<boolean>;
21
+ }
19
22
 
20
- export const GridPopoverContext = createContext<GridPopoverContextType>({
23
+ export const GridPopoverContext = createContext<GridPopoverContextType<any>>({
21
24
  anchorRef: { current: null },
22
25
  saving: false,
23
26
  setSaving: () => {},
24
- setProps: () => {},
25
- propsRef: { current: null },
27
+ field: "",
28
+ value: null,
29
+ data: {} as GridBaseRow,
30
+ selectedRows: [],
31
+ updateValue: async () => false,
26
32
  });
33
+
34
+ export const useGridPopoverContext = <RowType extends GridBaseRow>() =>
35
+ useContext<GridPopoverContextType<RowType>>(GridPopoverContext);
@@ -1,41 +1,35 @@
1
- import { ReactNode, RefObject, useCallback, useContext, useRef, useState } from "react";
2
- import { GridPopoverContext, PropsType } from "./GridPopoverContext";
1
+ import { ReactNode, RefObject, useCallback, useContext, useMemo, useRef, useState } from "react";
2
+ import { GridPopoverContext } from "./GridPopoverContext";
3
3
  import { ICellEditorParams } from "ag-grid-community";
4
4
  import { GridContext } from "./GridContext";
5
5
  import { sortBy } from "lodash-es";
6
6
  import { GridBaseRow } from "../components/Grid";
7
7
 
8
8
  interface GridPopoverContextProps {
9
+ props: ICellEditorParams;
9
10
  children: ReactNode;
10
11
  }
11
12
 
12
- export const GridPopoverContextProvider = (props: GridPopoverContextProps) => {
13
+ export const GridPopoverContextProvider = ({ props, children }: GridPopoverContextProps) => {
13
14
  const { getSelectedRows, updatingCells } = useContext(GridContext);
14
- const anchorRef = useRef<Element>();
15
- const propsRef = useRef<PropsType>({} as PropsType);
15
+ const anchorRef = useRef<Element>(props.eGridCell);
16
16
 
17
17
  const [saving, setSaving] = useState(false);
18
18
 
19
- const setProps = useCallback(
20
- (props: ICellEditorParams, multiEdit: boolean) => {
21
- // Then item that is clicked on will always be first in the list
22
- const selectedRows = multiEdit
23
- ? sortBy(getSelectedRows(), (row) => row.id !== props.data.id)
24
- : [props.data as GridBaseRow];
25
- const field = props.colDef?.field ?? "";
19
+ const { colDef } = props;
20
+ const { cellEditorParams } = colDef;
21
+ const multiEdit = cellEditorParams?.multiEdit ?? false;
22
+ // Then item that is clicked on will always be first in the list
23
+ const selectedRows = useMemo(
24
+ () => (multiEdit ? sortBy(getSelectedRows(), (row) => row.id !== props.data.id) : [props.data as GridBaseRow]),
25
+ [getSelectedRows, multiEdit, props.data],
26
+ );
27
+ const field = props.colDef?.field ?? "";
26
28
 
27
- anchorRef.current = props.eGridCell;
28
- propsRef.current = {
29
- value: props.value,
30
- data: props.data,
31
- field,
32
- selectedRows,
33
- updateValue: async (saveFn: (selectedRows: any[]) => Promise<boolean>): Promise<boolean> => {
34
- return !saving && (await updatingCells({ selectedRows, field }, saveFn, setSaving));
35
- },
36
- };
37
- },
38
- [getSelectedRows, saving, updatingCells],
29
+ const updateValue = useCallback(
30
+ async (saveFn: (selectedRows: any[]) => Promise<boolean>): Promise<boolean> =>
31
+ !saving && (await updatingCells({ selectedRows, field }, saveFn, setSaving)),
32
+ [field, saving, selectedRows, updatingCells],
39
33
  );
40
34
 
41
35
  return (
@@ -44,11 +38,14 @@ export const GridPopoverContextProvider = (props: GridPopoverContextProps) => {
44
38
  anchorRef: anchorRef as any as RefObject<Element>,
45
39
  saving,
46
40
  setSaving,
47
- propsRef,
48
- setProps,
41
+ selectedRows,
42
+ field,
43
+ data: props.data,
44
+ value: props.value,
45
+ updateValue,
49
46
  }}
50
47
  >
51
- {props.children}
48
+ {children}
52
49
  </GridPopoverContext.Provider>
53
50
  );
54
51
  };
@@ -34,7 +34,19 @@ export const TextAreaInput = (props: LuiTextAreaInputProps) => {
34
34
  <div className="LuiTextAreaInput-wrapper">
35
35
  {" "}
36
36
  {/* wrapper div used for error styling */}
37
- <textarea id={id} value={props.value} onChange={props.onChange} rows={5} {...props.inputProps} />
37
+ <textarea
38
+ id={id}
39
+ value={props.value}
40
+ onChange={props.onChange}
41
+ rows={5}
42
+ {...props.inputProps}
43
+ onMouseEnter={(e) => {
44
+ if (document.activeElement != e.currentTarget) {
45
+ e.currentTarget.focus();
46
+ e.currentTarget.selectionStart = e.currentTarget.value.length;
47
+ }
48
+ }}
49
+ />
38
50
  </div>
39
51
  </label>
40
52
 
@@ -24,8 +24,6 @@ export interface LuiTextInputProps {
24
24
 
25
25
  placeholder?: string;
26
26
  formatted?: string;
27
-
28
- onMouseEnter?: (e: React.MouseEvent<HTMLTextAreaElement>) => void;
29
27
  }
30
28
 
31
29
  export const TextInputFormatted = (props: LuiTextInputProps): JSX.Element => {
@@ -48,6 +46,9 @@ export const TextInputFormatted = (props: LuiTextInputProps): JSX.Element => {
48
46
  onChange={props.onChange}
49
47
  onFocus={props.onFocus}
50
48
  onClick={props.onClick}
49
+ onMouseEnter={(e) => {
50
+ e.currentTarget.focus();
51
+ }}
51
52
  {...props.inputProps}
52
53
  />
53
54
  <span className={"LuiTextInput-formatted"}>{props.formatted}</span>
@@ -1,28 +1,29 @@
1
- import { useState, useReducer, useEffect, useRef, useMemo, useCallback, useContext } from "react";
1
+ import { useCallback, useContext, useEffect, useMemo, useReducer, useRef, useState } from "react";
2
2
  import { flushSync } from "react-dom";
3
- import { useBEM, useCombinedRef, useLayoutEffect, useItems } from "../hooks";
4
- import { getPositionHelpers, positionMenu, positionContextMenu } from "../positionUtils";
3
+ import { useBEM, useCombinedRef, useItems, useLayoutEffect } from "../hooks";
4
+ import { getPositionHelpers, positionContextMenu, positionMenu } from "../positionUtils";
5
5
  import {
6
- mergeProps,
7
6
  batchedUpdates,
7
+ CloseReason,
8
8
  commonProps,
9
9
  floatEqual,
10
+ FocusPositions,
10
11
  getScrollAncestor,
11
12
  getTransition,
12
- safeCall,
13
+ HoverActionTypes,
13
14
  isMenuOpen,
14
- menuClass,
15
- menuArrowClass,
16
- CloseReason,
17
15
  Keys,
18
- FocusPositions,
19
- HoverActionTypes,
16
+ menuArrowClass,
17
+ menuClass,
18
+ mergeProps,
19
+ safeCall,
20
20
  } from "../utils";
21
21
  import { ControlledMenuProps, MenuDirection } from "../types";
22
22
  import { MenuListItemContext } from "../contexts/MenuListItemContext";
23
23
  import { HoverItemContext } from "../contexts/HoverItemContext";
24
24
  import { MenuListContext } from "../contexts/MenuListContext";
25
25
  import { SettingsContext } from "../contexts/SettingsContext";
26
+ import { debounce } from "lodash-es";
26
27
 
27
28
  export const MenuList = ({
28
29
  ariaLabel,
@@ -75,6 +76,7 @@ export const MenuList = ({
75
76
  const focusRef = useRef<HTMLDivElement>(null);
76
77
  const arrowRef = useRef<HTMLDivElement>(null);
77
78
  const prevOpen = useRef(false);
79
+ const latestWindowSize = useRef({ width: 0, height: 0 });
78
80
  const latestMenuSize = useRef({ width: 0, height: 0 });
79
81
  const latestHandlePosition = useRef(() => {});
80
82
  const { hoverItem, dispatch, updateItems } = useItems(menuRef, focusRef);
@@ -320,6 +322,36 @@ export const MenuList = ({
320
322
  return () => resizeObserver.unobserve(observeTarget);
321
323
  }, [reposition]);
322
324
 
325
+ // Matt added window resize observer
326
+ useEffect(() => {
327
+ if (typeof ResizeObserver !== "function" || reposition === "initial") return;
328
+
329
+ const callback = debounce(() => {
330
+ const { width, height } = menuRef.current.ownerDocument.body.getBoundingClientRect();
331
+ if (width === 0 || height === 0) return;
332
+ if (
333
+ floatEqual(width, latestWindowSize.current.width, 1) &&
334
+ floatEqual(height, latestWindowSize.current.height, 1)
335
+ ) {
336
+ return;
337
+ }
338
+ latestWindowSize.current = { width, height };
339
+ flushSync(() => {
340
+ latestHandlePosition.current();
341
+ forceReposSubmenu();
342
+ });
343
+ }, 250);
344
+
345
+ const resizeObserver = new ResizeObserver(callback);
346
+
347
+ const observeTarget = menuRef.current.ownerDocument.body;
348
+ resizeObserver.observe(observeTarget, { box: "border-box" });
349
+ return () => {
350
+ callback.cancel();
351
+ resizeObserver.unobserve(observeTarget);
352
+ };
353
+ }, [reposition]);
354
+
323
355
  useEffect(() => {
324
356
  if (!isOpen) {
325
357
  dispatch(HoverActionTypes.RESET, undefined, 0);
@@ -1,4 +1,3 @@
1
- // FIXME hacking a default context in here is probably bad
2
1
  import { createContext, MutableRefObject, RefObject } from "react";
3
2
  import { ControlledMenuProps, MenuReposition, MenuViewScroll, RectElement, TransitionFieldType } from "../types";
4
3
 
@@ -17,4 +16,5 @@ interface SettingsContextType extends ControlledMenuProps {
17
16
  viewScroll?: MenuViewScroll;
18
17
  }
19
18
 
19
+ // FIXME hacking a default context in here is probably bad, but the context is mess
20
20
  export const SettingsContext = createContext<SettingsContextType>({} as SettingsContextType);
@@ -3,9 +3,9 @@ import "./FormTest.scss";
3
3
  import { useCallback, useState } from "react";
4
4
  import { LuiAlertModal, LuiAlertModalButtons, LuiButton, LuiTextInput } from "@linzjs/lui";
5
5
  import { wait } from "../../utils/util";
6
- import { CellEditorCommon, CellParams } from "../../components/GridCell";
6
+ import { CellEditorCommon } from "../../components/GridCell";
7
7
  import { useGridPopoverHook } from "../../components/GridPopoverHook";
8
- import { GridBaseRow } from "../../components/Grid";
8
+ import { useGridPopoverContext } from "../../contexts/GridPopoverContext";
9
9
 
10
10
  export interface IFormTestRow {
11
11
  id: number;
@@ -16,28 +16,30 @@ export interface IFormTestRow {
16
16
  distance: number | null;
17
17
  }
18
18
 
19
- export const FormTest = <RowType extends GridBaseRow>(_props: CellEditorCommon): JSX.Element => {
20
- const props = _props as CellParams<RowType>;
21
- const [v1, ...v2] = props.value.split(" ");
19
+ export const FormTest = (props: CellEditorCommon): JSX.Element => {
20
+ const { value } = useGridPopoverContext<IFormTestRow>();
21
+ const [v1, ...v2] = value.split(" ");
22
22
 
23
23
  const [nameType, setNameType] = useState(v1);
24
24
  const [numba, setNumba] = useState(v2.join(" "));
25
25
 
26
- const save = useCallback(async (): Promise<boolean> => {
27
- // eslint-disable-next-line no-console
28
- console.log("onSave", props.selectedRows, nameType, numba);
26
+ const save = useCallback(
27
+ async (selectedRows: IFormTestRow[]): Promise<boolean> => {
28
+ // eslint-disable-next-line no-console
29
+ console.log("onSave", selectedRows, nameType, numba);
29
30
 
30
- // @ts-ignore
31
- props.selectedRows.forEach((row) => (row["name"] = [nameType, numba].join(" ")));
32
- await wait(1000);
31
+ selectedRows.forEach((row) => (row.name = [nameType, numba].join(" ")));
32
+ await wait(1000);
33
33
 
34
- // Close form
35
- return true;
36
- }, [nameType, numba, props.selectedRows]);
34
+ // Close form
35
+ return true;
36
+ },
37
+ [nameType, numba],
38
+ );
37
39
 
38
40
  const [showModal, setShowModal] = useState<boolean>(false);
39
41
 
40
- const { popoverWrapper } = useGridPopoverHook({ className: _props.className, save });
42
+ const { popoverWrapper } = useGridPopoverHook({ className: props.className, save });
41
43
 
42
44
  return popoverWrapper(
43
45
  <>