@linzjs/step-ag-grid 29.13.0 → 29.14.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 (44) hide show
  1. package/dist/index.css +26 -2
  2. package/dist/src/components/Grid.d.ts +36 -61
  3. package/dist/src/components/GridRangeSelectContextMenu.d.ts +10 -0
  4. package/dist/src/components/gridForm/GridFormDropDown.d.ts +1 -1
  5. package/dist/src/components/gridForm/GridFormPopoverMenu.d.ts +2 -1
  6. package/dist/src/components/gridHook/useGridContextMenu.d.ts +12 -7
  7. package/dist/src/components/gridHook/useGridCopy.d.ts +15 -0
  8. package/dist/src/components/gridHook/useGridCopySettings.d.ts +11 -0
  9. package/dist/src/components/gridHook/useGridRangeSelection.d.ts +25 -0
  10. package/dist/src/components/gridPopoverEdit/GridPopoverEditBearing.d.ts +2 -0
  11. package/dist/src/components/types.d.ts +1 -0
  12. package/dist/src/contexts/GridContext.d.ts +1 -0
  13. package/dist/src/lui/timeoutHook.d.ts +0 -6
  14. package/dist/src/lui/tsUtils.d.ts +5 -0
  15. package/dist/src/utils/__tests__/random.ts +19 -0
  16. package/dist/src/utils/util.d.ts +1 -0
  17. package/dist/step-ag-grid.cjs +560 -79
  18. package/dist/step-ag-grid.cjs.map +1 -1
  19. package/dist/step-ag-grid.esm.js +561 -82
  20. package/dist/step-ag-grid.esm.js.map +1 -1
  21. package/package.json +1 -1
  22. package/src/components/Grid.tsx +249 -179
  23. package/src/components/GridRangeSelectContextMenu.tsx +73 -0
  24. package/src/components/gridForm/GridFormDropDown.tsx +1 -3
  25. package/src/components/gridForm/GridFormPopoverMenu.tsx +2 -1
  26. package/src/components/gridHook/useGridContextMenu.tsx +22 -9
  27. package/src/components/gridHook/useGridCopy.ts +279 -0
  28. package/src/components/gridHook/useGridCopySettings.ts +28 -0
  29. package/src/components/gridHook/useGridRangeSelection.ts +235 -0
  30. package/src/components/gridPopoverEdit/GridPopoverEditBearing.ts +3 -0
  31. package/src/components/types.ts +2 -0
  32. package/src/contexts/GridContext.tsx +2 -0
  33. package/src/contexts/GridContextProvider.tsx +5 -2
  34. package/src/lui/timeoutHook.tsx +0 -19
  35. package/src/lui/tsUtils.ts +8 -0
  36. package/src/stories/grid/GridCopy.stories.tsx +175 -0
  37. package/src/stories/grid/GridPopoverEditBearing.stories.tsx +4 -4
  38. package/src/stories/grid/GridReadOnly.stories.tsx +1 -0
  39. package/src/stories/grid/interactions/GridKeyboardInteractions.stories.tsx +2 -2
  40. package/src/styles/ContextMenu.scss +61 -0
  41. package/src/styles/Grid.scss +26 -2
  42. package/src/utils/__tests__/random.ts +19 -0
  43. package/src/utils/bearing.ts +2 -2
  44. package/src/utils/util.ts +2 -0
@@ -38,22 +38,3 @@ export const useTimeoutHook = () => {
38
38
 
39
39
  return invoke;
40
40
  };
