@opencorestack/opengridx 0.1.6 → 0.1.7

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.
Files changed (129) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +9 -0
  3. package/dist/index.d.ts +6 -0
  4. package/dist/opengridx.es.js +6 -6
  5. package/dist/opengridx.es.js.map +1 -1
  6. package/dist/opengridx.umd.js +4 -4
  7. package/dist/opengridx.umd.js.map +1 -1
  8. package/docs/API_REFERENCE.md +177 -0
  9. package/docs/README.md +60 -0
  10. package/docs/components/cell.md +44 -0
  11. package/docs/components/column-group-header.md +57 -0
  12. package/docs/components/column-resize.md +63 -0
  13. package/docs/components/column-visibility.md +61 -0
  14. package/docs/components/datagrid.md +70 -0
  15. package/docs/components/filter-panel.md +26 -0
  16. package/docs/components/header.md +39 -0
  17. package/docs/components/pagination.md +34 -0
  18. package/docs/components/row.md +31 -0
  19. package/docs/components/toolbar.md +40 -0
  20. package/docs/components/tooltip.md +68 -0
  21. package/docs/customization/slots-api.md +227 -0
  22. package/docs/customization/theming.md +293 -0
  23. package/docs/features/aggregation-pivot.md +79 -0
  24. package/docs/features/cell-spanning.md +520 -0
  25. package/docs/features/clipboard.md +76 -0
  26. package/docs/features/custom-pagination.md +143 -0
  27. package/docs/features/data-source.md +72 -0
  28. package/docs/features/editing-reordering.md +99 -0
  29. package/docs/features/export-guide.md +434 -0
  30. package/docs/features/filtering.md +83 -0
  31. package/docs/features/infinite-scroll.md +87 -0
  32. package/docs/features/list-view.md +65 -0
  33. package/docs/features/loading-states.md +68 -0
  34. package/docs/features/master-detail.md +46 -0
  35. package/docs/features/pinning.md +67 -0
  36. package/docs/features/selection.md +70 -0
  37. package/docs/features/sorting-pagination.md +66 -0
  38. package/docs/features/state-persistence.md +67 -0
  39. package/docs/features/tree-data-grouping.md +67 -0
  40. package/docs/features/virtualization.md +188 -0
  41. package/docs/roadmap.md +71 -0
  42. package/lib/components/Cell/Cell.css +119 -0
  43. package/lib/components/Cell/Cell.tsx +264 -0
  44. package/lib/components/Cell/GridEditInputCell.css +62 -0
  45. package/lib/components/Cell/GridEditInputCell.tsx +152 -0
  46. package/lib/components/ColumnGroupHeader/ColumnGroupHeader.css +67 -0
  47. package/lib/components/ColumnGroupHeader/ColumnGroupHeader.tsx +220 -0
  48. package/lib/components/ColumnResizeHandle/ColumnResizeHandle.css +31 -0
  49. package/lib/components/ColumnResizeHandle/ColumnResizeHandle.tsx +85 -0
  50. package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.css +137 -0
  51. package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.tsx +190 -0
  52. package/lib/components/DataGrid/DataGrid.css +224 -0
  53. package/lib/components/DataGrid/DataGrid.tsx +2253 -0
  54. package/lib/components/DetailPanel/DetailPanel.css +36 -0
  55. package/lib/components/DetailPanel/DetailPanel.tsx +34 -0
  56. package/lib/components/FilterPanel/FilterPanel.css +196 -0
  57. package/lib/components/FilterPanel/FilterPanel.tsx +183 -0
  58. package/lib/components/FilterPanel/index.ts +1 -0
  59. package/lib/components/Header/ColumnMenu.css +68 -0
  60. package/lib/components/Header/ColumnMenu.tsx +249 -0
  61. package/lib/components/Header/Header.css +224 -0
  62. package/lib/components/Header/Header.tsx +516 -0
  63. package/lib/components/ListView/ListView.css +112 -0
  64. package/lib/components/ListView/ListViewRow.tsx +63 -0
  65. package/lib/components/Pagination/Pagination.css +91 -0
  66. package/lib/components/Pagination/Pagination.tsx +151 -0
  67. package/lib/components/QuickFilter/QuickFilter.css +23 -0
  68. package/lib/components/QuickFilter/QuickFilter.tsx +79 -0
  69. package/lib/components/Row/Row.css +126 -0
  70. package/lib/components/Row/Row.tsx +377 -0
  71. package/lib/components/Skeleton/Skeleton.css +55 -0
  72. package/lib/components/Skeleton/Skeleton.tsx +22 -0
  73. package/lib/components/SkeletonRow/SkeletonRow.css +32 -0
  74. package/lib/components/SkeletonRow/SkeletonRow.tsx +89 -0
  75. package/lib/components/SkeletonRow/index.ts +1 -0
  76. package/lib/components/Toolbar/GlobalSearch.css +113 -0
  77. package/lib/components/Toolbar/GlobalSearch.tsx +120 -0
  78. package/lib/components/Toolbar/GridToolbar.css +204 -0
  79. package/lib/components/Toolbar/GridToolbar.tsx +647 -0
  80. package/lib/components/Toolbar/PivotPanel.css +212 -0
  81. package/lib/components/Toolbar/PivotPanel.tsx +239 -0
  82. package/lib/components/Tooltip/Tooltip.css +59 -0
  83. package/lib/components/Tooltip/Tooltip.tsx +106 -0
  84. package/lib/components/ui/Button.css +89 -0
  85. package/lib/components/ui/Button.tsx +39 -0
  86. package/lib/components/ui/Checkbox.css +71 -0
  87. package/lib/components/ui/Checkbox.tsx +66 -0
  88. package/lib/components/ui/DragHandleIcon.css +19 -0
  89. package/lib/components/ui/DragHandleIcon.tsx +18 -0
  90. package/lib/components/ui/ExpandIcon.css +38 -0
  91. package/lib/components/ui/ExpandIcon.tsx +75 -0
  92. package/lib/components/ui/Input.css +59 -0
  93. package/lib/components/ui/Input.tsx +45 -0
  94. package/lib/components/ui/index.ts +15 -0
  95. package/lib/hooks/core/useDataGrid.ts +392 -0
  96. package/lib/hooks/core/useGridApiRef.ts +10 -0
  97. package/lib/hooks/features/useAggregation.ts +143 -0
  98. package/lib/hooks/features/useGridClipboard.ts +133 -0
  99. package/lib/hooks/features/useGridDataSource.ts +190 -0
  100. package/lib/hooks/features/useGridEditing.ts +100 -0
  101. package/lib/hooks/features/useGridSpanning.ts +349 -0
  102. package/lib/hooks/features/usePivot.ts +149 -0
  103. package/lib/hooks/useColumnReorder.ts +114 -0
  104. package/lib/hooks/useRowGrouping.ts +302 -0
  105. package/lib/hooks/useRowReorder.ts +83 -0
  106. package/lib/hooks/useTreeData.ts +323 -0
  107. package/lib/index.ts +106 -0
  108. package/lib/state/index.ts +17 -0
  109. package/lib/state/types.ts +48 -0
  110. package/lib/state/useGridStateStorage.ts +113 -0
  111. package/lib/styles/base/reset.css +21 -0
  112. package/lib/styles/base/variables.css +161 -0
  113. package/lib/styles/opengridx.css +27 -0
  114. package/lib/styles/opengridx.css.d.ts +4 -0
  115. package/lib/theme/DataGridThemeProvider.tsx +276 -0
  116. package/lib/theme/defaultStyles.ts +0 -0
  117. package/lib/theme/index.ts +21 -0
  118. package/lib/theme/presets.ts +148 -0
  119. package/lib/theme/types.ts +155 -0
  120. package/lib/types/index.ts +764 -0
  121. package/lib/utils/export/exportToExcelAdvanced.ts +516 -0
  122. package/lib/utils/export/index.ts +550 -0
  123. package/lib/utils/filtering/index.ts +240 -0
  124. package/lib/utils/pinning/index.ts +282 -0
  125. package/lib/utils/sorting/index.ts +55 -0
  126. package/lib/utils/virtualization/index.ts +106 -0
  127. package/lib/vite-env.d.ts +1 -0
  128. package/llms.txt +2 -1
  129. package/package.json +13 -4
