@opencorestack/opengridx 3.9.0 → 3.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +11 -7
  3. package/dist/index.d.ts +55 -0
  4. package/dist/opengridx.es.js +6346 -6279
  5. package/dist/opengridx.es.js.map +1 -1
  6. package/dist/opengridx.umd.js +16 -16
  7. package/dist/opengridx.umd.js.map +1 -1
  8. package/docs/API_REFERENCE.md +4 -0
  9. package/docs/architecture/datagrid-orchestration.md +4 -2
  10. package/docs/architecture/use-grid-virtualization.md +2 -0
  11. package/docs/components/pagination.md +1 -1
  12. package/docs/features/ai-toolkit.md +1 -0
  13. package/docs/features/rtl.md +1 -1
  14. package/docs/performance.md +19 -1
  15. package/docs/roadmap.md +26 -5
  16. package/lib/components/Cell/Cell.tsx +2 -1
  17. package/lib/components/DataGrid/DataGrid.tsx +19 -18
  18. package/lib/components/DataGrid/GridListView.tsx +1 -1
  19. package/lib/components/DataGrid/GridPinnedRows.tsx +1 -1
  20. package/lib/components/DataGrid/GridViewport.tsx +2 -1
  21. package/lib/components/DataGrid/GridVirtualRows.tsx +1 -1
  22. package/lib/components/Header/Header.tsx +4 -3
  23. package/lib/components/Row/Row.tsx +6 -2
  24. package/lib/components/Toolbar/AggregationPanel.tsx +90 -0
  25. package/lib/components/Toolbar/ColumnsPanelWrapper.tsx +56 -0
  26. package/lib/components/Toolbar/FilterPanelWrapper.tsx +82 -0
  27. package/lib/components/Toolbar/GridToolbar.tsx +7 -380
  28. package/lib/components/Toolbar/ToolbarIcons.tsx +35 -0
  29. package/lib/components/Toolbar/useToolbarPanel.ts +71 -0
  30. package/lib/hooks/core/gridKeyDown.ts +272 -0
  31. package/lib/hooks/core/useDataGrid.ts +13 -5
  32. package/lib/hooks/core/useGridApiMethods.ts +2 -2
  33. package/lib/hooks/core/useGridCellRangeDrag.ts +345 -0
  34. package/lib/hooks/core/useGridCellSelection.ts +17 -297
  35. package/lib/hooks/core/useGridColumnLookup.ts +2 -1
  36. package/lib/hooks/core/useGridColumns.tsx +3 -2
  37. package/lib/hooks/core/useGridControlledState.ts +3 -2
  38. package/lib/hooks/core/useGridHierarchy.ts +1 -1
  39. package/lib/hooks/core/useGridKeyboardNavigation.ts +51 -235
  40. package/lib/hooks/core/useGridLiveRowSelection.ts +2 -2
  41. package/lib/hooks/core/useGridRowInteractions.ts +1 -1
  42. package/lib/hooks/core/useGridRowSelection.ts +11 -2
  43. package/lib/hooks/core/useGridToolbarProps.ts +3 -2
  44. package/lib/hooks/features/useGridAiAssistant.ts +3 -2
  45. package/lib/hooks/useColumnReorder.ts +2 -1
  46. package/lib/hooks/useTreeData.ts +4 -2
  47. package/lib/types/locale.ts +22 -0
  48. package/lib/utils/aggregation/index.ts +16 -0
  49. package/lib/utils/aiAssistant.ts +46 -15
  50. package/lib/utils/export/excelCells.ts +244 -0
  51. package/lib/utils/export/exportCsv.ts +179 -0
  52. package/lib/utils/export/exportExcelHtml.ts +220 -0
  53. package/lib/utils/export/exportJson.ts +179 -0
  54. package/lib/utils/export/exportRows.ts +85 -0
  55. package/lib/utils/export/exportToExcelAdvanced.ts +22 -242
  56. package/lib/utils/export/index.ts +5 -695
  57. package/lib/utils/export/printGrid.ts +282 -0
  58. package/lib/utils/gridLocale.ts +30 -0
  59. package/lib/utils/selectionSets.ts +36 -0
  60. package/lib/utils/untypedColumns.ts +16 -0
  61. package/llms.txt +3 -1
  62. package/package.json +6 -2
  63. package/lib/theme/__baseline__/default-theme.darwin.chromium.json +0 -1
  64. package/lib/theme/__baseline__/default-theme.darwin.firefox.json +0 -1
  65. package/lib/theme/__baseline__/default-theme.darwin.webkit.json +0 -1