41
-
42
- interface IntervalHookProps {
43
- timeoutMs: number;
44
- callback: () => void;
45
- }
46
-
47
- export const useIntervalHook = ({ callback, timeoutMs }: IntervalHookProps) => {
48
- const callbackRef = useRef(callback);
49
- callbackRef.current = callback;
50
-
51
- useEffect(() => {
52
- const interval = setInterval(() => {
53
- callbackRef.current && callbackRef.current();
54
- }, timeoutMs);
55
- return () => {
56
- clearInterval(interval);
57
- };
58
- }, [timeoutMs]);
59
- };
@@ -0,0 +1,8 @@
1
+ export type KeysOfType<TObject, TValue> = {
2
+ [K in keyof TObject]: TObject[K] extends TValue ? K : never;
3
+ }[keyof TObject];
4
+
5
+ export const typedKeys = <T extends object>(obj: T): (keyof T)[] => Object.keys(obj) as (keyof T)[];
6
+
7
+ export const typedEntries = <T extends object>(obj: T): { [K in keyof T]-?: [K, T[K]] }[keyof T][] =>
8
+ Object.entries(obj) as { [K in keyof T]-?: [K, T[K]] }[keyof T][];
@@ -0,0 +1,175 @@
1
+ import '../../styles/GridTheme.scss';
2
+ import '../../styles/index.scss';
3
+ import '@linzjs/lui/dist/scss/base.scss';
4
+ import '@linzjs/lui/dist/fonts';
5
+
6
+ import { LuiMessagingContextProvider } from '@linzjs/lui';
7
+ import { Meta, StoryFn } from '@storybook/react-vite';
8
+ import { useMemo, useState } from 'react';
9
+
10
+ import {
11
+ ColDefT,
12
+ Grid,
13
+ GridCell,
14
+ GridCellBearingValueFormatter,
15
+ GridContextProvider,
16
+ GridFilterQuick,
17
+ GridFilters,
18
+ GridPopoverMenu,
19
+ GridProps,
20
+ GridUpdatingContextProvider,
21
+ GridWrapper,
22
+ } from '../..';
23
+ import { GridFilterColumnsToggle, GridFilterDownloadCsvButton } from '../../components';
24
+ import { GridCellFiller } from '../../components/GridCellFiller';
25
+ import { SeededRandomForTests } from '../../utils/__tests__/random';
26
+ import { waitForGridReady } from '../../utils/__tests__/storybookTestUtil';
27
+
28
+ export default {
29
+ title: 'Components / Grids',
30
+ component: Grid,
31
+ args: {
32
+ quickFilter: true,
33
+ quickFilterValue: '',
34
+ quickFilterPlaceholder: 'Quick filter...',
35
+ selectable: false,
36
+ rowSelection: 'multiple',
37
+ }, // Storybook hangs otherwise
38
+ parameters: {
39
+ docs: {
40
+ source: {
41
+ type: 'code',
42
+ },
43
+ },
44
+ },
45
+ decorators: [
46
+ (Story) => (
47
+ <LuiMessagingContextProvider version="v2">
48
+ <div style={{ maxWidth: 1024, height: '80vh', display: 'flex', flexDirection: 'column' }}>
49
+ <GridUpdatingContextProvider>
50
+ <GridContextProvider>
51
+ <Story />
52
+ </GridContextProvider>
53
+ </GridUpdatingContextProvider>
54
+ </div>
55
+ </LuiMessagingContextProvider>
56
+ ),
57
+ ],
58
+ } as Meta<typeof Grid>;
59
+
60
+ interface ITestRow {
61
+ id: number;
62
+ position?: string;
63
+ age: number;
64
+ height: string;
65
+ bearing: string | number | null;
66
+ }
67
+
68
+ const GridCopyTemplate: StoryFn<typeof Grid<ITestRow>> = (props: GridProps<ITestRow>) => {
69
+ const [externalSelectedIds, setExternalSelectedIds] = useState<number[]>([]);
70
+ const columnDefs: ColDefT<ITestRow>[] = useMemo(
71
+ () => [
72
+ GridCell({
73
+ field: 'id',
74
+ headerName: 'Id',
75
+ lockVisible: true,
76
+ }),
77
+ GridCell<ITestRow, ITestRow['position']>({
78
+ field: 'position',
79
+ headerName: 'Position',
80
+ cellRendererParams: {
81
+ error: ({ value }) => value === 'Manager' && 'Managers need management',
82
+ warning: ({ value }) => value === 'Tester' && 'Testers are testing',
83
+ info: ({ value }) => value === 'Developer' && 'Developers are awesome',
84
+ },
85
+ }),
86
+ {
87
+ headerName: 'Metrics',
88
+ marryChildren: true,
89
+ children: [
90
+ GridCell<ITestRow, ITestRow['age']>({
91
+ field: 'age',
92
+ headerName: 'Age',
93
+ }),
94
+ GridCell<ITestRow, ITestRow['height']>({
95
+ field: 'height',
96
+ headerName: 'Height',
97
+ }),
98
+ ],
99
+ },
100
+ GridCell({
101
+ field: 'bearing',
102
+ headerName: 'Bearing',
103
+ valueFormatter: GridCellBearingValueFormatter,
104
+ }),
105
+ GridCellFiller(),
106
+ GridPopoverMenu(
107
+ {},
108
+ {
109
+ editorParams: {
110
+ options: () => [
111
+ {
112
+ label: 'Test menu',
113
+ action: () => {},
114
+ },
115
+ ],
116
+ },
117
+ },
118
+ ),
119
+ ],
120
+ [],
121
+ );
122
+
123
+ const [rowData] = useState<ITestRow[]>(() => {
124
+ const random = new SeededRandomForTests(1000);
125
+ let id = 1000;
126
+ const positions = [
127
+ undefined,
128
+ 'Tester',
129
+ 'Developer',
130
+ 'Lawyer',
131
+ 'Barrista """',
132
+ 'Manager',
133
+ 'CEO',
134
+ 'CTO',
135
+ 'Architect',
136
+ ];
137
+ const result: ITestRow[] = [];
138
+ for (let i = 0; i < 1000; i++) {
139
+ result.push({
140
+ id: id++,
141
+ position: random.fromArray(positions),
142
+ age: 30,
143
+ height: `${random.next(3) + 3}'${random.next(12)}"`,
144
+ bearing: Math.random() * 360,
145
+ });
146
+ }
147
+ return result;
148
+ });
149
+
150
+ return (
151
+ <GridWrapper>
152
+ <GridFilters>
153
+ <GridFilterQuick />
154
+ <GridFilterColumnsToggle />
155
+ <GridFilterDownloadCsvButton fileName={'readOnlyGrid'} />
156
+ </GridFilters>
157
+ <Grid
158
+ data-testid={'readonly'}
159
+ {...props}
160
+ enableRangeSelection={true}
161
+ selectable={true}
162
+ enableClickSelection={true}
163
+ enableSelectionWithoutKeys={true}
164
+ autoSelectFirstRow={true}
165
+ externalSelectedIds={externalSelectedIds}
166
+ setExternalSelectedIds={setExternalSelectedIds}
167
+ columnDefs={columnDefs}
168
+ rowData={rowData}
169
+ />
170
+ </GridWrapper>
171
+ );
172
+ };
173
+
174
+ export const GridCopy = GridCopyTemplate.bind({});
175
+ GridCopy.play = waitForGridReady;
@@ -51,8 +51,8 @@ interface ITestRow {
51
51
  bearing: string | number | null;
52
52
  }