@@ -0,0 +1,71 @@
1
+ # OpenGridX Roadmap & Feature Status
2
+
3
+ This document tracks the current status of features in `OpenGridX` and outlines the roadmap for future development.
4
+
5
+ ## ✅ Implemented Features
6
+
7
+ ### Core
8
+ * **Virtualization**: High-performance row and column virtualization for handling large datasets.
9
+ * **Sorting**: Client-side and server-side multi-column sorting.
10
+ * **Filtering**: Client-side filtering with support for various operators.
11
+ * **Pagination**: Client-side and server-side pagination with customizable page sizes.
12
+ * **Selection**: Row selection (single/multiple) with checkbox support.
13
+
14
+ ### Advanced UI
15
+ * **Advanced Filtering**: Client-side complex filter builder (AND/OR logic, recursive groups).
16
+ * **Column Pinning**: Pin columns to the left or right.
17
+ * **Row Pinning**: Pin rows to the top or bottom.
18
+ * **Column Reordering**: Drag-and-drop column reordering.
19
+ * **Column Resizing**: Interactive column resizing.
20
+ * **Detail Panels**: Expandable rows to show custom detail views.
21
+ * **Row Reordering**: Drag-and-drop row reordering.
22
+
23
+ ### Data Management
24
+ * **Editing**: Inline cell editing with value parsing/formatting.
25
+ * **Tree Data**: Hierarchical data display (Client-side).
26
+ * **Row Grouping**: Grouping rows by column values (Client-side).
27
+ * **Spanning**: Row and Column spanning (Cell merging).
28
+ * **Export**: CSV, JSON, Print, basic Excel (HTML-table). **Advanced Excel Export** via ExcelJS — real `.xlsx` with styled headers, typed cells (number/date/boolean), column widths, frozen rows, auto-filter, alternating row colors, aggregation totals, and multi-sheet workbooks.
29
+ * **Pivot Mode**: Cross-tabulation of data — group rows into columns dynamically with aggregation (Sum, Avg, Count, Min, Max).
30
+
31
+ ### Server-Side Integration
32
+ * **Server-Side Data Source**: Hook-based integration for server-side sorting, filtering, and pagination.
33
+ * **Infinite Scroll**: Automatically load more rows as the user scrolls (`paginationMode='infinite'`).
34
+ * **Server-Side Tree Data**: Lazy loading children nodes, partial hierarchy fetching support.
35
+ * **Server-Side Aggregation**: Sum, Avg, Count, Min, Max — computed server-side over the full dataset, with a sticky footer row and toolbar Σ panel.
36
+
37
+ ### CSS & Theming *(completed 2026-02-19)*
38
+ * **CSS Barrel File** (`lib/styles/opengridx.css`): Single entry point — variables → reset → components.
39
+ * **Consistent Naming Convention**: `--ogx-*` CSS variables, `ogx__` BEM class prefix.
40
+ * **Theming API**: `<DataGridThemeProvider theme={myTheme}>` component mapping a `GridTheme` object to `--ogx-*` CSS variables, with built-in light/dark/compact presets.
41
+ * **Production Build Optimisation**: Code-split demo reduces initial bundle by 98.6% via React.lazy + manualChunks.
42
+
43
+ ### Accessibility & Clipboard *(completed 2026-03-03)*
44
+ * **ARIA roles**: `role="grid"`, `role="row"`, `role="gridcell"`, `role="columnheader"` on correct elements.
45
+ * **ARIA attributes**: `aria-sort`, `aria-selected`, `aria-label`, `aria-expanded`, `aria-readonly`, `aria-haspopup` throughout.
46
+ * **Keyboard navigation**: Tab / Enter / Escape / Arrow keys / Home / End / PageUp / PageDown.
47
+ * **`aria-rowcount` and `aria-colcount`**: Accurate counts including system columns.
48
+ * **Clipboard**: Full TSV-formatted copy support (Ctrl+C / Cmd+C) for seamless integration with Excel/Google Sheets.
49
+
50
+ ### Advanced Excel Export *(completed 2026-03-03)*
51
+ * **`exportToExcelAdvanced`**: Real `.xlsx` via ExcelJS (lazy-loaded — zero bundle cost unless used).
52
+ * **Features**: Styled headers, typed cells, column widths from `colDef.width`, frozen header row, auto-filter dropdowns, alternating row colors, aggregation totals rows, multi-sheet workbooks, standalone summary sheet.
53
+ * **API**: `ExcelAdvancedExportOptions`, `ExcelSheetDefinition`, `ExcelColumnStyle` — all exported and documented.
54
+ * **Demo**: `AdvancedExcelExportDemo` in demo app (`Data Management` category).
55
+ * **Docs**: `docs/features/export-guide.md` fully updated with feature comparison table and all options.
56
+
57
+ ---
58
+
59
+ ## 📅 Upcoming Features
60
+
61
+ ### Advanced Data Features
62
+ * **Interactive Pivot Builder**: Drag-and-drop UI for users to dynamically create pivot tables.
63
+ * **Native PDF Reporting**: Automated PDF generation with customizable layouts.
64
+ * **Advanced Charts Integration**: Inline sparklines and trend visualization.
65
+
66
+ ### DX & Publishing
67
+ * **npm publish**: Finalize `package.json` metadata, `README.md`, and publish to npm registry.
68
+
69
+ ## ✅ Implemented Features (Recent)
70
+ * **Storybook / Docs site**: Interactive component documentation with source code viewer and categorized navigation (implemented via `demo` app).
71
+ * **Quickstart & Installation Guides**: Comprehensive onboarding documentation for developers.
@@ -0,0 +1,119 @@
1
+ .ogx__cell {
2
+ position: relative;
3
+ display: flex;
4
+ align-items: center;
5
+ padding: var(--ogx-grid-cell-padding-y) var(--ogx-grid-cell-padding-x);
6
+ border-right: 1px solid var(--ogx-grid-border-color);
7
+ font-size: var(--ogx-grid-cell-font-size);
8
+ color: var(--ogx-grid-row-text);
9
+ line-height: var(--ogx-grid-line-height);
10
+ white-space: nowrap;
11
+ overflow: hidden;
12
+ text-overflow: ellipsis;
13
+ box-sizing: border-box;
14
+ background-color: inherit;
15
+ }
16
+ .ogx__cell:focus {
17
+ outline: none;
18
+ }
19
+ .ogx__cell--focused {
20
+ /* position tracked internally; ring only shows in keyboard mode (ogx--kb on container) */
21
+ z-index: 10;
22
+ }
23
+ /* Show ring when keyboard navigation is active */
24
+ .ogx--kb .ogx__cell--focused {
25
+ outline: 2px solid var(--ogx-color-primary, #2563eb);
26
+ outline-offset: -2px;
27
+ }
28
+ .ogx__cell--editable {
29
+ cursor: text;
30
+ }
31
+ .ogx__cell--editing {
32
+ padding: 0;
33
+ overflow: visible;
34
+ z-index: 20;
35
+ outline: 2px solid var(--ogx-color-primary);
36
+ outline-offset: -2px;
37
+ background-color: var(--ogx-grid-background);
38
+ }
39
+ .ogx__cell--align-left {
40
+ justify-content: flex-start;
41
+ text-align: left;
42
+ }
43
+ .ogx__cell--align-center {
44
+ justify-content: center;
45
+ text-align: center;
46
+ }
47
+ .ogx__cell--align-right {
48
+ justify-content: flex-end;
49
+ text-align: right;
50
+ }
51
+ .ogx__cell--pinned-left,
52
+ .ogx__cell--pinned-right {
53
+ position: sticky;
54
+ z-index: 2;
55
+ background: inherit;
56
+ background-color: var(--ogx-grid-background);
57
+ }
58
+ .ogx__cell--pinned-right {
59
+ border-left: 1px solid var(--ogx-grid-border-color);
60
+ }
61
+ .ogx:not(.ogx--row-spanning) .ogx__row:hover .ogx__cell--pinned-left,
62
+ .ogx:not(.ogx--row-spanning) .ogx__row:hover .ogx__cell--pinned-right {
63
+ background-color: var(--ogx-grid-row-hover-background);
64
+ }
65
+ .ogx__row--selected .ogx__cell--pinned-left,
66
+ .ogx__row--selected .ogx__cell--pinned-right {
67
+ background-color: var(--ogx-grid-row-selected-background);
68
+ }
69
+ .ogx:not(.ogx--row-spanning) .ogx__row--selected:hover .ogx__cell--pinned-left,
70
+ .ogx:not(.ogx--row-spanning) .ogx__row--selected:hover .ogx__cell--pinned-right {
71
+ background-color: var(--ogx-grid-row-selected-hover-background);
72
+ }
73
+ .ogx__cell--pinned-left-last::after {
74
+ content: '';
75
+ position: absolute;
76
+ top: 0;
77
+ right: -10px;
78
+ bottom: 0;
79
+ width: 10px;
80
+ pointer-events: none;
81
+ box-shadow: inset 10px 0 8px -8px rgba(0, 0, 0, 0.1);
82
+ }
83
+ .ogx__cell--pinned-right-first::before {
84
+ content: '';
85
+ position: absolute;
86
+ top: 0;
87
+ left: -10px;
88
+ bottom: 0;
89
+ width: 10px;
90
+ pointer-events: none;
91
+ box-shadow: inset -10px 0 8px -8px rgba(0, 0, 0, 0.1);
92
+ }
93
+ .ogx__cell--hidden {
94
+ visibility: hidden;
95
+ border: none;
96
+ background: transparent;
97
+ }
98
+ .ogx__cell-content {
99
+ overflow: hidden;
100
+ text-overflow: ellipsis;
101
+ white-space: nowrap;
102
+ width: 100%;
103
+ }
104
+ .ogx__cell--spanned {
105
+ background-color: var(--ogx-grid-background);
106
+ z-index: 10;
107
+ }
108
+ .ogx__row--even .ogx__cell--spanned {
109
+ background-color: var(--ogx-grid-row-alternate-background, var(--ogx-grid-background));
110
+ }
111
+ .ogx:not(.ogx--row-spanning) .ogx__row:hover .ogx__cell--spanned {
112
+ background-color: var(--ogx-grid-row-hover-background);
113
+ }
114
+ .ogx__row--selected .ogx__cell--spanned {
115
+ background-color: var(--ogx-grid-row-selected-background);
116
+ }
117
+ .ogx:not(.ogx--row-spanning) .ogx__row--selected:hover .ogx__cell--spanned {
118
+ background-color: var(--ogx-grid-row-selected-hover-background);
119
+ }
@@ -0,0 +1,264 @@
1
+
2
+ import React from 'react';
3
+ import type { GridColDef, GridRowModel, GridPinnedPosition, GridCellParams } from '../../types';
4
+ import type { CellColSpanInfo } from '../../hooks/features/useGridSpanning';
5
+
6
+ import { GridEditInputCell } from './GridEditInputCell';
7
+
8
+ export interface CellProps<R extends GridRowModel = GridRowModel> {
9
+ onCellClick?: (params: GridCellParams<R>) => void;
10
+ onCellEditStart?: (field: string, value: any) => void;
11
+ onCellValueChange?: (field: string, newValue: any) => void;
12
+ value: any;
13
+ row: R;
14
+ colDef: GridColDef<R>;
15
+ rowIndex: number;
16
+ colIndex: number;
17
+ isSelected?: boolean;
18
+ onClick?: (event: React.MouseEvent) => void;
19
+ width?: number;
20
+ pinnedPosition?: GridPinnedPosition | null;
21
+ pinnedOffset?: number;
22
+
23
+ isFocused?: boolean;
24
+ isFocusVisible?: boolean;
25
+
26
+ isEditable?: boolean;
27
+ isEditing?: boolean;
28
+ onEditStart?: () => void;
29
+ onEditStop?: (cancel?: boolean) => void;
30
+ onValueChange?: (newValue: any) => void;
31
+
32
+ colSpanInfo?: CellColSpanInfo;
33
+ rowSpan?: number;
34
+ isHiddenByRowSpan?: boolean;
35
+ }
36
+
37
+ function CellImpl<R extends GridRowModel = GridRowModel>(props: CellProps<R>) {
38
+ const {
39
+ onCellClick,
40
+ onCellEditStart,
41
+ onCellValueChange,
42
+ value,
43
+ row,
44
+ colDef,
45
+ rowIndex,
46
+ colIndex,
47
+ isSelected,
48
+ onClick,
49
+ width,
50
+ pinnedPosition,
51
+ pinnedOffset,
52
+ isFocused,
53
+ isFocusVisible,
54
+ isEditable,
55
+ isEditing,
56
+ onEditStart,
57
+ onEditStop,
58
+ onValueChange,
59
+
60
+ colSpanInfo,
61
+ rowSpan: rowSpanProp,
62
+ isHiddenByRowSpan
63
+ } = props;
64
+
65
+ if (colSpanInfo?.spannedByColSpan) {
66
+
67
+ return null;
68
+ }
69
+
70
+ if (isHiddenByRowSpan) {
71
+
72
+ return (
73
+ <div
74
+ className="ogx__cell ogx__cell--hidden"
75
+ style={{ width: width ?? colDef.width ?? 100 }}
76
+ role="presentation"
77
+ data-field={colDef.field}
78
+ data-colindex={colIndex}
79
+ />
80
+ );
81
+ }
82
+
83
+ const handleClick = (e: React.MouseEvent) => {
84
+ if (onCellClick) {
85
+ e.stopPropagation();
86
+ onCellClick({
87
+ row,
88
+ field: colDef.field,
89
+ value,
90
+ colDef,
91
+ rowIndex,
92
+ colIndex
93
+ });
94
+ }
95
+ onClick?.(e);
96
+ };
97
+
98
+ const handleValueChange = React.useCallback((newValue: any) => {
99
+ if (onCellValueChange) {
100
+ onCellValueChange(colDef.field, newValue);
101
+ } else {
102
+ onValueChange?.(newValue);
103
+ }
104
+ }, [onCellValueChange, onValueChange, colDef.field]);
105
+
106
+ const handleDoubleClick = (e: React.MouseEvent) => {
107
+ if (isEditable && (onEditStart || onCellEditStart)) {
108
+ e.stopPropagation();
109
+ if (onCellEditStart) {
110
+ onCellEditStart(colDef.field, value);
111
+ } else {
112
+ onEditStart?.();
113
+ }
114
+ }
115
+ };
116
+
117
+ const formattedValue = React.useMemo(() => {
118
+ if (colDef.valueFormatter) {
119
+ return colDef.valueFormatter({
120
+ value,
121
+ row,
122
+ field: colDef.field
123
+ });
124
+ }
125
+
126
+ if (value === null || value === undefined) {
127
+ return '';
128
+ }
129
+
130
+ return String(value);
131
+ }, [value, row, colDef]);
132
+
133
+ // Render custom cell content OR Edit Content
134
+ const cellContent = React.useMemo(() => {
135
+ if (isEditing && (onCellValueChange || onValueChange) && onEditStop) {
136
+ if (colDef.renderEditCell) {
137
+ return colDef.renderEditCell({
138
+ value,
139
+ row,
140
+ field: colDef.field,
141
+ colDef: colDef as any,
142
+ rowIndex,
143
+ colIndex
144
+ });
145
+ }
146
+ return (
147
+ <GridEditInputCell
148
+ value={value}
149
+ row={row}
150
+ field={colDef.field}
151
+ colDef={colDef as any}
152
+ rowIndex={rowIndex}
153
+ colIndex={colIndex}
154
+ onValueChange={handleValueChange}
155
+ onCommit={() => onEditStop()}
156
+ onCancel={() => onEditStop(true)}
157
+ />
158
+ );
159
+ }
160
+
161
+ if (colDef.renderCell) {
162
+ return colDef.renderCell({
163
+ value,
164
+ row,
165
+ field: colDef.field,
166
+ colDef: colDef as any,
167
+ rowIndex,
168
+ colIndex
169
+ });
170
+ }
171
+
172
+ return formattedValue;
173
+ }, [value, row, colDef, rowIndex, colIndex, formattedValue, isEditing, handleValueChange, onEditStop, onCellValueChange, onValueChange]);
174
+
175
+ const isPinned = pinnedPosition !== null && pinnedPosition !== undefined;
176
+
177
+ const resolvedCellClassName = React.useMemo(() => {
178
+ if (!colDef.cellClassName) return '';
179
+ if (typeof colDef.cellClassName === 'function') {
180
+ return colDef.cellClassName({ value, row, field: colDef.field, colDef: colDef as any, rowIndex, colIndex }) || '';
181
+ }
182
+ return colDef.cellClassName;
183
+ }, [colDef, value, row, rowIndex, colIndex]);
184
+
185
+ const classNames = [
186
+ 'ogx__cell',
187
+ `ogx__cell--align-${colDef.align || 'left'}`,
188
+ isSelected && 'ogx__cell--selected',
189
+ isFocused && 'ogx__cell--focused',
190
+ (isFocused && isFocusVisible) && 'ogx__cell--focus-visible',
191
+ colDef.type && `ogx__cell--type-${colDef.type}`,
192
+ isPinned && 'ogx__cell--pinned',
193
+ isPinned && `ogx__cell--pinned-${pinnedPosition}`,
194
+ isEditing && 'ogx__cell--editing',
195
+ (rowSpanProp && rowSpanProp > 1) && 'ogx__cell--spanned',
196
+ resolvedCellClassName
197
+ ].filter(Boolean).join(' ');
198
+
199
+ const colSpan = colSpanInfo?.cellProps?.colSpan || 1;
200
+ const rowSpan = rowSpanProp || 1;
201
+ const cellWidth = colSpanInfo?.cellProps?.width ?? width ?? colDef.width ?? 100;
202
+
203
+ const style: React.CSSProperties = {
204
+ width: 'var(--width)',
205
+ '--width': typeof cellWidth === 'number' ? `${cellWidth}px` : cellWidth,
206
+ flexGrow: colDef.flex ?? 0,
207
+ flexShrink: 0,
208
+ flexBasis: colDef.flex ? 'auto' : 'auto',
209
+ boxSizing: 'border-box',
210
+ minWidth: colDef.minWidth,
211
+ maxWidth: colDef.maxWidth,
212
+ position: isPinned ? 'sticky' : undefined,
213
+ zIndex: colDef.zIndex ?? (isPinned ? 3 : undefined),
214
+ padding: isEditing ? 0 : undefined
215
+ } as React.CSSProperties;
216
+
217
+ if (rowSpan > 1) {
218
+ style.height = `calc(var(--height) * ${rowSpan} - var(--border-width, 1px))`;
219
+ style.zIndex = 10;
220
+ if (isPinned) {
221
+ style.zIndex = 40;
222
+ }
223
+ }
224
+
225
+ if (isPinned && pinnedOffset !== undefined) {
226
+ if (pinnedPosition === 'left') {
227
+ style.left = pinnedOffset;
228
+ } else {
229
+ style.right = pinnedOffset;
230
+ }
231
+ }
232
+
233
+ const cellRef = React.useRef<HTMLDivElement>(null);
234
+
235
+ React.useLayoutEffect(() => {
236
+ if (isFocused && cellRef.current) {
237
+ cellRef.current.focus({ preventScroll: true });
238
+ }
239
+ }, [isFocused]);
240
+
241
+ return (
242
+ <div
243
+ ref={cellRef}
244
+ className={classNames}
245
+ style={style}
246
+ onClick={handleClick}
247
+ onDoubleClick={handleDoubleClick}
248
+ role="gridcell"
249
+ aria-colindex={colIndex + 1}
250
+ aria-readonly={!isEditable || undefined}
251
+ tabIndex={-1}
252
+ data-field={colDef.field}
253
+ data-colindex={colIndex}
254
+ {...(colSpan > 1 ? { 'aria-colspan': colSpan } : {})}
255
+ {...(rowSpan > 1 ? { 'aria-rowspan': rowSpan } : {})}
256
+ >
257
+ <div className="ogx__cell-content">
258
+ {cellContent}
259
+ </div>
260
+ </div>
261
+ );
262
+ }
263
+
264
+ export const Cell = React.memo(CellImpl) as typeof CellImpl;
@@ -0,0 +1,62 @@
1
+ /* ── Edit cell wrapper ───────────────────────────────────────────────── */
2
+ .ogx__edit-cell {
3
+ width: 100%;
4
+ height: 100%;
5
+ display: flex;
6
+ align-items: center;
7
+ padding: 0;
8
+ }
9
+
10
+ /* ── Shared input base ───────────────────────────────────────────────── */
11
+ .ogx__edit-input,
12
+ .ogx__edit-select {
13
+ width: 100%;
14
+ height: 100%;
15
+ padding: 0 10px;
16
+ border: none;
17
+ border-radius: 0;
18
+ font-family: var(--ogx-font-family, inherit);
19
+ font-size: var(--ogx-grid-cell-font-size, 14px);
20
+ color: var(--ogx-grid-row-text, #1e293b);
21
+ background: var(--ogx-grid-background, #fff);
22
+ box-sizing: border-box;
23
+ outline: none;
24
+ }
25
+
26
+ .ogx__edit-input:focus,
27
+ .ogx__edit-select:focus {
28
+ outline: none;
29
+ }
30
+
31
+ /* ── Number input — hide spinners ────────────────────────────────────── */
32
+ .ogx__edit-input--number::-webkit-inner-spin-button,
33
+ .ogx__edit-input--number::-webkit-outer-spin-button {
34
+ -webkit-appearance: none;
35
+ margin: 0;
36
+ }
37
+ .ogx__edit-input--number { -moz-appearance: textfield; appearance: textfield; }
38
+
39
+ /* ── Select ─────────────────────────────────────────────────────────── */
40
+ .ogx__edit-select {
41
+ padding: 0 6px;
42
+ cursor: pointer;
43
+ -webkit-appearance: auto;
44
+ appearance: auto;
45
+ }
46
+
47
+ /* ── Boolean checkbox ────────────────────────────────────────────────── */
48
+ .ogx__edit-boolean {
49
+ width: 100%;
50
+ height: 100%;
51
+ display: flex;
52
+ align-items: center;
53
+ justify-content: center;
54
+ outline: 2px solid var(--ogx-color-primary, #2563eb);
55
+ outline-offset: -2px;
56
+ }
57
+ .ogx__edit-checkbox {
58
+ width: 18px;
59
+ height: 18px;
60
+ cursor: pointer;
61
+ accent-color: var(--ogx-color-primary, #2563eb);
62
+ }
@@ -0,0 +1,152 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import type { GridRenderCellParams } from '../../types';
3
+
4
+ export interface GridEditInputCellProps extends GridRenderCellParams {
5
+ onValueChange: (value: any) => void;
6
+ onCommit: () => void;
7
+ onCancel: () => void;
8
+ }
9
+
10
+ // ─── Text / String ────────────────────────────────────────────────────────────
11
+ function TextEditor({ value, onValueChange, onCommit, onCancel }: GridEditInputCellProps) {
12
+ const [local, setLocal] = useState(String(value ?? ''));
13
+ const ref = useRef<HTMLInputElement>(null);
14
+ useEffect(() => { ref.current?.focus(); ref.current?.select(); }, []);
15
+
16
+ return (
17
+ <input
18
+ ref={ref}
19
+ type="text"
20
+ className="ogx__edit-input"
21
+ value={local}
22
+ onChange={e => { setLocal(e.target.value); onValueChange(e.target.value); }}
23
+ onBlur={onCommit}
24
+ onKeyDown={e => {
25
+ if (e.key === 'Enter') { e.stopPropagation(); onCommit(); }
26
+ if (e.key === 'Escape') { e.stopPropagation(); onCancel(); }
27
+ }}
28
+ />
29
+ );
30
+ }
31
+
32
+ // ─── Number ───────────────────────────────────────────────────────────────────
33
+ function NumberEditor({ value, onValueChange, onCommit, onCancel }: GridEditInputCellProps) {
34
+ const [local, setLocal] = useState(value ?? '');
35
+ const ref = useRef<HTMLInputElement>(null);
36
+ useEffect(() => { ref.current?.focus(); ref.current?.select(); }, []);
37
+
38
+ return (
39
+ <input
40
+ ref={ref}
41
+ type="number"
42
+ className="ogx__edit-input ogx__edit-input--number"
43
+ value={local}
44
+ onChange={e => {
45
+ const v = e.target.value === '' ? '' : Number(e.target.value);
46
+ setLocal(v);
47
+ onValueChange(v === '' ? null : v);
48
+ }}
49
+ onBlur={onCommit}
50
+ onKeyDown={e => {
51
+ if (e.key === 'Enter') { e.stopPropagation(); onCommit(); }
52
+ if (e.key === 'Escape') { e.stopPropagation(); onCancel(); }
53
+ }}
54
+ />
55
+ );
56
+ }
57
+
58
+ // ─── Boolean ──────────────────────────────────────────────────────────────────
59
+ function BooleanEditor({ value, onValueChange, onCommit, onCancel }: GridEditInputCellProps) {
60
+ const ref = useRef<HTMLInputElement>(null);
61
+ useEffect(() => { ref.current?.focus(); }, []);
62
+
63
+ const handleChange = () => {
64
+ const next = !value;
65
+ onValueChange(next);
66
+ // Commit immediately on toggle
67
+ setTimeout(onCommit, 0);
68
+ };
69
+
70
+ return (
71
+ <div className="ogx__edit-boolean">
72
+ <input
73
+ ref={ref}
74
+ type="checkbox"
75
+ className="ogx__edit-checkbox"
76
+ checked={!!value}
77
+ onChange={handleChange}
78
+ onKeyDown={e => {
79
+ if (e.key === 'Escape') { e.stopPropagation(); onCancel(); }
80
+ }}
81
+ />
82
+ </div>
83
+ );
84
+ }
85
+
86
+ // ─── SingleSelect / Enum ──────────────────────────────────────────────────────
87
+ function SelectEditor({ value, colDef, onValueChange, onCommit, onCancel }: GridEditInputCellProps) {
88
+ const ref = useRef<HTMLSelectElement>(null);
89
+ useEffect(() => { ref.current?.focus(); }, []);
90
+
91
+ const options = colDef.valueOptions ?? [];
92
+
93
+ return (
94
+ <select
95
+ ref={ref}
96
+ className="ogx__edit-select"
97
+ value={value ?? ''}
98
+ onChange={e => { onValueChange(e.target.value); }}
99
+ onBlur={onCommit}
100
+ onKeyDown={e => {
101
+ if (e.key === 'Enter') { e.stopPropagation(); onCommit(); }
102
+ if (e.key === 'Escape') { e.stopPropagation(); onCancel(); }
103
+ }}
104
+ >
105
+ {options.map(opt => {
106
+ const v = typeof opt === 'object' ? opt.value : opt;
107
+ const l = typeof opt === 'object' ? opt.label : String(opt);
108
+ return <option key={String(v)} value={String(v)}>{l}</option>;
109
+ })}
110
+ </select>
111
+ );
112
+ }
113
+
114
+ // ─── Date ─────────────────────────────────────────────────────────────────────
115
+ function DateEditor({ value, onValueChange, onCommit, onCancel }: GridEditInputCellProps) {
116
+ const [local, setLocal] = useState(value ?? '');
117
+ const ref = useRef<HTMLInputElement>(null);
118
+ useEffect(() => { ref.current?.focus(); }, []);
119
+
120
+ return (
121
+ <input
122
+ ref={ref}
123
+ type="date"
124
+ className="ogx__edit-input"
125
+ value={local}
126
+ onChange={e => { setLocal(e.target.value); onValueChange(e.target.value); }}
127
+ onBlur={onCommit}
128
+ onKeyDown={e => {
129
+ if (e.key === 'Enter') { e.stopPropagation(); onCommit(); }
130
+ if (e.key === 'Escape') { e.stopPropagation(); onCancel(); }
131
+ }}
132
+ />
133
+ );
134
+ }
135
+
136
+ // ─── Router ───────────────────────────────────────────────────────────────────
137
+ export function GridEditInputCell(props: GridEditInputCellProps) {
138
+ const { colDef } = props;
139
+
140
+ switch (colDef.type) {
141
+ case 'number':
142
+ return <div className="ogx__edit-cell"><NumberEditor {...props} /></div>;
143
+ case 'boolean':
144
+ return <div className="ogx__edit-cell"><BooleanEditor {...props} /></div>;
145
+ case 'singleSelect':
146
+ return <div className="ogx__edit-cell"><SelectEditor {...props} /></div>;
147
+ case 'date':
148
+ return <div className="ogx__edit-cell"><DateEditor {...props} /></div>;
149
+ default:
150
+ return <div className="ogx__edit-cell"><TextEditor {...props} /></div>;
151
+ }
152
+ }