@@ -0,0 +1,282 @@
1
+
2
+ import type {
3
+ GridColDef,
4
+ GridRowId,
5
+ GridRowModel,
6
+ GridValidRowModel,
7
+ GridAggregationModel,
8
+ GridGroupedExportRow,
9
+ } from '../../types';
10
+ import { groupHeaderLabel } from './groupLabel';
11
+ import {
12
+ aggregationForExport,
13
+ escapeHTML,
14
+ getExportColumns,
15
+ hasSelection,
16
+ rowsForExport,
17
+ shouldExportGrouped,
18
+ summaryLabelText,
19
+ } from './exportShared';
20
+
21
+ export interface CsvExportOptions<R extends GridValidRowModel = GridRowModel> {
22
+ fileName?: string;
23
+ includeHeaders?: boolean;
24
+ delimiter?: string;
25
+ /**
26
+ * Export only these row IDs. A non-empty selection takes precedence over `groupedRows` (the
27
+ * selected rows are exported flat), and the totals row is recomputed over the selected rows.
28
+ */
29
+ selectedRows?: (string | number)[];
30
+ /**
31
+ * The grid's `getRowId`, when it has one: `selectedRows` holds those ids, and the grid does not
32
+ * copy them onto `row.id` (v3.0+). Defaults to `row.id`.
33
+ */
34
+ getRowId?: (row: R) => GridRowId;
35
+ aggregationResult?: Record<string, unknown> | null;
36
+ aggregationModel?: GridAggregationModel | null;
37
+ /** When provided, emits group headers, leaf rows, subtotals, and a grand total instead of a flat row list. */
38
+ groupedRows?: GridGroupedExportRow[];
39
+ /**
40
+ * Prefix text cells that start with `=`, `+`, `-`, `@`, tab or carriage return with `'`, so
41
+ * spreadsheet apps show them as text instead of running them as formulas (CSV injection).
42
+ * Plain signed numbers are left alone. Default: `true`
43
+ * @since v3.0
44
+ */
45
+ escapeFormulas?: boolean;
46
+ /**
47
+ * Start the file with a UTF-8 byte-order mark, so Excel reads non-ASCII text (é, ü, ₹, 中文)
48
+ * correctly. Default: `true`
49
+ * @since v3.0
50
+ */
51
+ bom?: boolean;
52
+ }
53
+ import { aggregateTexts, exportCell } from './exportRows';
54
+
55
+ export interface PrintOptions<R extends GridValidRowModel = GridRowModel> {
56
+ title?: string;
57
+ /**
58
+ * Print only these row IDs. A non-empty selection takes precedence over `groupedRows` (the
59
+ * selected rows are printed flat), and the totals are recomputed over the selected rows.
60
+ */
61
+ selectedRows?: (string | number)[];
62
+ /**
63
+ * The grid's `getRowId`, when it has one: `selectedRows` holds those ids, and the grid does not
64
+ * copy them onto `row.id` (v3.0+). Defaults to `row.id`.
65
+ */
66
+ getRowId?: (row: R) => GridRowId;
67
+ aggregationResult?: Record<string, unknown> | null;
68
+ aggregationModel?: GridAggregationModel | null;
69
+ /** When provided, emits group headers, leaf rows, subtotals, and a grand total instead of a flat row list. */
70
+ groupedRows?: GridGroupedExportRow[];
71
+ }
72
+
73
+ const SAFE_IMAGE_URL = /^(?:https?:|data:image\/|blob:)/i;
74
+ const URL_SCHEME = /^[a-z][a-z\d+.-]*:/i;
75
+
76
+ /**
77
+ * The URL to use as an `<img src>` in the print window, or null when it is not an image URL:
78
+ * http(s), `data:image/…`, `blob:` and relative URLs are allowed; `javascript:`, `data:text/html`,
79
+ * `file:` and other schemes are not.
80
+ */
81
+ function printableImageUrl(value: string): string | null {
82
+ const url = value.trim();
83
+ if (!url) return null;
84
+ if (SAFE_IMAGE_URL.test(url)) return url;
85
+ return URL_SCHEME.test(url) ? null : url;
86
+ }
87
+
88
+ /**
89
+ * Open print dialog with formatted table
90
+ */
91
+ export function printGrid<R extends GridValidRowModel = GridRowModel>(rows: R[], columns: GridColDef<R>[], titleOrOptions?: string | PrintOptions<R>): Promise<void>;
92
+ export function printGrid<R extends GridValidRowModel = GridRowModel>(rows: R[], columns: GridColDef[], titleOrOptions?: string | PrintOptions<R>): Promise<void>;
93
+ export async function printGrid<R extends GridRowModel>(
94
+ rows: R[],
95
+ columns: GridColDef<R>[],
96
+ titleOrOptions?: string | PrintOptions<R>
97
+ ): Promise<void> {
98
+ // 1. Open window immediately to resolve user activation constraints and provide feedback
99
+ const printWindow = window.open('', '_blank');
100
+
101
+ if (!printWindow) {
102
+ console.error('Failed to open print window. Please check your popup blocker settings.');
103
+ return;
104
+ }
105
+
106
+ // 2. Show loading state
107
+ printWindow.document.write(`
108
+ <!DOCTYPE html>
109
+ <html>
110
+ <head>
111
+ <title>Preparing Print...</title>
112
+ <style>
113
+ body { font-family: sans-serif; height: 100vh; display: flex; align-items: center; justify-content: center; margin: 0; background: #f5f5f5; }
114
+ .loader { text-align: center; }
115
+ .spinner { border: 4px solid #f3f3f3; border-top: 4px solid #3498db; border-radius: 50%; width: 40px; height: 40px; animation: spin 1s linear infinite; margin: 0 auto 20px; }
116
+ @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
117
+ </style>
118
+ </head>
119
+ <body>
120
+ <div class="loader">
121
+ <div class="spinner"></div>
122
+ <h2>Generating Print Preview...</h2>
123
+ <p>Processing data, please wait.</p>
124
+ </div>
125
+ </body>
126
+ </html>
127
+ `);
128
+ printWindow.document.close();
129
+
130
+ // 3. Yield to main thread to allow the new window to render its loading state
131
+ await new Promise(resolve => setTimeout(resolve, 100));
132
+
133
+ try {
134
+ let title = 'Print';
135
+ let selectedRows: (string | number)[] | undefined;
136
+ let getRowId: ((row: R) => GridRowId) | undefined;
137
+ let aggregationResult: Record<string, unknown> | null = null;
138
+ let aggregationModel: GridAggregationModel | null = null;
139
+
140
+ let groupedRows: GridGroupedExportRow[] | undefined;
141
+
142
+ if (typeof titleOrOptions === 'string') {
143
+ title = titleOrOptions;
144
+ } else if (typeof titleOrOptions === 'object') {
145
+ title = titleOrOptions.title || 'Print';
146
+ selectedRows = titleOrOptions.selectedRows;
147
+ getRowId = titleOrOptions.getRowId;
148
+ aggregationResult = titleOrOptions.aggregationResult || null;
149
+ aggregationModel = titleOrOptions.aggregationModel || null;
150
+ groupedRows = titleOrOptions.groupedRows;
151
+ }
152
+
153
+ const exportColumns = getExportColumns(columns);
154
+ const aggModel = aggregationModel || {};
155
+
156
+ // Group-row rules are qualified with `tr` so they outrank the stripe rule, which has the same
157
+ // specificity, by coming later.
158
+ let html = `<!DOCTYPE html><html><head><title>${escapeHTML(title || 'Print')}</title><style>
159
+ body{font-family:Arial,sans-serif;margin:20px;}
160
+ h1{font-size:24px;margin-bottom:20px;}
161
+ table{border-collapse:collapse;width:100%;}
162
+ th,td{border:1px solid #ddd;padding:8px;text-align:left;}
163
+ th{background-color:#f5f5f5;font-weight:bold;}
164
+ tr:nth-child(even){background-color:#f9f9f9;}
165
+ tr.group-header{background:#e8eaf6;font-weight:bold;}
166
+ tr.group-subtotal{background:#f0f4ff;font-style:italic;}
167
+ tr.grand-total{background:#f5f5f5;font-weight:bold;}
168
+ @media print{body{margin:0;}@page{margin:1cm;}}
169
+ </style></head><body>`;
170
+
171
+ if (title) html += `<h1>${escapeHTML(title)}</h1>`;
172
+ html += '<table><thead><tr>';
173
+ exportColumns.forEach(col => { html += `<th>${escapeHTML(col.headerName || col.field)}</th>`; });
174
+ html += '</tr></thead><tbody>';
175
+
176
+ const dataCell = (row: R, col: GridColDef<R>, prefixHtml = '', centerImage = false): string => {
177
+ const { text } = exportCell(row, col);
178
+ const src = col.type === 'image' ? printableImageUrl(text) : null;
179
+ if (src !== null) {
180
+ const img = `<img src="${escapeHTML(src)}" alt="${escapeHTML(col.headerName || col.field)}" style="max-height:40px;border-radius:4px;">`;
181
+ return centerImage ? `<td><div style="display:flex;justify-content:center;">${img}</div></td>` : `<td>${img}</td>`;
182
+ }
183
+ return `<td>${prefixHtml}${escapeHTML(text)}</td>`;
184
+ };
185
+
186
+ if (shouldExportGrouped(groupedRows, selectedRows)) {
187
+ groupedRows.forEach(entry => {
188
+ const indent = '&nbsp;'.repeat(entry.depth * 4);
189
+ if (entry.type === 'group-header') {
190
+ html += `<tr class="group-header"><td colspan="${exportColumns.length}">${indent}${escapeHTML(groupHeaderLabel(entry, columns))}</td></tr>`;
191
+ } else if (entry.type === 'leaf' && entry.row) {
192
+ const row = entry.row as R;
193
+ html += '<tr>';
194
+ exportColumns.forEach((col, i) => { html += dataCell(row, col, i === 0 ? indent : ''); });
195
+ html += '</tr>';
196
+ } else if ((entry.type === 'group-subtotal' || entry.type === 'grand-total') && entry.aggregatedValues) {
197
+ const isGrand = entry.type === 'grand-total';
198
+ const texts = aggregateTexts(exportColumns, entry.aggregatedValues, aggModel);
199
+ html += `<tr class="${isGrand ? 'grand-total' : 'group-subtotal'}">`;
200
+ exportColumns.forEach((_, i) => {
201
+ if (i === 0) {
202
+ html += `<td>${isGrand ? '' : indent}${escapeHTML(summaryLabelText(isGrand ? 'Grand Total' : 'Subtotal', texts[0]))}</td>`;
203
+ return;
204
+ }
205
+ html += texts[i] ? `<td style="text-align:right;">${escapeHTML(texts[i])}</td>` : '<td></td>';
206
+ });
207
+ html += '</tr>';
208
+ }
209
+ });
210
+ } else {
211
+ const rowsToExport = rowsForExport(rows, selectedRows, getRowId);
212
+
213
+ if (rowsToExport.length === 0) {
214
+ printWindow.document.body.innerHTML = '<h3>No rows to export</h3>';
215
+ return;
216
+ }
217
+
218
+ rowsToExport.forEach(row => {
219
+ html += '<tr>';
220
+ exportColumns.forEach(col => { html += dataCell(row, col, '', true); });
221
+ html += '</tr>';
222
+ });
223
+
224
+ const aggResult = aggregationForExport(rowsToExport, hasSelection(selectedRows), columns, aggregationResult, aggregationModel);
225
+ if (aggResult) {
226
+ html += '<tfoot><tr style="font-size:11px;color:#666;background-color:#f5f5f5;">';
227
+ exportColumns.forEach(col => {
228
+ html += aggModel[col.field]
229
+ ? `<td style="text-align:right;font-weight:bold;">${escapeHTML(aggModel[col.field].toUpperCase())}</td>`
230
+ : '<td></td>';
231
+ });
232
+ html += '</tr><tr style="font-weight:bold;background-color:#f5f5f5;">';
233
+ aggregateTexts(exportColumns, aggResult, aggModel).forEach(text => {
234
+ html += text ? `<td style="text-align:right;">${escapeHTML(text)}</td>` : '<td></td>';
235
+ });
236
+ html += '</tr></tfoot>';
237
+ }
238
+ }
239
+
240
+ html += '</tbody></table></body></html>';
241
+
242
+ // Write the HTML content
243
+ printWindow.document.open();
244
+ printWindow.document.write(html);
245
+ printWindow.document.close();
246
+
247
+ // Set the document title explicitly (fixes about:blank issue)
248
+ printWindow.document.title = title || 'Print Preview';
249
+
250
+ printWindow.focus();
251
+
252
+ let hasPrinted = false;
253
+
254
+ printWindow.onload = () => {
255
+ if (!hasPrinted) {
256
+ hasPrinted = true;
257
+ printWindow.print();
258
+
259
+ setTimeout(() => {
260
+ printWindow.close();
261
+ }, 100);
262
+ }
263
+ };
264
+
265
+ setTimeout(() => {
266
+ if (!hasPrinted && printWindow && !printWindow.closed) {
267
+ hasPrinted = true;
268
+ printWindow.print();
269
+ setTimeout(() => {
270
+ if (!printWindow.closed) printWindow.close();
271
+ }, 100);
272
+ }
273
+ }, 500);
274
+
275
+ } catch (e) {
276
+ console.error('Print generation failed:', e);
277
+ if (printWindow && !printWindow.closed) {
278
+ const message = e instanceof Error ? e.message : String(e);
279
+ printWindow.document.body.innerHTML = `<div style="color: red; padding: 20px;"><h3>Error Generating Print Preview</h3><p>${escapeHTML(message)}</p></div>`;
280
+ }
281
+ }
282
+ }
@@ -1,6 +1,7 @@
1
1
  import type { GridFilterOperator, GridLocaleText } from '../types';