53
53
 
54
- const GridPopoverEditBearingTemplate: StoryFn<typeof Grid> = (props: GridProps) => {
55
- const [externalSelectedItems, setExternalSelectedItems] = useState<any[]>([]);
54
+ const GridPopoverEditBearingTemplate: StoryFn<typeof Grid<ITestRow>> = (props: GridProps<ITestRow>) => {
55
+ const [externalSelectedIds, setExternalSelectedIds] = useState<number[]>([]);
56
56
  const columnDefs: ColDefT<ITestRow>[] = useMemo(
57
57
  () => [
58
58
  GridCell({
@@ -111,8 +111,8 @@ const GridPopoverEditBearingTemplate: StoryFn<typeof Grid> = (props: GridProps)
111
111
  data-testid={'bearingsTestTable'}
112
112
  {...props}
113
113
  readOnly={false}
114
- externalSelectedItems={externalSelectedItems}
115
- setExternalSelectedItems={setExternalSelectedItems}
114
+ externalSelectedIds={externalSelectedIds}
115
+ setExternalSelectedIds={setExternalSelectedIds}
116
116
  columnDefs={columnDefs}
117
117
  rowData={rowData}
118
118
  domLayout={'autoHeight'}
@@ -244,6 +244,7 @@ const GridReadOnlyTemplate: StoryFn<typeof Grid<ITestRow>> = (props: GridProps<I
244
244
  <Grid
245
245
  data-testid={'readonly'}
246
246
  {...props}
247
+ enableRangeSelection={true}
247
248
  selectable={true}
248
249
  enableClickSelection={true}
249
250
  enableSelectionWithoutKeys={true}
@@ -234,8 +234,8 @@ const GridKeyboardInteractionsTemplate: StoryFn<typeof Grid<ITestRow>> = (props:
234
234
  );
235
235
  };
236
236
 
237
- export const GridKeyboardInteractions: StoryFn<typeof Grid<ITestRow>> = GridKeyboardInteractionsTemplate.bind({});
238
- GridKeyboardInteractions.play = async ({ canvasElement }) => {
237
+ export const KeyboardInteractions: StoryFn<typeof Grid<ITestRow>> = GridKeyboardInteractionsTemplate.bind({});
238
+ KeyboardInteractions.play = async ({ canvasElement }) => {
239
239
  const canvas = within(document.body);
240
240
 
241
241
  multiEditAction.mockClear();
@@ -0,0 +1,61 @@
1
+ @use '../../node_modules/@linzjs/lui/dist/scss/Core' as lui;
2
+
3
+ .GridContextMenu {
4
+ li.szh-menu__header {
5
+ color: lui.$fuscous;
6
+ text-transform: none;
7
+ font-size: 14px;
8
+ font-weight: 600;
9
+ padding-left: 16px;
10
+ padding-right: 16px;
11
+ }
12
+
13
+ li.szh-menu__item {
14
+ padding-left: 16px;
15
+ padding-right: 16px;
16
+
17
+ .copyMenuMenuItem {
18
+ display: flex;
19
+ gap: 8px;
20
+ align-items: center;
21
+ }
22
+
23
+ .copyMenuMenuItem__text {
24
+ color: lui.$charcoal;
25
+ font-size: 16px;
26
+ font-weight: 400;
27
+ width: 80px;
28
+ }
29
+
30
+ .copyMenuMenuItem__buttonDefault {
31
+ display: flex;
32
+ background: none;
33
+ padding: 6px 6px;
34
+ border: 2px solid transparent;
35
+
36
+ &.copyMenuMenuItem__buttonDefault--hidden:hover {
37
+ border: 2px solid lui.$charcoal;
38
+ }
39
+ }
40
+
41
+ .copyMenuMenuItem__buttonDefault--hidden .LuiIcon {
42
+ fill: transparent;
43
+ }
44
+ .copyMenuMenuItem__buttonDefault--hidden:hover .LuiIcon {
45
+ fill: lui.$fuscous;
46
+ }
47
+
48
+ .LuiIcon {
49
+ fill: lui.$fuscous;
50
+ }
51
+
52
+ .LuiIcon[data-icon="ic_tick"] {
53
+ padding-left: 4px;
54
+ padding-right: 4px;
55
+ }
56
+ }
57
+
58
+ li.szh-menu__item--hover {
59
+ background-color: lui.$polar;
60
+ }
61
+ }
@@ -7,8 +7,12 @@
7
7
  margin-right: auto;
8
8
  }
9
9
 
10
- .ag-unselectable .ag-cell:not([col-id="ag-Grid-SelectionColumn"]) {
11
- user-select: text !important;
10
+ .ag-cell:not([col-id="ag-Grid-SelectionColumn"]) {
11
+ user-select: text;
12
+ }
13
+
14
+ .rangeSelectingMultiple .ag-cell {
15
+ user-select: none;
12
16
  }
13
17
 
14
18
  .LabelPreferencesPanelGridCellAlignCenter .ag-cell-wrapper, .LabelPreferencesPanelGridCellAlignCenter .GridCell-container {
@@ -157,3 +161,23 @@ div.ag-ltr div.ag-header-cell-resize {
157
161
  font-size: lui.$base-font-size;
158
162
  @include lui.font-regular();
159
163
  }
164
+
165
+ div.ag-row div.ag-cell.rangeSelect {
166
+ background-color: rgba(72, 160, 244, 0.2);
167
+ }
168
+
169
+ div.ag-row div.ag-cell.rangeSelectLeft {
170
+ border-left: 3px solid #48a0f4;
171
+ }
172
+
173
+ div.ag-row div.ag-cell.rangeSelectRight {
174
+ border-right: 3px solid #48a0f4;
175
+ }
176
+
177
+ div.ag-row div.ag-cell.rangeSelectTop {
178
+ border-top: 3px solid #48a0f4;
179
+ }
180
+
181
+ div.ag-row div.ag-cell.rangeSelectBottom {
182
+ border-bottom: 3px solid #48a0f4;
183
+ }
@@ -0,0 +1,19 @@
1
+ export class SeededRandomForTests {
2
+ value: number;
3
+ constructor(seed: number) {
4
+ this.value = seed % 2147483647;
5
+ }
6
+
7
+ next(maxInc?: number) {
8
+ this.value = (this.value * 16807) % 2147483647;
9
+ const result = (this.value - 1) / 2147483646;
10
+ if (maxInc == null) {
11
+ return result;
12
+ }
13
+ return ((result * (maxInc + 1)) | 0) % (maxInc + 1);
14
+ }
15
+
16
+ fromArray<T>(arr: T[]): T {
17
+ return arr[this.next(arr.length - 1)];
18
+ }
19
+ }
@@ -1,6 +1,6 @@
1
1
  export const bearingValueFormatter = (value: any): string => {
2
2
  const safeValue = typeof value === 'string' ? parseFloat(value) : value;
3
- if (safeValue == null) {
3
+ if (safeValue == null || Number.isNaN(safeValue)) {
4
4
  return '–';
5
5
  }
6
6
  return convertDDToDMS(safeValue, false);
@@ -8,7 +8,7 @@ export const bearingValueFormatter = (value: any): string => {
8
8
 
9
9
  export const bearingCorrectionValueFormatter = (value: any): string => {
10
10
  const safeValue = value;
11
- if (safeValue == null) {
11
+ if (safeValue == null || Number.isNaN(safeValue)) {
12
12
  return '–';
13
13
  }
14
14
  if (typeof safeValue === 'string') {
package/src/utils/util.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { isEmpty, negate } from 'lodash-es';
2
2
  import natsort from 'natsort';
3
3
 
4
+ export type MaybePromise<T> = T | Promise<T>;
5
+
4
6
  export const isNotEmpty = negate(isEmpty);
5
7
 
6
8
  export const wait = (timeoutMs: number) =>