@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, style: { outline: 'none', userSelect: 'none' }, children: content }));
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
- 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-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) => {
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: _jsx("div", { className: styles.tableWidthAnchor, children: _jsxs(DataGrid, { items: items, columns: fluentColumns, resizableColumns: true, resizableColumnsOptions: { autoFitColumns: layoutMode === 'fill' && !allowOverflowX }, columnSizingOptions: columnSizingOptions, 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 }) => {
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(DataGridCell, { className: `${columnId === '__selection__' ? styles.selectionCellWrapper : ''} ${isFreezeCol ? styles.freezeCol : ''} ${isFreezeCol && colIdx === 0 ? styles.freezeColFirst : ''}`.trim(), children: renderCell(item) }));
238
- } }, rowId));
239
- } })] }) }) })] }), 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.') })] })) }) }))] }), 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 })), contextMenu &&
240
- createPortal(_jsx(GridContextMenu, { x: contextMenu.x, y: contextMenu.y, hasSelection: hasCellSelection, onCopy: handleCopy, onCut: handleCut, onPaste: () => void handlePaste(), onSelectAll: handleSelectAllCells, onClose: closeContextMenu }), document.body)] }));
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: max(100%, var(--data-table-total-min-width, 0px));
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; last column gets remainder so it extends to the right edge (no dead strip) */
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:not(:last-of-type),
191
- .tableWrapper[data-auto-fit=true][data-column-count] :global .fui-DataGridCell:not(:last-of-type) {
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[data-auto-fit=true][data-column-count] :global .fui-DataGridHeader .fui-DataGridRow .fui-DataGridHeaderCell:last-of-type,
195
- .tableWrapper[data-auto-fit=true][data-column-count] :global .fui-DataGridBody .fui-DataGridRow .fui-DataGridCell:last-of-type {
196
- min-width: calc(100% / var(--data-table-column-count, 1)) !important;
197
- width: auto !important;
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
- min-height: 100%;
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(--colorBrandStroke1, #0078d4) !important;
266
- outline-offset: -2px;
267
- border-radius: 2px;
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(--colorNeutralBackground1Selected, #cce4f7) !important;
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
- animation: cellCutDash 0.6s linear infinite;
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: 2px;
299
- bottom: 2px;
300
- width: 8px;
301
- height: 8px;
302
- background: var(--colorBrandStroke1, #0078d4);
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: block;
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, _index) => (_jsxs(React.Fragment, { children: [item.id === 'selectAll' && _jsx("div", { className: styles.contextMenuDivider }), _jsx("button", { type: "button", className: styles.contextMenuItem, onClick: handlers[item.id], disabled: item.disabledWhenNoSelection ? !hasSelection : false, children: item.label })] }, item.id))) }));
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
  }
@@ -4,5 +4,7 @@ export interface GridContextMenuProps extends GridContextMenuHandlerProps {
4
4
  x: number;
5
5
  y: number;
6
6
  hasSelection: boolean;
7
+ canUndo: boolean;
8
+ canRedo: boolean;
7
9
  }
8
10
  export declare function GridContextMenu(props: GridContextMenuProps): React.ReactElement;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alaarab/ogrid-fluent",
3
- "version": "1.2.2",
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",