@opencorestack/opengridx 3.8.0 → 3.8.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.
Files changed (36) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/ai.cjs.map +1 -1
  3. package/dist/ai.es.js.map +1 -1
  4. package/dist/index.d.ts +2 -2
  5. package/dist/opengridx.css +1 -1
  6. package/dist/opengridx.es.js +5210 -5199
  7. package/dist/opengridx.es.js.map +1 -1
  8. package/dist/opengridx.umd.js +16 -16
  9. package/dist/opengridx.umd.js.map +1 -1
  10. package/docs/API_REFERENCE.md +28 -0
  11. package/lib/components/AiAssistant/GridAiAssistantPanel.css +2 -2
  12. package/lib/components/Cell/Cell.css +1 -3
  13. package/lib/components/ColumnGroupHeader/ColumnGroupHeader.css +1 -2
  14. package/lib/components/DataGrid/DataGrid.css +1 -28
  15. package/lib/components/DataGrid/GridVirtualRows.tsx +2 -1
  16. package/lib/components/FilterPanel/FilterPanel.tsx +7 -39
  17. package/lib/components/Header/Header.css +2 -11
  18. package/lib/components/HeaderFilter/HeaderFilterCell.tsx +6 -37
  19. package/lib/components/ListView/ListView.css +0 -16
  20. package/lib/components/Row/Row.tsx +4 -1
  21. package/lib/hooks/features/useGridSpanning.ts +1 -1
  22. package/lib/styles/base/reset.css +6 -7
  23. package/lib/theme/__baseline__/default-theme.darwin.chromium.json +1 -1503
  24. package/lib/theme/__baseline__/default-theme.darwin.firefox.json +1 -1503
  25. package/lib/theme/__baseline__/default-theme.darwin.webkit.json +1 -1503
  26. package/lib/types/rows.ts +0 -2
  27. package/lib/utils/cellSelection.ts +0 -2
  28. package/lib/utils/editing/index.ts +1 -1
  29. package/lib/utils/filtering/filterInput.ts +45 -0
  30. package/lib/utils/filtering/index.ts +0 -28
  31. package/lib/utils/pinning/index.ts +0 -107
  32. package/lib/utils/sorting/index.ts +42 -33
  33. package/lib/utils/values.ts +5 -1
  34. package/llms.txt +7 -3
  35. package/package.json +1 -1
  36. package/lib/theme/defaultStyles.ts +0 -0
