@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
|
@@ -219,7 +219,17 @@ export const useGridCopy = <TData extends GridBaseRow>({
|
|
|
219
219
|
void navigator.clipboard.writeText(result).catch((err) => console.error('Failed to copy: ', err));
|
|
220
220
|
}
|
|
221
221
|
},
|
|
222
|
-
[
|
|
222
|
+
[
|
|
223
|
+
getCellValue,
|
|
224
|
+
ranges,
|
|
225
|
+
copyType,
|
|
226
|
+
rangeStartRef,
|
|
227
|
+
hasSelectedMoreThanOneCellRef,
|
|
228
|
+
rangeEndRef,
|
|
229
|
+
showToast,
|
|
230
|
+
getColDef,
|
|
231
|
+
getSelectedRowIds,
|
|
232
|
+
],
|
|
223
233
|
);
|
|
224
234
|
|
|
225
235
|
const onCopyEvent = useCallback(
|
|
@@ -240,7 +250,7 @@ export const useGridCopy = <TData extends GridBaseRow>({
|
|
|
240
250
|
|
|
241
251
|
onCopy();
|
|
242
252
|
},
|
|
243
|
-
[onCopy],
|
|
253
|
+
[onCopy, rangeStartRef, rangeEndRef, hasSelectedMoreThanOneCellRef],
|
|
244
254
|
);
|
|
245
255
|
|
|
246
256
|
const { cellContextMenu: rangeSelectContextMenu, contextMenuComponent: rangeSelectContextMenuComponent } =
|
|
@@ -255,7 +265,7 @@ export const useGridCopy = <TData extends GridBaseRow>({
|
|
|
255
265
|
? rangeSelectContextMenu(event)
|
|
256
266
|
: cellContextMenu(event);
|
|
257
267
|
},
|
|
258
|
-
[cellContextMenu, rangeSelectContextMenu],
|
|
268
|
+
[cellContextMenu, rangeSelectContextMenu, rangeStartRef, hasSelectedMoreThanOneCellRef],
|
|
259
269
|
);
|
|
260
270
|
|
|
261
271
|
return {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CellMouseDownEvent, CellMouseOverEvent, IRowNode } from 'ag-grid-community';
|
|
2
2
|
import { MutableRefObject, RefObject, useCallback, useEffect, useRef, useState } from 'react';
|
|
3
|
-
import { useInterval } from 'usehooks-ts';
|
|
4
3
|
|
|
4
|
+
import { useInterval } from '../../utils/useInterval';
|
|
5
5
|
import { GridBaseRow } from '../types';
|
|
6
6
|
|
|
7
7
|
export interface CellLocation {
|
|
@@ -79,7 +79,7 @@ export const useGridRangeSelection = <TData extends GridBaseRow>({
|
|
|
79
79
|
);
|
|
80
80
|
|
|
81
81
|
return { selectedColIds, selectedNodes };
|
|
82
|
-
}, []);
|
|
82
|
+
}, [gridDivRef, rangeSortedNodesRef, rangeStartRef, rangeEndRef]);
|
|
83
83
|
|
|
84
84
|
const redrawSelectedRanges = useCallback(() => {
|
|
85
85
|
const gridElement = gridDivRef.current!;
|
|
@@ -106,7 +106,7 @@ export const useGridRangeSelection = <TData extends GridBaseRow>({
|
|
|
106
106
|
}
|
|
107
107
|
});
|
|
108
108
|
});
|
|
109
|
-
}, []);
|
|
109
|
+
}, [ranges, gridDivRef]);
|
|
110
110
|
|
|
111
111
|
const updateRangeSelectionCellClasses = useCallback(
|
|
112
112
|
(justRefresh?: boolean) => {
|
|
@@ -158,7 +158,7 @@ export const useGridRangeSelection = <TData extends GridBaseRow>({
|
|
|
158
158
|
|
|
159
159
|
redrawSelectedRanges();
|
|
160
160
|
},
|
|
161
|
-
[
|
|
161
|
+
[redrawSelectedRanges, rangeStartRef, rangeEndRef, gridDivRef, hasSelectedMoreThanOneCellRef, rangeSortedNodesRef],
|
|
162
162
|
);
|
|
163
163
|
|
|
164
164
|
// Handle updates after scroll / grid refresh
|
|
@@ -178,18 +178,24 @@ export const useGridRangeSelection = <TData extends GridBaseRow>({
|
|
|
178
178
|
rangeEndRef.current = null;
|
|
179
179
|
|
|
180
180
|
updateRangeSelectionCellClasses();
|
|
181
|
-
}, [updateRangeSelectionCellClasses]);
|
|
181
|
+
}, [updateRangeSelectionCellClasses, hasSelectedMoreThanOneCellRef, rangeStartRef, rangeEndRef]);
|
|
182
182
|
|
|
183
183
|
const mouseDownRef = useRef<[number, number]>([0, 0]);
|
|
184
|
-
const trackMouseDown = useCallback(
|
|
185
|
-
|
|
186
|
-
|
|
184
|
+
const trackMouseDown = useCallback(
|
|
185
|
+
(e: MouseEvent) => {
|
|
186
|
+
mouseDownRef.current = [e.screenX, e.screenY];
|
|
187
|
+
},
|
|
188
|
+
[mouseDownRef],
|
|
189
|
+
);
|
|
187
190
|
|
|
188
|
-
const trackMouseUp = useCallback(
|
|
189
|
-
|
|
190
|
-
rangeEndRef.current
|
|
191
|
-
|
|
192
|
-
|
|
191
|
+
const trackMouseUp = useCallback(
|
|
192
|
+
(e: MouseEvent) => {
|
|
193
|
+
if (rangeEndRef.current) {
|
|
194
|
+
rangeEndRef.current.clickLocation = [e.screenX, e.screenY];
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
[rangeEndRef],
|
|
198
|
+
);
|
|
193
199
|
|
|
194
200
|
useEffect(() => {
|
|
195
201
|
document.addEventListener('mousedown', trackMouseDown, { capture: true });
|
|
@@ -198,7 +204,7 @@ export const useGridRangeSelection = <TData extends GridBaseRow>({
|
|
|
198
204
|
document.removeEventListener('mousemove', trackMouseDown, { capture: true });
|
|
199
205
|
document.removeEventListener('mouseup', trackMouseUp, { capture: true });
|
|
200
206
|
};
|
|
201
|
-
}, []);
|
|
207
|
+
}, [trackMouseDown, trackMouseUp]);
|
|
202
208
|
|
|
203
209
|
const onCellMouseOver = useCallback(
|
|
204
210
|
(e: CellMouseOverEvent, mouseDown?: boolean) => {
|
|
@@ -238,7 +244,14 @@ export const useGridRangeSelection = <TData extends GridBaseRow>({
|
|
|
238
244
|
|
|
239
245
|
updateRangeSelectionCellClasses();
|
|
240
246
|
},
|
|
241
|
-
[
|
|
247
|
+
[
|
|
248
|
+
enableRangeSelection,
|
|
249
|
+
updateRangeSelectionCellClasses,
|
|
250
|
+
hasSelectedMoreThanOneCellRef,
|
|
251
|
+
rangeEndRef,
|
|
252
|
+
rangeSortedNodesRef,
|
|
253
|
+
rangeStartRef,
|
|
254
|
+
],
|
|
242
255
|
);
|
|
243
256
|
|
|
244
257
|
const onCellMouseDown = useCallback(
|
|
@@ -249,7 +262,7 @@ export const useGridRangeSelection = <TData extends GridBaseRow>({
|
|
|
249
262
|
}
|
|
250
263
|
onCellMouseOver(e as unknown as CellMouseOverEvent, true);
|
|
251
264
|
},
|
|
252
|
-
[onCellMouseOver],
|
|
265
|
+
[onCellMouseOver, clearRangeSelection],
|
|
253
266
|
);
|
|
254
267
|
|
|
255
268
|
return { clearRangeSelection, ranges, onCellMouseDown, onCellMouseOver };
|
package/src/components/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ export * from './GridCell';
|
|
|
4
4
|
export * from './GridCellFiller';
|
|
5
5
|
export * from './GridCellMultiEditor';
|
|
6
6
|
export * from './GridCellMultiSelectClassRules';
|
|
7
|
+
export * from './gridDragUtil';
|
|
7
8
|
export * from './gridFilter';
|
|
8
9
|
export * from './gridForm';
|
|
9
10
|
export * from './gridHook';
|
package/src/components/types.ts
CHANGED
|
@@ -8,8 +8,12 @@ export interface GridBaseRow {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export interface GridOnRowDragEndProps<TData extends GridBaseRow> {
|
|
11
|
+
/** The row the user physically grabbed by its drag handle. */
|
|
11
12
|
movedRow: TData;
|
|
13
|
+
/** All rows being moved, ordered by display index. Single-element array when dragging an unselected row. */
|
|
14
|
+
movedRows: TData[];
|
|
12
15
|
targetRow: TData;
|
|
16
|
+
/** -1 = dropped above target, 1 = dropped below target. */
|
|
13
17
|
direction: -1 | 1;
|
|
14
18
|
}
|
|
15
19
|
|
|
@@ -122,7 +122,7 @@ export const GridContextProvider = <TData extends GridBaseRow>(props: PropsWithC
|
|
|
122
122
|
// this is the only way to get out of the loop
|
|
123
123
|
throw 'expected exception - exit_loop';
|
|
124
124
|
});
|
|
125
|
-
} catch
|
|
125
|
+
} catch {
|
|
126
126
|
// ignore
|
|
127
127
|
}
|
|
128
128
|
return id;
|
|
@@ -312,7 +312,7 @@ export const GridContextProvider = <TData extends GridBaseRow>(props: PropsWithC
|
|
|
312
312
|
const colId = col.colId;
|
|
313
313
|
// We need to make sure we aren't currently editing a cell otherwise tests will fail
|
|
314
314
|
// as they will start to edit the cell before this stuff has a chance to run
|
|
315
|
-
colId
|
|
315
|
+
if (colId) {
|
|
316
316
|
delay(() => {
|
|
317
317
|
if (
|
|
318
318
|
!gridApi.isDestroyed() &&
|
|
@@ -326,6 +326,7 @@ export const GridContextProvider = <TData extends GridBaseRow>(props: PropsWithC
|
|
|
326
326
|
gridApi.setFocusedCell(rowIndex, agGridSelectRowColId);
|
|
327
327
|
}
|
|
328
328
|
}, 100);
|
|
329
|
+
}
|
|
329
330
|
}
|
|
330
331
|
}
|
|
331
332
|
}
|
|
@@ -342,7 +343,9 @@ export const GridContextProvider = <TData extends GridBaseRow>(props: PropsWithC
|
|
|
342
343
|
if (flash) {
|
|
343
344
|
delay(() => {
|
|
344
345
|
try {
|
|
345
|
-
!gridApi.isDestroyed()
|
|
346
|
+
if (!gridApi.isDestroyed()) {
|
|
347
|
+
gridApi.flashCells({ rowNodes });
|
|
348
|
+
}
|
|
346
349
|
} catch {
|
|
347
350
|
// ignore, flash cells sometimes throws errors as nodes have gone out of scope
|
|
348
351
|
}
|
|
@@ -749,7 +752,9 @@ export const GridContextProvider = <TData extends GridBaseRow>(props: PropsWithC
|
|
|
749
752
|
// }
|
|
750
753
|
|
|
751
754
|
// This is needed to trigger postSortRowsHook
|
|
752
|
-
!gridApi.isDestroyed
|
|
755
|
+
if (!gridApi.isDestroyed) {
|
|
756
|
+
gridApi.refreshClientSideRowModel();
|
|
757
|
+
}
|
|
753
758
|
}
|
|
754
759
|
|
|
755
760
|
void (async () => {
|
|
@@ -782,7 +787,9 @@ export const GridContextProvider = <TData extends GridBaseRow>(props: PropsWithC
|
|
|
782
787
|
() =>
|
|
783
788
|
debounce((rowNodes?: IRowNode[]) => {
|
|
784
789
|
try {
|
|
785
|
-
|
|
790
|
+
if (gridApi) {
|
|
791
|
+
gridApi.redrawRows(rowNodes ? { rowNodes } : undefined);
|
|
792
|
+
}
|
|
786
793
|
} catch (ex) {
|
|
787
794
|
console.error(ex);
|
|
788
795
|
}
|
|
@@ -812,7 +819,9 @@ export const GridContextProvider = <TData extends GridBaseRow>(props: PropsWithC
|
|
|
812
819
|
const postHash = hasFocusedCell && getDisplayedRowsHash();
|
|
813
820
|
// Ag-grid has a bug where if a focused cell comes into view after a filter the filter loses focus
|
|
814
821
|
// So the focus is cleared to prevent this
|
|
815
|
-
preHash !== postHash
|
|
822
|
+
if (preHash !== postHash) {
|
|
823
|
+
gridApi.clearFocusedCell();
|
|
824
|
+
}
|
|
816
825
|
}
|
|
817
826
|
}, 200),
|
|
818
827
|
[gridApi],
|
|
@@ -864,7 +873,9 @@ export const GridContextProvider = <TData extends GridBaseRow>(props: PropsWithC
|
|
|
864
873
|
const visibleColumnsContainsAFlex = newVisibleColumns.some(isFlexColumn);
|
|
865
874
|
if (!visibleColumnsContainsAFlex) {
|
|
866
875
|
const fillerColumn = getColumns(isGridCellFiller)[0];
|
|
867
|
-
|
|
876
|
+
if (fillerColumn) {
|
|
877
|
+
newVisibleColumns.push(fillerColumn);
|
|
878
|
+
}
|
|
868
879
|
}
|
|
869
880
|
gridApi.setColumnsVisible(compact(newVisibleColumns.map(getColId)), true);
|
|
870
881
|
|
package/src/lui/ActionButton.tsx
CHANGED
|
@@ -50,7 +50,11 @@ export const ActionButton = ({
|
|
|
50
50
|
|
|
51
51
|
useEffect(() => {
|
|
52
52
|
if (inProgress == lastInProgress) return;
|
|
53
|
-
|
|
53
|
+
if (inProgress) {
|
|
54
|
+
setLocalInProgress(true);
|
|
55
|
+
} else {
|
|
56
|
+
setLocalInProgressDeferred(false, minimumInProgressTimeMs);
|
|
57
|
+
}
|
|
54
58
|
}, [inProgress, lastInProgress, setLocalInProgress, setLocalInProgressDeferred]);
|
|
55
59
|
|
|
56
60
|
const buttonText = (
|
package/src/lui/FormError.scss
CHANGED
|
@@ -45,7 +45,7 @@ export const TextAreaInput = (props: LuiTextAreaInputProps) => {
|
|
|
45
45
|
e.currentTarget.focus();
|
|
46
46
|
e.currentTarget.selectionStart = e.currentTarget.value.length;
|
|
47
47
|
}
|
|
48
|
-
props.onMouseEnter
|
|
48
|
+
props.onMouseEnter?.(e);
|
|
49
49
|
}}
|
|
50
50
|
data-allowtabtosave={props.allowTabToSave}
|
|
51
51
|
>
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
@use
|
|
2
|
-
|
|
3
|
-
.LuiTextInput.GridLuiTextInput {
|
|
4
|
-
margin-bottom: 0;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
.LuiTextInput-formatted {
|
|
8
|
-
position: absolute;
|
|
9
|
-
right: 14px;
|
|
10
|
-
top: 0;
|
|
11
|
-
line-height: 48px;
|
|
12
|
-
color: lui.$fuscous;
|
|
13
|
-
}
|
|
1
|
+
@use '../../node_modules/@linzjs/lui/dist/scss/Core' as lui;
|
|
2
|
+
|
|
3
|
+
.LuiTextInput.GridLuiTextInput {
|
|
4
|
+
margin-bottom: 0;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.LuiTextInput-formatted {
|
|
8
|
+
position: absolute;
|
|
9
|
+
right: 14px;
|
|
10
|
+
top: 0;
|
|
11
|
+
line-height: 48px;
|
|
12
|
+
color: lui.$fuscous;
|
|
13
|
+
}
|
|
@@ -36,7 +36,7 @@ export const TextInputFormatted = (props: LuiTextInputProps): ReactElement => {
|
|
|
36
36
|
className={'LuiTextInput-input'}
|
|
37
37
|
onMouseEnter={(e) => {
|
|
38
38
|
e.currentTarget.focus();
|
|
39
|
-
props.onMouseEnter
|
|
39
|
+
props.onMouseEnter?.(e);
|
|
40
40
|
}}
|
|
41
41
|
data-allowtabtosave={props.allowTabToSave}
|
|
42
42
|
/>
|
|
@@ -1,67 +1,66 @@
|
|
|
1
|
-
# React-Menu
|
|
2
|
-
|
|
3
|
-
> This is a fork of @szhsin/react-menu. v3.2.0
|
|
4
|
-
|
|
5
|
-
> An accessible and keyboard-friendly React menu library.
|
|
6
|
-
|
|
7
|
-
**[Live examples and docs](https://szhsin.github.io/react-menu/)**
|
|
8
|
-
|
|
9
|
-
## Features
|
|
10
|
-
|
|
11
|
-
- React menu components for easy and fast web development.
|
|
12
|
-
- Unlimited levels of submenu.
|
|
13
|
-
- Supports dropdown or context menu.
|
|
14
|
-
- Supports radio and checkbox menu items.
|
|
15
|
-
- Flexible menu positioning.
|
|
16
|
-
- Comprehensive keyboard interactions.
|
|
17
|
-
- Unstyled components and easy [customisation](https://szhsin.github.io/react-menu/#styling).
|
|
18
|
-
- [Optimal level support](https://github.com/reactwg/react-18/discussions/70) (level 3) of **React 18** concurrent rendering.
|
|
19
|
-
- Works in major browsers without polyfills.
|
|
20
|
-
- [WAI-ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/patterns/menu/) compliant.
|
|
21
|
-
|
|
22
|
-
## Install
|
|
23
|
-
|
|
24
|
-
with npm
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
npm install @szhsin/react-menu
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
or with Yarn
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
yarn add @szhsin/react-menu
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## Usage
|
|
37
|
-
|
|
38
|
-
```jsx
|
|
39
|
-
import { Menu, MenuItem, MenuButton, SubMenu } from
|
|
40
|
-
|
|
41
|
-
export default function App() {
|
|
42
|
-
return (
|
|
43
|
-
<Menu menuButton={<MenuButton>Open menu</MenuButton>}>
|
|
44
|
-
<MenuItem>New File</MenuItem>
|
|
45
|
-
<MenuItem>Save</MenuItem>
|
|
46
|
-
<SubMenu label="Edit">
|
|
47
|
-
<MenuItem>Cut</MenuItem>
|
|
48
|
-
<MenuItem>Copy</MenuItem>
|
|
49
|
-
<MenuItem>Paste</MenuItem>
|
|
50
|
-
</SubMenu>
|
|
51
|
-
<MenuItem>Print...</MenuItem>
|
|
52
|
-
</Menu>
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
**[Edit on CodeSandbox](https://codesandbox.io/s/react-menu-starter-3ez3c)**<br>
|
|
58
|
-
**[Visit more examples and docs](https://szhsin.github.io/react-menu/)**<br><br>
|
|
59
|
-
[Migration from v2 to v3](https://github.com/szhsin/react-menu/wiki/Migration-from-v2-to-v3)<br>
|
|
60
|
-
[Migration from v1 to v2](https://github.com/szhsin/react-menu/wiki/Migration-from-v1-to-v2)
|
|
61
|
-
|
|
62
|
-
_Please note that React-Menu v3 requires React 16.14.0 or higher. If you are not able to upgrade React, you could install v2/v1 which requires React 16.8.0._
|
|
63
|
-
|
|
64
|
-
## License
|
|
65
|
-
|
|
66
|
-
[MIT](https://github.com/szhsin/react-menu/blob/master/LICENSE) Licensed.
|
|
67
|
-
|
|
1
|
+
# React-Menu
|
|
2
|
+
|
|
3
|
+
> This is a fork of @szhsin/react-menu. v3.2.0
|
|
4
|
+
|
|
5
|
+
> An accessible and keyboard-friendly React menu library.
|
|
6
|
+
|
|
7
|
+
**[Live examples and docs](https://szhsin.github.io/react-menu/)**
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- React menu components for easy and fast web development.
|
|
12
|
+
- Unlimited levels of submenu.
|
|
13
|
+
- Supports dropdown or context menu.
|
|
14
|
+
- Supports radio and checkbox menu items.
|
|
15
|
+
- Flexible menu positioning.
|
|
16
|
+
- Comprehensive keyboard interactions.
|
|
17
|
+
- Unstyled components and easy [customisation](https://szhsin.github.io/react-menu/#styling).
|
|
18
|
+
- [Optimal level support](https://github.com/reactwg/react-18/discussions/70) (level 3) of **React 18** concurrent rendering.
|
|
19
|
+
- Works in major browsers without polyfills.
|
|
20
|
+
- [WAI-ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/patterns/menu/) compliant.
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
with npm
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @szhsin/react-menu
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
or with Yarn
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
yarn add @szhsin/react-menu
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
```jsx
|
|
39
|
+
import { Menu, MenuItem, MenuButton, SubMenu } from '@szhsin/react-menu';
|
|
40
|
+
|
|
41
|
+
export default function App() {
|
|
42
|
+
return (
|
|
43
|
+
<Menu menuButton={<MenuButton>Open menu</MenuButton>}>
|
|
44
|
+
<MenuItem>New File</MenuItem>
|
|
45
|
+
<MenuItem>Save</MenuItem>
|
|
46
|
+
<SubMenu label="Edit">
|
|
47
|
+
<MenuItem>Cut</MenuItem>
|
|
48
|
+
<MenuItem>Copy</MenuItem>
|
|
49
|
+
<MenuItem>Paste</MenuItem>
|
|
50
|
+
</SubMenu>
|
|
51
|
+
<MenuItem>Print...</MenuItem>
|
|
52
|
+
</Menu>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**[Edit on CodeSandbox](https://codesandbox.io/s/react-menu-starter-3ez3c)**<br>
|
|
58
|
+
**[Visit more examples and docs](https://szhsin.github.io/react-menu/)**<br><br>
|
|
59
|
+
[Migration from v2 to v3](https://github.com/szhsin/react-menu/wiki/Migration-from-v2-to-v3)<br>
|
|
60
|
+
[Migration from v1 to v2](https://github.com/szhsin/react-menu/wiki/Migration-from-v1-to-v2)
|
|
61
|
+
|
|
62
|
+
_Please note that React-Menu v3 requires React 16.14.0 or higher. If you are not able to upgrade React, you could install v2/v1 which requires React 16.8.0._
|
|
63
|
+
|
|
64
|
+
## License
|
|
65
|
+
|
|
66
|
+
[MIT](https://github.com/szhsin/react-menu/blob/master/LICENSE) Licensed.
|
|
@@ -173,7 +173,7 @@ export const ControlledMenuFr = (
|
|
|
173
173
|
if (isDown) {
|
|
174
174
|
lastTabDownEl.current = activeElement;
|
|
175
175
|
} else {
|
|
176
|
-
lastTabDownEl.current == activeElement
|
|
176
|
+
if (lastTabDownEl.current == activeElement) invokeSave(tabDirection);
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
179
|
}
|
|
@@ -191,7 +191,7 @@ export const ControlledMenuFr = (
|
|
|
191
191
|
if (isDown) {
|
|
192
192
|
lastEnterDownEl.current = activeElement;
|
|
193
193
|
} else {
|
|
194
|
-
lastEnterDownEl.current == activeElement
|
|
194
|
+
if (lastEnterDownEl.current == activeElement) invokeSave(CloseReason.CLICK);
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
197
|
}
|
|
@@ -175,7 +175,9 @@ export const MenuList = ({
|
|
|
175
175
|
setOverflowData(undefined); // reset overflowData after closing
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
|
|
178
|
+
if (endTransition) {
|
|
179
|
+
safeCall(endTransition);
|
|
180
|
+
}
|
|
179
181
|
};
|
|
180
182
|
|
|
181
183
|
const handlePosition = useCallback(
|
|
@@ -455,7 +457,11 @@ export const MenuList = ({
|
|
|
455
457
|
let maxHeight: number | undefined;
|
|
456
458
|
let overflowAmt: number | undefined;
|
|
457
459
|
if (overflowData) {
|
|
458
|
-
|
|
460
|
+
if (setDownOverflow) {
|
|
461
|
+
overflowAmt = overflowData.overflowAmt;
|
|
462
|
+
} else {
|
|
463
|
+
maxHeight = overflowData.height;
|
|
464
|
+
}
|
|
459
465
|
}
|
|
460
466
|
|
|
461
467
|
const listContext = useMemo(
|
|
@@ -194,7 +194,7 @@ export const SubMenuFr = ({
|
|
|
194
194
|
const openMenu = (...args: any[]) => {
|
|
195
195
|
stopTimer();
|
|
196
196
|
setHover();
|
|
197
|
-
!isDisabled
|
|
197
|
+
if (!isDisabled) _openMenu(...args);
|
|
198
198
|
};
|
|
199
199
|
|
|
200
200
|
const setHover = () => !isHovering && !isDisabled && dispatch(HoverActionTypes.SET, menuItemRef?.current, 0);
|
|
@@ -226,7 +226,7 @@ export const SubMenuFr = ({
|
|
|
226
226
|
// LEFT key is bubbled up from submenu items
|
|
227
227
|
case Keys.LEFT:
|
|
228
228
|
if (isOpen) {
|
|
229
|
-
menuItemRef?.current
|
|
229
|
+
menuItemRef?.current.focus();
|
|
230
230
|
toggleMenu(false);
|
|
231
231
|
handled = true;
|
|
232
232
|
}
|
|
@@ -251,7 +251,7 @@ export const SubMenuFr = ({
|
|
|
251
251
|
case Keys.ENTER:
|
|
252
252
|
case Keys.SPACE:
|
|
253
253
|
case Keys.RIGHT:
|
|
254
|
-
openTrigger !== 'none'
|
|
254
|
+
if (openTrigger !== 'none') openMenu(FocusPositions.FIRST);
|
|
255
255
|
break;
|
|
256
256
|
}
|
|
257
257
|
};
|
|
@@ -264,7 +264,7 @@ export const SubMenuFr = ({
|
|
|
264
264
|
// Don't set focus when parent menu is closed, otherwise focus will be lost
|
|
265
265
|
// and onBlur event will be fired with relatedTarget setting as null.
|
|
266
266
|
if (isHovering && isParentOpen) {
|
|
267
|
-
menuItemRef?.current
|
|
267
|
+
menuItemRef?.current?.focus();
|
|
268
268
|
} else {
|
|
269
269
|
toggleMenu(false);
|
|
270
270
|
}
|
|
@@ -276,11 +276,11 @@ export const SubMenuFr = ({
|
|
|
276
276
|
|
|
277
277
|
useImperativeHandle(instanceRef, () => ({
|
|
278
278
|
openMenu: (...args) => {
|
|
279
|
-
isParentOpen
|
|
279
|
+
if (isParentOpen) openMenu(...args);
|
|
280
280
|
},
|
|
281
281
|
closeMenu: () => {
|
|
282
282
|
if (isOpen) {
|
|
283
|
-
menuItemRef?.current
|
|
283
|
+
menuItemRef?.current?.focus();
|
|
284
284
|
toggleMenu(false);
|
|
285
285
|
}
|
|
286
286
|
},
|
|
@@ -18,11 +18,12 @@ export const useBEM = ({ block, element, modifiers, className }: useBemProps) =>
|
|
|
18
18
|
const blockElement = element ? `${block}__${element}` : block;
|
|
19
19
|
|
|
20
20
|
let classString = blockElement;
|
|
21
|
-
modifiers
|
|
21
|
+
if (modifiers) {
|
|
22
22
|
Object.keys(modifiers).forEach((name) => {
|
|
23
23
|
const value = modifiers[name];
|
|
24
24
|
if (value) classString += ` ${blockElement}--${value === true ? name : `${name}-${value}`}`;
|
|
25
25
|
});
|
|
26
|
+
}
|
|
26
27
|
|
|
27
28
|
let expandedClassName = typeof className === 'function' ? className(modifiers) : className;
|
|
28
29
|
if (typeof expandedClassName === 'string') {
|
|
@@ -17,11 +17,11 @@ export const useItemState = (
|
|
|
17
17
|
const timeoutId = useRef<ReturnType<typeof setTimeout>>(undefined);
|
|
18
18
|
|
|
19
19
|
const setHover = () => {
|
|
20
|
-
!isHovering && !isDisabled
|
|
20
|
+
if (!isHovering && !isDisabled) dispatch(HoverActionTypes.SET, menuItemRef?.current, 0);
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
const unsetHover = () => {
|
|
24
|
-
!isDisabled
|
|
24
|
+
if (!isDisabled) dispatch(HoverActionTypes.UNSET, menuItemRef?.current, 0);
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
const onBlur = (e: FocusEvent) => {
|
|
@@ -48,7 +48,9 @@ export const useItemState = (
|
|
|
48
48
|
timeoutId.current = undefined;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
!keepHover
|
|
51
|
+
if (!keepHover) {
|
|
52
|
+
unsetHover();
|
|
53
|
+
}
|
|
52
54
|
};
|
|
53
55
|
|
|
54
56
|
useItemEffect(isDisabled, menuItemRef, updateItems);
|
|
@@ -57,7 +59,9 @@ export const useItemState = (
|
|
|
57
59
|
// Don't set focus when parent menu is closed, otherwise focus will be lost
|
|
58
60
|
// and onBlur event will be fired with relatedTarget setting as null.
|
|
59
61
|
if (isHovering && isParentOpen) {
|
|
60
|
-
focusRef?.current
|
|
62
|
+
if (focusRef?.current) {
|
|
63
|
+
focusRef.current.focus();
|
|
64
|
+
}
|
|
61
65
|
}
|
|
62
66
|
}, [focusRef, isHovering, isParentOpen]);
|
|
63
67
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
@use
|
|
2
|
-
@use
|
|
1
|
+
@use 'var';
|
|
2
|
+
@use 'mixins';
|
|
3
3
|
|
|
4
4
|
.szh-menu {
|
|
5
|
-
@include mixins.reset-list
|
|
6
|
-
@include mixins.remove-focus
|
|
5
|
+
@include mixins.reset-list();
|
|
6
|
+
@include mixins.remove-focus();
|
|
7
7
|
box-sizing: border-box;
|
|
8
8
|
width: max-content;
|
|
9
9
|
z-index: 100;
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
&__item {
|
|
45
|
-
@include mixins.remove-focus
|
|
45
|
+
@include mixins.remove-focus();
|
|
46
46
|
cursor: pointer;
|
|
47
47
|
|
|
48
48
|
&--hover {
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
&__radio-group {
|
|
68
|
-
@include mixins.reset-list
|
|
68
|
+
@include mixins.reset-list();
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
&__divider {
|