@ornery/ui-grid-react 0.1.4 → 0.1.5

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ornery/ui-grid-react",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "React wrapper for @ornery/ui-grid",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
package/src/UiGrid.tsx CHANGED
@@ -19,7 +19,13 @@ export interface UiGridProps {
19
19
  className?: string;
20
20
  }
21
21
 
22
- export function UiGrid({ options, onRegisterApi, cellRenderer, expandableRenderer, className }: UiGridProps) {
22
+ export function UiGrid({
23
+ options,
24
+ onRegisterApi,
25
+ cellRenderer,
26
+ expandableRenderer,
27
+ className,
28
+ }: UiGridProps) {
23
29
  const state = useGridState(options, onRegisterApi);
24
30
 
25
31
  const {
@@ -80,9 +86,18 @@ export function UiGrid({ options, onRegisterApi, cellRenderer, expandableRendere
80
86
  style={{ gridColumn: '1 / -1', paddingInlineStart: `${item.depth * 1.25 + 1}rem` }}
81
87
  onClick={() => state.toggleGroup(item)}
82
88
  >
83
- <strong>{item.field}: {item.label}</strong>
84
- <span>{item.count} {labels.groupRowsSuffix}</span>
85
- <svg className="toggle-icon group-disclosure-icon" viewBox="0 0 24 24" aria-hidden="true" focusable={false}>
89
+ <strong>
90
+ {item.field}: {item.label}
91
+ </strong>
92
+ <span>
93
+ {item.count} {labels.groupRowsSuffix}
94
+ </span>
95
+ <svg
96
+ className="toggle-icon group-disclosure-icon"
97
+ viewBox="0 0 24 24"
98
+ aria-hidden="true"
99
+ focusable={false}
100
+ >
86
101
  <path d={item.collapsed ? 'M10 7l5 5-5 5z' : 'M7 10l5 5 5-5z'} />
87
102
  </svg>
88
103
  <span className="sr-only ui-grid-sr-only">{state.groupDisclosureLabel(item)}</span>
@@ -107,10 +122,13 @@ export function UiGrid({ options, onRegisterApi, cellRenderer, expandableRendere
107
122
  if (item.kind !== 'row') return null;
108
123
  const rowItem = item as RowItem;
109
124
 
110
- return visibleColumns.map((column) => (
125
+ return visibleColumns.map((column) => {
126
+ const pinned = state.isPinned(column);
127
+ const pinOffset = pinned ? state.pinnedOffset(column) : null;
128
+ return (
111
129
  <div
112
130
  key={`${rowItem.row.id}-${column.name}`}
113
- className={cellClassName(rowItem, column)}
131
+ className={`${cellClassName(rowItem, column)}${pinned ? ' is-pinned' : ''}`}
114
132
  data-part="body-cell"
115
133
  role="gridcell"
116
134
  tabIndex={0}
@@ -120,8 +138,17 @@ export function UiGrid({ options, onRegisterApi, cellRenderer, expandableRendere
120
138
  onClick={() => state.focusCell(rowItem.row, column)}
121
139
  onDoubleClick={(e) => state.handleCellDoubleClick(rowItem.row, column, e)}
122
140
  onKeyDown={(e) => state.handleCellKeyDown(rowItem.row, column, e)}
141
+ style={{
142
+ position: pinned ? 'sticky' : undefined,
143
+ left: pinOffset?.side === 'left' ? pinOffset.offset : undefined,
144
+ right: pinOffset?.side === 'right' ? pinOffset.offset : undefined,
145
+ zIndex: pinned ? 2 : undefined,
146
+ }}
123
147
  >
124
- <div className="cell-shell" style={{ paddingInlineStart: state.cellIndent(rowItem.row, column) }}>
148
+ <div
149
+ className="cell-shell"
150
+ style={{ paddingInlineStart: state.cellIndent(rowItem.row, column) }}
151
+ >
125
152
  {treeViewFeature && state.showTreeToggle(rowItem.row, column) && (
126
153
  <button
127
154
  type="button"
@@ -132,7 +159,9 @@ export function UiGrid({ options, onRegisterApi, cellRenderer, expandableRendere
132
159
  onClick={(e) => state.toggleTreeRow(rowItem.row, e)}
133
160
  >
134
161
  <svg className="toggle-icon" viewBox="0 0 24 24" aria-hidden="true" focusable={false}>
135
- <path d={state.isTreeRowExpanded(rowItem.row) ? 'M7 10l5 5 5-5z' : 'M10 7l5 5-5 5z'} />
162
+ <path
163
+ d={state.isTreeRowExpanded(rowItem.row) ? 'M7 10l5 5 5-5z' : 'M10 7l5 5-5 5z'}
164
+ />
136
165
  </svg>
137
166
  </button>
138
167
  )}
@@ -164,14 +193,16 @@ export function UiGrid({ options, onRegisterApi, cellRenderer, expandableRendere
164
193
  onBlur={(e) => state.handleEditorBlur(e)}
165
194
  />
166
195
  ) : cellRenderer ? (
167
- cellRenderer(state.cellContext(rowItem.row, column)) ?? state.displayValue(rowItem.row, column)
196
+ (cellRenderer(state.cellContext(rowItem.row, column)) ??
197
+ state.displayValue(rowItem.row, column))
168
198
  ) : (
169
199
  state.displayValue(rowItem.row, column)
170
200
  )}
171
201
  </span>
172
202
  </div>
173
203
  </div>
174
- ));
204
+ );
205
+ });
175
206
  }
176
207
 
177
208
  function cellClassName(item: RowItem, column: GridColumnDef): string {
@@ -222,11 +253,21 @@ export function UiGrid({ options, onRegisterApi, cellRenderer, expandableRendere
222
253
  </div>
223
254
 
224
255
  <div className="hero-actions">
225
- <button type="button" className="action action-secondary" data-part="action benchmark-action" onClick={() => state.runBenchmark()}>
256
+ <button
257
+ type="button"
258
+ className="action action-secondary"
259
+ data-part="action benchmark-action"
260
+ onClick={() => state.runBenchmark()}
261
+ >
226
262
  Benchmark
227
263
  </button>
228
264
  {csvExportFeature && (
229
- <button type="button" className="action action-secondary" data-part="action export-action" onClick={() => state.exportCsv()}>
265
+ <button
266
+ type="button"
267
+ className="action action-secondary"
268
+ data-part="action export-action"
269
+ onClick={() => state.exportCsv()}
270
+ >
230
271
  Export CSV
231
272
  </button>
232
273
  )}
@@ -237,7 +278,11 @@ export function UiGrid({ options, onRegisterApi, cellRenderer, expandableRendere
237
278
  </div>
238
279
  </header>
239
280
 
240
- <section className="metrics-strip" data-part="metrics" aria-label="Grid performance metrics">
281
+ <section
282
+ className="metrics-strip"
283
+ data-part="metrics"
284
+ aria-label="Grid performance metrics"
285
+ >
241
286
  <article data-part="metric-card">
242
287
  <strong>{pipelineMs.toFixed(2)} ms</strong>
243
288
  <span>pipeline</span>
@@ -256,15 +301,22 @@ export function UiGrid({ options, onRegisterApi, cellRenderer, expandableRendere
256
301
  </article>
257
302
  </section>
258
303
 
259
- <section className="grid-frame ui-grid" data-part="grid-frame" role="grid" aria-label={options.title ?? 'Data grid'}>
304
+ <section
305
+ className="grid-frame ui-grid"
306
+ data-part="grid-frame"
307
+ role="grid"
308
+ aria-label={options.title ?? 'Data grid'}
309
+ >
260
310
  <div className="grid-toolbar" data-part="grid-toolbar">
261
311
  <div>
262
312
  <strong>{visibleRowCount}</strong>
263
- <span>{labels.toolbarOf} {totalRows} {labels.toolbarRows}</span>
313
+ <span>
314
+ {labels.toolbarOf} {totalRows} {labels.toolbarRows}
315
+ </span>
264
316
  </div>
265
317
  <p>
266
- `gridOptions` compatibility layer: sorting, filtering, grouping, column moving, templating,
267
- and virtualized rendering.
318
+ `gridOptions` compatibility layer: sorting, filtering, grouping, column moving,
319
+ templating, and virtualized rendering.
268
320
  </p>
269
321
  </div>
270
322
 
@@ -276,13 +328,22 @@ export function UiGrid({ options, onRegisterApi, cellRenderer, expandableRendere
276
328
  role="row"
277
329
  style={{ gridTemplateColumns }}
278
330
  >
279
- {visibleColumns.map((column) => (
331
+ {visibleColumns.map((column) => {
332
+ const pinned = state.isPinned(column);
333
+ const pinOffset = pinned ? state.pinnedOffset(column) : null;
334
+ return (
280
335
  <div
281
336
  key={column.name}
282
- className={`header-cell ui-grid-header-cell${sortingFeature && state.sortDirection(column) !== 'none' ? ' is-active' : ''}`}
337
+ className={`header-cell ui-grid-header-cell${sortingFeature && state.sortDirection(column) !== 'none' ? ' is-active' : ''}${pinned ? ' is-pinned' : ''}`}
283
338
  data-part="header-cell"
284
339
  role="columnheader"
285
- aria-sort={sortingFeature ? state.sortAriaSort(column) as any : undefined}
340
+ aria-sort={sortingFeature ? (state.sortAriaSort(column) as any) : undefined}
341
+ style={{
342
+ position: pinned ? 'sticky' : undefined,
343
+ left: pinOffset?.side === 'left' ? pinOffset.offset : undefined,
344
+ right: pinOffset?.side === 'right' ? pinOffset.offset : undefined,
345
+ zIndex: pinned ? 2 : undefined,
346
+ }}
286
347
  >
287
348
  <span className="header-label">{state.headerLabel(column)}</span>
288
349
 
@@ -297,36 +358,81 @@ export function UiGrid({ options, onRegisterApi, cellRenderer, expandableRendere
297
358
  onClick={() => state.toggleSort(column)}
298
359
  >
299
360
  {renderSortIcon(column)}
300
- <span className="sr-only ui-grid-sr-only">{state.sortButtonLabel(column)}</span>
361
+ <span className="sr-only ui-grid-sr-only">
362
+ {state.sortButtonLabel(column)}
363
+ </span>
301
364
  </button>
302
365
  )}
303
366
 
304
- {groupingFeature && state.isGroupingEnabled() && column.enableGrouping !== false && (
305
- <button
306
- type="button"
307
- className={`chip-action${state.isGrouped(column) ? ' chip-action-active' : ''}`}
308
- data-part="group-toggle"
309
- aria-label={state.groupingButtonLabel(column)}
310
- title={state.groupingButtonLabel(column)}
311
- onClick={(e) => state.toggleGrouping(column, e)}
312
- >
313
- <svg viewBox="0 0 24 24" aria-hidden="true" focusable={false}>
314
- <path d="M4 6h8v4H4V6Zm0 8h8v4H4v-4Zm10-8h6v4h-6V6Zm0 8h6v4h-6v-4Z" />
315
- </svg>
316
- <span className="sr-only ui-grid-sr-only">{state.groupingButtonLabel(column)}</span>
317
- </button>
318
- )}
367
+ {groupingFeature &&
368
+ state.isGroupingEnabled() &&
369
+ column.enableGrouping !== false && (
370
+ <button
371
+ type="button"
372
+ className={`chip-action${state.isGrouped(column) ? ' chip-action-active' : ''}`}
373
+ data-part="group-toggle"
374
+ aria-label={state.groupingButtonLabel(column)}
375
+ title={state.groupingButtonLabel(column)}
376
+ onClick={(e) => state.toggleGrouping(column, e)}
377
+ >
378
+ <svg viewBox="0 0 24 24" aria-hidden="true" focusable={false}>
379
+ <path d="M4 6h8v4H4V6Zm0 8h8v4H4v-4Zm10-8h6v4h-6V6Zm0 8h6v4h-6v-4Z" />
380
+ </svg>
381
+ <span className="sr-only ui-grid-sr-only">
382
+ {state.groupingButtonLabel(column)}
383
+ </span>
384
+ </button>
385
+ )}
386
+ {state.pinningFeature &&
387
+ state.isPinningEnabled() &&
388
+ state.isColumnPinnable(column) && (
389
+ <button
390
+ type="button"
391
+ className={`chip-action${pinned ? ' chip-action-active' : ''}`}
392
+ data-part="pin-toggle"
393
+ aria-label={pinned ? labels.unpin : labels.pinLeft}
394
+ title={pinned ? labels.unpin : labels.pinLeft}
395
+ onClick={() => state.togglePin(column)}
396
+ >
397
+ <svg viewBox="0 0 24 24" aria-hidden="true" focusable={false}>
398
+ <path d="M16 12V4h1V2H7v2h1v8l-2 2v2h5v6l1 1 1-1v-6h5v-2l-2-2z" />
399
+ </svg>
400
+ <span className="sr-only ui-grid-sr-only">
401
+ {pinned ? labels.unpin : labels.pinLeft}
402
+ </span>
403
+ </button>
404
+ )}
319
405
  </div>
320
406
  </div>
321
- ))}
407
+ );
408
+ })}
322
409
  </div>
323
410
 
324
411
  {/* Filter row */}
325
412
  {filteringFeature && state.isFilteringEnabled() && (
326
- <div className="filter-grid ui-grid-header" data-part="filters" style={{ gridTemplateColumns }}>
327
- {visibleColumns.map((column) => (
328
- <label key={column.name} className="filter-cell ui-grid-filter-container" data-part="filter-cell">
329
- <span className="sr-only ui-grid-sr-only">{labels.filterColumn} {state.headerLabel(column)}</span>
413
+ <div
414
+ className="filter-grid ui-grid-header"
415
+ data-part="filters"
416
+ style={{ gridTemplateColumns }}
417
+ >
418
+ {visibleColumns.map((column) => {
419
+ const pinned = state.isPinned(column);
420
+ const pinOffset = pinned ? state.pinnedOffset(column) : null;
421
+ return (
422
+ <label
423
+ key={column.name}
424
+ className={`filter-cell ui-grid-filter-container${pinned ? ' is-pinned' : ''}`}
425
+ data-part="filter-cell"
426
+ style={{
427
+ position: pinned ? 'sticky' : undefined,
428
+ left: pinOffset?.side === 'left' ? pinOffset.offset : undefined,
429
+ right: pinOffset?.side === 'right' ? pinOffset.offset : undefined,
430
+ zIndex: pinned ? 2 : undefined,
431
+ }}
432
+ >
433
+ <span className="sr-only ui-grid-sr-only">
434
+ {labels.filterColumn} {state.headerLabel(column)}
435
+ </span>
330
436
  <input
331
437
  className="ui-grid-filter-input"
332
438
  type="text"
@@ -336,7 +442,8 @@ export function UiGrid({ options, onRegisterApi, cellRenderer, expandableRendere
336
442
  onChange={(e) => state.updateFilter(column.name, e.target.value)}
337
443
  />
338
444
  </label>
339
- ))}
445
+ );
446
+ })}
340
447
  </div>
341
448
  )}
342
449
 
@@ -387,7 +494,12 @@ export function UiGrid({ options, onRegisterApi, cellRenderer, expandableRendere
387
494
 
388
495
  {/* Pagination footer */}
389
496
  {paginationFeature && state.showPaginationControls() && (
390
- <footer className="pagination-bar ui-grid-pagination" data-part="pagination" role="navigation" aria-label={labels.paginationPage}>
497
+ <footer
498
+ className="pagination-bar ui-grid-pagination"
499
+ data-part="pagination"
500
+ role="navigation"
501
+ aria-label={labels.paginationPage}
502
+ >
391
503
  <p>{state.paginationSummary()}</p>
392
504
  <div className="pagination-controls">
393
505
  <button
@@ -397,12 +509,20 @@ export function UiGrid({ options, onRegisterApi, cellRenderer, expandableRendere
397
509
  disabled={paginationCurrentPage <= 1}
398
510
  onClick={() => state.previousPage()}
399
511
  >
400
- <svg className="pagination-icon" viewBox="0 0 24 24" aria-hidden="true" focusable={false}>
512
+ <svg
513
+ className="pagination-icon"
514
+ viewBox="0 0 24 24"
515
+ aria-hidden="true"
516
+ focusable={false}
517
+ >
401
518
  <path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" />
402
519
  </svg>
403
520
  <span className="sr-only">{labels.paginationPrevious}</span>
404
521
  </button>
405
- <span>{labels.paginationPage} {paginationCurrentPage} {labels.paginationOf} {paginationTotalPages}</span>
522
+ <span>
523
+ {labels.paginationPage} {paginationCurrentPage} {labels.paginationOf}{' '}
524
+ {paginationTotalPages}
525
+ </span>
406
526
  <button
407
527
  type="button"
408
528
  className="action action-secondary pagination-button"
@@ -410,7 +530,12 @@ export function UiGrid({ options, onRegisterApi, cellRenderer, expandableRendere
410
530
  disabled={paginationCurrentPage >= paginationTotalPages}
411
531
  onClick={() => state.nextPage()}
412
532
  >
413
- <svg className="pagination-icon" viewBox="0 0 24 24" aria-hidden="true" focusable={false}>
533
+ <svg
534
+ className="pagination-icon"
535
+ viewBox="0 0 24 24"
536
+ aria-hidden="true"
537
+ focusable={false}
538
+ >
414
539
  <path d="M8.59 16.59L10 18l6-6-6-6-1.41 1.41L13.17 12z" />
415
540
  </svg>
416
541
  <span className="sr-only">{labels.paginationNext}</span>
@@ -424,7 +549,9 @@ export function UiGrid({ options, onRegisterApi, cellRenderer, expandableRendere
424
549
  onChange={(e) => state.onPageSizeChange(e.target.value)}
425
550
  >
426
551
  {state.pageSizeOptions().map((size) => (
427
- <option key={size} value={size}>{size}</option>
552
+ <option key={size} value={size}>
553
+ {size}
554
+ </option>
428
555
  ))}
429
556
  </select>
430
557
  </label>
@@ -0,0 +1,49 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import type { GridColumnDef } from '@ornery/ui-grid';
3
+ import {
4
+ buildGridTemplateColumns,
5
+ computeViewportHeightPx,
6
+ computeViewportRows,
7
+ formatPaginationSummary,
8
+ orderVisibleColumns,
9
+ resolveBenchmarkIterations,
10
+ } from './gridStateMath';
11
+
12
+ describe('gridStateMath', () => {
13
+ const columns: GridColumnDef[] = [
14
+ { name: 'status', width: '2fr' },
15
+ { name: 'owner', visible: false },
16
+ { name: 'revenue', width: '120px' },
17
+ { name: 'customer' },
18
+ ];
19
+
20
+ it('orders only visible columns by the provided column order', () => {
21
+ expect(orderVisibleColumns(columns, ['customer', 'revenue', 'status', 'owner']).map((column) => column.name)).toEqual([
22
+ 'customer',
23
+ 'revenue',
24
+ 'status',
25
+ ]);
26
+ });
27
+
28
+ it('builds grid template columns deterministically', () => {
29
+ expect(buildGridTemplateColumns(orderVisibleColumns(columns, ['status', 'revenue', 'customer']))).toBe('2fr 120px minmax(11rem, max-content)');
30
+ });
31
+
32
+ it('resolves benchmark iterations with a minimum of one', () => {
33
+ expect(resolveBenchmarkIterations(undefined, undefined)).toBe(25);
34
+ expect(resolveBenchmarkIterations(undefined, 7)).toBe(7);
35
+ expect(resolveBenchmarkIterations(0, 7)).toBe(1);
36
+ });
37
+
38
+ it('formats pagination summaries', () => {
39
+ expect(formatPaginationSummary(0, 0, 0)).toBe('0-0 of 0');
40
+ expect(formatPaginationSummary(42, 10, 19)).toBe('11-20 of 42');
41
+ });
42
+
43
+ it('computes viewport height and viewport rows', () => {
44
+ expect(computeViewportHeightPx(undefined, undefined)).toBe('560px');
45
+ expect(computeViewportHeightPx(620, 480)).toBe('620px');
46
+ expect(computeViewportRows(undefined, undefined)).toBe(13);
47
+ expect(computeViewportRows(220, 44)).toBe(5);
48
+ });
49
+ });
@@ -0,0 +1,32 @@
1
+ import type { GridColumnDef } from '@ornery/ui-grid';
2
+ import { gridColumnWidth } from '@ornery/ui-grid';
3
+
4
+ export function orderVisibleColumns(columns: readonly GridColumnDef[], order: readonly string[]): GridColumnDef[] {
5
+ return [...columns]
6
+ .filter((column) => column.visible !== false)
7
+ .sort((left, right) => order.indexOf(left.name) - order.indexOf(right.name));
8
+ }
9
+
10
+ export function buildGridTemplateColumns(columns: readonly GridColumnDef[]): string {
11
+ return columns.map((column) => gridColumnWidth(column)).join(' ');
12
+ }
13
+
14
+ export function resolveBenchmarkIterations(iterations?: number, configuredIterations?: number): number {
15
+ return Math.max(1, iterations ?? configuredIterations ?? 25);
16
+ }
17
+
18
+ export function formatPaginationSummary(totalItems: number, firstRowIndex: number, lastRowIndex: number): string {
19
+ if (totalItems === 0) {
20
+ return '0-0 of 0';
21
+ }
22
+
23
+ return `${firstRowIndex + 1}-${lastRowIndex + 1} of ${totalItems}`;
24
+ }
25
+
26
+ export function computeViewportHeightPx(viewportHeight?: number, autoViewportHeight?: number | null): string {
27
+ return `${viewportHeight ?? autoViewportHeight ?? 560}px`;
28
+ }
29
+
30
+ export function computeViewportRows(viewportHeight?: number, rowHeight?: number): number {
31
+ return Math.max(1, Math.ceil((viewportHeight ?? 560) / (rowHeight ?? 44)));
32
+ }
package/src/index.ts CHANGED
@@ -4,6 +4,8 @@ export { useGridState } from './useGridState';
4
4
  export type { UseGridStateResult } from './useGridState';
5
5
  export { useVirtualScroll } from './useVirtualScroll';
6
6
  export type { UseVirtualScrollOptions, UseVirtualScrollResult } from './useVirtualScroll';
7
+ export { orderVisibleColumns, buildGridTemplateColumns, resolveBenchmarkIterations, formatPaginationSummary, computeViewportHeightPx, computeViewportRows } from './gridStateMath';
8
+ export { enableReactUiGridWasmEngine, registerReactUiGridWasmEngineFromModule } from './rustWasmGridEngine';
7
9
 
8
10
  export type {
9
11
  GridOptions,
@@ -0,0 +1,56 @@
1
+ import { beforeEach, describe, expect, it } from 'vitest';
2
+ import {
3
+ activeGridEngineBackend,
4
+ clearRustWasmGridEngine,
5
+ defaultGridEngine,
6
+ } from '@ornery/ui-grid';
7
+ import { registerReactUiGridWasmEngineFromModule } from './rustWasmGridEngine';
8
+ import { SORT_DIRECTIONS } from '@ornery/ui-grid';
9
+ import type { BuildGridPipelineContext, PipelineResult } from '@ornery/ui-grid';
10
+
11
+ function createContext(): BuildGridPipelineContext {
12
+ return {
13
+ options: {
14
+ id: 'react-engine-spec',
15
+ data: [
16
+ { id: 'row-1', owner: 'Alice' },
17
+ { id: 'row-2', owner: 'Bob' },
18
+ ],
19
+ columnDefs: [{ name: 'owner' }],
20
+ },
21
+ columns: [{ name: 'owner' }],
22
+ activeFilters: {},
23
+ sortState: { columnName: null, direction: SORT_DIRECTIONS.none },
24
+ groupByColumns: [],
25
+ collapsedGroups: {},
26
+ hiddenRowReasons: {},
27
+ expandedRows: {},
28
+ expandedTreeRows: {},
29
+ currentPage: 1,
30
+ pageSize: 0,
31
+ rowSize: 44,
32
+ };
33
+ }
34
+
35
+ describe('rustWasmGridEngine', () => {
36
+ beforeEach(() => {
37
+ clearRustWasmGridEngine();
38
+ });
39
+
40
+ it('registers the real module shape into the shared engine seam', () => {
41
+ const sentinel: PipelineResult = {
42
+ visibleRows: [],
43
+ displayItems: [],
44
+ virtualizationEnabled: true,
45
+ pipelineMs: 0,
46
+ totalItems: 77,
47
+ };
48
+
49
+ registerReactUiGridWasmEngineFromModule({
50
+ build_pipeline_js: () => sentinel,
51
+ });
52
+
53
+ expect(defaultGridEngine.buildPipeline(createContext())).toBe(sentinel);
54
+ expect(activeGridEngineBackend()).toBe('rust-wasm');
55
+ });
56
+ });
@@ -0,0 +1,21 @@
1
+ import type { BuildGridPipelineContext, PipelineResult } from '@ornery/ui-grid';
2
+ import { registerRustWasmGridEngine } from '@ornery/ui-grid';
3
+
4
+ const uiGridWasmModulePath = '../../../dist/ui-grid-wasm/ui_grid_wasm.js';
5
+
6
+ type UiGridWasmModule = {
7
+ build_pipeline_js(context: unknown): PipelineResult;
8
+ };
9
+
10
+ export function registerReactUiGridWasmEngineFromModule(module: UiGridWasmModule): void {
11
+ registerRustWasmGridEngine({
12
+ buildPipeline(context: BuildGridPipelineContext): PipelineResult {
13
+ return module.build_pipeline_js(context);
14
+ }
15
+ });
16
+ }
17
+
18
+ export async function enableReactUiGridWasmEngine(): Promise<void> {
19
+ const module = await import(/* @vite-ignore */ uiGridWasmModulePath);
20
+ registerReactUiGridWasmEngineFromModule(module);
21
+ }