@linzjs/step-ag-grid 29.11.4 → 29.12.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/dist/src/utils/__tests__/storybookTestUtil.ts +28 -1
- package/dist/src/utils/__tests__/testQuick.ts +2 -0
- package/dist/src/utils/util.d.ts +1 -0
- package/dist/step-ag-grid.cjs +226 -41
- package/dist/step-ag-grid.cjs.map +1 -1
- package/dist/step-ag-grid.esm.js +227 -43
- package/dist/step-ag-grid.esm.js.map +1 -1
- package/package.json +2 -1
- package/src/components/Grid.tsx +48 -4
- package/src/contexts/GridContextProvider.tsx +50 -45
- package/src/stories/grid/GridSorting.stories.tsx +189 -0
- package/src/utils/__tests__/storybookTestUtil.ts +28 -1
- package/src/utils/__tests__/testQuick.ts +2 -0
- package/src/utils/util.ts +19 -0
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": "29.
|
|
5
|
+
"version": "29.12.0",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"aggrid",
|
|
8
8
|
"ag-grid",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"clsx": "^2.1.1",
|
|
39
39
|
"debounce-promise": "^3.1.2",
|
|
40
40
|
"matcher": "^5.0.0",
|
|
41
|
+
"natsort": "^2.0.3",
|
|
41
42
|
"react-transition-state": "^2.3.1",
|
|
42
43
|
"usehooks-ts": "^3.1.1",
|
|
43
44
|
"uuid": "^13.0.0"
|
package/src/components/Grid.tsx
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
RowDragMoveEvent,
|
|
24
24
|
SelectionChangedEvent,
|
|
25
25
|
SelectionColumnDef,
|
|
26
|
+
ValueFormatterParams,
|
|
26
27
|
} from 'ag-grid-community';
|
|
27
28
|
import { AgGridReact } from 'ag-grid-react';
|
|
28
29
|
import clsx from 'clsx';
|
|
@@ -32,7 +33,7 @@ import { useInterval } from 'usehooks-ts';
|
|
|
32
33
|
|
|
33
34
|
import { AutoSizeColumnsResult, StartCellEditingProps, useGridContext } from '../contexts/GridContext';
|
|
34
35
|
import { GridUpdatingContext } from '../contexts/GridUpdatingContext';
|
|
35
|
-
import { fnOrVar, isNotEmpty } from '../utils/util';
|
|
36
|
+
import { compareNaturalInsensitive, fnOrVar, isNotEmpty } from '../utils/util';
|
|
36
37
|
import { clickInputWhenContainingCellClicked } from './clickInputWhenContainingCellClicked';
|
|
37
38
|
import { GridHeaderSelect } from './gridHeader';
|
|
38
39
|
import { GridContextMenuComponent, useGridContextMenu } from './gridHook';
|
|
@@ -600,18 +601,51 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
|
|
|
600
601
|
|
|
601
602
|
const adjustColDef = (colDef: ColDef<TData>): ColDef<TData> => {
|
|
602
603
|
const flex = colDef.flex ?? (sizeColumns === 'fit' ? 1 : undefined);
|
|
603
|
-
|
|
604
|
+
const valueFormatter = colDef.valueFormatter;
|
|
605
|
+
const sortable = colDef.sortable && params.defaultColDef?.sortable !== false;
|
|
606
|
+
let comparator = colDef.comparator;
|
|
607
|
+
if (sortable && !comparator) {
|
|
608
|
+
comparator = (value1, value2, node1, node2) => {
|
|
609
|
+
let r = 0;
|
|
610
|
+
if (typeof valueFormatter === 'function') {
|
|
611
|
+
r = compareNaturalInsensitive(
|
|
612
|
+
valueFormatter({
|
|
613
|
+
data: node1.data,
|
|
614
|
+
value: value1,
|
|
615
|
+
node: node1,
|
|
616
|
+
colDef: adjustedColDef,
|
|
617
|
+
...NotAGridValueFormatterCall,
|
|
618
|
+
} as ValueFormatterParams<TData>),
|
|
619
|
+
valueFormatter({
|
|
620
|
+
data: node2.data,
|
|
621
|
+
value: value2,
|
|
622
|
+
node: node2,
|
|
623
|
+
colDef: adjustedColDef,
|
|
624
|
+
...NotAGridValueFormatterCall,
|
|
625
|
+
} as ValueFormatterParams<TData>),
|
|
626
|
+
);
|
|
627
|
+
} else {
|
|
628
|
+
r = compareNaturalInsensitive(value1, value2);
|
|
629
|
+
}
|
|
630
|
+
// secondary compare as primary sort column rows are equal
|
|
631
|
+
return r === 0 ? compareNaturalInsensitive(node1.data?.id, node2.data?.id) : r;
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
const adjustedColDef = {
|
|
604
635
|
...colDef,
|
|
605
636
|
// You cannot pass a width to a flex
|
|
606
637
|
width: !!colDef.flex ? undefined : colDef.width,
|
|
607
|
-
flexAutoSizeWidth: colDef.width,
|
|
638
|
+
...(!!colDef.flex && { flexAutoSizeWidth: colDef.width }),
|
|
608
639
|
// If this is allowed flex columns don't size based on flex
|
|
609
640
|
suppressSizeToFit: true,
|
|
610
641
|
// Auto-sizing flex columns breaks everything
|
|
611
642
|
flex,
|
|
612
643
|
suppressAutoSize: !!flex,
|
|
613
|
-
sortable
|
|
644
|
+
sortable,
|
|
645
|
+
comparator,
|
|
614
646
|
} as ColDef<TData> & { flexAutoSizeWidth?: number };
|
|
647
|
+
|
|
648
|
+
return adjustedColDef;
|
|
615
649
|
};
|
|
616
650
|
|
|
617
651
|
return columnDefs.map((colDef) => adjustColDefOrGroup(colDef));
|
|
@@ -969,3 +1003,13 @@ const quickFilterParser = (filterStr: string) => {
|
|
|
969
1003
|
// filter is exact matches exactly groups separated by commas
|
|
970
1004
|
return filterStr.split(',').map((str) => str.trim());
|
|
971
1005
|
};
|
|
1006
|
+
|
|
1007
|
+
/**
|
|
1008
|
+
* Columns to indicate to user when they debug why things are broken in the default comparator if their valueFormatter
|
|
1009
|
+
* is too complicated.
|
|
1010
|
+
*/
|
|
1011
|
+
const NotAGridValueFormatterCall = {
|
|
1012
|
+
column: 'Default comparator has no access to column, write your own comparator' as unknown,
|
|
1013
|
+
api: 'Default comparator has no access to api, write your own comparator' as unknown,
|
|
1014
|
+
context: 'Default comparator has no access to context, write your own comparator' as unknown,
|
|
1015
|
+
};
|
|
@@ -439,59 +439,64 @@ export const GridContextProvider = <TData extends GridBaseRow>(props: PropsWithC
|
|
|
439
439
|
return null;
|
|
440
440
|
}
|
|
441
441
|
|
|
442
|
-
|
|
442
|
+
try {
|
|
443
|
+
const colIdsSet = colIds instanceof Set ? colIds : new Set(colIds);
|
|
443
444
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
445
|
+
const getVisibleColStates = () => {
|
|
446
|
+
const colStates = gridApi.getColumnState();
|
|
447
|
+
return colStates.filter((colState) => {
|
|
448
|
+
const colId = colState.colId;
|
|
449
|
+
return (isEmpty(colIdsSet) || colIdsSet.has(colId)) && !userSizedColIds?.has(colId);
|
|
450
|
+
});
|
|
451
|
+
};
|
|
452
|
+
const getFlexColStates = () => getVisibleColStates().filter(colStateFlexed);
|
|
453
|
+
const getNonFlexColStates = () => getVisibleColStates().filter(colStateNotFlexed);
|
|
454
|
+
|
|
455
|
+
// You cannot autosize flex columns it will break layout randomly
|
|
456
|
+
// So, a flex column is assumed to be 150 wide, unless width is provided
|
|
457
|
+
const flexColumns = getFlexColStates().map(colStateId);
|
|
458
|
+
let width = 0;
|
|
459
|
+
flexColumns.forEach((colId) => {
|
|
460
|
+
const colDef = gridApi.getColumnDef(colId) as { flexAutoSizeWidth?: number } | undefined;
|
|
461
|
+
width += colDef?.flexAutoSizeWidth ?? 200;
|
|
449
462
|
});
|
|
450
|
-
};
|
|
451
|
-
const getFlexColStates = () => getVisibleColStates().filter(colStateFlexed);
|
|
452
|
-
const getNonFlexColStates = () => getVisibleColStates().filter(colStateNotFlexed);
|
|
453
|
-
|
|
454
|
-
// You cannot autosize flex columns it will break layout randomly
|
|
455
|
-
// So, a flex column is assumed to be 150 wide, unless width is provided
|
|
456
|
-
const flexColumns = getFlexColStates().map(colStateId);
|
|
457
|
-
let width = 0;
|
|
458
|
-
flexColumns.forEach((colId) => {
|
|
459
|
-
const colDef = gridApi.getColumnDef(colId) as { flexAutoSizeWidth?: number } | undefined;
|
|
460
|
-
width += colDef?.flexAutoSizeWidth ?? 200;
|
|
461
|
-
});
|
|
462
463
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
464
|
+
const nonFlexColumns = getNonFlexColStates();
|
|
465
|
+
const nonFlexColumnIds = nonFlexColumns.map(colStateId);
|
|
466
|
+
gridApi.autoSizeColumns({ colIds: nonFlexColumnIds, skipHeader });
|
|
466
467
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
468
|
+
const calcSubWidth = () => {
|
|
469
|
+
const updatedFlexColumns = getVisibleColStates().filter((colState) =>
|
|
470
|
+
nonFlexColumnIds.includes(colState.colId),
|
|
471
|
+
);
|
|
472
|
+
return sumBy(
|
|
473
|
+
updatedFlexColumns.filter((col) => !col.hide),
|
|
474
|
+
'width',
|
|
475
|
+
);
|
|
476
|
+
};
|
|
476
477
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
478
|
+
// ag-grid updates widths asynchronously, so we wait here for the default calculated width to change
|
|
479
|
+
let lastSubWidth = calcSubWidth();
|
|
480
|
+
const endTime = Date.now() + 1000;
|
|
481
|
+
while (Date.now() < endTime) {
|
|
482
|
+
await wait(40);
|
|
483
|
+
const newSubWidth = calcSubWidth();
|
|
484
|
+
if (lastSubWidth !== newSubWidth) {
|
|
485
|
+
lastSubWidth = newSubWidth;
|
|
486
|
+
break;
|
|
487
|
+
}
|
|
486
488
|
}
|
|
487
|
-
}
|
|
488
489
|
|
|
489
|
-
|
|
490
|
+
width += lastSubWidth;
|
|
490
491
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
492
|
+
gridApi.sizeColumnsToFit();
|
|
493
|
+
return {
|
|
494
|
+
width,
|
|
495
|
+
};
|
|
496
|
+
} catch (ex) {
|
|
497
|
+
console.info('autosize failed', ex);
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
495
500
|
},
|
|
496
501
|
[gridApi],
|
|
497
502
|
);
|
|
@@ -0,0 +1,189 @@
|
|
|
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 { Meta, StoryFn } from '@storybook/react-vite';
|
|
7
|
+
import { useMemo, useState } from 'react';
|
|
8
|
+
import { expect } from 'storybook/test';
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
ColDefT,
|
|
12
|
+
compareNaturalInsensitive,
|
|
13
|
+
Grid,
|
|
14
|
+
GridCell,
|
|
15
|
+
GridContextProvider,
|
|
16
|
+
GridProps,
|
|
17
|
+
GridUpdatingContextProvider,
|
|
18
|
+
GridWrapper,
|
|
19
|
+
wait,
|
|
20
|
+
} from '../..';
|
|
21
|
+
import {
|
|
22
|
+
clickColumnHeaderToSort,
|
|
23
|
+
gridColumnValues,
|
|
24
|
+
waitForGridReady,
|
|
25
|
+
waitForGridRows,
|
|
26
|
+
} 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: 'single',
|
|
37
|
+
}, // Storybook hangs otherwise
|
|
38
|
+
parameters: {
|
|
39
|
+
docs: {
|
|
40
|
+
source: {
|
|
41
|
+
type: 'code',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
decorators: [
|
|
46
|
+
(Story) => (
|
|
47
|
+
<div style={{ maxWidth: 1024, height: 400, display: 'flex', flexDirection: 'column' }}>
|
|
48
|
+
<GridUpdatingContextProvider>
|
|
49
|
+
<GridContextProvider>
|
|
50
|
+
<Story />
|
|
51
|
+
</GridContextProvider>
|
|
52
|
+
</GridUpdatingContextProvider>
|
|
53
|
+
</div>
|
|
54
|
+
),
|
|
55
|
+
],
|
|
56
|
+
} as Meta<typeof Grid>;
|
|
57
|
+
|
|
58
|
+
interface ITestRow {
|
|
59
|
+
id: number;
|
|
60
|
+
numeric?: number;
|
|
61
|
+
text?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const GridReadOnlyTemplate: StoryFn<typeof Grid<ITestRow>> = (props: GridProps<ITestRow>) => {
|
|
65
|
+
const [externalSelectedItems, setExternalSelectedItems] = useState<any[]>([]);
|
|
66
|
+
const columnDefs: ColDefT<ITestRow>[] = useMemo(
|
|
67
|
+
() => [
|
|
68
|
+
GridCell({
|
|
69
|
+
field: 'id',
|
|
70
|
+
headerName: 'Id',
|
|
71
|
+
lockVisible: true,
|
|
72
|
+
}),
|
|
73
|
+
GridCell({
|
|
74
|
+
field: 'numeric',
|
|
75
|
+
headerName: 'Numeric',
|
|
76
|
+
}),
|
|
77
|
+
GridCell({
|
|
78
|
+
field: 'text',
|
|
79
|
+
headerName: 'Text',
|
|
80
|
+
}),
|
|
81
|
+
GridCell({
|
|
82
|
+
colId: 'customComparatorText',
|
|
83
|
+
headerName: 'Cust. comp. id as text',
|
|
84
|
+
valueFormatter: ({ data }) => data?.text ?? '',
|
|
85
|
+
comparator: (_v1, _v2, n1, n2) => compareNaturalInsensitive(String(n1.data?.id), String(n2.data?.id)) ?? 0,
|
|
86
|
+
}),
|
|
87
|
+
GridCell({
|
|
88
|
+
colId: 'customComparatorNumeric',
|
|
89
|
+
headerName: 'Cust. comp. abs number',
|
|
90
|
+
valueFormatter: ({ data }) => String(data?.numeric ?? ''),
|
|
91
|
+
comparator: (_v1, _v2, n1, n2) => {
|
|
92
|
+
return compareNaturalInsensitive(n1.data?.numeric, n2.data?.numeric) ?? 0;
|
|
93
|
+
},
|
|
94
|
+
}),
|
|
95
|
+
],
|
|
96
|
+
[],
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const [rowData] = useState<ITestRow[]>([
|
|
100
|
+
{ id: 1009, numeric: -1.1, text: 'ade' },
|
|
101
|
+
{ id: 1000, numeric: 1, text: 'ade' },
|
|
102
|
+
{ id: 1001, numeric: 11, text: 'cdc' },
|
|
103
|
+
{ id: 1002, numeric: 21, text: '2' },
|
|
104
|
+
{ id: 1003, numeric: 2.1, text: '10' },
|
|
105
|
+
{ id: 1004, numeric: 2.01, text: 'b' },
|
|
106
|
+
{ id: 1005, numeric: 2.21, text: 'b' },
|
|
107
|
+
{ id: 1006, numeric: 3, text: undefined },
|
|
108
|
+
{ id: 1008, numeric: 3, text: 'e' },
|
|
109
|
+
{ id: 1007, numeric: undefined, text: 'e' },
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<GridWrapper maxHeight={400}>
|
|
114
|
+
<Grid
|
|
115
|
+
data-testid={'readonly'}
|
|
116
|
+
{...props}
|
|
117
|
+
selectable={true}
|
|
118
|
+
enableClickSelection={true}
|
|
119
|
+
enableSelectionWithoutKeys={true}
|
|
120
|
+
autoSelectFirstRow={true}
|
|
121
|
+
externalSelectedItems={externalSelectedItems}
|
|
122
|
+
setExternalSelectedItems={setExternalSelectedItems}
|
|
123
|
+
columnDefs={columnDefs}
|
|
124
|
+
rowData={rowData}
|
|
125
|
+
/>
|
|
126
|
+
</GridWrapper>
|
|
127
|
+
);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export const Sorting = GridReadOnlyTemplate.bind({});
|
|
131
|
+
Sorting.play = async (context) => {
|
|
132
|
+
const { canvasElement } = context;
|
|
133
|
+
await waitForGridReady(context);
|
|
134
|
+
await waitForGridRows({ grid: canvasElement });
|
|
135
|
+
|
|
136
|
+
const test = async (colId: string | null, expected: string[] | null, idExpected?: string[]) => {
|
|
137
|
+
if (colId !== null) {
|
|
138
|
+
await clickColumnHeaderToSort(colId, canvasElement);
|
|
139
|
+
await wait(500);
|
|
140
|
+
expected && expect(gridColumnValues(colId, canvasElement)).toEqual(expected);
|
|
141
|
+
}
|
|
142
|
+
idExpected && expect(gridColumnValues('id', canvasElement)).toEqual(idExpected);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// Default sort order
|
|
146
|
+
await test(null, null, ['1009', '1000', '1001', '1002', '1003', '1004', '1005', '1006', '1008', '1007']);
|
|
147
|
+
|
|
148
|
+
await test('numeric', ['–', '-1.1', '1', '2.01', '2.1', '2.21', '3', '3', '11', '21']);
|
|
149
|
+
await test('numeric', ['21', '11', '3', '3', '2.21', '2.1', '2.01', '1', '-1.1', '–']);
|
|
150
|
+
|
|
151
|
+
await test(
|
|
152
|
+
'text',
|
|
153
|
+
['–', '2', '10', 'ade', 'ade', 'b', 'b', 'cdc', 'e', 'e'],
|
|
154
|
+
['1006', '1002', '1003', '1000', '1009', '1004', '1005', '1001', '1007', '1008'],
|
|
155
|
+
);
|
|
156
|
+
await test(
|
|
157
|
+
'text',
|
|
158
|
+
['e', 'e', 'cdc', 'b', 'b', 'ade', 'ade', '10', '2', '–'],
|
|
159
|
+
['1008', '1007', '1001', '1005', '1004', '1009', '1000', '1003', '1002', '1006'],
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
await test('customComparatorText', null, [
|
|
163
|
+
'1000',
|
|
164
|
+
'1001',
|
|
165
|
+
'1002',
|
|
166
|
+
'1003',
|
|
167
|
+
'1004',
|
|
168
|
+
'1005',
|
|
169
|
+
'1006',
|
|
170
|
+
'1007',
|
|
171
|
+
'1008',
|
|
172
|
+
'1009',
|
|
173
|
+
]);
|
|
174
|
+
await test('customComparatorText', null, [
|
|
175
|
+
'1009',
|
|
176
|
+
'1008',
|
|
177
|
+
'1007',
|
|
178
|
+
'1006',
|
|
179
|
+
'1005',
|
|
180
|
+
'1004',
|
|
181
|
+
'1003',
|
|
182
|
+
'1002',
|
|
183
|
+
'1001',
|
|
184
|
+
'1000',
|
|
185
|
+
]);
|
|
186
|
+
|
|
187
|
+
await test('customComparatorNumeric', ['–', '-1.1', '1', '2.01', '2.1', '2.21', '3', '3', '11', '21']);
|
|
188
|
+
await test('customComparatorNumeric', ['21', '11', '3', '3', '2.21', '2.1', '2.01', '1', '-1.1', '–']);
|
|
189
|
+
};
|
|
@@ -1,4 +1,31 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { sortBy } from 'lodash-es';
|
|
2
|
+
import { expect, userEvent, waitFor } from 'storybook/test';
|
|
3
|
+
|
|
4
|
+
import { findQuick, getAllQuick } from './testQuick';
|
|
2
5
|
|
|
3
6
|
export const waitForGridReady = ({ canvasElement }: { canvasElement: HTMLElement }) =>
|
|
4
7
|
waitFor(() => expect(canvasElement.querySelector('.Grid-ready')).toBeInTheDocument(), { timeout: 5000 });
|
|
8
|
+
|
|
9
|
+
export const waitForGridRows = async (props?: { grid?: HTMLElement; timeout?: number }) =>
|
|
10
|
+
waitFor(() => expect(getAllQuick({ classes: '.ag-row' }, props?.grid).length > 0).toBe(true), {
|
|
11
|
+
timeout: props?.timeout ?? 4000,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export const clickColumnHeaderToSort = async (colId: string, within: HTMLElement): Promise<void> => {
|
|
15
|
+
const user = userEvent.setup({
|
|
16
|
+
delay: 100,
|
|
17
|
+
});
|
|
18
|
+
const header = await findQuick({ selector: `.ag-header-cell[col-id="${colId}"]` }, within);
|
|
19
|
+
await expect(header).toBeInTheDocument();
|
|
20
|
+
// For some reason clicks in storybook don't trigger ag-grid, but manual clicks do
|
|
21
|
+
await user.click(header);
|
|
22
|
+
await user.keyboard('{Enter}');
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const gridColumnValues = (colId: string, within: HTMLElement): string[] => {
|
|
26
|
+
return sortBy(
|
|
27
|
+
getAllQuick({ tagName: `[col-id="${colId}"]`, classes: `.ag-cell` }, within),
|
|
28
|
+
// @ts-expect-error css style type
|
|
29
|
+
(el) => el.parentElement?.attributeStyleMap.get('transform')?.[0]?.y.value,
|
|
30
|
+
).map((cell) => cell.innerText);
|
|
31
|
+
};
|
|
@@ -25,6 +25,7 @@ export interface IQueryQuick {
|
|
|
25
25
|
role?: string;
|
|
26
26
|
classes?: string;
|
|
27
27
|
child?: IQueryQuick;
|
|
28
|
+
selector?: string;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
const escapeSelectorParam = (param: string): string => param.replace(/["\\]/g, '\\$&');
|
|
@@ -51,6 +52,7 @@ const quickSelector = <T extends HTMLElement>(
|
|
|
51
52
|
let lastIQueryQuick = props;
|
|
52
53
|
for (let loop: IQueryQuick | undefined = props; loop; loop = loop.child) {
|
|
53
54
|
lastIQueryQuick = loop;
|
|
55
|
+
loop.selector && (selector += loop.selector);
|
|
54
56
|
loop.tagName && (selector += loop.tagName);
|
|
55
57
|
loop.ariaLabel && (selector += `[aria-label='${escapeSelectorParam(loop.ariaLabel)}']`);
|
|
56
58
|
loop.role && (selector += `[role="${escapeSelectorParam(loop.role)}"]`);
|
package/src/utils/util.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isEmpty, negate } from 'lodash-es';
|
|
2
|
+
import natsort from 'natsort';
|
|
2
3
|
|
|
3
4
|
export const isNotEmpty = negate(isEmpty);
|
|
4
5
|
|
|
@@ -58,3 +59,21 @@ export const sanitiseFileName = (filename: string): string => {
|
|
|
58
59
|
if (!fileExt) return valid.slice().slice(0, 64);
|
|
59
60
|
return valid.slice(0, -fileExt.length - 1).slice(0, 64) + '.' + fileExt;
|
|
60
61
|
};
|
|
62
|
+
|
|
63
|
+
const naturalSortInsensitive = natsort({ insensitive: true });
|
|
64
|
+
const santitizeNaturalValue = (v: string | number | null | undefined): string => {
|
|
65
|
+
if (v === '–' || v === '-' || v == null) {
|
|
66
|
+
return '';
|
|
67
|
+
}
|
|
68
|
+
return String(v);
|
|
69
|
+
};
|
|
70
|
+
export const compareNaturalInsensitive = (
|
|
71
|
+
a?: object | string | number | null,
|
|
72
|
+
b?: object | string | number | null,
|
|
73
|
+
): number => {
|
|
74
|
+
if ((a !== null && typeof a === 'object') || (b !== null && typeof b === 'object')) {
|
|
75
|
+
// We can't compare objects
|
|
76
|
+
return 0;
|
|
77
|
+
}
|
|
78
|
+
return naturalSortInsensitive(santitizeNaturalValue(a), santitizeNaturalValue(b));
|
|
79
|
+
};
|