@linzjs/step-ag-grid 30.4.4 → 31.2.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/GridTheme.scss +12 -7
- package/dist/index.css +6 -3
- package/dist/src/components/gridDragUtil.d.ts +10 -0
- package/dist/src/components/gridDragUtil.test.d.ts +1 -0
- package/dist/src/components/index.d.ts +1 -0
- package/dist/src/components/types.d.ts +4 -0
- package/dist/src/utils/__tests__/testQuick.ts +7 -7
- package/dist/src/utils/__tests__/testUtil.ts +3 -1
- package/dist/src/utils/__tests__/vitestUtil.ts +3 -1
- package/dist/src/utils/storybook.d.ts +4 -0
- package/dist/src/utils/useInterval.d.ts +17 -0
- package/dist/src/utils/useIsomorphicLayoutEffect.d.ts +17 -0
- package/dist/step-ag-grid.cjs +402 -218
- package/dist/step-ag-grid.cjs.map +1 -1
- package/dist/step-ag-grid.esm.js +402 -219
- package/dist/step-ag-grid.esm.js.map +1 -1
- package/package.json +26 -52
- package/src/components/Grid.tsx +75 -8
- package/src/components/PostSortRowsHook.ts +3 -1
- package/src/components/gridDragUtil.test.ts +115 -0
- package/src/components/gridDragUtil.ts +32 -0
- package/src/components/gridFilter/GridFilterColumnsMultiSelect.scss +18 -20
- package/src/components/gridFilter/GridFilterColumnsToggle.tsx +3 -4
- package/src/components/gridFilter/useGridFilter.ts +2 -2
- package/src/components/gridForm/GridFormDropDown.tsx +1 -1
- package/src/components/gridForm/GridFormInlineTextInput.tsx +1 -2
- package/src/components/gridForm/GridFormMultiSelect.tsx +10 -4
- package/src/components/gridForm/GridFormSubComponentTextArea.tsx +1 -2
- package/src/components/gridForm/GridFormSubComponentTextInput.tsx +1 -2
- package/src/components/gridForm/GridFormTextArea.tsx +1 -2
- package/src/components/gridForm/GridFormTextInput.tsx +1 -2
- package/src/components/gridHeader/GridHeaderSelect.tsx +3 -1
- package/src/components/gridHook/useGridCopy.ts +13 -3
- package/src/components/gridHook/useGridRangeSelection.ts +29 -16
- package/src/components/index.ts +1 -0
- package/src/components/types.ts +4 -0
- package/src/contexts/GridContextProvider.tsx +18 -7
- package/src/lui/ActionButton.scss +1 -1
- package/src/lui/ActionButton.tsx +5 -1
- package/src/lui/FormError.scss +1 -1
- package/src/lui/TextAreaInput.tsx +1 -1
- package/src/lui/TextInputFormatted.scss +13 -13
- package/src/lui/TextInputFormatted.tsx +1 -1
- package/src/react-menu3/README.md +66 -67
- package/src/react-menu3/components/ControlledMenu.tsx +2 -2
- package/src/react-menu3/components/Menu.tsx +1 -1
- package/src/react-menu3/components/MenuItem.tsx +1 -1
- package/src/react-menu3/components/MenuList.tsx +8 -2
- package/src/react-menu3/components/SubMenu.tsx +6 -6
- package/src/react-menu3/hooks/useBEM.ts +2 -1
- package/src/react-menu3/hooks/useItemState.ts +8 -4
- package/src/react-menu3/styles/_var.scss +1 -1
- package/src/react-menu3/styles/core.scss +6 -6
- package/src/react-menu3/styles/index.scss +11 -8
- package/src/react-menu3/styles/theme-dark.scss +1 -1
- package/src/react-menu3/styles/transitions/slide.scss +8 -8
- package/src/react-menu3/utils/utils.ts +2 -1
- package/src/stories/grid/FormTest.scss +13 -13
- package/src/stories/grid/GridDragRow.stories.tsx +61 -8
- package/src/stories/grid/GridInlineText.stories.tsx +2 -2
- package/src/stories/grid/GridSorting.stories.tsx +6 -2
- package/src/styles/ComponentLoadingWrapper.scss +1 -1
- package/src/styles/ContextMenu.scss +1 -1
- package/src/styles/Grid.scss +11 -9
- package/src/styles/GridCell.scss +1 -1
- package/src/styles/GridFilterColumnsToggle.scss +1 -1
- package/src/styles/GridFormInlineTextInput.scss +7 -7
- package/src/styles/GridFormMultiSelectGrid.scss +1 -1
- package/src/styles/GridFormSubComponentTextInput.scss +4 -4
- package/src/styles/GridIcon.scss +1 -1
- package/src/styles/GridPopoverMenu.scss +1 -1
- package/src/styles/GridTheme.scss +12 -7
- package/src/styles/index.scss +16 -16
- package/src/styles/react-menu-customisations.scss +119 -120
- package/src/utils/__tests__/testQuick.ts +7 -7
- package/src/utils/__tests__/testUtil.ts +3 -1
- package/src/utils/__tests__/vitestUtil.ts +3 -1
- package/src/utils/deferredPromise.ts +1 -1
- package/src/utils/storybook.ts +5 -0
- package/src/utils/useInterval.ts +44 -0
- package/src/utils/useIsomorphicLayoutEffect.ts +18 -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": "
|
|
5
|
+
"version": "31.2.0",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"aggrid",
|
|
8
8
|
"ag-grid",
|
|
@@ -40,21 +40,21 @@
|
|
|
40
40
|
"matcher": "^5.0.0",
|
|
41
41
|
"natsort": "^2.0.3",
|
|
42
42
|
"react-transition-state": "^2.3.1",
|
|
43
|
-
"usehooks-ts": "^3.1.1"
|
|
44
|
-
"uuid": "^13.0.0"
|
|
43
|
+
"usehooks-ts": "^3.1.1"
|
|
45
44
|
},
|
|
46
45
|
"scripts": {
|
|
47
|
-
"build": "run-s clean
|
|
46
|
+
"build": "node --eval \"fs.mkdirSync('./dist', { recursive: true })\" && run-s clean lintall css bundle",
|
|
48
47
|
"yalc": "run-s clean css bundle && yalc push",
|
|
49
|
-
"clean": "
|
|
48
|
+
"clean": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\"",
|
|
50
49
|
"bundle": "rollup -c",
|
|
51
50
|
"css": "sass ./src/styles/index.scss:dist/index.css --no-source-map",
|
|
52
|
-
"test": "cross-env
|
|
51
|
+
"test": "cross-env vitest run",
|
|
53
52
|
"test:watch": "vitest --watch",
|
|
54
|
-
"
|
|
55
|
-
"lint:
|
|
56
|
-
"lint:
|
|
57
|
-
"
|
|
53
|
+
"lintall": "run-s lint:oxlint",
|
|
54
|
+
"lint:oxlint": "oxlint ./src",
|
|
55
|
+
"lint:fix": "oxlint ./src --fix",
|
|
56
|
+
"format": "oxfmt --write ./src",
|
|
57
|
+
"format:check": "oxfmt --check ./src",
|
|
58
58
|
"storybook": "storybook dev -p 6006",
|
|
59
59
|
"build-storybook": "storybook build",
|
|
60
60
|
"deploy-storybook": "npx --yes -p @storybook/storybook-deployer storybook-to-ghpages",
|
|
@@ -62,11 +62,6 @@
|
|
|
62
62
|
"upgrade": "npx ncu -i --format group",
|
|
63
63
|
"prepare": "husky"
|
|
64
64
|
},
|
|
65
|
-
"eslintConfig": {
|
|
66
|
-
"extends": [
|
|
67
|
-
"react-app"
|
|
68
|
-
]
|
|
69
|
-
},
|
|
70
65
|
"browserslist": {
|
|
71
66
|
"production": [
|
|
72
67
|
">0.2%",
|
|
@@ -79,74 +74,53 @@
|
|
|
79
74
|
"last 1 safari version"
|
|
80
75
|
]
|
|
81
76
|
},
|
|
82
|
-
"optionalDependencies": {
|
|
83
|
-
"@rollup/rollup-linux-x64-gnu": "^4.53.3",
|
|
84
|
-
"@swc/core-linux-x64-gnu": "^1.15.3"
|
|
85
|
-
},
|
|
86
77
|
"devDependencies": {
|
|
87
78
|
"@chromatic-com/storybook": "^4.1.3",
|
|
88
79
|
"@linzjs/lui": "^24.3.1",
|
|
89
|
-
"@linzjs/
|
|
90
|
-
"@linzjs/windows": "^8.6.3",
|
|
80
|
+
"@linzjs/windows": "^9.5.6",
|
|
91
81
|
"@rollup/plugin-commonjs": "^28.0.9",
|
|
92
82
|
"@rollup/plugin-json": "^6.1.0",
|
|
93
83
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
94
|
-
"@storybook/addon-docs": "^9.1.
|
|
95
|
-
"@storybook/addon-links": "^9.1.
|
|
96
|
-
"@storybook/react": "^9.1.
|
|
97
|
-
"@storybook/react-vite": "^9.1.
|
|
84
|
+
"@storybook/addon-docs": "^9.1.20",
|
|
85
|
+
"@storybook/addon-links": "^9.1.20",
|
|
86
|
+
"@storybook/react": "^9.1.20",
|
|
87
|
+
"@storybook/react-vite": "^9.1.20",
|
|
98
88
|
"@testing-library/dom": "^10.4.1",
|
|
99
89
|
"@testing-library/react": "^16.3.0",
|
|
100
90
|
"@testing-library/user-event": "^14.6.1",
|
|
101
91
|
"@types/debounce-promise": "^3.1.9",
|
|
102
|
-
"@types/
|
|
92
|
+
"@types/jest": "^30.0.0",
|
|
93
|
+
"@types/jsdom": "^28.0.1",
|
|
103
94
|
"@types/lodash-es": "^4.17.12",
|
|
104
95
|
"@types/node": "^22.19.2",
|
|
105
96
|
"@types/react": "^18.3.27",
|
|
106
97
|
"@types/react-dom": "^18.3.7",
|
|
107
|
-
"@types/uuid": "^11.0.0",
|
|
108
98
|
"@vitejs/plugin-react-swc": "^3.11.0",
|
|
109
99
|
"@vitest/ui": "^3.2.4",
|
|
110
|
-
"babel-preset-react-app": "^10.1.0",
|
|
111
|
-
"canvas": "^3.2.0",
|
|
112
100
|
"chromatic": "^13.3.4",
|
|
113
101
|
"cross-env": "^10.1.0",
|
|
114
|
-
"css-loader": "^7.1.2",
|
|
115
|
-
"eslint-plugin-react": "^7.37.5",
|
|
116
|
-
"eslint-plugin-storybook": "^9.1.17",
|
|
117
102
|
"husky": "^9.1.7",
|
|
118
|
-
"jsdom": "
|
|
119
|
-
"lodash-es": "^4.
|
|
120
|
-
"mkdirp": "^3.0.1",
|
|
103
|
+
"jsdom": "28.1.0",
|
|
104
|
+
"lodash-es": "^4.18.1",
|
|
121
105
|
"npm-run-all": "^4.1.5",
|
|
122
|
-
"
|
|
123
|
-
"
|
|
106
|
+
"oxfmt": "^0.44.0",
|
|
107
|
+
"oxlint": "^1.59.0",
|
|
108
|
+
"postcss": "^8.5.8",
|
|
124
109
|
"postcss-scss": "^4.0.9",
|
|
125
110
|
"react": ">=18",
|
|
126
|
-
"react-app-polyfill": "^3.0.0",
|
|
127
111
|
"react-dom": "^18.3.1",
|
|
128
|
-
"
|
|
129
|
-
"rollup": "^4.53.3",
|
|
112
|
+
"rollup": "^4.60.1",
|
|
130
113
|
"rollup-plugin-copy": "^3.5.0",
|
|
131
114
|
"rollup-plugin-peer-deps-external": "^2.2.4",
|
|
132
115
|
"rollup-plugin-postcss": "^4.0.2",
|
|
133
|
-
"rollup-plugin-typescript2": "^0.
|
|
116
|
+
"rollup-plugin-typescript2": "^0.37.0",
|
|
134
117
|
"sass": "^1.96.0",
|
|
135
|
-
"
|
|
136
|
-
"storybook": "^9.1.17",
|
|
137
|
-
"storybook-css-modules-preset": "^1.1.1",
|
|
138
|
-
"style-loader": "^4.0.0",
|
|
118
|
+
"storybook": "^9.1.20",
|
|
139
119
|
"typescript": "^5.9.3",
|
|
140
|
-
"vite": "^7.2
|
|
141
|
-
"vite-plugin-html": "^3.2.2",
|
|
120
|
+
"vite": "^7.3.2",
|
|
142
121
|
"vite-tsconfig-paths": "^5.1.4",
|
|
143
122
|
"vitest": "^3.2.4"
|
|
144
123
|
},
|
|
145
|
-
"babel": {
|
|
146
|
-
"presets": [
|
|
147
|
-
"react-app"
|
|
148
|
-
]
|
|
149
|
-
},
|
|
150
124
|
"husky": {
|
|
151
125
|
"hooks": {
|
|
152
126
|
"pre-commit": "npm run lint"
|
package/src/components/Grid.tsx
CHANGED
|
@@ -31,10 +31,11 @@ import { AgGridReact } from 'ag-grid-react';
|
|
|
31
31
|
import clsx from 'clsx';
|
|
32
32
|
import { defer, delay, difference, isEmpty, last, omit, xorBy } from 'lodash-es';
|
|
33
33
|
import { ReactElement, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
34
|
-
import { useInterval } from 'usehooks-ts';
|
|
35
34
|
|
|
36
35
|
import { AutoSizeColumnsResult, StartCellEditingProps, useGridContext } from '../contexts/GridContext';
|
|
37
36
|
import { GridUpdatingContext } from '../contexts/GridUpdatingContext';
|
|
37
|
+
import { isStorybook } from '../utils/storybook';
|
|
38
|
+
import { useInterval } from '../utils/useInterval';
|
|
38
39
|
import { compareNaturalInsensitive, fnOrVar, isNotEmpty } from '../utils/util';
|
|
39
40
|
import { clickInputWhenContainingCellClicked } from './clickInputWhenContainingCellClicked';
|
|
40
41
|
import { GridHeaderSelect } from './gridHeader';
|
|
@@ -353,7 +354,9 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
|
|
|
353
354
|
if (autoSelectFirstRow && selectable) {
|
|
354
355
|
selectRowsById([firstRowId]);
|
|
355
356
|
} else {
|
|
356
|
-
|
|
357
|
+
if (!isStorybook) {
|
|
358
|
+
focusByRowById(firstRowId, true);
|
|
359
|
+
}
|
|
357
360
|
}
|
|
358
361
|
}
|
|
359
362
|
}, [
|
|
@@ -777,7 +780,7 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
|
|
|
777
780
|
const adjustedColDef = {
|
|
778
781
|
...colDef,
|
|
779
782
|
// You cannot pass a width to a flex
|
|
780
|
-
width:
|
|
783
|
+
width: colDef.flex ? undefined : colDef.width,
|
|
781
784
|
...(!!colDef.flex && { flexAutoSizeWidth: colDef.width }),
|
|
782
785
|
// If this is allowed flex columns don't size based on flex
|
|
783
786
|
suppressSizeToFit: true,
|
|
@@ -857,11 +860,39 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
|
|
|
857
860
|
});
|
|
858
861
|
}, []);
|
|
859
862
|
|
|
863
|
+
const clearDragRowClasses = useCallback(() => {
|
|
864
|
+
document.querySelectorAll(`.ag-row-dragging`)?.forEach((el) => {
|
|
865
|
+
el.classList.remove('ag-row-dragging');
|
|
866
|
+
});
|
|
867
|
+
}, []);
|
|
868
|
+
|
|
860
869
|
const gridElementRef = useRef<Element>(undefined);
|
|
870
|
+
const multiDragAppliedRef = useRef(false);
|
|
871
|
+
const multiDragCountRef = useRef(0);
|
|
861
872
|
const onRowDragMove = useCallback(
|
|
862
873
|
(event: RowDragMoveEvent) => {
|
|
863
874
|
if (startDragYRef.current === null) {
|
|
864
875
|
startDragYRef.current = event.y;
|
|
876
|
+
|
|
877
|
+
// On first move event, apply ag-row-dragging class to all selected rows for multi-drag
|
|
878
|
+
if (rowSelection === 'multiple' && event.node.isSelected()) {
|
|
879
|
+
const selectedNodes = event.api.getSelectedNodes().filter((n) => n.displayed && n.data != null);
|
|
880
|
+
if (selectedNodes.length > 1) {
|
|
881
|
+
multiDragAppliedRef.current = true;
|
|
882
|
+
multiDragCountRef.current = selectedNodes.length;
|
|
883
|
+
// Find the grid body element for scoped queries
|
|
884
|
+
const targetEl = event.event.target as Element | undefined;
|
|
885
|
+
const gridElement = targetEl?.closest('.ag-body');
|
|
886
|
+
if (gridElement) {
|
|
887
|
+
gridElementRef.current = gridElement;
|
|
888
|
+
for (const node of selectedNodes) {
|
|
889
|
+
gridElement.querySelectorAll(`[row-id='${node.data.id}']`)?.forEach((el) => {
|
|
890
|
+
el.classList.add('ag-row-dragging');
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
865
896
|
}
|
|
866
897
|
|
|
867
898
|
const yDiff = event.y - startDragYRef.current;
|
|
@@ -882,7 +913,7 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
|
|
|
882
913
|
});
|
|
883
914
|
}
|
|
884
915
|
},
|
|
885
|
-
[clearHighlightRowClasses],
|
|
916
|
+
[clearHighlightRowClasses, rowSelection],
|
|
886
917
|
);
|
|
887
918
|
|
|
888
919
|
const onCellFocused = useCallback(
|
|
@@ -919,6 +950,9 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
|
|
|
919
950
|
const onRowDragEnd = useCallback(
|
|
920
951
|
(event: RowDragEndEvent<TData>) => {
|
|
921
952
|
clearHighlightRowClasses();
|
|
953
|
+
clearDragRowClasses();
|
|
954
|
+
multiDragAppliedRef.current = false;
|
|
955
|
+
multiDragCountRef.current = 0;
|
|
922
956
|
gridElementRef.current = undefined;
|
|
923
957
|
if (!params.onRowDragEnd || startDragYRef.current === null) {
|
|
924
958
|
return;
|
|
@@ -931,10 +965,29 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
|
|
|
931
965
|
if (!movedRow || !targetRow || movedRow === targetRow || yDiff === 0) {
|
|
932
966
|
return;
|
|
933
967
|
}
|
|
934
|
-
|
|
968
|
+
|
|
969
|
+
// Determine movedRows: if dragged row is part of a multi-selection, include all selected & displayed rows
|
|
970
|
+
let movedRows: TData[];
|
|
971
|
+
if (rowSelection === 'multiple' && event.node.isSelected()) {
|
|
972
|
+
movedRows = event.api
|
|
973
|
+
.getSelectedNodes()
|
|
974
|
+
.filter((n) => n.displayed && n.data != null)
|
|
975
|
+
.sort((a, b) => (a.rowIndex ?? 0) - (b.rowIndex ?? 0))
|
|
976
|
+
.map((n) => n.data as TData);
|
|
977
|
+
} else {
|
|
978
|
+
movedRows = [movedRow];
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// If targetRow is one of the moved rows, no-op
|
|
982
|
+
if (movedRows.some((r) => r.id === targetRow.id)) {
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
const direction: -1 | 1 = yDiff > 0 ? 1 : -1;
|
|
987
|
+
void params.onRowDragEnd({ movedRow, movedRows, targetRow, direction });
|
|
935
988
|
}
|
|
936
989
|
},
|
|
937
|
-
[params, clearHighlightRowClasses],
|
|
990
|
+
[params, clearHighlightRowClasses, clearDragRowClasses, rowSelection],
|
|
938
991
|
);
|
|
939
992
|
|
|
940
993
|
useEffect(
|
|
@@ -1078,7 +1131,12 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
|
|
|
1078
1131
|
onRowClicked={params.onRowClicked}
|
|
1079
1132
|
onRowDataUpdated={onRowDataUpdated}
|
|
1080
1133
|
onRowDoubleClicked={params.onRowDoubleClicked}
|
|
1081
|
-
onRowDragCancel={
|
|
1134
|
+
onRowDragCancel={() => {
|
|
1135
|
+
clearHighlightRowClasses();
|
|
1136
|
+
clearDragRowClasses();
|
|
1137
|
+
multiDragAppliedRef.current = false;
|
|
1138
|
+
multiDragCountRef.current = 0;
|
|
1139
|
+
}}
|
|
1082
1140
|
onRowDragEnd={onRowDragEnd}
|
|
1083
1141
|
onRowDragMove={onRowDragMove}
|
|
1084
1142
|
onSelectionChanged={synchroniseExternalStateToGridSelection}
|
|
@@ -1089,7 +1147,16 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
|
|
|
1089
1147
|
preventDefaultOnContextMenu={true}
|
|
1090
1148
|
quickFilterParser={quickFilterParser}
|
|
1091
1149
|
rowClassRules={params.rowClassRules}
|
|
1092
|
-
rowDragText={
|
|
1150
|
+
rowDragText={
|
|
1151
|
+
params.rowDragText ??
|
|
1152
|
+
(rowSelection === 'multiple'
|
|
1153
|
+
? () => {
|
|
1154
|
+
const count = multiDragCountRef.current;
|
|
1155
|
+
if (count > 1) return `Moving ${count} rows`;
|
|
1156
|
+
return '';
|
|
1157
|
+
}
|
|
1158
|
+
: undefined)
|
|
1159
|
+
}
|
|
1093
1160
|
rowHeight={rowHeight}
|
|
1094
1161
|
rowSelection={
|
|
1095
1162
|
selectable
|
|
@@ -160,7 +160,9 @@ export const usePostSortRowsHook = ({ setStaleGrid }: PostSortRowsHookProps) =>
|
|
|
160
160
|
};
|
|
161
161
|
wasStale = true;
|
|
162
162
|
// For some reason AgGrid mis-positions the inserted row.
|
|
163
|
-
|
|
163
|
+
if (lastNewNode) {
|
|
164
|
+
redrawRows();
|
|
165
|
+
}
|
|
164
166
|
} else if (changedRowCount == 1 && newRowCount === 0) {
|
|
165
167
|
// User edited one row so, do nothing, retain sort
|
|
166
168
|
wasStale = true;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { reorderRows } from './gridDragUtil';
|
|
4
|
+
|
|
5
|
+
interface TestRow {
|
|
6
|
+
id: number;
|
|
7
|
+
name: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const makeRows = (...ids: number[]): TestRow[] => ids.map((id) => ({ id, name: `row-${id}` }));
|
|
11
|
+
const row = (id: number): TestRow => ({ id, name: `row-${id}` });
|
|
12
|
+
const ids = (rows: TestRow[]) => rows.map((r) => r.id);
|
|
13
|
+
|
|
14
|
+
describe('reorderRows', () => {
|
|
15
|
+
const rows = makeRows(1, 2, 3, 4, 5);
|
|
16
|
+
|
|
17
|
+
describe('single row move', () => {
|
|
18
|
+
test('move row down (below target)', () => {
|
|
19
|
+
// Drag row 1 below row 3: [2, 3, 1, 4, 5]
|
|
20
|
+
expect(ids(reorderRows(rows, [row(1)], row(3), 1))).toEqual([2, 3, 1, 4, 5]);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('move row up (above target)', () => {
|
|
24
|
+
// Drag row 4 above row 2: [1, 4, 2, 3, 5]
|
|
25
|
+
expect(ids(reorderRows(rows, [row(4)], row(2), -1))).toEqual([1, 4, 2, 3, 5]);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('move to first position', () => {
|
|
29
|
+
// Drag row 3 above row 1: [3, 1, 2, 4, 5]
|
|
30
|
+
expect(ids(reorderRows(rows, [row(3)], row(1), -1))).toEqual([3, 1, 2, 4, 5]);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('move to last position', () => {
|
|
34
|
+
// Drag row 2 below row 5: [1, 3, 4, 5, 2]
|
|
35
|
+
expect(ids(reorderRows(rows, [row(2)], row(5), 1))).toEqual([1, 3, 4, 5, 2]);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('move to adjacent position down', () => {
|
|
39
|
+
// Drag row 2 below row 3: [1, 3, 2, 4, 5]
|
|
40
|
+
expect(ids(reorderRows(rows, [row(2)], row(3), 1))).toEqual([1, 3, 2, 4, 5]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('move to adjacent position up', () => {
|
|
44
|
+
// Drag row 3 above row 2: [1, 3, 2, 4, 5]
|
|
45
|
+
expect(ids(reorderRows(rows, [row(3)], row(2), -1))).toEqual([1, 3, 2, 4, 5]);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('multi-row move', () => {
|
|
50
|
+
test('move multiple contiguous rows down', () => {
|
|
51
|
+
// Drag rows 1,2 below row 4: [3, 4, 1, 2, 5]
|
|
52
|
+
expect(ids(reorderRows(rows, [row(1), row(2)], row(4), 1))).toEqual([3, 4, 1, 2, 5]);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('move multiple contiguous rows up', () => {
|
|
56
|
+
// Drag rows 4,5 above row 2: [1, 4, 5, 2, 3]
|
|
57
|
+
expect(ids(reorderRows(rows, [row(4), row(5)], row(2), -1))).toEqual([1, 4, 5, 2, 3]);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('move rows with gaps — gaps are collapsed', () => {
|
|
61
|
+
// Drag rows 1,3,5 below row 4: [2, 4, 1, 3, 5]
|
|
62
|
+
expect(ids(reorderRows(rows, [row(1), row(3), row(5)], row(4), 1))).toEqual([2, 4, 1, 3, 5]);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('move rows with gaps above target', () => {
|
|
66
|
+
// Drag rows 2,4 above row 5: [1, 3, 2, 4, 5]
|
|
67
|
+
expect(ids(reorderRows(rows, [row(2), row(4)], row(5), -1))).toEqual([1, 3, 2, 4, 5]);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('move rows with gaps to first position', () => {
|
|
71
|
+
// Drag rows 3,5 above row 1: [3, 5, 1, 2, 4]
|
|
72
|
+
expect(ids(reorderRows(rows, [row(3), row(5)], row(1), -1))).toEqual([3, 5, 1, 2, 4]);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('move rows with gaps to last position', () => {
|
|
76
|
+
// Drag rows 1,3 below row 5: [2, 4, 5, 1, 3]
|
|
77
|
+
expect(ids(reorderRows(rows, [row(1), row(3)], row(5), 1))).toEqual([2, 4, 5, 1, 3]);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('preserves insertion order of movedRows', () => {
|
|
81
|
+
// movedRows given in display order [2,4] should insert as [2,4] not [4,2]
|
|
82
|
+
const result = reorderRows(rows, [row(2), row(4)], row(1), -1);
|
|
83
|
+
expect(ids(result)).toEqual([2, 4, 1, 3, 5]);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('edge cases', () => {
|
|
88
|
+
test('target is one of the moved rows — returns original array', () => {
|
|
89
|
+
const result = reorderRows(rows, [row(2), row(3)], row(2), 1);
|
|
90
|
+
expect(result).toBe(rows);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('empty movedRows — returns original array', () => {
|
|
94
|
+
const result = reorderRows(rows, [], row(3), 1);
|
|
95
|
+
expect(result).toBe(rows);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('target not found in rows — returns original array', () => {
|
|
99
|
+
const result = reorderRows(rows, [row(1)], row(99), 1);
|
|
100
|
+
expect(result).toBe(rows);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('single row array — move is no-op (target is the moved row)', () => {
|
|
104
|
+
const single = makeRows(1);
|
|
105
|
+
const result = reorderRows(single, [row(1)], row(1), 1);
|
|
106
|
+
expect(result).toBe(single);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('two row array — swap positions', () => {
|
|
110
|
+
const two = makeRows(1, 2);
|
|
111
|
+
expect(ids(reorderRows(two, [row(1)], row(2), 1))).toEqual([2, 1]);
|
|
112
|
+
expect(ids(reorderRows(two, [row(2)], row(1), -1))).toEqual([2, 1]);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { GridBaseRow } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Reorders rows by removing movedRows from the array and inserting them at the target position.
|
|
5
|
+
* @param rows - The current row array.
|
|
6
|
+
* @param movedRows - Rows to move, in desired insertion order (typically display order).
|
|
7
|
+
* @param targetRow - The row to insert relative to.
|
|
8
|
+
* @param direction - -1 inserts above targetRow, 1 inserts below targetRow.
|
|
9
|
+
* @returns A new array with the rows reordered, or the original array if the operation is a no-op.
|
|
10
|
+
*/
|
|
11
|
+
export function reorderRows<T extends GridBaseRow>(rows: T[], movedRows: T[], targetRow: T, direction: -1 | 1): T[] {
|
|
12
|
+
if (movedRows.length === 0) return rows;
|
|
13
|
+
|
|
14
|
+
const movedIds = new Set(movedRows.map((r) => r.id));
|
|
15
|
+
|
|
16
|
+
// If targetRow is one of the moved rows, no-op
|
|
17
|
+
if (movedIds.has(targetRow.id)) return rows;
|
|
18
|
+
|
|
19
|
+
// Remove moved rows from the array
|
|
20
|
+
const remaining = rows.filter((r) => !movedIds.has(r.id));
|
|
21
|
+
|
|
22
|
+
// Find where to insert relative to the target
|
|
23
|
+
const targetIndex = remaining.findIndex((r) => r.id === targetRow.id);
|
|
24
|
+
if (targetIndex === -1) return rows;
|
|
25
|
+
|
|
26
|
+
const insertIndex = direction === 1 ? targetIndex + 1 : targetIndex;
|
|
27
|
+
|
|
28
|
+
// Insert movedRows at the calculated position
|
|
29
|
+
const result = [...remaining];
|
|
30
|
+
result.splice(insertIndex, 0, ...movedRows);
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
@@ -1,24 +1,22 @@
|
|
|
1
|
-
@use
|
|
1
|
+
@use '@linzjs/lui/dist/scss/Core' as lui;
|
|
2
2
|
|
|
3
3
|
.GridFilterColsMultiSelect {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
background: #fff;
|
|
5
|
+
padding: 8px;
|
|
6
|
+
@include lui.font-semibold();
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
.LuiSelect-label-text {
|
|
9
|
+
color: lui.$fuscous;
|
|
10
|
+
display: inline-block;
|
|
11
|
+
}
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
}
|
|
13
|
+
.LuiCheckboxInput-selectAll,
|
|
14
|
+
.LuiCheckboxInput-item {
|
|
15
|
+
color: lui.$charcoal;
|
|
16
|
+
margin-bottom: 0;
|
|
17
|
+
font-weight: 600;
|
|
18
|
+
font-size: 16px;
|
|
19
|
+
line-height: 24px;
|
|
20
|
+
letter-spacing: 0;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -45,7 +45,7 @@ export const GridFilterColumnsToggle = ({ saveState = true }: GridFilterColumnsT
|
|
|
45
45
|
} else if (!invisibleIds.every((id) => typeof id === 'string')) {
|
|
46
46
|
console.error(`stored invisible ids not strings: ${stored}`);
|
|
47
47
|
} else {
|
|
48
|
-
invisibleIds
|
|
48
|
+
if (invisibleIds) setInvisibleColumnIds(invisibleIds);
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
} catch (ex) {
|
|
@@ -57,10 +57,9 @@ export const GridFilterColumnsToggle = ({ saveState = true }: GridFilterColumnsT
|
|
|
57
57
|
|
|
58
58
|
// Save state on column visibility change
|
|
59
59
|
useEffect(() => {
|
|
60
|
-
loaded &&
|
|
61
|
-
columnStorageKey &&
|
|
62
|
-
saveState &&
|
|
60
|
+
if (loaded && columnStorageKey && saveState) {
|
|
63
61
|
window.localStorage.setItem(columnStorageKey, JSON.stringify(invisibleColumnIds));
|
|
62
|
+
}
|
|
64
63
|
}, [columnStorageKey, invisibleColumnIds, loaded, saveState]);
|
|
65
64
|
|
|
66
65
|
const toggleColumn = useCallback(
|
|
@@ -8,9 +8,9 @@ export const useGridFilter = <TData extends GridBaseRow>(filter: GridFilterExter
|
|
|
8
8
|
|
|
9
9
|
useEffect(() => {
|
|
10
10
|
const thisFilter = filter;
|
|
11
|
-
thisFilter
|
|
11
|
+
if (thisFilter) addExternalFilter(thisFilter);
|
|
12
12
|
return () => {
|
|
13
|
-
thisFilter
|
|
13
|
+
if (thisFilter) removeExternalFilter(thisFilter);
|
|
14
14
|
};
|
|
15
15
|
}, [addExternalFilter, filter, removeExternalFilter]);
|
|
16
16
|
};
|
|
@@ -50,7 +50,7 @@ export type SelectOption<TOptionValue = any> = FinalSelectOption<TOptionValue>;
|
|
|
50
50
|
export interface GridFormDropDownProps<TData extends GridBaseRow, TOptionValue> extends CellEditorCommon {
|
|
51
51
|
// This overrides CellEditorCommon to provide some common class options
|
|
52
52
|
className?: // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
|
|
53
|
-
|
|
53
|
+
| 'GridPopoverEditDropDown-containerSmall'
|
|
54
54
|
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
|
|
55
55
|
| 'GridPopoverEditDropDown-containerMedium'
|
|
56
56
|
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
|
|
@@ -11,8 +11,7 @@ import { useGridPopoverHook } from '../GridPopoverHook';
|
|
|
11
11
|
import { GridBaseRow } from '../types';
|
|
12
12
|
|
|
13
13
|
export interface GridFormInlineTextInput<TData extends GridBaseRow>
|
|
14
|
-
extends TextInputValidatorProps<TData>,
|
|
15
|
-
CellEditorCommon {
|
|
14
|
+
extends TextInputValidatorProps<TData>, CellEditorCommon {
|
|
16
15
|
placeholder?: string;
|
|
17
16
|
units?: string;
|
|
18
17
|
onSave?: (props: { selectedRows: TData[]; selectedRowIds: TData['id'][]; value: string }) => MaybePromise<boolean>;
|
|
@@ -51,7 +51,7 @@ export interface GridFormMultiSelectSaveProps<TData extends GridBaseRow> {
|
|
|
51
51
|
|
|
52
52
|
export interface GridFormMultiSelectProps<TData extends GridBaseRow> extends CellEditorCommon {
|
|
53
53
|
className?: // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
|
|
54
|
-
|
|
54
|
+
| 'GridMultiSelect-containerSmall'
|
|
55
55
|
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
|
|
56
56
|
| 'GridMultiSelect-containerMedium'
|
|
57
57
|
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
|
|
@@ -322,11 +322,15 @@ const FilterInput = (props: {
|
|
|
322
322
|
void triggerSave();
|
|
323
323
|
return;
|
|
324
324
|
}
|
|
325
|
-
|
|
325
|
+
if (onSelectFilter) {
|
|
326
|
+
addCustomFilterValue();
|
|
327
|
+
}
|
|
326
328
|
}
|
|
327
329
|
lastKeyWasEnter.current = true;
|
|
328
330
|
} else if (e.key === 'Control') {
|
|
329
|
-
lastKeyWasEnter.current
|
|
331
|
+
if (lastKeyWasEnter.current) {
|
|
332
|
+
setFilter('');
|
|
333
|
+
}
|
|
330
334
|
lastKeyWasEnter.current = false;
|
|
331
335
|
} else {
|
|
332
336
|
lastKeyWasEnter.current = false;
|
|
@@ -392,7 +396,9 @@ const MenuRadioItem = (props: {
|
|
|
392
396
|
e.keepOpen = true;
|
|
393
397
|
toggleValue(item);
|
|
394
398
|
}
|
|
395
|
-
item.checked
|
|
399
|
+
if (item.checked) {
|
|
400
|
+
props.onChecked?.();
|
|
401
|
+
}
|
|
396
402
|
}}
|
|
397
403
|
>
|
|
398
404
|
<LuiCheckboxInput
|
|
@@ -8,8 +8,7 @@ import { CellEditorCommon } from '../GridCell';
|
|
|
8
8
|
import { GridBaseRow } from '../types';
|
|
9
9
|
|
|
10
10
|
export interface GridSubComponentTextAreaProps<TData extends GridBaseRow>
|
|
11
|
-
extends TextInputValidatorProps<TData>,
|
|
12
|
-
CellEditorCommon {
|
|
11
|
+
extends TextInputValidatorProps<TData>, CellEditorCommon {
|
|
13
12
|
placeholder?: string;
|
|
14
13
|
width?: string | number;
|
|
15
14
|
defaultValue: string;
|
|
@@ -7,8 +7,7 @@ import { CellEditorCommon } from '../GridCell';
|
|
|
7
7
|
import { GridBaseRow } from '../types';
|
|
8
8
|
|
|
9
9
|
export interface GridFormSubComponentTextInputProps<TData extends GridBaseRow>
|
|
10
|
-
extends TextInputValidatorProps<TData>,
|
|
11
|
-
CellEditorCommon {
|
|
10
|
+
extends TextInputValidatorProps<TData>, CellEditorCommon {
|
|
12
11
|
placeholder?: string;
|
|
13
12
|
width?: string | number;
|
|
14
13
|
defaultValue: string;
|
|
@@ -8,8 +8,7 @@ import { useGridPopoverHook } from '../GridPopoverHook';
|
|
|
8
8
|
import { GridBaseRow } from '../types';
|
|
9
9
|
|
|
10
10
|
export interface GridFormTextAreaProps<TData extends GridBaseRow>
|
|
11
|
-
extends TextInputValidatorProps<TData>,
|
|
12
|
-
CellEditorCommon {
|
|
11
|
+
extends TextInputValidatorProps<TData>, CellEditorCommon {
|
|
13
12
|
placeholder?: string;
|
|
14
13
|
width?: string | number;
|
|
15
14
|
onSave?: (props: { selectedRows: TData[]; value: string }) => Promise<boolean>;
|
|
@@ -8,8 +8,7 @@ import { useGridPopoverHook } from '../GridPopoverHook';
|
|
|
8
8
|
import { GridBaseRow } from '../types';
|
|
9
9
|
|
|
10
10
|
export interface GridFormTextInputProps<TData extends GridBaseRow>
|
|
11
|
-
extends TextInputValidatorProps<TData>,
|
|
12
|
-
CellEditorCommon {
|
|
11
|
+
extends TextInputValidatorProps<TData>, CellEditorCommon {
|
|
13
12
|
placeholder?: string;
|
|
14
13
|
units?: string;
|
|
15
14
|
width?: string | number;
|
|
@@ -19,7 +19,9 @@ export const GridHeaderSelect = ({ api }: IHeaderParams) => {
|
|
|
19
19
|
|
|
20
20
|
api.addEventListener('selectionChanged', clickHandler);
|
|
21
21
|
return () => {
|
|
22
|
-
!api.isDestroyed()
|
|
22
|
+
if (!api.isDestroyed()) {
|
|
23
|
+
api.removeEventListener('selectionChanged', clickHandler);
|
|
24
|
+
}
|
|
23
25
|
};
|
|
24
26
|
}, [api, updateCounter]);
|
|
25
27
|
|