@linzjs/step-ag-grid 11.0.0 → 12.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.
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": "11.0.0",
5
+ "version": "12.0.0",
6
6
  "keywords": [
7
7
  "aggrid",
8
8
  "ag-grid",
@@ -1,8 +1,8 @@
1
1
  import clsx from "clsx";
2
2
  import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
3
3
  import { AgGridReact } from "ag-grid-react";
4
- import { CellClickedEvent, ColDef } from "ag-grid-community";
5
- import { CellEvent, GridReadyEvent, SelectionChangedEvent } from "ag-grid-community/dist/lib/events";
4
+ import { CellClickedEvent, ColDef, ModelUpdatedEvent } from "ag-grid-community";
5
+ import { AgGridEvent, CellEvent, GridReadyEvent, SelectionChangedEvent } from "ag-grid-community/dist/lib/events";
6
6
  import { GridOptions } from "ag-grid-community/dist/lib/entities/gridOptions";
7
7
  import { difference, isEmpty, last, xorBy } from "lodash-es";
8
8
  import { GridContext } from "../contexts/GridContext";
@@ -20,16 +20,12 @@ export interface GridProps {
20
20
  readOnly?: boolean; // set all editables to false when read only, make all styles black, otherwise style is gray for not editable
21
21
  selectable?: boolean;
22
22
  ["data-testid"]?: string;
23
- quickFilter?: boolean;
24
- quickFilterPlaceholder?: string;
25
- quickFilterValue?: string;
26
23
  domLayout?: GridOptions["domLayout"];
27
24
  externalSelectedItems?: any[];
28
25
  setExternalSelectedItems?: (items: any[]) => void;
29
26
  defaultColDef?: GridOptions["defaultColDef"];
30
27
  columnDefs: ColDef[];
31
28
  rowData: GridOptions["rowData"];
32
- noRowsOverlayText?: string;
33
29
  postSortRows?: GridOptions["postSortRows"];
34
30
  animateRows?: boolean;
35
31
  rowClassRules?: GridOptions["rowClassRules"];
@@ -47,7 +43,6 @@ export const Grid = (params: GridProps): JSX.Element => {
47
43
  gridReady,
48
44
  setGridApi,
49
45
  prePopupOps,
50
- setQuickFilter,
51
46
  ensureRowVisible,
52
47
  selectRowsById,
53
48
  focusByRowById,
@@ -55,10 +50,11 @@ export const Grid = (params: GridProps): JSX.Element => {
55
50
  sizeColumnsToFit,
56
51
  externallySelectedItemsAreInSync,
57
52
  setExternallySelectedItemsAreInSync,
53
+ isExternalFilterPresent,
54
+ doesExternalFilterPass,
58
55
  } = useContext(GridContext);
59
56
  const { checkUpdating } = useContext(GridUpdatingContext);
60
57
 
61
- const [internalQuickFilter, setInternalQuickFilter] = useState("");
62
58
  const lastSelectedIds = useRef<number[]>([]);
63
59
  const [staleGrid, setStaleGrid] = useState(false);
64
60
  const postSortRows = usePostSortRowsHook({ setStaleGrid });
@@ -142,26 +138,6 @@ export const Grid = (params: GridProps): JSX.Element => {
142
138
  setExternallySelectedItemsAreInSync(true);
143
139
  }, [gridReady, params.externalSelectedItems, ensureRowVisible, selectRowsById, setExternallySelectedItemsAreInSync]);
144
140
 
145
- /**
146
- * Synchronise quick filter to grid
147
- */
148
- const updateQuickFilter = useCallback(() => {
149
- if (!gridReady) return;
150
- if (params.quickFilter) {
151
- setQuickFilter(internalQuickFilter);
152
- return;
153
- }
154
- if (params.quickFilterValue == null) return;
155
- setQuickFilter(params.quickFilterValue);
156
- }, [gridReady, internalQuickFilter, params.quickFilter, params.quickFilterValue, setQuickFilter]);
157
-
158
- /**
159
- * Synchronise quick filter to grid
160
- */
161
- useEffect(() => {
162
- updateQuickFilter();
163
- }, [updateQuickFilter]);
164
-
165
141
  const combineEditables =
166
142
  (...editables: (boolean | EditableCallback | undefined)[]) =>
167
143
  (params: EditableCallbackParams): boolean => {
@@ -201,7 +177,7 @@ export const Grid = (params: GridProps): JSX.Element => {
201
177
  maxWidth: 42,
202
178
  suppressSizeToFit: true,
203
179
  checkboxSelection: true,
204
- headerComponent: GridHeaderSelect,
180
+ headerComponent: params.rowSelection === "multiple" ? GridHeaderSelect : null,
205
181
  suppressHeaderKeyboardEvent: (e) => {
206
182
  if ((e.event.key === "Enter" || e.event.key === " ") && !e.event.repeat) {
207
183
  const selectedNodeCount = e.api.getSelectedRows().length;
@@ -220,27 +196,37 @@ export const Grid = (params: GridProps): JSX.Element => {
220
196
  ]
221
197
  : adjustColDefs;
222
198
  }, [
223
- clickSelectorCheckboxWhenContainingCellClicked,
224
199
  params.columnDefs,
225
200
  params.selectable,
201
+ params.rowSelection,
226
202
  params.readOnly,
227
- params.defaultColDef,
203
+ params.defaultColDef?.editable,
204
+ clickSelectorCheckboxWhenContainingCellClicked,
228
205
  ]);
229
206
 
230
207
  const onGridReady = useCallback(
231
208
  (event: GridReadyEvent) => {
232
209
  setGridApi(event.api);
233
210
  synchroniseExternallySelectedItemsToGrid();
234
- updateQuickFilter();
235
211
  },
236
- [setGridApi, synchroniseExternallySelectedItemsToGrid, updateQuickFilter],
212
+ [setGridApi, synchroniseExternallySelectedItemsToGrid],
237
213
  );
238
214
 
239
215
  const noRowsOverlayComponent = useCallback(
240
- () => <span>{params.noRowsOverlayText ?? "There are currently no rows"}</span>,
241
- [params.noRowsOverlayText],
216
+ (event: AgGridEvent) => {
217
+ const hasData = (params.rowData?.length ?? 0) > 0;
218
+ const hasFilteredData = event.api.getDisplayedRowCount() > 0;
219
+ return (
220
+ <span>{!hasData ? "There are currently no rows" : !hasFilteredData ? "All rows have been filtered" : ""}</span>
221
+ );
222
+ },
223
+ [params.rowData?.length],
242
224
  );
243
225
 
226
+ const onModelUpdated = useCallback((event: ModelUpdatedEvent) => {
227
+ event.api.getDisplayedRowCount() === 0 ? event.api.showNoRowsOverlay() : event.api.hideOverlay();
228
+ }, []);
229
+
244
230
  const refreshSelectedRows = useCallback((event: CellEvent): void => {
245
231
  // Force-refresh all selected rows to re-run class function, to update selection highlighting
246
232
  event.api.refreshCells({
@@ -326,20 +312,6 @@ export const Grid = (params: GridProps): JSX.Element => {
326
312
  gridReady && params.rowData && "Grid-ready",
327
313
  )}
328
314
  >
329
- {params.quickFilter && (
330
- <div className="Grid-quickFilter">
331
- <input
332
- aria-label="Search"
333
- className="lui-margin-top-xxs lui-margin-bottom-xxs Grid-quickFilterBox"
334
- type="text"
335
- placeholder={params.quickFilterPlaceholder ?? "Search..."}
336
- value={internalQuickFilter}
337
- onChange={(event): void => {
338
- setInternalQuickFilter(event.target.value);
339
- }}
340
- />
341
- </div>
342
- )}
343
315
  <div style={{ flex: 1 }}>
344
316
  <AgGridReact
345
317
  animateRows={params.animateRows}
@@ -360,12 +332,15 @@ export const Grid = (params: GridProps): JSX.Element => {
360
332
  columnDefs={columnDefs}
361
333
  rowData={params.rowData}
362
334
  noRowsOverlayComponent={noRowsOverlayComponent}
335
+ onModelUpdated={onModelUpdated}
363
336
  onGridReady={onGridReady}
364
337
  onSortChanged={ensureSelectedRowIsVisible}
365
338
  postSortRows={params.postSortRows ?? postSortRows}
366
339
  onSelectionChanged={synchroniseExternalStateToGridSelection}
367
340
  onColumnMoved={params.onColumnMoved}
368
341
  alwaysShowVerticalScroll={params.alwaysShowVerticalScroll}
342
+ isExternalFilterPresent={isExternalFilterPresent}
343
+ doesExternalFilterPass={doesExternalFilterPass}
369
344
  />
370
345
  </div>
371
346
  </div>
@@ -0,0 +1,12 @@
1
+ import { useContext, useEffect } from "react";
2
+ import { GridContext, GridFilterExternal } from "../contexts/GridContext";
3
+
4
+ export const useGridFilter = (filter: GridFilterExternal) => {
5
+ const { addExternalFilter, removeExternalFilter } = useContext(GridContext);
6
+
7
+ useEffect(() => {
8
+ const thisFilter = filter;
9
+ addExternalFilter(thisFilter);
10
+ return () => removeExternalFilter(thisFilter);
11
+ }, [addExternalFilter, filter, removeExternalFilter]);
12
+ };
@@ -0,0 +1,12 @@
1
+ import { ReactNode } from "react";
2
+
3
+ export interface GridWrapperProps {
4
+ children: ReactNode;
5
+ maxHeight?: number | string;
6
+ }
7
+
8
+ export const GridWrapper = ({ children, maxHeight }: GridWrapperProps) => (
9
+ <div className={"Grid-wrapper"} style={{ maxHeight }}>
10
+ {children}
11
+ </div>
12
+ );
@@ -0,0 +1,29 @@
1
+ import { useContext, useEffect, useState } from "react";
2
+ import { GridContext } from "../../contexts/GridContext";
3
+
4
+ export interface GridFilterQuickProps {
5
+ quickFilterPlaceholder?: string;
6
+ defaultValue?: string;
7
+ }
8
+
9
+ export const GridFilterQuick = ({ quickFilterPlaceholder, defaultValue }: GridFilterQuickProps) => {
10
+ const { setQuickFilter } = useContext(GridContext);
11
+ const [quickFilterValue, setQuickFilterValue] = useState(defaultValue ?? "");
12
+
13
+ useEffect(() => {
14
+ setQuickFilter(quickFilterValue);
15
+ }, [quickFilterValue, setQuickFilter]);
16
+
17
+ return (
18
+ <input
19
+ aria-label="Search"
20
+ className="lui-margin-top-xxs lui-margin-bottom-xxs Grid-quickFilterBox"
21
+ type="text"
22
+ placeholder={quickFilterPlaceholder ?? "Search..."}
23
+ value={quickFilterValue}
24
+ onChange={(event): void => {
25
+ setQuickFilterValue(event.target.value);
26
+ }}
27
+ />
28
+ );
29
+ };
@@ -0,0 +1,7 @@
1
+ import { ReactNode } from "react";
2
+
3
+ export interface GridFiltersProps {
4
+ children: ReactNode;
5
+ }
6
+
7
+ export const GridFilters = ({ children }: GridFiltersProps) => <div className="Grid-container-filters">{children}</div>;
@@ -2,6 +2,8 @@ import { createContext } from "react";
2
2
  import { GridApi, RowNode } from "ag-grid-community";
3
3
  import { GridBaseRow } from "../components/Grid";
4
4
 
5
+ export type GridFilterExternal = (data: any, rowNode: RowNode) => boolean;
6
+
5
7
  export interface GridContextType {
6
8
  gridReady: boolean;
7
9
  setGridApi: (gridApi: GridApi | undefined) => void;
@@ -31,6 +33,10 @@ export interface GridContextType {
31
33
  externallySelectedItemsAreInSync: boolean;
32
34
  setExternallySelectedItemsAreInSync: (inSync: boolean) => void;
33
35
  waitForExternallySelectedItemsToBeInSync: () => Promise<void>;
36
+ addExternalFilter: (filter: GridFilterExternal) => void;
37
+ removeExternalFilter: (filter: GridFilterExternal) => void;
38
+ isExternalFilterPresent: () => boolean;
39
+ doesExternalFilterPass: (node: RowNode) => boolean;
34
40
  }
35
41
 
36
42
  export const GridContext = createContext<GridContextType>({
@@ -104,4 +110,18 @@ export const GridContext = createContext<GridContextType>({
104
110
  waitForExternallySelectedItemsToBeInSync: async () => {
105
111
  console.error("no context provider for waitForExternallySelectedItemsToBeInSync");
106
112
  },
113
+ addExternalFilter: () => {
114
+ console.error("no context provider for addExternalFilter");
115
+ },
116
+ removeExternalFilter: () => {
117
+ console.error("no context provider for removeExternalFilter");
118
+ },
119
+ isExternalFilterPresent: () => {
120
+ console.error("no context provider for isExternalFilterPresent");
121
+ return false;
122
+ },
123
+ doesExternalFilterPass: () => {
124
+ console.error("no context provider for doesExternalFilterPass");
125
+ return true;
126
+ },
107
127
  });
@@ -1,7 +1,7 @@
1
- import { ReactElement, ReactNode, useCallback, useContext, useRef, useState } from "react";
1
+ import { ReactElement, ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
2
2
  import { ColDef, GridApi, RowNode } from "ag-grid-community";
3
- import { GridContext } from "./GridContext";
4
- import { defer, delay, difference, isEmpty, last, sortBy } from "lodash-es";
3
+ import { GridContext, GridFilterExternal } from "./GridContext";
4
+ import { debounce, defer, delay, difference, isEmpty, last, remove, sortBy } from "lodash-es";
5
5
  import { isNotEmpty, wait } from "../utils/util";
6
6
  import { GridUpdatingContext } from "./GridUpdatingContext";
7
7
  import { GridBaseRow } from "../components/Grid";
@@ -20,14 +20,30 @@ export const GridContextProvider = (props: GridContextProps): ReactElement => {
20
20
  const { modifyUpdating } = useContext(GridUpdatingContext);
21
21
  const [gridApi, _setGridApi] = useState<GridApi>();
22
22
  const [gridReady, setGridReady] = useState(false);
23
+ const [quickFilter, setQuickFilter] = useState("");
23
24
  const idsBeforeUpdate = useRef<number[]>([]);
24
25
  const prePopupFocusedCell = useRef<CellPosition>();
25
26
  const [externallySelectedItemsAreInSync, setExternallySelectedItemsAreInSync] = useState(false);
27
+ const externalFilters = useRef<GridFilterExternal[]>([]);
26
28
 
27
- const setGridApi = useCallback((gridApi: GridApi | undefined) => {
28
- _setGridApi(gridApi);
29
- setGridReady(!!gridApi);
30
- }, []);
29
+ /**
30
+ * Set quick filter directly on grid, based on previously save quickFilter state.
31
+ */
32
+ useEffect(() => {
33
+ gridApi?.setQuickFilter(quickFilter);
34
+ }, [gridApi, quickFilter]);
35
+
36
+ /**
37
+ * Set the grid api when the grid is ready.
38
+ */
39
+ const setGridApi = useCallback(
40
+ (gridApi: GridApi | undefined) => {
41
+ _setGridApi(gridApi);
42
+ gridApi?.setQuickFilter(quickFilter);
43
+ setGridReady(!!gridApi);
44
+ },
45
+ [quickFilter],
46
+ );
31
47
 
32
48
  /**
33
49
  * Wraps things that require gridApi in common handling, for when gridApi not present.
@@ -52,16 +68,6 @@ export const GridContextProvider = (props: GridContextProps): ReactElement => {
52
68
  prePopupFocusedCell.current = gridApi?.getFocusedCell() ?? undefined;
53
69
  }, [gridApi]);
54
70
 
55
- /**
56
- * Set the quick filter value to grid.
57
- */
58
- const setQuickFilter = useCallback(
59
- (quickFilter: string) => {
60
- gridApiOp((gridApi) => gridApi.setQuickFilter(quickFilter));
61
- },
62
- [gridApiOp],
63
- );
64
-
65
71
  /**
66
72
  * Get all row id's in grid.
67
73
  */
@@ -376,6 +382,30 @@ export const GridContextProvider = (props: GridContextProps): ReactElement => {
376
382
  }
377
383
  }, [externallySelectedItemsAreInSync]);
378
384
 
385
+ const onFilterChanged = useMemo(
386
+ () =>
387
+ debounce(() => {
388
+ gridApi && gridApi.onFilterChanged();
389
+ }, 200),
390
+ [gridApi],
391
+ );
392
+
393
+ const addExternalFilter = (filter: GridFilterExternal) => {
394
+ externalFilters.current.push(filter);
395
+ onFilterChanged();
396
+ };
397
+
398
+ const removeExternalFilter = (filter: GridFilterExternal) => {
399
+ remove(externalFilters.current, (v) => v === filter);
400
+ onFilterChanged();
401
+ };
402
+
403
+ const isExternalFilterPresent = (): boolean => externalFilters.current.length > 0;
404
+
405
+ const doesExternalFilterPass = (node: RowNode): boolean => {
406
+ return externalFilters.current.every((filter) => filter(node.data, node));
407
+ };
408
+
379
409
  return (
380
410
  <GridContext.Provider
381
411
  value={{
@@ -402,6 +432,10 @@ export const GridContextProvider = (props: GridContextProps): ReactElement => {
402
432
  externallySelectedItemsAreInSync,
403
433
  setExternallySelectedItemsAreInSync,
404
434
  waitForExternallySelectedItemsToBeInSync,
435
+ addExternalFilter,
436
+ removeExternalFilter,
437
+ isExternalFilterPresent,
438
+ doesExternalFilterPass,
405
439
  }}
406
440
  >
407
441
  {props.children}
@@ -95,7 +95,6 @@ const GridPopoverEditBearingTemplate: ComponentStory<typeof Grid> = (props: Grid
95
95
 
96
96
  return (
97
97
  <Grid
98
- quickFilter={true}
99
98
  data-testid={"bearingsTestTable"}
100
99
  {...props}
101
100
  readOnly={false}
@@ -7,15 +7,18 @@ import { ComponentMeta, ComponentStory } from "@storybook/react/dist/ts3.9/clien
7
7
  import { GridUpdatingContextProvider } from "../../contexts/GridUpdatingContextProvider";
8
8
  import { GridContextProvider } from "../../contexts/GridContextProvider";
9
9
  import { Grid, GridProps } from "../../components/Grid";
10
- import { useMemo, useState } from "react";
10
+ import { useCallback, useMemo, useState } from "react";
11
11
  import { wait } from "../../utils/util";
12
12
  import { GridPopoverMenu } from "../../components/gridPopoverEdit/GridPopoverMenu";
13
13
  import { ColDefT, GridCell } from "../../components/GridCell";
14
14
  import { GridPopoverMessage } from "../../components/gridPopoverEdit/GridPopoverMessage";
15
- import { MenuOption } from "../../components/gridForm/GridFormPopoverMenu";
16
15
  import { GridFormSubComponentTextInput } from "../../components/gridForm/GridFormSubComponentTextInput";
17
16
  import { GridFormSubComponentTextArea } from "../../components/gridForm/GridFormSubComponentTextArea";
18
17
  import { GridIcon } from "../../components/GridIcon";
18
+ import { useGridFilter } from "../../components/GridFilter";
19
+ import { GridFilterQuick } from "../../components/gridFilter/GridFilterQuick";
20
+ import { GridFilters } from "../../components/gridFilter/GridFilters";
21
+ import { GridWrapper } from "../../components/GridWrapper";
19
22
 
20
23
  export default {
21
24
  title: "Components / Grids",
@@ -29,7 +32,7 @@ export default {
29
32
  },
30
33
  decorators: [
31
34
  (Story) => (
32
- <div style={{ width: 1024, height: 400 }}>
35
+ <div style={{ width: 1024, height: 400, display: "flex", flexDirection: "column" }}>
33
36
  <GridUpdatingContextProvider>
34
37
  <GridContextProvider>
35
38
  <Story />
@@ -173,7 +176,7 @@ const GridReadOnlyTemplate: ComponentStory<typeof Grid> = (props: GridProps) =>
173
176
  <GridFormSubComponentTextArea placeholder={"Other"} maxLength={5} required defaultValue={""} />
174
177
  ),
175
178
  },
176
- ] as MenuOption<ITestRow>[];
179
+ ];
177
180
  },
178
181
  },
179
182
  },
@@ -201,17 +204,46 @@ const GridReadOnlyTemplate: ComponentStory<typeof Grid> = (props: GridProps) =>
201
204
  ]);
202
205
 
203
206
  return (
204
- <Grid
205
- {...props}
206
- selectable={true}
207
- externalSelectedItems={externalSelectedItems}
208
- setExternalSelectedItems={setExternalSelectedItems}
209
- columnDefs={columnDefs}
210
- rowData={rowData}
211
- domLayout={"autoHeight"}
212
- autoSelectFirstRow={true}
213
- />
207
+ <GridWrapper maxHeight={200}>
208
+ <GridFilters>
209
+ <GridFilterQuick quickFilterPlaceholder={"Custom placeholder..."} />
210
+ <div>
211
+ Custom filter: Age less than:
212
+ <GridFilterLessThan field={"age"} />
213
+ </div>
214
+ </GridFilters>
215
+ <Grid
216
+ {...props}
217
+ selectable={true}
218
+ autoSelectFirstRow={true}
219
+ externalSelectedItems={externalSelectedItems}
220
+ setExternalSelectedItems={setExternalSelectedItems}
221
+ columnDefs={columnDefs}
222
+ rowData={rowData}
223
+ />
224
+ </GridWrapper>
214
225
  );
215
226
  };
216
227
 
228
+ const GridFilterLessThan = (props: { field: keyof ITestRow }): JSX.Element => {
229
+ const [value, setValue] = useState<number>();
230
+
231
+ const filter = useCallback(
232
+ (data: ITestRow): boolean => value == null || data[props.field] < value,
233
+ [props.field, value],
234
+ );
235
+
236
+ useGridFilter(filter);
237
+
238
+ const updateValue = (newValue: string) => {
239
+ try {
240
+ setValue(newValue.trim() == "" ? undefined : parseInt(newValue));
241
+ } catch {
242
+ // ignore number parse exception
243
+ }
244
+ };
245
+
246
+ return <input type={"text"} defaultValue={value} onChange={(e) => updateValue(e.target.value)} />;
247
+ };
248
+
217
249
  export const ReadOnlySingleSelection = GridReadOnlyTemplate.bind({});
@@ -27,23 +27,26 @@ const updateValue = jest
27
27
  .mockImplementation((saveFn: (selectedRows: any[]) => Promise<boolean>, _tabDirection: 1 | 0 | -1) => saveFn([]));
28
28
 
29
29
  const onSave = jest.fn<Promise<boolean>, [GridFormMultiSelectSaveProps<any>]>().mockImplementation(async () => true);
30
+ const onSelectFilter = jest.fn();
30
31
 
31
- const options = Object.freeze([
32
- { label: "Zero", value: 0 },
33
- { label: "One", value: 1 },
34
- {
35
- label: "Sub component",
36
- value: 2,
37
- subComponent: () => (
38
- <GridFormSubComponentTextInput placeholder={"Text input"} maxLength={5} required defaultValue={""} />
39
- ),
40
- },
41
- { label: "Other", value: 3 },
42
- ]) as MultiSelectOption[];
43
-
32
+ let options: MultiSelectOption[] = [];
44
33
  const Template: ComponentStory<typeof GridFormMultiSelect> = (props) => {
34
+ options = [
35
+ { label: "Zero", value: 0 },
36
+ { label: "One", value: 1 },
37
+ {
38
+ label: "Sub component",
39
+ value: 2,
40
+ subComponent: () => (
41
+ <GridFormSubComponentTextInput placeholder={"Text input"} maxLength={5} required defaultValue={""} />
42
+ ),
43
+ },
44
+ { label: "Other", value: 3 },
45
+ ];
45
46
  const config: GridFormMultiSelectProps<any> = {
46
47
  filtered: true,
48
+ onSelectFilter,
49
+ filterHelpText: "Press enter to add free-text",
47
50
  onSave,
48
51
  options,
49
52
  };
@@ -76,6 +79,10 @@ const Template: ComponentStory<typeof GridFormMultiSelect> = (props) => {
76
79
 
77
80
  export const GridFormMultiSelectInteractions_ = Template.bind({});
78
81
  GridFormMultiSelectInteractions_.play = async ({ canvasElement }) => {
82
+ updateValue.mockClear();
83
+ onSave.mockClear();
84
+ onSelectFilter.mockClear();
85
+
79
86
  const canvas = within(canvasElement);
80
87
 
81
88
  const getOption = (name: RegExp | string) => canvas.findByRole("menuitem", { name });
@@ -150,4 +157,30 @@ GridFormMultiSelectInteractions_.play = async ({ canvasElement }) => {
150
157
  expect(canvas.queryByText("Zero")).not.toBeInTheDocument();
151
158
  expect(canvas.queryByText("Sub component")).not.toBeInTheDocument();
152
159
  expect(canvas.queryByText("Other")).not.toBeInTheDocument();
160
+
161
+ userEvent.type(filterText, "x");
162
+ expect(canvas.queryByText("One")).not.toBeInTheDocument();
163
+ expect(canvas.queryByText("No Options")).toBeInTheDocument();
164
+
165
+ // Check enter works to add custom free-text
166
+ userEvent.type(filterText, "{Enter}");
167
+ expect(onSelectFilter).toHaveBeenCalledWith({
168
+ filter: "onx",
169
+ options: [
170
+ {
171
+ ...options[0],
172
+ checked: true,
173
+ },
174
+ {
175
+ ...options[1],
176
+ },
177
+ {
178
+ ...options[2],
179
+ checked: true,
180
+ },
181
+ {
182
+ ...options[3],
183
+ },
184
+ ],
185
+ });
153
186
  };
@@ -9,6 +9,12 @@
9
9
  flex-direction: column;
10
10
  }
11
11
 
12
+ .Grid-wrapper {
13
+ display: flex;
14
+ flex-direction: column;
15
+ flex: 1;
16
+ }
17
+
12
18
  .Grid-sortIsStale {
13
19
  span.ag-icon.ag-icon-desc::after {
14
20
  content: "*";
@@ -19,12 +25,16 @@
19
25
  }
20
26
  }
21
27
 
22
- .Grid-quickFilter {
28
+ .Grid-container-filters {
23
29
  width: 100%;
24
30
  margin-bottom: 16px;
25
31
  flex: 0;
32
+ display: flex;
33
+ flex-direction: row;
34
+ grid-column-gap: 1em;
26
35
  }
27
36
 
37
+
28
38
  .Grid-quickFilterBox {
29
39
  width: 100%;
30
40
  height: 48px;