@@ -546,6 +546,21 @@ In every exporter, a non-empty `selectedRows` takes precedence over `groupedRows
546
546
 
547
547
  Every exporter takes `columns` typed for your rows (`GridColDef<R>[]`) or untyped (`GridColDef[]`), and its options also accept `getRowId?: (row: R) => GridRowId`. Pass the grid's `getRowId` when you use one: `selectedRows` holds those ids, and since v3.0 the grid no longer copies them onto `row.id`.
548
548
 
549
+ ### Export option types
550
+
551
+ Each exporter's options are exported as a type. Fields common to all of them: `fileName`, `selectedRows`, `getRowId`, `aggregationResult`, `aggregationModel`, `groupedRows`.
552
+
553
+ | Type | For | Its own fields |
554
+ | :--- | :--- | :--- |
555
+ | `CsvExportOptions<R>` | `exportToCsv` | `includeHeaders`, `delimiter`, `escapeFormulas`, `bom` |
556
+ | `JsonExportOptions<R>` | `exportToJson` | `pretty` |
557
+ | `ExcelExportOptions<R>` | `exportToExcel` (HTML `.xls`) | `sheetName`, `includeHeaders`, `escapeFormulas` |
558
+ | `ExcelAdvancedExportOptions<R>` | `exportToExcelAdvanced` (`.xlsx`) | `sheets`, `columnStyles`, `headerFillColor`, `headerTextColor`, `headerFontSize`, `bodyFontSize` |
559
+ | `ExcelSheetDefinition` | one entry of `sheets` | `name`, `rows` (`'all'` / `'selected'`), `includeHeaders`, `includeSummary`, `autoFilter`, `frozenHeader`, `alternateRowColor`; `{ type: 'summary', name? }` adds a totals sheet |
560
+ | `ExcelColumnStyle` | a value of `columnStyles` (by field) | `numFmt` (e.g. `'$#,##0.00'`), `width`, `alignment`, `embedImage`, `imageWidth`, `imageHeight` |
561
+
562
+ `PdfExportOptions` and `PrintOptions` are described in [PDF Export](features/pdf-export.md) and the [Export Guide](features/export-guide.md).
563
+
549
564
  ---
550
565
 
551
566
  ## 🤖 AI Toolkit (`@opencorestack/opengridx/ai`)
@@ -652,6 +667,10 @@ Context provider for overriding the grid's visual system.
652
667
  - **`overlays`**: colours (`background`, `text`, `border`, `shadow`, `item*`) and, v3.7, `fontFamily`, `fontWeight`, `fontSize` for menus and panels.
653
668
  - **`toolbar`**, **`scrollbar`** (`thumbColor`, `trackColor`, `size`), **`skeleton`** (`baseColor`, `highlightColor`), **`transitions`**.
654
669
 
670
+ #### Theme types
671
+
672
+ `DataGridThemeProviderProps`: `theme: GridTheme`, `children`, `className?`, `style?` (both on the wrapper element). Each key of `GridTheme` has its own exported type, for building a theme in parts: `GridThemeColors` (with `GridThemeGrayScale` for `gray`), `GridThemeTypography`, `GridThemeSpacing`, `GridThemeBorders`, `GridThemeShadows`, `GridThemeGrid`, `GridThemeOverlays`, `GridThemeToolbar`, `GridThemeScrollbar`, `GridThemeSkeleton`, `GridThemeTransitions`. Every field is optional.
673
+
655
674
  Presets: `darkTheme`, `roseTheme`, `emeraldTheme`, `amberTheme`, `compactTheme`. See [Theming](customization/theming.md).
656
675
 
657
676
  ---
@@ -836,6 +855,15 @@ If the key can change while the grid stays mounted, remount the grid with it (`<
836
855
 
837
856
  ## ⚙️ Interfaces & Types
838
857
 
858
+ ### Small shared types
859
+
860
+ | Type | Definition | Used by |
861
+ | :--- | :--- | :--- |
862
+ | `GridAlignment` | `'left' \| 'center' \| 'right'` | `GridColDef.align`, `headerAlign` |
863
+ | `GridSortDirection` | `'asc' \| 'desc' \| null` | a column's sort state (`null`: unsorted) |
864
+ | `GridSortModel` | `{ field: string; sort: GridSortDirection }`: like `GridSortItem`, but `sort` may be `null` | a single column's sort state |
865
+ | `GridPinnedPosition` | `'left' \| 'right'` (logical: inline start / end in RTL) | column pinning |
866
+
839
867
  ### `GridSortItem`
