@ornery/ui-grid-vanilla 0.1.7-hotfix

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.
@@ -0,0 +1,245 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ import {
4
+ SORT_DIRECTIONS,
5
+ activeGridEngineBackend,
6
+ clearRustWasmGridEngine,
7
+ } from '@ornery/ui-grid-core';
8
+ import {
9
+ mountVanillaUiGrid,
10
+ registerVanillaUiGridRustModule,
11
+ type GridOptions,
12
+ type UiGridIconOverrides,
13
+ type UiGridRustWebModule,
14
+ type UiGridApi,
15
+ } from './index';
16
+ import { createVanillaGridController } from './grid-controller';
17
+
18
+ async function waitFor<T>(resolve: () => T | null | undefined, timeoutMs = 10000): Promise<T> {
19
+ const startedAt = Date.now();
20
+
21
+ while (Date.now() - startedAt < timeoutMs) {
22
+ const value = resolve();
23
+ if (value) {
24
+ return value;
25
+ }
26
+
27
+ await new Promise((resolveNext) => window.setTimeout(resolveNext, 20));
28
+ }
29
+
30
+ throw new Error('Timed out waiting for expected element state');
31
+ }
32
+
33
+ describe('mountVanillaUiGrid integration', () => {
34
+ beforeEach(() => {
35
+ document.body.innerHTML = '<div id="app"></div>';
36
+ });
37
+
38
+ afterEach(() => {
39
+ clearRustWasmGridEngine();
40
+ document.body.innerHTML = '';
41
+ });
42
+
43
+ it('mounts the standalone element and handles header sort interaction', async () => {
44
+ let gridApi: UiGridApi | undefined;
45
+ const target = document.getElementById('app');
46
+ if (!target) {
47
+ throw new Error('Expected test root element');
48
+ }
49
+
50
+ const options: GridOptions = {
51
+ id: 'vanilla-rust-keyboard-grid',
52
+ enableGrouping: true,
53
+ enableSorting: true,
54
+ enableFiltering: true,
55
+ virtualizationThreshold: 99,
56
+ data: [
57
+ {
58
+ id: 'row-1',
59
+ name: 'Gamma',
60
+ status: 'Pilot',
61
+ account: { owner: 'Mina Patel' },
62
+ },
63
+ {
64
+ id: 'row-2',
65
+ name: 'Beta',
66
+ status: 'Active',
67
+ account: { owner: 'Jordan Silva' },
68
+ },
69
+ ],
70
+ columnDefs: [{ name: 'name', displayName: 'Customer' }, { name: 'status' }],
71
+ onRegisterApi: (api) => {
72
+ gridApi = api as UiGridApi;
73
+ },
74
+ };
75
+
76
+ const grid = await mountVanillaUiGrid(
77
+ target,
78
+ options,
79
+ undefined,
80
+ 'ui-grid-element-vanilla-test',
81
+ );
82
+
83
+ await waitFor(() => gridApi);
84
+ if (!gridApi) {
85
+ throw new Error('Expected grid API registration');
86
+ }
87
+ const shadowRoot = await waitFor(() => grid.shadowRoot);
88
+ const sortButton = await waitFor(
89
+ () =>
90
+ shadowRoot.querySelector('.header-action[data-column="name"]') as HTMLButtonElement | null,
91
+ );
92
+
93
+ sortButton.click();
94
+ expect(gridApi.core.getVisibleRows().map((row) => row.entity['name'])).toEqual([
95
+ 'Beta',
96
+ 'Gamma',
97
+ ]);
98
+
99
+ const sortButtonAfterFirstClick = await waitFor(
100
+ () =>
101
+ shadowRoot.querySelector('.header-action[data-column="name"]') as HTMLButtonElement | null,
102
+ );
103
+ sortButtonAfterFirstClick.click();
104
+ expect(gridApi.core.getVisibleRows().map((row) => row.entity['name'])).toEqual([
105
+ 'Gamma',
106
+ 'Beta',
107
+ ]);
108
+ }, 15000);
109
+
110
+ it('registers Rust/WASM bindings through the vanilla API', async () => {
111
+ const module: UiGridRustWebModule = {
112
+ default: vi.fn(async () => undefined),
113
+ build_pipeline_js: vi.fn((context) => ({
114
+ visibleRows: context.options.data,
115
+ displayItems: [],
116
+ virtualizationEnabled: false,
117
+ pipelineMs: 0,
118
+ totalItems: context.options.data.length,
119
+ })),
120
+ };
121
+
122
+ await registerVanillaUiGridRustModule(module);
123
+ expect(module.default).toHaveBeenCalledTimes(1);
124
+ expect(activeGridEngineBackend()).toBe('rust-wasm');
125
+ });
126
+
127
+ it('controller API exposes subscribe and action methods', () => {
128
+ const controller = createVanillaGridController({
129
+ id: 'controller-smoke',
130
+ data: [
131
+ { id: 'r1', name: 'Gamma' },
132
+ { id: 'r2', name: 'Alpha' },
133
+ ],
134
+ columnDefs: [{ name: 'name' }],
135
+ enableSorting: true,
136
+ enableFiltering: true,
137
+ });
138
+
139
+ const changes: number[] = [];
140
+ const unsubscribe = controller.subscribe((snapshot) => {
141
+ changes.push(snapshot.pipeline.visibleRows.length);
142
+ });
143
+
144
+ controller.setFilter('name', 'Alpha');
145
+ controller.sortColumn('name', SORT_DIRECTIONS.asc);
146
+ unsubscribe();
147
+
148
+ expect(changes.length).toBeGreaterThan(1);
149
+ expect(controller.getSnapshot().pipeline.visibleRows.map((row) => row.entity['name'])).toEqual([
150
+ 'Alpha',
151
+ ]);
152
+ });
153
+
154
+ it('applies overridable SVG icons for controls', async () => {
155
+ const target = document.getElementById('app');
156
+ if (!target) {
157
+ throw new Error('Expected test root element');
158
+ }
159
+
160
+ const options: GridOptions = {
161
+ id: 'vanilla-icon-grid',
162
+ enableSorting: true,
163
+ data: [{ id: 'row-1', name: 'Gamma' }],
164
+ columnDefs: [{ name: 'name', displayName: 'Customer' }],
165
+ };
166
+
167
+ const grid = await mountVanillaUiGrid(
168
+ target,
169
+ options,
170
+ undefined,
171
+ 'ui-grid-element-vanilla-test',
172
+ );
173
+ const overrides: UiGridIconOverrides = {
174
+ sortNone: { path: 'M3 3h18v18H3z' },
175
+ };
176
+
177
+ (grid as unknown as { controlIcons: UiGridIconOverrides }).controlIcons = overrides;
178
+
179
+ const shadowRoot = await waitFor(() => grid.shadowRoot);
180
+ const iconPath = shadowRoot.querySelector('.header-action .control-icon path');
181
+
182
+ expect(iconPath?.getAttribute('d')).toBe('M3 3h18v18H3z');
183
+ });
184
+
185
+ it('renders native template slots for cells and expandable rows', async () => {
186
+ const target = document.getElementById('app');
187
+ if (!target) {
188
+ throw new Error('Expected test root element');
189
+ }
190
+
191
+ const options: GridOptions = {
192
+ id: 'vanilla-slot-grid',
193
+ enableSorting: true,
194
+ enableExpandable: true,
195
+ data: [
196
+ {
197
+ id: 'row-1',
198
+ name: 'Gamma',
199
+ status: 'Active',
200
+ account: { owner: 'Mina Patel' },
201
+ },
202
+ ],
203
+ columnDefs: [
204
+ { name: 'name', displayName: 'Customer' },
205
+ { name: 'status', displayName: 'Status' },
206
+ ],
207
+ };
208
+
209
+ const grid = await mountVanillaUiGrid(
210
+ target,
211
+ options,
212
+ undefined,
213
+ 'ui-grid-element-vanilla-test',
214
+ );
215
+
216
+ const statusTemplate = document.createElement('template');
217
+ statusTemplate.slot = 'cell-status';
218
+ statusTemplate.innerHTML =
219
+ '<span class="status-pill status-pill-{{valueLower}}">{{value}}</span>';
220
+
221
+ const expandableTemplate = document.createElement('template');
222
+ expandableTemplate.slot = 'expandable-row';
223
+ expandableTemplate.innerHTML = '<div class="detail-card">Owner {{row.account.owner}}</div>';
224
+
225
+ grid.append(statusTemplate, expandableTemplate);
226
+
227
+ const statusPill = await waitFor(
228
+ () => grid.shadowRoot?.querySelector('.status-pill-active') as HTMLElement | null,
229
+ );
230
+
231
+ expect(statusPill.textContent).toContain('Active');
232
+
233
+ const expandButton = await waitFor(
234
+ () => grid.shadowRoot?.querySelector('.row-toggle-expand') as HTMLButtonElement | null,
235
+ );
236
+
237
+ expandButton.click();
238
+
239
+ const detailCard = await waitFor(
240
+ () => grid.shadowRoot?.querySelector('.detail-card') as HTMLElement | null,
241
+ );
242
+
243
+ expect(detailCard.textContent).toContain('Owner Mina Patel');
244
+ });
245
+ });
@@ -0,0 +1,52 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ vi.mock('@ornery/ui-grid-core', async (importOriginal) => {
4
+ const actual = await importOriginal<typeof import('@ornery/ui-grid-core')>();
5
+ return {
6
+ ...actual,
7
+ registerRustWasmGridEngine: vi.fn(),
8
+ };
9
+ });
10
+
11
+ import { registerRustWasmGridEngine } from '@ornery/ui-grid-core';
12
+ import { defineUiGridElement, mountVanillaUiGrid } from './index';
13
+
14
+ describe('mountVanillaUiGrid', () => {
15
+ beforeEach(() => {
16
+ document.body.innerHTML = '<div id="app"></div>';
17
+ vi.clearAllMocks();
18
+ });
19
+
20
+ it('defines and mounts the standalone element with the provided options', async () => {
21
+ const target = document.getElementById('app');
22
+ if (!target) {
23
+ throw new Error('Expected test root element');
24
+ }
25
+
26
+ const options = {
27
+ id: 'vanilla-demo-grid',
28
+ data: [{ name: 'Alice' }],
29
+ columnDefs: [{ name: 'name' }],
30
+ };
31
+
32
+ const module = {
33
+ default: vi.fn(async () => undefined),
34
+ build_pipeline_js: vi.fn(),
35
+ };
36
+
37
+ const grid = await mountVanillaUiGrid(target, options, module);
38
+
39
+ expect(module.default).toHaveBeenCalledTimes(1);
40
+ expect(registerRustWasmGridEngine).toHaveBeenCalledTimes(1);
41
+ expect(customElements.get('ui-grid-element')).toBeDefined();
42
+ expect(grid.tagName.toLowerCase()).toBe('ui-grid-element');
43
+ expect(grid.options).toBe(options);
44
+ expect(target.firstElementChild).toBe(grid);
45
+ });
46
+
47
+ it('exposes defineUiGridElement compatibility alias', async () => {
48
+ await defineUiGridElement('ui-grid-element');
49
+ await defineUiGridElement('ui-grid-element');
50
+ expect(customElements.get('ui-grid-element')).toBeDefined();
51
+ });
52
+ });
package/src/index.ts ADDED
@@ -0,0 +1,60 @@
1
+ import { registerRustWasmGridEngine } from '@ornery/ui-grid-core';
2
+ import type { BuildGridPipelineContext, GridOptions, PipelineResult } from '@ornery/ui-grid-core';
3
+
4
+ import {
5
+ defineStandaloneUiGridElement,
6
+ type VanillaUiGridElement,
7
+ } from './ui-grid-standalone.element';
8
+ export {
9
+ createVanillaGridController,
10
+ type GridControllerSnapshot,
11
+ type GridSaveState,
12
+ type VanillaGridController,
13
+ } from './grid-controller';
14
+ export {
15
+ defineStandaloneUiGridElement,
16
+ UiGridStandaloneElement,
17
+ type VanillaUiGridElement,
18
+ type UiGridControlIconKey,
19
+ type UiGridIconDefinition,
20
+ type UiGridIconOverrides,
21
+ } from './ui-grid-standalone.element';
22
+
23
+ export type { GridOptions, UiGridApi } from '@ornery/ui-grid-core';
24
+
25
+ export interface UiGridRustWebModule {
26
+ default(input?: unknown): Promise<unknown>;
27
+ build_pipeline_js(context: BuildGridPipelineContext): PipelineResult;
28
+ }
29
+
30
+ export { defineStandaloneUiGridElement as defineUiGridElement };
31
+
32
+ export async function registerVanillaUiGridRustModule(
33
+ module: UiGridRustWebModule,
34
+ input?: unknown,
35
+ ): Promise<void> {
36
+ await module.default(input);
37
+ registerRustWasmGridEngine({
38
+ buildPipeline(context: BuildGridPipelineContext): PipelineResult {
39
+ return module.build_pipeline_js(context);
40
+ },
41
+ });
42
+ }
43
+
44
+ export async function mountVanillaUiGrid(
45
+ target: Element,
46
+ options: GridOptions,
47
+ rustModule?: UiGridRustWebModule,
48
+ tagName = 'ui-grid-element',
49
+ ): Promise<VanillaUiGridElement> {
50
+ if (rustModule) {
51
+ await registerVanillaUiGridRustModule(rustModule);
52
+ }
53
+
54
+ await defineStandaloneUiGridElement(tagName);
55
+
56
+ const grid = document.createElement(tagName) as VanillaUiGridElement;
57
+ grid.options = options;
58
+ target.replaceChildren(grid);
59
+ return grid;
60
+ }