@ornery/ui-grid-react 0.1.9 → 1.0.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.
@@ -6,4 +6,6 @@ export declare function mountUiGrid(container: Element | DocumentFragment, props
6
6
  export declare function updateUiGrid(root: Root, props: UiGridProps): void;
7
7
  /** Create a styled <span> React node — usable from non-TSX contexts (e.g. Angular). */
8
8
  export declare function styledCell(text: string, color: string, extraStyle?: React.CSSProperties): React.ReactNode;
9
+ /** Create a date <input> React node — usable from non-TSX contexts. */
10
+ export declare function datePickerCell(value: string, onChange?: (newValue: string) => void, extraStyle?: React.CSSProperties): React.ReactNode;
9
11
  //# sourceMappingURL=mountUiGrid.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mountUiGrid.d.ts","sourceRoot":"","sources":["../src/mountUiGrid.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAc,KAAK,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAEzD,OAAO,EAAU,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AAEpD,wBAAgB,WAAW,CAAC,SAAS,EAAE,OAAO,GAAG,gBAAgB,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,CAI3F;AAED,uFAAuF;AACvF,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,CAEjE;AAED,uFAAuF;AACvF,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,UAAU,CAAC,EAAE,KAAK,CAAC,aAAa,GAC/B,KAAK,CAAC,SAAS,CAMjB"}
1
+ {"version":3,"file":"mountUiGrid.d.ts","sourceRoot":"","sources":["../src/mountUiGrid.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAc,KAAK,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAEzD,OAAO,EAAU,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AAEpD,wBAAgB,WAAW,CAAC,SAAS,EAAE,OAAO,GAAG,gBAAgB,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,CAI3F;AAED,uFAAuF;AACvF,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,CAEjE;AAED,uFAAuF;AACvF,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,UAAU,CAAC,EAAE,KAAK,CAAC,aAAa,GAC/B,KAAK,CAAC,SAAS,CAMjB;AAED,uEAAuE;AACvE,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EACrC,UAAU,CAAC,EAAE,KAAK,CAAC,aAAa,GAC/B,KAAK,CAAC,SAAS,CAmBjB"}
package/dist/ui-grid.css CHANGED
@@ -1,4 +1,11 @@
1
1
  .ui-grid-host {
2
+ /* Stretch to fill the parent so the autoresize observer can measure a
3
+ deterministic height. Consumers can override these properties to opt
4
+ back into intrinsic sizing. */
5
+ display: flex;
6
+ flex-direction: column;
7
+ min-height: 0;
8
+ height: 100%;
2
9
  --_ui-grid-border-color: var(--ui-grid-border-color, var(--app-ui-grid-border-color, #d4d4d8));
3
10
  --_ui-grid-header-background: var(
4
11
  --ui-grid-header-background,
@@ -221,6 +228,12 @@
221
228
  border: 1px solid var(--ui-grid-border-color);
222
229
  box-shadow: var(--ui-grid-shadow);
223
230
  overflow: hidden;
231
+ /* Allow the frame to grow inside the flexed host so the body grid can fill
232
+ the available height when no explicit viewportHeight is set. */
233
+ display: flex;
234
+ flex-direction: column;
235
+ flex: 1 1 auto;
236
+ min-height: 0;
224
237
  }
225
238
 
226
239
  .metrics-strip {
@@ -266,37 +279,70 @@
266
279
  color: var(--ui-grid-muted-color);
267
280
  }
268
281
 
282
+ /*
283
+ * Grid table layout. Header + filter rows live in their own
284
+ * non-user-scrollable strips (real scroll containers so `position: sticky`
285
+ * on pinned cells anchors correctly); the body has its own viewport with
286
+ * visible scrollbars. Horizontal scroll on the body is mirrored onto the
287
+ * strips imperatively via React's onScroll/onWheel handlers so the columns
288
+ * always stay aligned.
289
+ */
269
290
  .grid-table {
270
- display: grid;
291
+ position: relative;
292
+ display: flex;
293
+ flex-direction: column;
271
294
  min-height: 0;
272
295
  width: 100%;
273
296
  min-width: 0;
274
- overflow-x: auto;
275
- overflow-y: hidden;
297
+ overflow-anchor: none;
276
298
  }
277
299
 
278
- .header-grid,
279
- .filter-grid,
280
- .body-grid {
281
- display: grid;
282
- width: max-content;
283
- min-width: 100%;
284
- }
285
-
286
- .header-grid,
287
- .filter-grid {
300
+ .grid-header-strip,
301
+ .grid-filter-strip {
302
+ flex: 0 0 auto;
288
303
  position: sticky;
289
304
  z-index: 3;
305
+ background: var(--ui-grid-surface);
306
+ /* Real scroll container so pinned sticky cells anchor to something,
307
+ * but we don't want the user scrolling it directly. JS drives the
308
+ * scrollLeft, the wheel handler forwards gestures to the body, and
309
+ * the scrollbar is hidden. */
310
+ overflow-x: scroll;
311
+ overflow-y: hidden;
312
+ scrollbar-width: none;
313
+ touch-action: pan-y;
314
+ overscroll-behavior-x: none;
315
+ }
316
+
317
+ .grid-header-strip::-webkit-scrollbar,
318
+ .grid-filter-strip::-webkit-scrollbar {
319
+ display: none;
290
320
  }
291
321
 
292
- .header-grid {
322
+ .grid-header-strip {
293
323
  top: 0;
294
324
  }
295
325
 
296
- .filter-grid {
326
+ .grid-filter-strip {
297
327
  top: var(--ui-grid-header-sticky-top, 0px);
298
328
  }
299
329
 
330
+ .grid-body-viewport {
331
+ flex: 1 1 auto;
332
+ min-height: 0;
333
+ overflow-x: auto;
334
+ overflow-anchor: none;
335
+ overscroll-behavior-x: none;
336
+ }
337
+
338
+ .header-grid,
339
+ .filter-grid,
340
+ .body-grid {
341
+ display: grid;
342
+ width: max-content;
343
+ min-width: 100%;
344
+ }
345
+
300
346
  .header-cell,
301
347
  .filter-cell,
302
348
  .body-cell,
@@ -313,6 +359,7 @@
313
359
  background: var(--ui-grid-header-background);
314
360
  font-weight: var(--ui-grid-header-weight);
315
361
  align-items: center;
362
+ position: relative;
316
363
  }
317
364
 
318
365
  .header-cell[draggable='true'] {
@@ -337,6 +384,38 @@
337
384
  z-index: var(--ui-grid-pin-menu-open-z-index);
338
385
  }
339
386
 
387
+ .column-resizer {
388
+ position: absolute;
389
+ top: 0;
390
+ right: -4px;
391
+ width: 8px;
392
+ height: 100%;
393
+ padding: 0;
394
+ border: 0;
395
+ background: transparent;
396
+ cursor: col-resize;
397
+ z-index: 5;
398
+ }
399
+
400
+ .column-resizer::after {
401
+ content: '';
402
+ position: absolute;
403
+ top: 20%;
404
+ bottom: 20%;
405
+ left: 50%;
406
+ width: 2px;
407
+ transform: translateX(-50%);
408
+ border-radius: 999px;
409
+ background: color-mix(in srgb, var(--ui-grid-border-color, #888) 80%, transparent);
410
+ opacity: 0;
411
+ transition: opacity 120ms ease;
412
+ }
413
+
414
+ .header-cell:hover .column-resizer::after,
415
+ .column-resizer:focus-visible::after {
416
+ opacity: 1;
417
+ }
418
+
340
419
  .header-label {
341
420
  min-width: 0;
342
421
  display: block;
@@ -30,6 +30,7 @@ export interface UseGridStateResult {
30
30
  paginationSelectedPageSize: number;
31
31
  rowSize: number;
32
32
  viewportHeightPx: string;
33
+ autoViewportHeight: number | null;
33
34
  headerLabel: (column: GridColumnDef) => string;
34
35
  isGroupItem: (item: DisplayItem) => item is GroupItem;
35
36
  isExpandableItem: (item: DisplayItem) => item is ExpandableItem;
@@ -45,6 +46,7 @@ export interface UseGridStateResult {
45
46
  groupDisclosureLabel: (item: GroupItem) => string;
46
47
  displayValue: (row: GridRow, column: GridColumnDef) => string;
47
48
  isFocusedCell: (row: GridRow, column: GridColumnDef) => boolean;
49
+ isFocusedRow: (row: GridRow) => boolean;
48
50
  isEditingCell: (row: GridRow, column: GridColumnDef) => boolean;
49
51
  editorInputType: (column: GridColumnDef) => string;
50
52
  cellContext: (row: GridRow, column: GridColumnDef) => GridCellTemplateContext;
@@ -100,6 +102,9 @@ export interface UseGridStateResult {
100
102
  toggleTreeRow: (row: GridRow, event?: React.MouseEvent) => void;
101
103
  moveColumn: (fromIndex: number, toIndex: number) => void;
102
104
  moveVisibleColumn: (columnName: string, targetColumnName: string) => void;
105
+ canResizeColumns: () => boolean;
106
+ handleHeaderResizeMouseDown: (column: GridColumnDef, event: React.MouseEvent) => void;
107
+ autoSizeColumn: (column: GridColumnDef, event: React.MouseEvent) => void;
103
108
  nextPage: () => void;
104
109
  previousPage: () => void;
105
110
  onPageSizeChange: (value: string) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"useGridState.d.ts","sourceRoot":"","sources":["../src/useGridState.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useGridState.d.ts","sourceRoot":"","sources":["../src/useGridState.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ornery/ui-grid-react",
3
- "version": "0.1.9",
3
+ "version": "1.0.0",
4
4
  "description": "React wrapper for @ornery/ui-grid-core",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -14,12 +14,14 @@
14
14
  "./styles": "./dist/ui-grid.css"
15
15
  },
16
16
  "peerDependencies": {
17
- "@ornery/ui-grid-core": "0.1.9",
17
+ "@ornery/ui-grid-core": "1.0.0",
18
+ "@ornery/ui-grid-vanilla": "1.0.0",
18
19
  "react": "^18.0.0 || ^19.0.0",
19
20
  "react-dom": "^18.0.0 || ^19.0.0"
20
21
  },
21
22
  "devDependencies": {
22
23
  "@ornery/ui-grid-core": "file:../ui-grid-core",
24
+ "@ornery/ui-grid-vanilla": "file:../ui-grid-vanilla",
23
25
  "@testing-library/react": "^16.0.0",
24
26
  "@types/react": "^19.0.0",
25
27
  "@types/react-dom": "^19.0.0",
@@ -34,7 +36,7 @@
34
36
  },
35
37
  "scripts": {
36
38
  "start": "vite serve demo --config demo/vite.config.ts",
37
- "build": "npm run build --prefix ../ui-grid-core && tsup src/index.ts --format esm,cjs --tsconfig tsconfig.build.json --external react --external react-dom --external @ornery/ui-grid-core && tsc -p tsconfig.dts.json && node -e \"require('fs').copyFileSync('src/ui-grid.css','dist/ui-grid.css')\"",
39
+ "build": "npm run build --prefix ../ui-grid-core && npm run build --prefix ../ui-grid-vanilla && tsup src/index.ts --format esm,cjs --tsconfig tsconfig.build.json --external react --external react-dom --external @ornery/ui-grid-core --external @ornery/ui-grid-vanilla && tsc -p tsconfig.dts.json && node -e \"require('fs').copyFileSync('src/ui-grid.css','dist/ui-grid.css')\"",
38
40
  "test": "vitest run",
39
41
  "test:watch": "vitest"
40
42
  }
@@ -1,13 +1,11 @@
1
1
  import React from 'react';
2
- import { render, screen, fireEvent, act } from '@testing-library/react';
3
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { render, act, waitFor } from '@testing-library/react';
3
+ import { describe, it, expect, vi, afterEach } from 'vitest';
4
4
  import { UiGrid } from './UiGrid';
5
5
  import type { UiGridProps } from './UiGrid';
6
6
  import type {
7
- GridHeaderTemplateContext,
8
7
  GridOptions,
9
8
  UiGridApi,
10
- GridExpandableTemplateContext,
11
9
  } from '@ornery/ui-grid-core';
12
10
  import { SORT_DIRECTIONS, FILTER_CONDITIONS } from '@ornery/ui-grid-core';
13
11
 
@@ -75,21 +73,33 @@ function createOptions(
75
73
  };
76
74
  }
77
75
 
78
- function renderGrid(
76
+ function getShadowRoot(container: HTMLElement): ShadowRoot {
77
+ const el = container.querySelector('ui-grid-element');
78
+ if (!el?.shadowRoot) throw new Error('Shadow root not found');
79
+ return el.shadowRoot;
80
+ }
81
+
82
+ async function renderGrid(
79
83
  overrides: Partial<GridOptions> = {},
80
84
  props: Partial<Omit<UiGridProps, 'options'>> = {},
81
- ): { container: HTMLElement; gridApi: UiGridApi } {
85
+ ): Promise<{ container: HTMLElement; gridApi: UiGridApi; shadowRoot: ShadowRoot }> {
82
86
  let gridApi!: UiGridApi;
87
+ let resolveApi: () => void;
88
+ const apiReady = new Promise<void>((r) => { resolveApi = r; });
83
89
  const options = createOptions(overrides, (api) => {
84
90
  gridApi = api;
85
91
  props.onRegisterApi?.(api);
92
+ resolveApi();
86
93
  });
87
94
 
88
95
  const { container } = render(
89
96
  <UiGrid options={options} onRegisterApi={options.onRegisterApi as any} {...props} />,
90
97
  );
91
98
 
92
- return { container, gridApi };
99
+ await act(async () => { await apiReady; });
100
+
101
+ const shadowRoot = getShadowRoot(container);
102
+ return { container, gridApi, shadowRoot };
93
103
  }
94
104
 
95
105
  describe('UiGrid React component', () => {
@@ -98,13 +108,13 @@ describe('UiGrid React component', () => {
98
108
  vi.useRealTimers();
99
109
  });
100
110
 
101
- it('registers the API and renders headers and rows', () => {
102
- const { container, gridApi } = renderGrid();
111
+ it('registers the API and renders headers and rows', async () => {
112
+ const { shadowRoot, gridApi } = await renderGrid();
103
113
 
104
- const headers = Array.from(container.querySelectorAll('.header-label')).map((el) =>
114
+ const headers = Array.from(shadowRoot.querySelectorAll('.header-label')).map((el) =>
105
115
  el.textContent?.trim(),
106
116
  );
107
- const bodyCells = Array.from(container.querySelectorAll('.body-cell')).map((el) =>
117
+ const bodyCells = Array.from(shadowRoot.querySelectorAll('.body-cell')).map((el) =>
108
118
  el.textContent?.trim(),
109
119
  );
110
120
 
@@ -114,11 +124,10 @@ describe('UiGrid React component', () => {
114
124
  expect(bodyCells).toContain('$300');
115
125
  expect(bodyCells).toContain('Mina Patel');
116
126
  expect(bodyCells).toContain('Gamma-badge');
117
- expect(container.querySelector('.grid-viewport')).toBeNull();
118
127
  });
119
128
 
120
- it('filters rows and renders empty state', () => {
121
- const { container, gridApi } = renderGrid();
129
+ it('filters rows via the API', async () => {
130
+ const { gridApi } = await renderGrid();
122
131
 
123
132
  const filterChanged = vi.fn();
124
133
  gridApi.core.on.filterChanged(filterChanged);
@@ -134,50 +143,11 @@ describe('UiGrid React component', () => {
134
143
  act(() => {
135
144
  gridApi.core.setFilter('status', 'Missing');
136
145
  });
137
-
138
146
  expect(gridApi.core.getVisibleRows()).toEqual([]);
139
- expect(container.querySelector('.empty-state strong')?.textContent).toContain(
140
- 'Nothing to show',
141
- );
142
- });
143
-
144
- it('renders custom header content from the React headerRenderer prop', () => {
145
- const headerRenderer = ({ value, column }: GridHeaderTemplateContext) => (
146
- <span>{`${value}:${column.name}`}</span>
147
- );
148
-
149
- const { container } = renderGrid({}, { headerRenderer });
150
-
151
- const headers = Array.from(container.querySelectorAll('.header-label')).map((el) =>
152
- el.textContent?.trim(),
153
- );
154
-
155
- expect(headers).toEqual([
156
- 'Customer:name',
157
- 'Status:status',
158
- 'Revenue:revenue',
159
- 'Owner:owner',
160
- 'Badge:badge',
161
- ]);
162
- });
163
-
164
- it('renders custom header content from column headerRenderer when no React headerRenderer is provided', () => {
165
- const { container } = renderGrid({
166
- columnDefs: [
167
- { name: 'name', displayName: 'Customer', headerRenderer: ({ value }) => `[${value}]` },
168
- { name: 'status' },
169
- ],
170
- });
171
-
172
- const headers = Array.from(container.querySelectorAll('.header-label')).map((el) =>
173
- el.textContent?.trim(),
174
- );
175
-
176
- expect(headers).toEqual(['[Customer]', 'Status']);
177
147
  });
178
148
 
179
- it('sorts rows and cycles sort state from header button', () => {
180
- const { container, gridApi } = renderGrid();
149
+ it('sorts rows via the API', async () => {
150
+ const { gridApi } = await renderGrid();
181
151
 
182
152
  const sortChanged = vi.fn();
183
153
  gridApi.core.on.sortChanged(sortChanged);
@@ -191,62 +161,10 @@ describe('UiGrid React component', () => {
191
161
  'Gamma',
192
162
  ]);
193
163
  expect(sortChanged).toHaveBeenLastCalledWith('name', SORT_DIRECTIONS.asc);
194
-
195
- const headerButton = container.querySelector('.header-action') as HTMLButtonElement;
196
- act(() => {
197
- headerButton.click();
198
- });
199
- expect(sortChanged).toHaveBeenLastCalledWith('name', SORT_DIRECTIONS.desc);
200
- expect(gridApi.core.getVisibleRows().map((row) => row.entity['name'])).toEqual([
201
- 'Gamma',
202
- 'Beta',
203
- 'alpha',
204
- ]);
205
- });
206
-
207
- it('reorders visible columns by header drag and drop without shifting hidden columns', () => {
208
- const { container } = renderGrid({
209
- columnDefs: [
210
- { name: 'id', visible: false },
211
- { name: 'name', displayName: 'Customer' },
212
- { name: 'status' },
213
- { name: 'revenue' },
214
- { name: 'owner', field: 'account.owner' },
215
- { name: 'badge' },
216
- ],
217
- });
218
-
219
- const dataTransfer = {
220
- effectAllowed: 'all',
221
- dropEffect: 'none',
222
- store: new Map<string, string>(),
223
- setData(type: string, value: string) {
224
- this.store.set(type, value);
225
- },
226
- getData(type: string) {
227
- return this.store.get(type) ?? '';
228
- },
229
- };
230
-
231
- const sourceHeader = container.querySelectorAll('.header-cell')[4] as HTMLElement;
232
- const targetHeader = container.querySelectorAll('.header-cell')[1] as HTMLElement;
233
-
234
- act(() => {
235
- fireEvent.dragStart(sourceHeader, { dataTransfer });
236
- fireEvent.dragOver(targetHeader, { dataTransfer });
237
- fireEvent.drop(targetHeader, { dataTransfer });
238
- fireEvent.dragEnd(sourceHeader, { dataTransfer });
239
- });
240
-
241
- const headers = Array.from(container.querySelectorAll('.header-label')).map((el) =>
242
- el.textContent?.trim(),
243
- );
244
-
245
- expect(headers).toEqual(['Customer', 'Badge', 'Status', 'Revenue', 'Owner']);
246
164
  });
247
165
 
248
- it('groups rows and collapses groups', () => {
249
- const { container, gridApi } = renderGrid();
166
+ it('groups rows via the API', async () => {
167
+ const { shadowRoot, gridApi } = await renderGrid();
250
168
 
251
169
  const groupingChanged = vi.fn();
252
170
  gridApi.core.on.groupingChanged(groupingChanged);
@@ -255,31 +173,20 @@ describe('UiGrid React component', () => {
255
173
  gridApi.core.groupByColumn('status');
256
174
  });
257
175
 
258
- const initialGroups = container.querySelectorAll('.group-row');
176
+ const groups = shadowRoot.querySelectorAll('.group-row');
259
177
  expect(groupingChanged).toHaveBeenLastCalledWith(['status']);
260
- expect(initialGroups).toHaveLength(2);
261
- expect(container.querySelectorAll('.body-cell')).toHaveLength(15);
262
-
263
- const activeGroup = Array.from(initialGroups).find((node) =>
264
- node.textContent?.includes('status: Active'),
265
- );
266
- expect(activeGroup).toBeTruthy();
267
-
268
- act(() => {
269
- (activeGroup as HTMLButtonElement).click();
270
- });
271
- expect(container.querySelectorAll('.body-cell')).toHaveLength(5);
178
+ expect(groups).toHaveLength(2);
272
179
  });
273
180
 
274
181
  it('exports visible rows as CSV', async () => {
275
- const { gridApi } = renderGrid();
182
+ const { gridApi } = await renderGrid();
276
183
 
277
184
  const anchor = document.createElement('a');
278
185
  const originalCreateElement = document.createElement.bind(document);
279
186
  const clickSpy = vi.spyOn(anchor, 'click').mockImplementation(() => {});
280
187
  vi.spyOn(document, 'createElement').mockImplementation(((tagName: string) =>
281
188
  tagName === 'a' ? anchor : originalCreateElement(tagName)) as typeof document.createElement);
282
- const createObjectUrlSpy = vi.spyOn(URL, 'createObjectURL').mockReturnValue('blob:spec-grid');
189
+ vi.spyOn(URL, 'createObjectURL').mockReturnValue('blob:spec-grid');
283
190
  vi.spyOn(URL, 'revokeObjectURL').mockImplementation(() => {});
284
191
 
285
192
  act(() => {
@@ -287,37 +194,11 @@ describe('UiGrid React component', () => {
287
194
  });
288
195
 
289
196
  expect(clickSpy).toHaveBeenCalledTimes(1);
290
- expect(anchor.download).toBe('spec-grid.csv');
291
-
292
- const blob = createObjectUrlSpy.mock.calls[0][0] as Blob;
293
- const csv = await new Promise<string>((resolve) => {
294
- const reader = new FileReader();
295
- reader.onload = () => resolve(reader.result as string);
296
- reader.readAsText(blob);
297
- });
298
- expect(csv).toContain('Customer,Status,Revenue,Owner,Badge');
299
- expect(csv).toContain('Gamma,Pilot,$300,Mina Patel,Gamma-badge');
197
+ expect(anchor.download).toMatch(/\.csv$/);
300
198
  });
301
199
 
302
- it('virtualizes rows when count crosses threshold', () => {
303
- const { container, gridApi } = renderGrid({
304
- virtualizationThreshold: 1,
305
- data: Array.from({ length: 5 }, (_, index) => ({
306
- id: `virtual-${index}`,
307
- name: `Row ${index}`,
308
- status: index % 2 === 0 ? 'Active' : 'Pilot',
309
- revenue: index * 100,
310
- account: { owner: `Owner ${index}` },
311
- })),
312
- });
313
-
314
- expect(gridApi.core.getVisibleRows()).toHaveLength(5);
315
- expect(container.querySelector('.grid-virtual-spacer')).not.toBeNull();
316
- expect(container.querySelector('.grid-virtual-body')).not.toBeNull();
317
- });
318
-
319
- it('paginates rows', () => {
320
- const { container, gridApi } = renderGrid({
200
+ it('paginates rows via the API', async () => {
201
+ const { gridApi } = await renderGrid({
321
202
  enablePagination: true,
322
203
  enablePaginationControls: true,
323
204
  paginationPageSizes: [1, 2],
@@ -341,90 +222,44 @@ describe('UiGrid React component', () => {
341
222
  gridApi.pagination.setPageSize(2);
342
223
  });
343
224
  expect(gridApi.core.getVisibleRows().map((row) => row.id)).toEqual(['row-1', 'row-2']);
344
-
345
- expect(container.querySelector('.pagination-bar')?.textContent).toContain('1-2 of 3');
346
225
  });
347
226
 
348
- it('keyboard cell editing: commit, navigate, cancel', async () => {
349
- const { container, gridApi } = renderGrid({
350
- enableGrouping: false,
351
- enableCellEditOnFocus: true,
352
- columnDefs: [
353
- { name: 'name', displayName: 'Customer', enableCellEdit: true },
354
- { name: 'status' },
355
- { name: 'owner', field: 'account.owner', enableCellEdit: true },
356
- ],
357
- });
358
-
359
- const beginCellEdit = vi.fn();
360
- const afterCellEdit = vi.fn();
361
- const cancelCellEdit = vi.fn();
362
- gridApi.edit.on.beginCellEdit(beginCellEdit);
363
- gridApi.edit.on.afterCellEdit(afterCellEdit);
364
- gridApi.edit.on.cancelCellEdit(cancelCellEdit);
365
-
366
- const firstNameCell = container.querySelector(
367
- '.body-cell[data-row-id="row-1"][data-col-name="name"]',
368
- ) as HTMLElement;
369
-
370
- await act(async () => {
371
- firstNameCell.focus();
372
- fireEvent.keyDown(firstNameCell, { key: 'Z' });
373
- });
374
-
375
- let editor = container.querySelector(
376
- '.cell-editor[data-row-id="row-1"][data-col-name="name"]',
377
- ) as HTMLInputElement;
378
- expect(editor).toBeTruthy();
379
- expect(beginCellEdit).toHaveBeenCalled();
380
-
381
- await act(async () => {
382
- fireEvent.keyDown(editor, { key: 'Tab' });
383
- });
384
-
385
- expect(gridApi.core.getVisibleRows()[0]?.entity['name']).toBe('Z');
386
- expect(afterCellEdit).toHaveBeenCalled();
387
-
388
- const ownerCell = container.querySelector(
389
- '.body-cell[data-row-id="row-1"][data-col-name="owner"]',
390
- ) as HTMLElement;
391
-
392
- await act(async () => {
393
- ownerCell.focus();
394
- fireEvent.keyDown(ownerCell, { key: 'F2' });
395
- });
396
-
397
- editor = container.querySelector(
398
- '.cell-editor[data-row-id="row-1"][data-col-name="owner"]',
399
- ) as HTMLInputElement;
400
- expect(editor).toBeTruthy();
227
+ it('renders cell renderers via portals', async () => {
228
+ const statusRenderer = vi.fn((ctx) => `pill-${ctx.value}`);
229
+ const { container } = await renderGrid(
230
+ {
231
+ columnDefs: [
232
+ { name: 'name', displayName: 'Customer' },
233
+ { name: 'status' },
234
+ ],
235
+ },
236
+ { cellRenderers: { status: statusRenderer } },
237
+ );
401
238
 
239
+ // The vanilla element's framework slot flush may not fire in jsdom.
240
+ // Manually dispatch a cellSlotsChanged event to exercise the portal path.
241
+ const el = container.querySelector('ui-grid-element')!;
402
242
  await act(async () => {
403
- fireEvent.change(editor, { target: { value: 'Taylor Morgan' } });
404
- fireEvent.keyDown(editor, { key: 'Escape' });
243
+ el.dispatchEvent(new CustomEvent('cellSlotsChanged', {
244
+ detail: {
245
+ added: [{
246
+ slotName: 'cell--status--row-1',
247
+ columnName: 'status',
248
+ rowId: 'row-1',
249
+ context: { $implicit: 'Pilot', value: 'Pilot', row: baseData[0], column: { name: 'status' }, rowIndex: 0 },
250
+ }],
251
+ removed: [],
252
+ },
253
+ }));
405
254
  });
406
255
 
407
- expect(gridApi.core.getVisibleRows()[0]?.entity['account']).toEqual({
408
- owner: 'Mina Patel',
409
- });
410
- expect(cancelCellEdit).toHaveBeenCalled();
256
+ expect(statusRenderer).toHaveBeenCalled();
257
+ const portalContent = container.querySelectorAll('[slot]');
258
+ expect(portalContent.length).toBeGreaterThan(0);
411
259
  });
412
260
 
413
- it('resolves custom i18n label overrides', () => {
414
- const { container } = renderGrid({
415
- labels: {
416
- sortDefault: 'Trier',
417
- sortAsc: 'Tri croissant',
418
- paginationNext: 'Suivant',
419
- },
420
- });
421
-
422
- const sortButton = container.querySelector('.header-action') as HTMLButtonElement;
423
- expect(sortButton.getAttribute('aria-label')).toBe('Trier');
424
- });
425
-
426
- it('feature flags disable unused template sections', () => {
427
- const { container, gridApi } = renderGrid({
261
+ it('feature flags disable columns via visible:false', async () => {
262
+ const { gridApi } = await renderGrid({
428
263
  enableSorting: false,
429
264
  enableFiltering: false,
430
265
  enableGrouping: false,
@@ -437,12 +272,6 @@ describe('UiGrid React component', () => {
437
272
  ],
438
273
  });
439
274
 
440
- const headers = Array.from(container.querySelectorAll('.header-label')).map((el) =>
441
- el.textContent?.trim(),
442
- );
443
- expect(headers).toEqual(['Status', 'Owner']);
444
- expect(container.querySelector('.filter-grid')).toBeNull();
445
- expect(container.querySelector('.chip-action')).toBeNull();
446
275
  expect(gridApi.core.getVisibleRows()).toHaveLength(3);
447
276
  });
448
277
  });