@linzjs/step-ag-grid 29.11.3 → 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.
@@ -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
+ };
@@ -0,0 +1,84 @@
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
+
9
+ import { ColDefT, Grid, GridCell, GridContextProvider, GridProps, GridUpdatingContextProvider } from '../../..';
10
+ import { waitForGridReady } from '../../../utils/__tests__/storybookTestUtil';
11
+ import { FormTest, IFormTestRow } from '../FormTest';
12
+
13
+ export default {
14
+ title: 'Components / Grid Size',
15
+ component: Grid,
16
+ args: {
17
+ quickFilterValue: '',
18
+ selectable: true,
19
+ },
20
+ decorators: [
21
+ (Story) => (
22
+ <GridUpdatingContextProvider>
23
+ <GridContextProvider>
24
+ <Story />
25
+ </GridContextProvider>
26
+ </GridUpdatingContextProvider>
27
+ ),
28
+ ],
29
+ } as Meta<typeof Grid>;
30
+
31
+ const GridPopoutEditGenericTemplate: StoryFn<typeof Grid<IFormTestRow>> = (props: GridProps<IFormTestRow>) => {
32
+ const [externalSelectedItems, setExternalSelectedItems] = useState<any[]>([]);
33
+ const columnDefs: ColDefT<IFormTestRow>[] = useMemo(
34
+ () => [
35
+ GridCell({
36
+ field: 'id',
37
+ headerName: 'Id',
38
+ }),
39
+ GridCell(
40
+ {
41
+ field: 'name',
42
+ headerName: 'Popout Generic Edit',
43
+ flex: 1,
44
+ },
45
+ {
46
+ multiEdit: true,
47
+ editor: FormTest,
48
+ editorParams: {},
49
+ },
50
+ ),
51
+ ],
52
+ [],
53
+ );
54
+
55
+ const [rowData] = useState([
56
+ { id: 1000, name: 'IS IS DP12345', nameType: 'IS', numba: 'IX', plan: 'DP 12345' },
57
+ {
58
+ id: 1001,
59
+ name: 'PEG V SD523PEG V SD523PEG V SD523PEG V SD523PEG V SD523PEG V SD523PEG V SD523PEG V SD523PEG V SD523PEG V SD523PEG V SD523PEG V SD523',
60
+ nameType: 'PEG',
61
+ numba: 'V',
62
+ plan: 'SD 523',
63
+ },
64
+ ] as IFormTestRow[]);
65
+
66
+ return (
67
+ <>
68
+ Auto-size. Col 1 should autosize, Col 2 should flex to fill.
69
+ <Grid
70
+ {...props}
71
+ externalSelectedItems={externalSelectedItems}
72
+ setExternalSelectedItems={setExternalSelectedItems}
73
+ columnDefs={columnDefs}
74
+ hideSelectColumn={true}
75
+ selectable={true}
76
+ rowData={rowData}
77
+ domLayout={'autoHeight'}
78
+ />
79
+ </>
80
+ );
81
+ };
82
+
83
+ export const _AutoSize = GridPopoutEditGenericTemplate.bind({});
84
+ _AutoSize.play = waitForGridReady;
@@ -0,0 +1,84 @@
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
+
9
+ import { ColDefT, Grid, GridCell, GridContextProvider, GridProps, GridUpdatingContextProvider } from '../../..';
10
+ import { waitForGridReady } from '../../../utils/__tests__/storybookTestUtil';
11
+ import { FormTest, IFormTestRow } from '../FormTest';
12
+
13
+ export default {
14
+ title: 'Components / Grid Size',
15
+ component: Grid,
16
+ args: {
17
+ quickFilterValue: '',
18
+ selectable: true,
19
+ },
20
+ decorators: [
21
+ (Story) => (
22
+ <GridUpdatingContextProvider>
23
+ <GridContextProvider>
24
+ <Story />
25
+ </GridContextProvider>
26
+ </GridUpdatingContextProvider>
27
+ ),
28
+ ],
29
+ } as Meta<typeof Grid>;
30
+
31
+ const GridPopoutEditGenericTemplate: StoryFn<typeof Grid<IFormTestRow>> = (props: GridProps<IFormTestRow>) => {
32
+ const [externalSelectedItems, setExternalSelectedItems] = useState<any[]>([]);
33
+ const columnDefs: ColDefT<IFormTestRow>[] = useMemo(
34
+ () => [
35
+ GridCell({
36
+ field: 'id',
37
+ headerName: 'Id',
38
+ }),
39
+ GridCell(
40
+ {
41
+ field: 'name',
42
+ headerName: 'Popout Generic Edit',
43
+ },
44
+ {
45
+ multiEdit: true,
46
+ editor: FormTest,
47
+ editorParams: {},
48
+ },
49
+ ),
50
+ ],
51
+ [],
52
+ );
53
+
54
+ const [rowData] = useState([
55
+ { id: 1000, name: 'IS IS DP12345', nameType: 'IS', numba: 'IX', plan: 'DP 12345' },
56
+ {
57
+ id: 1001,
58
+ name: 'PEG V SD523PEG V SD523PEG V SD523PEG V SD523PEG V SD523PEG V SD523PEG V SD523PEG V SD523PEG V SD523PEG V SD523PEG V SD523PEG V SD523',
59
+ nameType: 'PEG',
60
+ numba: 'V',
61
+ plan: 'SD 523',
62
+ },
63
+ ] as IFormTestRow[]);
64
+
65
+ return (
66
+ <>
67
+ Equisized flex columns spread across container
68
+ <Grid
69
+ {...props}
70
+ externalSelectedItems={externalSelectedItems}
71
+ setExternalSelectedItems={setExternalSelectedItems}
72
+ columnDefs={columnDefs}
73
+ sizeColumns={'fit'}
74
+ hideSelectColumn={true}
75
+ selectable={true}
76
+ rowData={rowData}
77
+ domLayout={'autoHeight'}
78
+ />
79
+ </>
80
+ );
81
+ };
82
+
83
+ export const _FitSize = GridPopoutEditGenericTemplate.bind({});
84
+ _FitSize.play = waitForGridReady;
@@ -0,0 +1,86 @@
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
+
9
+ import { ColDefT, Grid, GridCell, GridContextProvider, GridProps, GridUpdatingContextProvider } from '../../..';
10
+ import { waitForGridReady } from '../../../utils/__tests__/storybookTestUtil';
11
+ import { FormTest, IFormTestRow } from '../FormTest';
12
+
13
+ export default {
14
+ title: 'Components / Grid Size',
15
+ component: Grid,
16
+ args: {
17
+ quickFilterValue: '',
18
+ selectable: true,
19
+ },
20
+ decorators: [
21
+ (Story) => (
22
+ <GridUpdatingContextProvider>
23
+ <GridContextProvider>
24
+ <Story />
25
+ </GridContextProvider>
26
+ </GridUpdatingContextProvider>
27
+ ),
28
+ ],
29
+ } as Meta<typeof Grid>;
30
+
31
+ const GridPopoutEditGenericTemplate: StoryFn<typeof Grid<IFormTestRow>> = (props: GridProps<IFormTestRow>) => {
32
+ const [externalSelectedItems, setExternalSelectedItems] = useState<any[]>([]);
33
+ const columnDefs: ColDefT<IFormTestRow>[] = useMemo(
34
+ () => [
35
+ GridCell({
36
+ field: 'id',
37
+ headerName: 'Id',
38
+ flex: 2,
39
+ }),
40
+ GridCell(
41
+ {
42
+ field: 'name',
43
+ headerName: 'Popout Generic Edit',
44
+ flex: 1,
45
+ },
46
+ {
47
+ multiEdit: true,
48
+ editor: FormTest,
49
+ editorParams: {},
50
+ },
51
+ ),
52
+ ],
53
+ [],
54
+ );
55
+
56
+ const [rowData] = useState([
57
+ { id: 1000, name: 'IS IS DP12345', nameType: 'IS', numba: 'IX', plan: 'DP 12345' },
58
+ {
59
+ id: 1001,
60
+ name: 'PEG V SD523PEG V SD523PEG V SD523PEG V SD523PEG V SD523PEG V SD523PEG V SD523PEG V SD523PEG V SD523PEG V SD523PEG V SD523PEG V SD523',
61
+ nameType: 'PEG',
62
+ numba: 'V',
63
+ plan: 'SD 523',
64
+ },
65
+ ] as IFormTestRow[]);
66
+
67
+ return (
68
+ <>
69
+ 1:3 flex columns spread across container. These columns should not be the same size.
70
+ <Grid
71
+ {...props}
72
+ externalSelectedItems={externalSelectedItems}
73
+ setExternalSelectedItems={setExternalSelectedItems}
74
+ columnDefs={columnDefs}
75
+ sizeColumns={'fit'}
76
+ hideSelectColumn={true}
77
+ selectable={true}
78
+ rowData={rowData}
79
+ domLayout={'autoHeight'}
80
+ />
81
+ </>
82
+ );
83
+ };
84
+
85
+ export const _FitSizeFlex = GridPopoutEditGenericTemplate.bind({});
86
+ _FitSizeFlex.play = waitForGridReady;
@@ -10,13 +10,15 @@ import {
10
10
  PanelHeader,
11
11
  PanelsContextProvider,
12
12
  } from '@linzjs/windows';
