@alaarab/ogrid-fluent 1.2.2 → 1.3.1
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.
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import { useMemo, useRef, useEffect } from 'react';
|
|
3
|
+
import { useMemo, useRef, useEffect, useCallback } from 'react';
|
|
4
4
|
import { createPortal } from 'react-dom';
|
|
5
5
|
import { DataGrid, DataGridHeader, DataGridRow, DataGridHeaderCell, DataGridBody, DataGridCell, createTableColumn, Spinner, Checkbox, Popover, PopoverSurface, } from '@fluentui/react-components';
|
|
6
6
|
import { ColumnHeaderFilter } from '../ColumnHeaderFilter';
|
|
7
7
|
import { InlineCellEditor } from './InlineCellEditor';
|
|
8
8
|
import { StatusBar } from './StatusBar';
|
|
9
9
|
import { GridContextMenu } from './GridContextMenu';
|
|
10
|
-
import { useDataGridState, getHeaderFilterConfig, getCellRenderDescriptor, } from '@alaarab/ogrid-core';
|
|
10
|
+
import { useDataGridState, getHeaderFilterConfig, getCellRenderDescriptor, MarchingAntsOverlay, } from '@alaarab/ogrid-core';
|
|
11
11
|
import styles from './DataGridTable.module.css';
|
|
12
12
|
function DataGridTableInner(props) {
|
|
13
13
|
const wrapperRef = useRef(null);
|
|
14
|
+
const tableContainerRef = useRef(null);
|
|
14
15
|
const state = useDataGridState({ props, wrapperRef });
|
|
15
|
-
const { flatColumns, visibleCols, totalColCount, hasCheckboxCol, rowIndexByRowId, selectedRowIds, updateSelection, handleRowCheckboxChange, handleSelectAll, allSelected, someSelected, setEditingCell, pendingEditorValue, setPendingEditorValue, activeCell, setActiveCell, handleCellMouseDown, handleSelectAllCells, contextMenu, setContextMenu, handleCellContextMenu, closeContextMenu, handleCopy, handleCut, handlePaste, handleGridKeyDown, handleFillHandleMouseDown, containerWidth, minTableWidth, columnSizingOverrides, setColumnSizingOverrides, statusBarConfig, showEmptyInGrid, hasCellSelection, selectionRange, headerFilterInput, cellDescriptorInput, commitCellEdit, cancelPopoverEdit, popoverAnchorEl, setPopoverAnchorEl, } = state;
|
|
16
|
+
const { flatColumns, visibleCols, totalColCount, hasCheckboxCol, rowIndexByRowId, selectedRowIds, updateSelection, handleRowCheckboxChange, handleSelectAll, allSelected, someSelected, setEditingCell, pendingEditorValue, setPendingEditorValue, activeCell, setActiveCell, handleCellMouseDown, handleSelectAllCells, contextMenu, setContextMenu, handleCellContextMenu, closeContextMenu, canUndo, canRedo, onUndo, onRedo, handleCopy, handleCut, handlePaste, handleGridKeyDown, handleFillHandleMouseDown, containerWidth, minTableWidth, columnSizingOverrides, setColumnSizingOverrides, statusBarConfig, showEmptyInGrid, hasCellSelection, selectionRange, copyRange, cutRange, colOffset, headerFilterInput, cellDescriptorInput, commitCellEdit, cancelPopoverEdit, popoverAnchorEl, setPopoverAnchorEl, } = state;
|
|
16
17
|
const { items, getRowId, emptyState, layoutMode = 'fill', rowSelection = 'none', freezeRows, freezeCols, isLoading = false, loadingMessage = 'Loading\u2026', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, } = props;
|
|
17
18
|
const fitToContent = layoutMode === 'content';
|
|
18
19
|
const columnSizingOptions = useMemo(() => {
|
|
@@ -97,9 +98,10 @@ function DataGridTableInner(props) {
|
|
|
97
98
|
content = _jsx("span", { style: cellStyle, children: content });
|
|
98
99
|
const cellClassNames = [
|
|
99
100
|
styles.cellContent,
|
|
100
|
-
descriptor.isActive ? styles.activeCellContent : '',
|
|
101
|
+
descriptor.isActive && !descriptor.isInRange ? styles.activeCellContent : '',
|
|
101
102
|
descriptor.isInRange ? styles.cellInRange : '',
|
|
102
103
|
descriptor.isInCutRange ? styles.cellCut : '',
|
|
104
|
+
descriptor.isInCopyRange ? styles.cellCopied : '',
|
|
103
105
|
descriptor.isPinned ? styles.pinnedCell : '',
|
|
104
106
|
descriptor.isPinned && descriptor.pinnedSide === 'left' ? styles.pinnedLeft : '',
|
|
105
107
|
descriptor.isPinned && descriptor.pinnedSide === 'right' ? styles.pinnedRight : '',
|
|
@@ -107,15 +109,9 @@ function DataGridTableInner(props) {
|
|
|
107
109
|
.filter(Boolean)
|
|
108
110
|
.join(' ');
|
|
109
111
|
if (descriptor.canEditAny) {
|
|
110
|
-
return (_jsxs("div", { className: cellClassNames, "data-row-index": descriptor.rowIndex, "data-col-index": descriptor.globalColIndex, ...(descriptor.isInRange ? { 'data-in-range': 'true' } : {}), role: "button", tabIndex: descriptor.isActive ? 0 : -1, onMouseDown: (e) => handleCellMouseDown(e, descriptor.rowIndex, descriptor.globalColIndex), onClick: () => setActiveCell({ rowIndex: descriptor.rowIndex, columnIndex: descriptor.globalColIndex }), onDoubleClick: () => setEditingCell({ rowId: descriptor.rowId, columnId: col.columnId }), onContextMenu: handleCellContextMenu, style: {
|
|
111
|
-
minHeight: '100%',
|
|
112
|
-
cursor: 'cell',
|
|
113
|
-
outline: 'none',
|
|
114
|
-
position: 'relative',
|
|
115
|
-
userSelect: 'none',
|
|
116
|
-
}, children: [content, descriptor.isSelectionEndCell && (_jsx("div", { className: styles.fillHandle, onMouseDown: handleFillHandleMouseDown, "aria-label": "Fill handle" }))] }));
|
|
112
|
+
return (_jsxs("div", { className: cellClassNames, "data-row-index": descriptor.rowIndex, "data-col-index": descriptor.globalColIndex, ...(descriptor.isInRange ? { 'data-in-range': 'true' } : {}), role: "button", tabIndex: descriptor.isActive ? 0 : -1, onMouseDown: (e) => handleCellMouseDown(e, descriptor.rowIndex, descriptor.globalColIndex), onClick: () => setActiveCell({ rowIndex: descriptor.rowIndex, columnIndex: descriptor.globalColIndex }), onDoubleClick: () => setEditingCell({ rowId: descriptor.rowId, columnId: col.columnId }), onContextMenu: handleCellContextMenu, style: { cursor: 'cell' }, children: [content, descriptor.isSelectionEndCell && (_jsx("div", { className: styles.fillHandle, onMouseDown: handleFillHandleMouseDown, "aria-label": "Fill handle" }))] }));
|
|
117
113
|
}
|
|
118
|
-
return (_jsx("div", { className: cellClassNames, "data-row-index": descriptor.rowIndex, "data-col-index": descriptor.globalColIndex, ...(descriptor.isInRange ? { 'data-in-range': 'true' } : {}), tabIndex: descriptor.isActive ? 0 : -1, onMouseDown: (e) => handleCellMouseDown(e, descriptor.rowIndex, descriptor.globalColIndex), onClick: () => setActiveCell({ rowIndex: descriptor.rowIndex, columnIndex: descriptor.globalColIndex }), onContextMenu: handleCellContextMenu,
|
|
114
|
+
return (_jsx("div", { className: cellClassNames, "data-row-index": descriptor.rowIndex, "data-col-index": descriptor.globalColIndex, ...(descriptor.isInRange ? { 'data-in-range': 'true' } : {}), tabIndex: descriptor.isActive ? 0 : -1, onMouseDown: (e) => handleCellMouseDown(e, descriptor.rowIndex, descriptor.globalColIndex), onClick: () => setActiveCell({ rowIndex: descriptor.rowIndex, columnIndex: descriptor.globalColIndex }), onContextMenu: handleCellContextMenu, children: content }));
|
|
119
115
|
},
|
|
120
116
|
}));
|
|
121
117
|
if (hasCheckboxCol) {
|
|
@@ -198,9 +194,16 @@ function DataGridTableInner(props) {
|
|
|
198
194
|
root.addEventListener('dblclick', onDblClick, true);
|
|
199
195
|
return () => root.removeEventListener('dblclick', onDblClick, true);
|
|
200
196
|
}, [flatColumns, setColumnSizingOverrides]);
|
|
201
|
-
|
|
197
|
+
// Sync Fluent's internal resize state back to our React state so that
|
|
198
|
+
// re-renders (e.g. on cell click) don't reset column widths.
|
|
199
|
+
const handleColumnResize = useCallback((_e, data) => {
|
|
200
|
+
setColumnSizingOverrides((prev) => ({
|
|
201
|
+
...prev,
|
|
202
|
+
[String(data.columnId)]: { widthPx: data.width },
|
|
203
|
+
}));
|
|
204
|
+
}, [setColumnSizingOverrides]);
|
|
205
|
+
return (_jsxs("div", { ref: wrapperRef, tabIndex: 0, className: `${styles.tableWrapper} ${rowSelection !== 'none' ? styles.selectableGrid : ''}`, role: "region", "aria-label": ariaLabel ?? (ariaLabelledBy ? undefined : 'Data grid'), "aria-labelledby": ariaLabelledBy, "data-empty": showEmptyInGrid ? 'true' : undefined, "data-auto-fit": layoutMode === 'fill' && !allowOverflowX ? 'true' : undefined, "data-column-count": totalColCount, "data-freeze-rows": freezeRows != null && freezeRows >= 1 ? freezeRows : undefined, "data-freeze-cols": freezeCols != null && freezeCols >= 1 ? freezeCols : undefined, "data-overflow-x": allowOverflowX ? 'true' : 'false', "data-container-width": containerWidth, "data-min-table-width": Math.round(minTableWidth), "data-has-selection": rowSelection !== 'none' ? 'true' : undefined, onContextMenu: (e) => {
|
|
202
206
|
e.preventDefault();
|
|
203
|
-
setContextMenu({ x: e.clientX, y: e.clientY });
|
|
204
207
|
}, style: {
|
|
205
208
|
['--data-table-column-count']: totalColCount,
|
|
206
209
|
['--data-table-width']: showEmptyInGrid
|
|
@@ -217,26 +220,26 @@ function DataGridTableInner(props) {
|
|
|
217
220
|
: fitToContent
|
|
218
221
|
? 'max-content'
|
|
219
222
|
: '100%',
|
|
220
|
-
}, onKeyDown: handleGridKeyDown, children: [_jsxs("div", { className: styles.tableScrollContent, children: [_jsxs("div", { className: isLoading && items.length > 0 ? styles.loadingOverlayContainer : undefined, children: [isLoading && items.length > 0 && (_jsx("div", { className: styles.loadingOverlay, "aria-live": "polite", children: _jsxs("div", { className: styles.loadingOverlayContent, children: [_jsx(Spinner, { size: "small" }), _jsx("span", { className: styles.loadingOverlayText, children: loadingMessage })] }) })), _jsx("div", { className: isLoading && items.length > 0 ? styles.loadingDimmed : undefined, children:
|
|
221
|
-
const colIdx = visibleCols.findIndex((c) => c.columnId === columnId);
|
|
222
|
-
const isFreezeCol = freezeCols != null && freezeCols >= 1 && colIdx >= 0 && colIdx < freezeCols;
|
|
223
|
-
return (_jsx(DataGridHeaderCell, { className: `${columnId === '__selection__' ? styles.selectionHeaderCellWrapper : ''} ${isFreezeCol ? styles.freezeCol : ''} ${isFreezeCol && colIdx === 0 ? styles.freezeColFirst : ''}`.trim(), children: renderHeaderCell() }));
|
|
224
|
-
} }) }), _jsx(DataGridBody, { children: ({ item }) => {
|
|
225
|
-
const rowId = getRowId(item);
|
|
226
|
-
const isSelected = selectedRowIds.has(rowId);
|
|
227
|
-
return (_jsx(DataGridRow, { className: `${isSelected ? styles.selectedRow : ''} ${activeCell !== null && (rowIndexByRowId.get(rowId) ?? -1) === activeCell.rowIndex
|
|
228
|
-
? styles.activeRow
|
|
229
|
-
: ''}`, onClick: () => {
|
|
230
|
-
if (rowSelection === 'single') {
|
|
231
|
-
const isCurrentlySelected = selectedRowIds.has(rowId);
|
|
232
|
-
updateSelection(isCurrentlySelected ? new Set() : new Set([rowId]));
|
|
233
|
-
}
|
|
234
|
-
}, children: ({ renderCell, columnId }) => {
|
|
223
|
+
}, onKeyDown: handleGridKeyDown, children: [_jsxs("div", { className: styles.tableScrollContent, children: [_jsxs("div", { className: isLoading && items.length > 0 ? styles.loadingOverlayContainer : undefined, children: [isLoading && items.length > 0 && (_jsx("div", { className: styles.loadingOverlay, "aria-live": "polite", children: _jsxs("div", { className: styles.loadingOverlayContent, children: [_jsx(Spinner, { size: "small" }), _jsx("span", { className: styles.loadingOverlayText, children: loadingMessage })] }) })), _jsx("div", { className: isLoading && items.length > 0 ? styles.loadingDimmed : undefined, children: _jsxs("div", { className: styles.tableWidthAnchor, ref: tableContainerRef, children: [_jsxs(DataGrid, { items: items, columns: fluentColumns, resizableColumns: true, resizableColumnsOptions: { autoFitColumns: layoutMode === 'fill' && !allowOverflowX }, columnSizingOptions: columnSizingOptions, onColumnResize: handleColumnResize, getRowId: (item) => String(getRowId(item)), focusMode: "composite", className: styles.dataGrid, children: [_jsx(DataGridHeader, { className: freezeRows != null && freezeRows >= 1 ? styles.stickyHeader : undefined, children: _jsx(DataGridRow, { children: ({ renderHeaderCell, columnId }) => {
|
|
235
224
|
const colIdx = visibleCols.findIndex((c) => c.columnId === columnId);
|
|
236
225
|
const isFreezeCol = freezeCols != null && freezeCols >= 1 && colIdx >= 0 && colIdx < freezeCols;
|
|
237
|
-
return (_jsx(
|
|
238
|
-
} },
|
|
239
|
-
|
|
240
|
-
|
|
226
|
+
return (_jsx(DataGridHeaderCell, { className: `${columnId === '__selection__' ? styles.selectionHeaderCellWrapper : ''} ${isFreezeCol ? styles.freezeCol : ''} ${isFreezeCol && colIdx === 0 ? styles.freezeColFirst : ''}`.trim(), children: renderHeaderCell() }));
|
|
227
|
+
} }) }), _jsx(DataGridBody, { children: ({ item }) => {
|
|
228
|
+
const rowId = getRowId(item);
|
|
229
|
+
const isSelected = selectedRowIds.has(rowId);
|
|
230
|
+
return (_jsx(DataGridRow, { className: `${isSelected ? styles.selectedRow : ''} ${activeCell !== null && (rowIndexByRowId.get(rowId) ?? -1) === activeCell.rowIndex
|
|
231
|
+
? styles.activeRow
|
|
232
|
+
: ''}`, onClick: () => {
|
|
233
|
+
if (rowSelection === 'single') {
|
|
234
|
+
const isCurrentlySelected = selectedRowIds.has(rowId);
|
|
235
|
+
updateSelection(isCurrentlySelected ? new Set() : new Set([rowId]));
|
|
236
|
+
}
|
|
237
|
+
}, children: ({ renderCell, columnId }) => {
|
|
238
|
+
const colIdx = visibleCols.findIndex((c) => c.columnId === columnId);
|
|
239
|
+
const isFreezeCol = freezeCols != null && freezeCols >= 1 && colIdx >= 0 && colIdx < freezeCols;
|
|
240
|
+
return (_jsx(DataGridCell, { className: `${columnId === '__selection__' ? styles.selectionCellWrapper : ''} ${isFreezeCol ? styles.freezeCol : ''} ${isFreezeCol && colIdx === 0 ? styles.freezeColFirst : ''}`.trim(), children: renderCell(item) }));
|
|
241
|
+
} }, rowId));
|
|
242
|
+
} })] }), _jsx(MarchingAntsOverlay, { containerRef: tableContainerRef, selectionRange: selectionRange, copyRange: copyRange, cutRange: cutRange, colOffset: colOffset }), statusBarConfig && (_jsx(StatusBar, { totalCount: statusBarConfig.totalCount, filteredCount: statusBarConfig.filteredCount, selectedCount: statusBarConfig.selectedCount ?? selectedRowIds.size, selectedCellCount: selectionRange ? (Math.abs(selectionRange.endRow - selectionRange.startRow) + 1) * (Math.abs(selectionRange.endCol - selectionRange.startCol) + 1) : undefined }))] }) })] }), showEmptyInGrid && emptyState && (_jsx("div", { className: styles.emptyStateInGrid, children: _jsx("div", { className: styles.emptyStateInGridMessageSticky, children: emptyState.render ? (emptyState.render()) : (_jsxs(_Fragment, { children: [_jsx("span", { className: styles.emptyStateInGridIcon, "aria-hidden": true, children: "\uD83D\uDCCB" }), _jsx("div", { className: styles.emptyStateInGridTitle, children: "No results found" }), _jsx("div", { className: styles.emptyStateInGridMessage, children: emptyState.message != null ? (emptyState.message) : emptyState.hasActiveFilters ? (_jsxs(_Fragment, { children: ["No items match your current filters. Try adjusting your search or", ' ', _jsx("button", { type: "button", className: styles.emptyStateInGridLink, onClick: emptyState.onClearAll, children: "clear all filters" }), ' ', "to see all items."] })) : ('There are no items available at this time.') })] })) }) }))] }), contextMenu &&
|
|
243
|
+
createPortal(_jsx(GridContextMenu, { x: contextMenu.x, y: contextMenu.y, hasSelection: hasCellSelection, canUndo: canUndo, canRedo: canRedo, onUndo: onUndo ?? (() => { }), onRedo: onRedo ?? (() => { }), onCopy: handleCopy, onCut: handleCut, onPaste: () => void handlePaste(), onSelectAll: handleSelectAllCells, onClose: closeContextMenu }), document.body)] }));
|
|
241
244
|
}
|
|
242
245
|
export const DataGridTable = React.memo(DataGridTableInner);
|
|
@@ -9,9 +9,11 @@
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
.tableWidthAnchor {
|
|
12
|
+
position: relative;
|
|
12
13
|
/* When table fits (data-auto-fit): fill 100% so last column gets space. When overflow: size to content for scroll. */
|
|
13
14
|
width: max-content;
|
|
14
|
-
min-width:
|
|
15
|
+
/* No min-width: 100% — anchor sizes to grid content so status bar aligns with the DataGrid border.
|
|
16
|
+
.tableScrollContent provides the full-width background color so no gap is visible. */
|
|
15
17
|
background-color: var(--colorNeutralBackground1, #ffffff);
|
|
16
18
|
}
|
|
17
19
|
|
|
@@ -28,7 +30,6 @@
|
|
|
28
30
|
width: 100%;
|
|
29
31
|
min-width: 0;
|
|
30
32
|
max-width: 100%;
|
|
31
|
-
margin-bottom: 15px;
|
|
32
33
|
border-radius: var(--borderRadiusMedium, 4px);
|
|
33
34
|
box-sizing: border-box;
|
|
34
35
|
/* Border is applied to the grid itself so we don't draw an empty bordered area
|
|
@@ -120,6 +121,8 @@
|
|
|
120
121
|
text-overflow: ellipsis !important;
|
|
121
122
|
white-space: nowrap !important;
|
|
122
123
|
font-size: 12px;
|
|
124
|
+
padding: 0 !important;
|
|
125
|
+
height: 1px;
|
|
123
126
|
border-right: 1px solid var(--colorNeutralStroke1, #c4c4c4);
|
|
124
127
|
/* Prevent long unbroken content from forcing intrinsic widths / overlap */
|
|
125
128
|
}
|
|
@@ -185,16 +188,21 @@
|
|
|
185
188
|
width: 100% !important;
|
|
186
189
|
}
|
|
187
190
|
.tableWrapper[data-auto-fit=true][data-column-count] :global {
|
|
188
|
-
/* Equal base width for all columns
|
|
191
|
+
/* Equal base width for all columns — override Fluent's inline width/min-width/max-width */
|
|
189
192
|
}
|
|
190
|
-
.tableWrapper[data-auto-fit=true][data-column-count] :global .fui-DataGridHeaderCell
|
|
191
|
-
.tableWrapper[data-auto-fit=true][data-column-count] :global .fui-DataGridCell
|
|
193
|
+
.tableWrapper[data-auto-fit=true][data-column-count] :global .fui-DataGridHeaderCell,
|
|
194
|
+
.tableWrapper[data-auto-fit=true][data-column-count] :global .fui-DataGridCell {
|
|
192
195
|
width: calc(100% / var(--data-table-column-count, 1)) !important;
|
|
196
|
+
min-width: 0 !important;
|
|
197
|
+
max-width: none !important;
|
|
193
198
|
}
|
|
194
|
-
.tableWrapper
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
199
|
+
.tableWrapper {
|
|
200
|
+
/* Selection column must stay 48px even in auto-fit mode (needs same specificity depth to win) */
|
|
201
|
+
}
|
|
202
|
+
.tableWrapper[data-auto-fit=true][data-column-count] .selectionHeaderCellWrapper, .tableWrapper[data-auto-fit=true][data-column-count] .selectionCellWrapper {
|
|
203
|
+
width: 48px !important;
|
|
204
|
+
min-width: 48px !important;
|
|
205
|
+
max-width: 48px !important;
|
|
198
206
|
}
|
|
199
207
|
.tableWrapper {
|
|
200
208
|
/* Hide resize handle on last column only when table fits (autoFitColumns); when overflow/scroll, last column can resize (Fluent docs) */
|
|
@@ -255,51 +263,43 @@
|
|
|
255
263
|
|
|
256
264
|
.cellContent {
|
|
257
265
|
width: 100%;
|
|
258
|
-
|
|
266
|
+
height: 100%;
|
|
259
267
|
display: flex;
|
|
260
268
|
align-items: center;
|
|
269
|
+
padding: 4px 8px;
|
|
270
|
+
box-sizing: border-box;
|
|
271
|
+
overflow: hidden;
|
|
272
|
+
text-overflow: ellipsis;
|
|
273
|
+
white-space: nowrap;
|
|
261
274
|
user-select: none;
|
|
275
|
+
outline: none;
|
|
262
276
|
}
|
|
263
277
|
|
|
264
278
|
.activeCellContent {
|
|
265
|
-
outline: 2px solid var(--
|
|
266
|
-
outline-offset: -
|
|
267
|
-
|
|
268
|
-
z-index: 1;
|
|
279
|
+
outline: 2px solid var(--ogrid-selection, #217346) !important;
|
|
280
|
+
outline-offset: -1px;
|
|
281
|
+
z-index: 2;
|
|
269
282
|
position: relative;
|
|
283
|
+
overflow: visible;
|
|
270
284
|
}
|
|
271
285
|
|
|
272
286
|
.cellInRange {
|
|
273
|
-
background-color: var(--
|
|
274
|
-
position: relative;
|
|
275
|
-
box-shadow: inset 0 0 0 1px var(--colorBrandStroke1, #0078d4);
|
|
287
|
+
background-color: var(--ogrid-bg-range, rgba(33, 115, 70, 0.12)) !important;
|
|
276
288
|
}
|
|
277
289
|
|
|
278
290
|
.cellCut {
|
|
279
|
-
outline: 2px dashed var(--colorBrandStroke1, #0078d4);
|
|
280
|
-
outline-offset: -2px;
|
|
281
291
|
background-color: var(--colorNeutralBackground1Hover, rgba(0, 0, 0, 0.04)) !important;
|
|
282
|
-
|
|
292
|
+
opacity: 0.7;
|
|
283
293
|
}
|
|
284
294
|
|
|
285
|
-
@keyframes cellCutDash {
|
|
286
|
-
0% {
|
|
287
|
-
outline-offset: -2px;
|
|
288
|
-
}
|
|
289
|
-
50% {
|
|
290
|
-
outline-offset: -3px;
|
|
291
|
-
}
|
|
292
|
-
100% {
|
|
293
|
-
outline-offset: -2px;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
295
|
.fillHandle {
|
|
297
296
|
position: absolute;
|
|
298
|
-
right:
|
|
299
|
-
bottom:
|
|
300
|
-
width:
|
|
301
|
-
height:
|
|
302
|
-
background: var(--
|
|
297
|
+
right: -3px;
|
|
298
|
+
bottom: -3px;
|
|
299
|
+
width: 7px;
|
|
300
|
+
height: 7px;
|
|
301
|
+
background: var(--ogrid-selection, #217346);
|
|
302
|
+
border: 1px solid #fff;
|
|
303
303
|
border-radius: 1px;
|
|
304
304
|
cursor: crosshair;
|
|
305
305
|
pointer-events: auto;
|
|
@@ -419,7 +419,10 @@
|
|
|
419
419
|
}
|
|
420
420
|
|
|
421
421
|
.contextMenuItem {
|
|
422
|
-
display:
|
|
422
|
+
display: flex;
|
|
423
|
+
align-items: center;
|
|
424
|
+
justify-content: space-between;
|
|
425
|
+
gap: 24px;
|
|
423
426
|
width: 100%;
|
|
424
427
|
padding: 6px 12px;
|
|
425
428
|
border: none;
|
|
@@ -437,6 +440,15 @@
|
|
|
437
440
|
cursor: not-allowed;
|
|
438
441
|
}
|
|
439
442
|
|
|
443
|
+
.contextMenuItemLabel {
|
|
444
|
+
flex: 1;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
.contextMenuItemShortcut {
|
|
448
|
+
color: var(--colorNeutralForeground3, rgba(0, 0, 0, 0.4));
|
|
449
|
+
font-size: 0.85em;
|
|
450
|
+
}
|
|
451
|
+
|
|
440
452
|
.contextMenuDivider {
|
|
441
453
|
height: 1px;
|
|
442
454
|
margin: 4px 0;
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import { GRID_CONTEXT_MENU_ITEMS, getContextMenuHandlers } from '@alaarab/ogrid-core';
|
|
3
|
+
import { GRID_CONTEXT_MENU_ITEMS, getContextMenuHandlers, formatShortcut } from '@alaarab/ogrid-core';
|
|
4
4
|
import styles from './DataGridTable.module.css';
|
|
5
5
|
export function GridContextMenu(props) {
|
|
6
|
-
const { x, y, hasSelection, onClose } = props;
|
|
6
|
+
const { x, y, hasSelection, canUndo, canRedo, onClose } = props;
|
|
7
7
|
const ref = React.useRef(null);
|
|
8
8
|
const handlers = React.useMemo(() => getContextMenuHandlers(props), [props]);
|
|
9
|
+
const isDisabled = React.useCallback((item) => {
|
|
10
|
+
if (item.disabledWhenNoSelection && !hasSelection)
|
|
11
|
+
return true;
|
|
12
|
+
if (item.id === 'undo' && !canUndo)
|
|
13
|
+
return true;
|
|
14
|
+
if (item.id === 'redo' && !canRedo)
|
|
15
|
+
return true;
|
|
16
|
+
return false;
|
|
17
|
+
}, [hasSelection, canUndo, canRedo]);
|
|
9
18
|
React.useEffect(() => {
|
|
10
19
|
const handleClickOutside = (e) => {
|
|
11
20
|
if (ref.current && !ref.current.contains(e.target))
|
|
@@ -22,5 +31,5 @@ export function GridContextMenu(props) {
|
|
|
22
31
|
document.removeEventListener('keydown', handleKeyDown, true);
|
|
23
32
|
};
|
|
24
33
|
}, [onClose]);
|
|
25
|
-
return (_jsx("div", { ref: ref, className: styles.contextMenu, role: "menu", style: { left: x, top: y }, "aria-label": "Grid context menu", children: GRID_CONTEXT_MENU_ITEMS.map((item
|
|
34
|
+
return (_jsx("div", { ref: ref, className: styles.contextMenu, role: "menu", style: { left: x, top: y }, "aria-label": "Grid context menu", children: GRID_CONTEXT_MENU_ITEMS.map((item) => (_jsxs(React.Fragment, { children: [item.dividerBefore && _jsx("div", { className: styles.contextMenuDivider }), _jsxs("button", { type: "button", className: styles.contextMenuItem, onClick: handlers[item.id], disabled: isDisabled(item), children: [_jsx("span", { className: styles.contextMenuItemLabel, children: item.label }), item.shortcut && (_jsx("span", { className: styles.contextMenuItemShortcut, children: formatShortcut(item.shortcut) }))] })] }, item.id))) }));
|
|
26
35
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alaarab/ogrid-fluent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "OGrid Fluent UI implementation – DataGrid-powered data table with sorting, filtering, pagination, column chooser, and CSV export.",
|
|
5
5
|
"main": "dist/esm/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|