2
2
  import { HEADER_FILTER_OPERATOR_LABELS } from './headerFilters';
3
3
  import { defaultCellSelectionAnnouncement } from './cellSelection';
4
+ import { AI_CHIP_TEXTS_EN, type GridAiChipTexts } from './aiAssistant';
4
5
 
5
6
  /** `localeText` keys that hold a lookup record; the resolved locale has a function for each. */
6
7
  type GridLocaleRecordKey = 'headerFilterOperators' | 'filterPanelOperators' | 'toolbarAggregationFunctions' | 'pivotAggregationFunctions';
@@ -197,6 +198,17 @@ export const GRID_LOCALE_EN: GridLocaleScalars = {
197
198
  aiAssistantNotValidated: 'The reply was not validated',
198
199
  aiAssistantFailed: m => `The assistant could not answer: ${m}`,
199
200
  aiAssistantUnknownError: 'Unknown error',
201
+ aiAssistantChipSearch: AI_CHIP_TEXTS_EN.search,
202
+ aiAssistantChipNoFilters: AI_CHIP_TEXTS_EN.noFilters,
203
+ aiAssistantChipSort: AI_CHIP_TEXTS_EN.sort,
204
+ aiAssistantChipNoSorting: AI_CHIP_TEXTS_EN.noSorting,
205
+ aiAssistantChipGroup: AI_CHIP_TEXTS_EN.group,
206
+ aiAssistantChipNoGrouping: AI_CHIP_TEXTS_EN.noGrouping,
207
+ aiAssistantChipSummary: AI_CHIP_TEXTS_EN.summary,
208
+ aiAssistantChipNoSummaries: AI_CHIP_TEXTS_EN.noSummaries,
209
+ aiAssistantChipPivot: AI_CHIP_TEXTS_EN.pivot,
210
+ aiAssistantChipNoPivot: AI_CHIP_TEXTS_EN.noPivot,
211
+ aiAssistantChipVisibility: AI_CHIP_TEXTS_EN.visibility,
200
212
 
201
213
  paginationLabel: 'Pagination',
202
214
  paginationFirstPageLabel: 'Go to first page',
@@ -220,6 +232,24 @@ function pickScalar<K extends GridLocaleScalarKey>(target: GridLocaleScalars, lo
220
232
  if (value !== undefined) target[key] = value as GridLocaleScalars[K];
221
233
  }
222
234
 
235
+ /** The AI assistant's change-chip texts out of a resolved locale. */
236
+ export function aiChipTextsOf(locale: ResolvedGridLocale): GridAiChipTexts {
237
+ return {
238
+ operator: op => locale.filterPanelOperator(op as GridFilterOperator),
239
+ search: locale.aiAssistantChipSearch,
240
+ noFilters: locale.aiAssistantChipNoFilters,
241
+ sort: locale.aiAssistantChipSort,
242
+ noSorting: locale.aiAssistantChipNoSorting,
243
+ group: locale.aiAssistantChipGroup,
244
+ noGrouping: locale.aiAssistantChipNoGrouping,
245
+ summary: locale.aiAssistantChipSummary,
246
+ noSummaries: locale.aiAssistantChipNoSummaries,
247
+ pivot: locale.aiAssistantChipPivot,
248
+ noPivot: locale.aiAssistantChipNoPivot,
249
+ visibility: locale.aiAssistantChipVisibility,
250
+ };
251
+ }
252
+
223
253
  /**
224
254
  * Fills every string `localeText` leaves out with its English default. Pure; memoise the result
225
255
  * per `localeText` object.
@@ -0,0 +1,36 @@
1
+ import type { GridRowId } from '../types';
2
+
3
+ // Select-all at 1M rows built the same 1M-id set three or four times: once to compute the next
4
+ // model, once more from that model array (`new Set(rowSelectionModel)`), and a full count to decide
5
+ // "all selected". These weak caches let the set built for a model be reused when that very array
6
+ // comes back as `rowSelectionModel`, and remember that it contains a given list of ids. Keyed by
7
+ // identity, so they never apply to an array or set the grid did not build; entries are collected
8
+ // with their keys.
9
+
10
+ const setByModel = new WeakMap<readonly GridRowId[], ReadonlySet<GridRowId>>();
11
+ const containsAll = new WeakMap<ReadonlySet<GridRowId>, readonly GridRowId[]>();
12
+
13
+ /** The ids of `model` as a set: the one the grid built for this array, or a new one. */
14
+ export function selectionSetOf(model: readonly GridRowId[]): ReadonlySet<GridRowId> {
15
+ let set = setByModel.get(model);
16
+ if (!set) {
17
+ set = new Set(model);
18
+ setByModel.set(model, set);
19
+ }
20
+ return set;
21
+ }
22
+
23
+ /** Records that `set` holds exactly the ids of `model` (the grid built both). */
24
+ export function rememberSelectionSet(model: readonly GridRowId[], set: ReadonlySet<GridRowId>): void {
25
+ setByModel.set(model, set);
26
+ }
27
+
28
+ /** Records that `set` contains every id of `ids` (it was built by selecting them). */
29
+ export function markContainsAll(set: ReadonlySet<GridRowId>, ids: readonly GridRowId[]): void {
30
+ containsAll.set(set, ids);
31
+ }
32
+
33
+ /** Whether `set` is known to contain every id of this very `ids` array. False when unknown. */
34
+ export function isKnownToContainAll(set: ReadonlySet<GridRowId>, ids: readonly GridRowId[]): boolean {
35
+ return containsAll.get(set) === ids;
36
+ }
@@ -0,0 +1,16 @@
1
+ import type { GridColDef, GridRowModel } from '../types';
2
+
3
+ /**
4
+ * A column typed for the grid's rows (`GridColDef<R>`) as the untyped `GridColDef` that public
5
+ * callback parameters, the toolbar and the row store use. TypeScript rejects the plain widening
6
+ * because the column's callbacks take `R` (`strictFunctionTypes`); it is sound here because the grid
7
+ * only ever calls them with its own rows. The one place this widening is written (R4, 3.10).
8
+ */
9
+ export function untypedColumn<R extends GridRowModel>(column: GridColDef<R>): GridColDef {
10
+ return column as unknown as GridColDef;
11
+ }
12
+
13
+ /** `untypedColumn` for a list. */
14
+ export function untypedColumns<R extends GridRowModel>(columns: readonly GridColDef<R>[]): GridColDef[] {
15
+ return columns as unknown as GridColDef[];
16
+ }
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # OpenGridX — AI Agent Context
2
2
  # Package: @opencorestack/opengridx
3
- # Version: 3.9.0
3
+ # Version: 3.10.0
4
4
  # Docs: https://opencorestack.github.io/OpenGridX/
5
5
  # This file is machine-readable. AI developers: start here.
6
6
 
@@ -733,3 +733,5 @@ Arrows move between cells (skipping hidden columns); Home/End, Ctrl+Home/End, Pa
733
733
  25. **Quick filter text** (3.7.1+): besides the raw value and any `valueFormatter` text, `singleSelect` columns match their option label, `boolean` columns Yes / No, and `date` columns the locale date shown. Exports read values through the same `valueGetter` handling as the grid (a throw exports an empty cell), and CSV / HTML-Excel escape formula text from a `valueFormatter` unless it reads as a plain number.
734
734
  26. **localeText covers every UI string (3.9+)**: ~200 optional keys (column menu, toolbar, filter / columns / pivot panels, AI panel, pagination, row and header checkboxes, overlays, announcements), English by default. An inline object of strings is fine; define function entries (`rowSelectLabel: id => …`) outside the render so rows stay memoised. Components used outside a grid show English.
735
735
  27. **Theme keys added in 3.9**: `grid.checkboxCheckedBackground`, `checkboxCheckmark`, `expandIconColor`, `expandIconHoverColor`; `overlays.itemIndicatorColor` (menu selection dot), `itemSelectedIcon`, `iconColor`, `sectionLabelText`, `dividerColor`. The demo's Theme Builder edits all of them.
736
+ 28. **3.10**: the AI assistant's change chips are `localeText` keys too (`aiAssistantChipSort(field, direction)`, `aiAssistantChipSummary(fn, field)`, …). Select-all at 1M rows takes ~130 ms. The library forbids `as unknown as` outside documented boundaries and `any` everywhere (ESLint), and CI checks that `llms.txt`, CLAUDE.md and the CHANGELOG name the `package.json` version.
737
+ 29. **Large client-side datasets**: sorting or filtering 1M rows blocks for ~0.4–0.5 s (one native sort). There is no time-sliced mode (measured and rejected: 1.5–2× slower overall). For interactive sorting and filtering at that size use a `dataSource`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencorestack/opengridx",
3
- "version": "3.9.0",
3
+ "version": "3.10.0",
4
4
  "description": "OpenGridX: High-performance React data infrastructure. Unlock advanced Row Grouping, Excel Export, and Column Pinning without the usual \"Pro\" gatekeeping. Built for speed, scale, and complete architectural freedom. Fully open, virtualization-ready, and feature-complete.",
5
5
  "type": "module",
6
6
  "main": "./dist/opengridx.umd.js",
@@ -29,6 +29,7 @@
29
29
  "!lib/**/*.test.tsx",
30
30
  "!lib/**/__screenshots__",
31
31
  "!lib/**/__snapshots__",
32
+ "!lib/**/__baseline__",
32
33
  "docs/architecture",
33
34
  "docs/assets",
34
35
  "docs/components",
@@ -65,7 +66,9 @@
65
66
  "coverage": "vitest run --coverage",
66
67
  "prepublishOnly": "npm run build:lib",
67
68
  "wiki:build": "node scripts/build-wiki.mjs",
68
- "check:compiler": "node scripts/check-react-compiler.mjs"
69
+ "check:compiler": "node scripts/check-react-compiler.mjs",
70
+ "check:package": "node scripts/check-package.mjs",
71
+ "check:versions": "node scripts/check-versions.mjs"
69
72
  },
70
73
  "keywords": [
71
74
  "react",
@@ -138,6 +141,7 @@
138
141
  "jspdf": "^4.2.1",
139
142
  "jspdf-autotable": "^5.0.8",
140
143
  "playwright": "^1.58.2",
144
+ "prism-react-renderer": "^2.4.1",
141
145
  "react": "^19.2.0",
142
146
  "react-dom": "^19.2.0",
143
147
  "react-router-dom": "^7.13.1",