13
- import { useContext, useMemo, useState } from 'react';
13
+ import { useContext, useEffect, useMemo, useState } from 'react';
14
14
 
15
15
  import {
16
16
  ColDefT,
17
17
  Grid,
18
18
  GridCell,
19
19
  GridContextProvider,
20
+ GridFilterColumnsToggle,
21
+ GridFilters,
20
22
  GridIcon,
21
23
  GridPopoverMenu,
22
24
  GridPopoverMessage,
@@ -93,11 +95,13 @@ export const PanelContentsWithResize = () => {
93
95
  GridCell({
94
96
  field: 'age',
95
97
  headerName: 'Age',
98
+ width: 80,
99
+ flex: 1,
96
100
  }),
97
101
  GridCell({
98
102
  field: 'desc',
99
103
  headerName: 'Description',
100
- flex: 1,
104
+ flex: 2,
101
105
  }),
102
106
  GridPopoverMessage(
103
107
  {
@@ -148,28 +152,57 @@ export const PanelContentsWithResize = () => {
148
152
  [],
149
153
  );
150
154
 
151
- const [rowData] = useState([
152
- /* Your grid row data */
153
- /* exclude */
154
- {
155
- id: 1000,
156
- position: 'Tester',
157
- age: 30,
158
- desc: 'Tests application',
159
- dd: '1',
160
- },
161
- { id: 1001, position: 'Developer', age: 12, desc: 'Develops application', dd: '2' },
162
- { id: 1002, position: 'Manager', age: 65, desc: 'Manages', dd: '3' },
163
- /* exclude */
164
- ]);
155
+ const [rowData, setRowData] = useState<ITestRow[] | null>(null);
156
+
157
+ useEffect(() => {
158
+ setTimeout(() => {
159
+ setRowData([
160
+ {
161
+ id: 1000,
162
+ position: 'Tester',
163
+ age: 30,
164
+ desc: 'Tests application',
165
+ dd: '1',
166
+ },
167
+ { id: 1001, position: 'Developer', age: 12, desc: 'Develops application', dd: '2' },
168
+ { id: 1002, position: 'Manager', age: 65, desc: 'Manages', dd: '3' },
169
+ /* exclude */
170
+ ]);
171
+ }, 2000);
172
+ setTimeout(() => {
173
+ setRowData([
174
+ {
175
+ id: 1000,
176
+ position: 'Tester',
177
+ age: 30,
178
+ desc: 'Tests application',
179
+ dd: '1',
180
+ },
181
+ {
182
+ id: 1001,
183
+ position: 'Developer DeveloperDeveloper DeveloperDeveloper DeveloperDeveloper DeveloperDeveloper Developer',
184
+ age: 12,
185
+ desc: 'Develops application',
186
+ dd: '2',
187
+ },
188
+ { id: 1002, position: 'Manager', age: 65, desc: 'Manages', dd: '3' },
189
+ /* exclude */
190
+ ]);
191
+ }, 5000);
192
+ }, []);
165
193
 
166
194
  return (
167
195
  <GridUpdatingContextProvider>
168
196
  <GridContextProvider>
169
197
  <GridWrapper>
198
+ <GridFilters>
199
+ <GridFilterColumnsToggle />
200
+ </GridFilters>
170
201
  <Grid
171
202
  columnDefs={columnDefs}
172
203
  rowData={rowData}
204
+ selectable={true}
205
+ hideSelectColumn={true}
173
206
  onContentSize={initialResizePanel}
174
207
  sizeColumns={'auto-skip-headers'}
175
208
  />
@@ -1,4 +1,31 @@
1
- import { expect, waitFor } from 'storybook/test';
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
+ };