@linzjs/step-ag-grid 30.2.0 → 30.4.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": "30.2.0",
5
+ "version": "30.4.0",
6
6
  "keywords": [
7
7
  "aggrid",
8
8
  "ag-grid",
@@ -26,6 +26,8 @@ interface FilterUIProps {
26
26
 
27
27
  const EMPTY_KEY = '__EMPTY__';
28
28
  const DEFAULT_EMPTY_LABEL = '-';
29
+ const filterStateMap = new Map<string, boolean>();
30
+ const filterClickCountMap = new Map<string, number>();
29
31
 
30
32
  const FilterUI: React.FC<FilterUIProps> = ({
31
33
  allValues,
@@ -76,7 +78,6 @@ export class GridFilterColumnsMultiSelect implements IFilterComp {
76
78
  private reactRoot: Root | null = null;
77
79
  private filterButtonClickListener: ((e: Event) => void) | null = null;
78
80
  private filterButton: Element | null = null;
79
- private hasOpened = false;
80
81
 
81
82
  private normalizeCellValue(value: unknown): string {
82
83
  if (typeof value === 'string') return value.trim() === '' ? EMPTY_KEY : value;
@@ -134,13 +135,25 @@ export class GridFilterColumnsMultiSelect implements IFilterComp {
134
135
 
135
136
  if (this.filterButton) {
136
137
  this.filterButtonClickListener = (e: Event) => {
137
- if (this.hasOpened) {
138
- e.stopPropagation();
138
+ const clickCount = filterClickCountMap.get(columnId) || 0;
139
+ filterClickCountMap.set(columnId, clickCount + 1);
140
+
141
+ if (clickCount === 0) {
142
+ e.stopImmediatePropagation();
143
+ e.preventDefault();
144
+ this.params.api.hidePopupMenu();
145
+ filterStateMap.set(columnId, false);
146
+ return;
147
+ }
148
+
149
+ const isOpen = filterStateMap.get(columnId) || false;
150
+ if (isOpen) {
151
+ e.stopImmediatePropagation();
139
152
  e.preventDefault();
140
153
  this.params.api.hidePopupMenu();
141
- this.hasOpened = false;
154
+ filterStateMap.set(columnId, false);
142
155
  } else {
143
- this.hasOpened = true;
156
+ filterStateMap.set(columnId, true);
144
157
  }
145
158
  };
146
159
 
@@ -16,6 +16,7 @@ export interface GridFormInlineTextInput<TData extends GridBaseRow>
16
16
  placeholder?: string;
17
17
  units?: string;
18
18
  onSave?: (props: { selectedRows: TData[]; selectedRowIds: TData['id'][]; value: string }) => MaybePromise<boolean>;
19
+ onChange?: (props: { selectedRows: TData[]; selectedRowIds: TData['id'][]; value: string }) => void;
19
20
  helpText?: string;
20
21
  }
21
22
 
@@ -103,7 +104,14 @@ export const GridFormInlineTextInput = <TData extends GridBaseRow>(props: GridFo
103
104
  ref={inputRef}
104
105
  type={'text'}
105
106
  value={value}
106
- onChange={(e) => setValue(e.target.value)}
107
+ onChange={(e) => {
108
+ setValue(e.target.value);
109
+ if (props.onChange) {
110
+ const selectedRows = getSelectedRows<TData>();
111
+ const selectedRowIds = selectedRows.map((data) => data.id);
112
+ props.onChange({ selectedRows, selectedRowIds, value: e.target.value });
113
+ }
114
+ }}
107
115
  placeholder={props.placeholder ?? 'Type here'}
108
116
  onBlur={() => {
109
117
  void triggerSave(CloseReason.BLUR);
@@ -3,8 +3,9 @@ import '../../styles/index.scss';
3
3
  import '@linzjs/lui/dist/scss/base.scss';
4
4
  import '@linzjs/lui/dist/fonts';
5
5
 
6
- import { Meta, StoryFn } from '@storybook/react-vite';
6
+ import type { Meta, StoryFn } from '@storybook/react-vite';
7
7
  import { useCallback, useContext, useMemo, useState } from 'react';
8
+ import { expect, screen, userEvent, within } from 'storybook/test';
8
9
 
9
10
  import {
10
11
  ActionButton,
@@ -27,6 +28,11 @@ interface IFormTestRow {
27
28
  text2: string | null;
28
29
  }
29
30
 
31
+ const initialRows: IFormTestRow[] = [
32
+ { id: 1000, text1: 'a text1', text2: 'a text2' },
33
+ { id: 1001, text1: 'b text1', text2: 'b text2' },
34
+ ];
35
+
30
36
  export default {
31
37
  title: 'Components / Grids',
32
38
  component: Grid,
@@ -42,11 +48,6 @@ export default {
42
48
  <Story />
43
49
  </GridContextProvider>
44
50
  </GridUpdatingContextProvider>
45
- <GridUpdatingContextProvider>
46
- <GridContextProvider>
47
- <Story />
48
- </GridContextProvider>
49
- </GridUpdatingContextProvider>
50
51
  </div>
51
52
  ),
52
53
  ],
@@ -55,18 +56,7 @@ export default {
55
56
  const GridInlineTextTemplate: StoryFn<typeof Grid<IFormTestRow>> = (props: GridProps<IFormTestRow>) => {
56
57
  const { selectRowsWithFlashDiff } = useContext(GridContext);
57
58
  const [externalSelectedItems, setExternalSelectedItems] = useState<any[]>([]);
58
- const [rowData, setRowData] = useState([
59
- {
60
- id: 1000,
61
- text1: 'a text1',
62
- text2: 'a text2',
63
- },
64
- {
65
- id: 1001,
66
- text1: 'b text1',
67
- text2: 'b text2',
68
- },
69
- ] as IFormTestRow[]);
59
+ const [rowData, setRowData] = useState<IFormTestRow[]>(initialRows);
70
60
 
71
61
  const columnDefs: ColDefT<IFormTestRow>[] = useMemo(
72
62
  () => [
@@ -183,3 +173,94 @@ const GridInlineTextTemplate: StoryFn<typeof Grid<IFormTestRow>> = (props: GridP
183
173
 
184
174
  export const _EditInlineText = GridInlineTextTemplate.bind({});
185
175
  _EditInlineText.play = waitForGridReady;
176
+
177
+ const _InlineText_ChangeEventsTemplate: StoryFn<typeof Grid<IFormTestRow>> = (props: GridProps<IFormTestRow>) => {
178
+ const [externalSelectedItems, setExternalSelectedItems] = useState<any[]>([]);
179
+ const [isDirty, setIsDirty] = useState(false);
180
+ const [rowData, setRowData] = useState<IFormTestRow[]>(initialRows);
181
+
182
+ // Use a named handler for onChange
183
+ const handleInlineEditChange = () => {
184
+ setIsDirty(true);
185
+ };
186
+
187
+ const columnDefs: ColDefT<IFormTestRow>[] = useMemo(
188
+ () => [
189
+ GridCell({
190
+ field: 'id',
191
+ headerName: 'Id',
192
+ }),
193
+ GridInlineTextInput(
194
+ {
195
+ field: 'text1',
196
+ headerName: 'Text input 1',
197
+ },
198
+ {
199
+ editorParams: {
200
+ placeholder: 'Enter some text...',
201
+ onChange: handleInlineEditChange,
202
+ onSave: ({ selectedRowIds, value }) => {
203
+ setRowData(rowData.map((data) => (selectedRowIds.includes(data.id) ? { ...data, text1: value } : data)));
204
+ return true;
205
+ },
206
+ },
207
+ },
208
+ ),
209
+ GridInlineTextInput(
210
+ {
211
+ field: 'text2',
212
+ headerName: 'Text input 2',
213
+ },
214
+ {
215
+ editorParams: {
216
+ placeholder: 'Enter some text...',
217
+ onChange: handleInlineEditChange,
218
+ onSave: ({ selectedRowIds, value }) => {
219
+ setRowData(rowData.map((data) => (selectedRowIds.includes(data.id) ? { ...data, text2: value } : data)));
220
+ return true;
221
+ },
222
+ },
223
+ },
224
+ ),
225
+ ],
226
+ [rowData],
227
+ );
228
+
229
+ return (
230
+ <>
231
+ <Grid
232
+ {...props}
233
+ selectable={true}
234
+ hideSelectColumn={true}
235
+ externalSelectedItems={externalSelectedItems}
236
+ setExternalSelectedItems={setExternalSelectedItems}
237
+ columnDefs={columnDefs}
238
+ rowData={rowData}
239
+ domLayout={'autoHeight'}
240
+ defaultColDef={{ minWidth: 70 }}
241
+ sizeColumns={'auto'}
242
+ singleClickEdit={true}
243
+ enableMultilineBulkEdit={true}
244
+ />
245
+ <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 8 }}>
246
+ <button type="button" data-testid="inline-edit-save" disabled={!isDirty} onClick={() => setIsDirty(false)}>
247
+ Save
248
+ </button>
249
+ </div>
250
+ </>
251
+ );
252
+ };
253
+
254
+ export const _InlineText_ChangeEvents = _InlineText_ChangeEventsTemplate.bind({});
255
+ _InlineText_ChangeEvents.play = async (context) => {
256
+ await waitForGridReady(context);
257
+ const canvas = within(context.canvasElement);
258
+ const saveButtons = canvas.getAllByTestId('inline-edit-save');
259
+ const saveButton = saveButtons[0];
260
+ await expect(saveButton).toBeDisabled();
261
+ const textCells = canvas.getAllByText('a text1');
262
+ await userEvent.click(textCells[0]);
263
+ const input = await screen.findByPlaceholderText('Enter some text...');
264
+ await userEvent.type(input, 'X');
265
+ await expect(saveButton).toBeEnabled();
266
+ };