840
868
  ```typescript
841
869
  interface GridSortItem {
@@ -71,7 +71,7 @@
71
71
  .ogx-ai-panel__suggestion {
72
72
  padding: 3px 10px;
73
73
  border: 1px solid var(--ogx-border-color, var(--ogx-grid-border-color));
74
- border-radius: var(--ogx-border-radius-round, 999px);
74
+ border-radius: 999px;
75
75
  background: var(--ogx-grid-header-background);
76
76
  color: inherit;
77
77
  font: inherit;
@@ -119,7 +119,7 @@
119
119
  align-items: center;
120
120
  gap: 4px;
121
121
  padding: 2px 4px 2px 10px;
122
- border-radius: var(--ogx-border-radius-round, 999px);
122
+ border-radius: 999px;
123
123
  background: var(--ogx-color-primary-light, #e0e7ff);
124
124
  color: var(--ogx-color-primary-dark, #1e3a8a);
125
125
  }
@@ -27,9 +27,7 @@
27
27
  outline: 2px solid var(--ogx-grid-cell-focus-border, var(--ogx-color-primary, #2563eb));
28
28
  outline-offset: -2px;
29
29
  }
30
- .ogx__cell--editable {
31
- cursor: text;
32
- }
30
+
33
31
  /*
34
32
  * Cell range selection (cellSelection, v3.3). The tint is a background image, so it also covers
35
33
  * pinned cells' own background; the outline is an inset box-shadow, so the layout never shifts.
@@ -67,7 +67,6 @@
67
67
  }
68
68
 
69
69
  /* ─── Dark-mode overrides ────────────────────────────────────────────────────*/
70
- [data-theme="dark"] .ogx-col-group-cell__label,
71
- .ogx--dark .ogx-col-group-cell__label {
70
+ [data-theme="dark"] .ogx-col-group-cell__label {
72
71
  color: var(--ogx-color-gray-300, #cbd5e1);
73
72
  }
@@ -67,24 +67,7 @@
67
67
  background: var(--ogx-grid-background);
68
68
  box-shadow: 0 -4px 6px -1px rgba(0, 0, 0, 0.05);
69
69
  }
70
- .ogx--loading {
71
- opacity: 0.6;
72
- pointer-events: none;
73
- }
74
- .ogx--loading::after {
75
- content: '';
76
- position: absolute;
77
- top: 0;
78
- left: 0;
79
- right: 0;
80
- bottom: 0;
81
- background: color-mix(in srgb, var(--ogx-grid-background) 60%, transparent);
82
- backdrop-filter: blur(2px);
83
- display: flex;
84
- align-items: center;
85
- justify-content: center;
86
- z-index: var(--ogx-z-index-modal);
87
- }
70
+
88
71
  /* No height prop: fill the container, as a block child of a sized box (height: 100%) or as a
89
72
  flex item (flex: 1 with min-height: 0). In an auto-height parent 100% resolves to auto. */
90
73
  .ogx--fill {
@@ -312,16 +295,6 @@
312
295
  text-overflow: ellipsis;
313
296
  }
314
297
 
315
- .ogx--density-compact .ogx__cell,
316
- .ogx--density-compact .ogx__header-cell {
317
- padding: var(--ogx-spacing-xs) var(--ogx-spacing-sm);
318
- font-size: var(--ogx-font-size-xs);
319
- }
320
- .ogx--density-comfortable .ogx__cell,
321
- .ogx--density-comfortable .ogx__header-cell {
322
- padding: var(--ogx-spacing-md) var(--ogx-spacing-lg);
323
- font-size: var(--ogx-font-size-md);
324
- }
325
298
  .ogx__group-cell-content {
326
299
  font-weight: 500;
327
300
  text-align: start;
@@ -228,7 +228,8 @@ export function GridVirtualRows<R extends GridRowModel>({
228
228
  {Array.from({ length: infiniteScrollSkeletonCount }).map((_, i) => (
229
229
  <Row<R>
230
230
  key={`__skeleton_${i}__`}
231
- row={{ id: `__skeleton_${i}__`, _isSkeleton: true } as unknown as R}
231
+ row={{ id: `__skeleton_${i}__` } as unknown as R}
232
+ skeleton
232
233
  columns={virtualColumns}
233
234
  rowIndex={unpinnedRowsLength + i}
234
235
  rowHeight={rowHeight}
@@ -7,9 +7,9 @@ import type {
7
7
  GridFilterOperator
8
8
  } from '../../types';
9
9
  import { filterValueToInputText, getOperatorsForType, NO_VALUE_OPERATORS } from '../../utils/filtering';
10
+ import { filterValueFromInputText, getFilterValueOptions, isSameFilterValue, matchesFilterValueOption } from '../../utils/filtering/filterInput';
10
11
  import { Input } from '../ui/Input';
11
12
 
12
-
13
13
  export interface FilterPanelProps {
14
14
  filterModel: GridFilterModel;
15
15
  columns: GridColDef[];
@@ -29,17 +29,10 @@ function getActiveFilter(items: readonly FilterEntry[], field: string): GridFilt
29
29
  return items.find((entry): entry is GridFilterItem => isFilterItem(entry) && entry.field === field);
30
30
  }
31
31
 
32
- function sameValue(a: unknown, b: unknown): boolean {
33
- if (Object.is(a, b)) return true;
34
- if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
35
- if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((v, i) => sameValue(v, b[i]));
36
- return false;
37
- }
38
-
39
32
  function sameEntry(a: FilterEntry, b: FilterEntry): boolean {
40
33
  if (a === b) return true;
41
34
  if (isFilterItem(a) && isFilterItem(b)) {
42
- return a.id === b.id && a.field === b.field && a.operator === b.operator && sameValue(a.value, b.value);
35
+ return a.id === b.id && a.field === b.field && a.operator === b.operator && isSameFilterValue(a.value, b.value);
43
36
  }
44
37
  if (!isFilterItem(a) && !isFilterItem(b)) {
45
38
  return a.id === b.id && a.logicOperator === b.logicOperator && sameEntries(a.items, b.items);
@@ -52,36 +45,11 @@ function sameEntries(a: readonly FilterEntry[], b: readonly FilterEntry[]): bool
52
45
  return a.length === b.length && a.every((entry, i) => sameEntry(entry, b[i]));
53
46
  }
54
47
 
55
- interface ValueOption {
56
- key: string;
57
- value: unknown;
58
- label: string;
59
- }
60
-
61
- function getValueOptions(col: GridColDef): ValueOption[] {
62
- return (col.valueOptions ?? []).map((option, index) => (
63
- option !== null && typeof option === 'object'
64
- ? { key: String(index), value: option.value, label: option.label }
65
- : { key: String(index), value: option, label: String(option) }
66
- ));
67
- }
68
-
69
- function matchesOption(option: ValueOption, value: unknown): boolean {
70
- if (Object.is(option.value, value)) return true;
71
- return value != null && String(option.value).toLowerCase() === String(value).toLowerCase();
72
- }
73
-
74
48
  /** Text shown in the value input for a committed value. Never used to emit anything. */
75
49
  function toInputText(value: unknown, col: GridColDef): string {
76
50
  return filterValueToInputText(value, col.type);
77
51
  }
78
52
 
79
- /** The item value for typed text. isAnyOf takes a comma-separated list. */
80
- function fromInputText(text: string, operator: GridFilterOperator): unknown {
81
- if (operator === 'isAnyOf') return text.split(',').map(part => part.trim()).filter(part => part !== '');
82
- return text;
83
- }
84
-
85
53
  /** Carry a value over to a newly picked operator: lists for isAnyOf, scalars for the rest. */
86
54
  function convertValueForOperator(value: unknown, operator: GridFilterOperator): unknown {
87
55
  if (NO_VALUE_OPERATORS.has(operator)) return undefined;
@@ -144,7 +112,7 @@ const FilterRow: React.FC<{
144
112
  const unchanged = current ? text === toInputText(current.value, colRef.current) : text.trim() === '';
145
113
  if (unchanged) return;
146
114
  const operator = current?.operator ?? defaultOperator;
147
- onChangeRef.current(buildItem(operator, fromInputText(text, operator)));
115
+ onChangeRef.current(buildItem(operator, filterValueFromInputText(text, operator)));
148
116
  }, [defaultOperator, buildItem]);
149
117
 
150
118
  // Text still waiting for the debounce; flushed when the panel closes (unmounts) meanwhile.
@@ -172,7 +140,7 @@ const FilterRow: React.FC<{
172
140
 
173
141
  const handleOperatorChange = (op: GridFilterOperator) => {
174
142
  // Keep what the user typed even if the debounce has not committed it yet.
175
- const value = draft !== null ? fromInputText(draft, op) : item?.value;
143
+ const value = draft !== null ? filterValueFromInputText(draft, op) : item?.value;
176
144
  setDraft(null);
177
145
  onChange(buildItem(op, convertValueForOperator(value, op)));
178
146
  };
@@ -183,7 +151,7 @@ const FilterRow: React.FC<{
183
151
  };
184
152
 
185
153
  const showValue = !NO_VALUE_OPERATORS.has(currentOperator);
186
- const valueOptions = getValueOptions(col);
154
+ const valueOptions = getFilterValueOptions(col);
187
155
  const valueClassName = `ogx-filter__value-input${isActive ? ' ogx-filter__value-input--active' : ''}`;
188
156
  const valueLabel = `Filter value for ${label}`;
189
157
 
@@ -215,7 +183,7 @@ const FilterRow: React.FC<{
215
183
  className={`${valueClassName} ogx-filter__value-multiselect`}
216
184
  multiple
217
185
  size={Math.min(valueOptions.length, 4)}
218
- value={valueOptions.filter(o => selected.some(v => matchesOption(o, v))).map(o => o.key)}
186
+ value={valueOptions.filter(o => selected.some(v => matchesFilterValueOption(o, v))).map(o => o.key)}
219
187
  onChange={(e) => {
220
188
  const keys = new Set(Array.from(e.target.selectedOptions, option => option.value));
221
189
  onChange(buildItem('isAnyOf', valueOptions.filter(o => keys.has(o.key)).map(o => o.value)));
@@ -226,7 +194,7 @@ const FilterRow: React.FC<{
226
194
  </select>
227
195
  );
228
196
  } else if (showValue && col.type === 'singleSelect' && valueOptions.length > 0 && (currentOperator === 'is' || currentOperator === 'not')) {
229
- const current = valueOptions.find(o => matchesOption(o, item?.value));
197
+ const current = valueOptions.find(o => matchesFilterValueOption(o, item?.value));
230
198
  valueControl = (
231
199
  <select
232
200
  id={`${filterId}-val`}
@@ -207,12 +207,7 @@
207
207
  background-color: var(--ogx-grid-header-hover-background);
208
208
  z-index: 6;
209
209
  }
210
- .ogx__header-cell--focus-visible {
211
- outline: 2px solid var(--ogx-grid-cell-focus-border, var(--ogx-color-primary));
212
- outline-offset: -2px;
213
- background-color: var(--ogx-grid-header-hover-background);
214
- z-index: 6;
215
- }
210
+
216
211
  .ogx__header-cell:focus, .ogx__header-cell:focus-visible {
217
212
  outline: none !important;
218
213
  }
@@ -241,8 +236,4 @@
241
236
  background: var(--ogx-grid-header-hover-background);
242
237
  color: var(--ogx-grid-header-text);
243
238
  }
244
- .ogx__menu-icon-btn--active {
245
- opacity: 1;
246
- background: var(--ogx-color-primary-light);
247
- color: var(--ogx-color-primary);
248
- }
239
+
@@ -10,6 +10,7 @@ import {
10
10
  getHeaderFilterOperators,
11
11
  type GridHeaderFilterState,
12
12
  } from '../../utils/headerFilters';
13
+ import { filterValueFromInputText, getFilterValueOptions, isSameFilterValue, matchesFilterValueOption } from '../../utils/filtering/filterInput';
13
14
  import { Input } from '../ui/Input';
14
15
  import { HeaderFilterOperatorMenu } from './HeaderFilterOperatorMenu';
15
16
  import type { HeaderFilterLocale } from '../../utils/headerFilterLocale';
@@ -29,42 +30,10 @@ export interface HeaderFilterCellProps {
29
30
  locale: HeaderFilterLocale;
30
31
  }
31
32
 
32
- interface ValueOption {
33
- key: string;
34
- value: unknown;
35
- label: string;
36
- }
37
-
38
- function getValueOptions(col: GridColDef): ValueOption[] {
39
- return (col.valueOptions ?? []).map((option, index) => (
40
- option !== null && typeof option === 'object'
41
- ? { key: String(index), value: option.value, label: option.label }
42
- : { key: String(index), value: option, label: String(option) }
43
- ));
44
- }
45
-
46
- function matchesOption(option: ValueOption, value: unknown): boolean {
47
- const single = Array.isArray(value) ? value[0] : value;
48
- if (Object.is(option.value, single)) return true;
49
- return single != null && String(option.value).toLowerCase() === String(single).toLowerCase();
50
- }
51
-
52
- /** The item value for typed text. isAnyOf takes a comma-separated list. */
53
- function fromInputText(text: string, operator: GridFilterOperator): unknown {
54
- if (operator === 'isAnyOf') return text.split(',').map(part => part.trim()).filter(part => part !== '');
55
- return text;
56
- }
57
-
58
- function sameValue(a: unknown, b: unknown): boolean {
59
- if (Object.is(a, b)) return true;
60
- if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((v, i) => sameValue(v, b[i]));
61
- return false;
62
- }
63
-
64
33
  function sameItem(a: GridFilterItem | undefined, b: GridFilterItem | undefined): boolean {
65
34
  if (a === b) return true;
66
35
  if (!a || !b) return false;
67
- return a.operator === b.operator && sameValue(a.value, b.value);
36
+ return a.operator === b.operator && isSameFilterValue(a.value, b.value);
68
37
  }
69
38
 
70
39
  function ClearIcon() {
@@ -81,7 +50,7 @@ export function HeaderFilterCell({ colDef, state, style, className, ariaColIndex
81
50
  const label = colDef.headerName || colDef.field;
82
51
  const item = state.kind === 'editable' ? state.item : undefined;
83
52
  const operator: GridFilterOperator = item?.operator ?? getHeaderFilterDefaultOperator(colDef);
84
- const valueOptions = getValueOptions(colDef);
53
+ const valueOptions = getFilterValueOptions(colDef);
85
54
  const usesSelect = colDef.type === 'boolean' || (colDef.type === 'singleSelect' && valueOptions.length > 0);
86
55
  const showsValue = !NO_VALUE_OPERATORS.has(operator);
87
56
 
@@ -117,7 +86,7 @@ export function HeaderFilterCell({ colDef, state, style, className, ariaColIndex
117
86
  const unchanged = current ? text === filterValueToInputText(current.value, col.type) : text.trim() === '';
118
87
  if (unchanged) return;
119
88
  const op = current?.operator ?? getHeaderFilterDefaultOperator(col);
120
- const next = buildHeaderFilterItem(col, op, fromInputText(text, op));
89
+ const next = buildHeaderFilterItem(col, op, filterValueFromInputText(text, op));
121
90
  setLastEmitted(next);
122
91
  emit(col.field, next);
123
92
  }, []);
@@ -173,7 +142,7 @@ export function HeaderFilterCell({ colDef, state, style, className, ariaColIndex
173
142
 
174
143
  const selectOperator = (op: GridFilterOperator) => {
175
144
  // Keep what was typed even if the debounce has not committed it yet.
176
- const value = draft !== null ? fromInputText(draft, op) : item?.value;
145
+ const value = draft !== null ? filterValueFromInputText(draft, op) : item?.value;
177
146
  setDraft(null);
178
147
  setMenuAnchor(null);
179
148
  report(buildHeaderFilterItem(colDef, op, convertHeaderFilterValue(value, op)));
@@ -271,7 +240,7 @@ export function HeaderFilterCell({ colDef, state, style, className, ariaColIndex
271
240
  </select>
272
241
  );
273
242
  } else if (usesSelect) {
274
- const selected = valueOptions.find(o => matchesOption(o, item?.value));
243
+ const selected = valueOptions.find(o => matchesFilterValueOption(o, item?.value));
275
244
  valueControl = (
276
245
  <select
277
246
  ref={selectRef}
@@ -109,22 +109,6 @@
109
109
  color: var(--ogx-text-secondary, #9ca3af);
110
110
  }
111
111
 
112
- /* ─── Responsive: auto switch at ≤768px when using ogx--list-auto class ─── */
113
- @media (max-width: 768px) {
114
- .ogx--list-auto .ogx__viewport {
115
- display: none;
116
- }
117
- .ogx--list-auto .ogx-list-view {
118
- display: flex;
119
- }
120
- }
121
-
122
- @media (min-width: 769px) {
123
- .ogx--list-auto .ogx-list-view {
124
- display: none;
125
- }
126
- }
127
-
128
112
  /* Loading with no rows yet: a progress bar, or slots.loadingOverlay centred. */
129
113
  .ogx-list-view__loading {
130
114
  position: relative;
@@ -46,6 +46,8 @@ function mergeColSpan<R extends GridRowModel>(
46
46
 
47
47
  export interface RowProps<R extends GridRowModel = GridRowModel> {
48
48
  row: R;
49
+ /** A shimmer placeholder shown while infinite scroll loads more rows (`row` is unused). */
50
+ skeleton?: boolean;
49
51
  /** The row's id (from getRowId). Defaults to `row.id`; the grid always passes it. */
50
52
  rowId?: GridRowId;
51
53
  columns: GridColDef<R>[];
@@ -108,6 +110,7 @@ export interface RowProps<R extends GridRowModel = GridRowModel> {
108
110
  function RowImpl<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
109
111
  const {
110
112
  row,
113
+ skeleton = false,
111
114
  rowId: rowIdProp,
112
115
  columns,
113
116
  rowIndex,
@@ -197,7 +200,7 @@ function RowImpl<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
197
200
  }, [onEditStop, id]);
198
201
 
199
202
  // ── Skeleton row (shown during infinite-scroll fetch) ─────────────────────
200
- if (row._isSkeleton) {
203
+ if (skeleton) {
201
204
  // Render shimmer placeholders — one per visible column, matching row height
202
205
  const cellCount = Math.max(columns.length, 5);
203
206
  return (
@@ -356,7 +356,7 @@ function computeSection<R extends GridRowModel>(
356
356
 
357
357
  for (let r = 0; r < rows.length; r++) {
358
358
  const row = rows[r];
359
- if (!row || row._isSkeleton) continue;
359
+ if (!row) continue;
360
360
  // Synthetic rows hold no data of their own: they are never a span origin (spans from above
361
361
  // already end before them).
362
362
  if (isSyntheticRowId(getRowId(row), rowMetaMap)) continue;
@@ -5,18 +5,17 @@
5
5
  :where([class*="ogx"], [class*="ogx"] *)::after {
6
6
  box-sizing: border-box;
7
7
  }
8
- .ogx--dragging {
9
- user-select: none;
10
- -webkit-user-select: none;
11
- -moz-user-select: none;
12
- -ms-user-select: none;
13
- }
8
+
14
9
  .ogx *:focus {
15
10
  outline: none;
16
11
  }
17
- .ogx button:not(.ogx-menu-item) {
12
+ /* Background and border at zero specificity: a component's own button class (.ogx-button--contained,
13
+ the AI panel's suggestions, …) must win over this reset. Font and cursor keep their old weight. */
14
+ :where(.ogx button:not(.ogx-menu-item)) {
18
15
  background: none;
19
16
  border: none;
17
+ }
18
+ .ogx button:not(.ogx-menu-item) {
20
19
  cursor: pointer;
21
20
  font-family: inherit;
22
21
  font-size: inherit;