@ornery/ui-grid-react 1.0.5 → 1.0.6

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.
@@ -1 +1 @@
1
- {"version":3,"file":"UiGrid.d.ts","sourceRoot":"","sources":["../src/UiGrid.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EACV,WAAW,EACX,uBAAuB,EAEvB,SAAS,EACV,MAAM,sBAAsB,CAAC;AAQ9B,MAAM,WAAW,mBAAmB;IAClC,CAAC,UAAU,EAAE,MAAM,GAAG,CAAC,OAAO,EAAE,uBAAuB,KAAK,KAAK,CAAC,SAAS,CAAC;CAC7E;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,WAAW,CAAC;IACrB,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,SAAS,KAAK,IAAI,CAAC;IACzC,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAaD,wBAAgB,MAAM,CAAC,EAAE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,EAAE,WAAW,2CAqLvF"}
1
+ {"version":3,"file":"UiGrid.d.ts","sourceRoot":"","sources":["../src/UiGrid.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAEV,WAAW,EACX,uBAAuB,EAEvB,SAAS,EACV,MAAM,sBAAsB,CAAC;AAQ9B,MAAM,WAAW,mBAAmB;IAClC,CAAC,UAAU,EAAE,MAAM,GAAG,CAAC,OAAO,EAAE,uBAAuB,KAAK,KAAK,CAAC,SAAS,CAAC;CAC7E;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,WAAW,CAAC;IACrB,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,SAAS,KAAK,IAAI,CAAC;IACzC,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAaD,wBAAgB,MAAM,CAAC,EAAE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,EAAE,WAAW,2CAsSvF"}
package/dist/index.js CHANGED
@@ -58,6 +58,33 @@ function UiGrid({ options, onRegisterApi, cellRenderers, className }) {
58
58
  const optionsRef = import_react.default.useRef(options);
59
59
  optionsRef.current = options;
60
60
  const currentSlotColumnsRef = import_react.default.useRef([]);
61
+ const wrapperRenderVersionRef = import_react.default.useRef(0);
62
+ const scheduledWrapperRenderVersionRef = import_react.default.useRef(0);
63
+ const wrapperRenderWaitersRef = import_react.default.useRef([]);
64
+ const benchmarkSubscriptionUnsubscribeRef = import_react.default.useRef(null);
65
+ import_react.default.useLayoutEffect(() => {
66
+ wrapperRenderVersionRef.current += 1;
67
+ const ready = wrapperRenderWaitersRef.current.filter(
68
+ (waiter) => waiter.target <= wrapperRenderVersionRef.current
69
+ );
70
+ if (ready.length === 0) {
71
+ return;
72
+ }
73
+ wrapperRenderWaitersRef.current = wrapperRenderWaitersRef.current.filter(
74
+ (waiter) => waiter.target > wrapperRenderVersionRef.current
75
+ );
76
+ for (const waiter of ready) {
77
+ waiter.resolve();
78
+ }
79
+ });
80
+ import_react.default.useEffect(() => {
81
+ return () => {
82
+ if (benchmarkSubscriptionUnsubscribeRef.current) {
83
+ benchmarkSubscriptionUnsubscribeRef.current();
84
+ benchmarkSubscriptionUnsubscribeRef.current = null;
85
+ }
86
+ };
87
+ }, []);
61
88
  import_react.default.useEffect(() => {
62
89
  const container = containerRef.current;
63
90
  if (!container) return;
@@ -81,6 +108,10 @@ function UiGrid({ options, onRegisterApi, cellRenderers, className }) {
81
108
  void mount();
82
109
  return () => {
83
110
  disposed = true;
111
+ if (benchmarkSubscriptionUnsubscribeRef.current) {
112
+ benchmarkSubscriptionUnsubscribeRef.current();
113
+ benchmarkSubscriptionUnsubscribeRef.current = null;
114
+ }
84
115
  if (el) {
85
116
  el.removeEventListener("cellSlotsChanged", handleCellSlotsChanged);
86
117
  el.remove();
@@ -109,6 +140,7 @@ function UiGrid({ options, onRegisterApi, cellRenderers, className }) {
109
140
  const col = options.columnDefs?.find((c) => c.name === entry.columnName);
110
141
  const value = col?.field ? getNestedValue(row, col.field) : row[entry.columnName];
111
142
  if (entry.context.value !== value || entry.context.row !== row) {
143
+ markWrapperRenderScheduled();
112
144
  nextSlots.set(key, {
113
145
  ...entry,
114
146
  context: { ...entry.context, $implicit: value, value, row }
@@ -118,6 +150,67 @@ function UiGrid({ options, onRegisterApi, cellRenderers, className }) {
118
150
  }
119
151
  if (changed) setSlots(nextSlots);
120
152
  }, [options.data]);
153
+ function markWrapperRenderScheduled() {
154
+ scheduledWrapperRenderVersionRef.current = Math.max(
155
+ scheduledWrapperRenderVersionRef.current,
156
+ wrapperRenderVersionRef.current + 1
157
+ );
158
+ }
159
+ function waitForScheduledWrapperRender() {
160
+ const target = scheduledWrapperRenderVersionRef.current;
161
+ if (target <= wrapperRenderVersionRef.current) {
162
+ return Promise.resolve();
163
+ }
164
+ return new Promise((resolve) => {
165
+ wrapperRenderWaitersRef.current.push({ target, resolve });
166
+ });
167
+ }
168
+ function createWrappedGridApi(api, opts) {
169
+ const benchmarkListeners = /* @__PURE__ */ new Set();
170
+ const now = () => typeof performance === "undefined" ? Date.now() : performance.now();
171
+ if (benchmarkSubscriptionUnsubscribeRef.current) {
172
+ benchmarkSubscriptionUnsubscribeRef.current();
173
+ }
174
+ benchmarkSubscriptionUnsubscribeRef.current = api.core.on.benchmarkComplete((result) => {
175
+ for (const listener of benchmarkListeners) {
176
+ listener(result);
177
+ }
178
+ });
179
+ return {
180
+ ...api,
181
+ core: {
182
+ ...api.core,
183
+ on: {
184
+ ...api.core.on,
185
+ benchmarkComplete: (listener) => {
186
+ benchmarkListeners.add(listener);
187
+ return () => benchmarkListeners.delete(listener);
188
+ }
189
+ },
190
+ benchmark: async (iterations) => {
191
+ const loops = Math.max(1, iterations ?? opts.benchmark?.iterations ?? 25);
192
+ const started = now();
193
+ let lastResult = null;
194
+ for (let index = 0; index < loops; index += 1) {
195
+ lastResult = await api.core.benchmark(1);
196
+ await waitForScheduledWrapperRender();
197
+ }
198
+ const totalMs = now() - started;
199
+ const result = {
200
+ iterations: loops,
201
+ totalMs,
202
+ averageMs: totalMs / loops,
203
+ visibleRows: lastResult?.visibleRows ?? 0,
204
+ renderedItems: lastResult?.renderedItems ?? 0
205
+ };
206
+ for (const listener of benchmarkListeners) {
207
+ listener(result);
208
+ }
209
+ return result;
210
+ }
211
+ }
212
+ };
213
+ }
121
214
  function applyOptions(el, opts) {
122
215
  const renderers2 = cellRenderersRef.current;
123
216
  const cellSlotColumns = [];
@@ -131,8 +224,9 @@ function UiGrid({ options, onRegisterApi, cellRenderers, className }) {
131
224
  const wrappedOptions = {
132
225
  ...opts,
133
226
  onRegisterApi: (api) => {
134
- onRegisterApiRef.current?.(api);
135
- opts.onRegisterApi?.(api);
227
+ const wrappedApi = createWrappedGridApi(api, opts);
228
+ onRegisterApiRef.current?.(wrappedApi);
229
+ opts.onRegisterApi?.(wrappedApi);
136
230
  }
137
231
  };
138
232
  el.options = wrappedOptions;
@@ -147,6 +241,9 @@ function UiGrid({ options, onRegisterApi, cellRenderers, className }) {
147
241
  const detail = event.detail;
148
242
  const el = elementRef.current;
149
243
  if (!el) return;
244
+ if (detail.removed.length > 0 || detail.added.length > 0) {
245
+ markWrapperRenderScheduled();
246
+ }
150
247
  setSlots((prev) => {
151
248
  const next = new Map(prev);
152
249
  for (const slot of detail.removed) {
@@ -185,7 +282,15 @@ function UiGrid({ options, onRegisterApi, cellRenderers, className }) {
185
282
  }
186
283
  }
187
284
  }
188
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: containerRef, className, style: { display: "block", height: "100%", minHeight: 0 }, children: portals });
285
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
286
+ "div",
287
+ {
288
+ ref: containerRef,
289
+ className,
290
+ style: { display: "block", height: "100%", minHeight: 0 },
291
+ children: portals
292
+ }
293
+ );
189
294
  }
190
295
  function getNestedValue(obj, field) {
191
296
  const parts = field.split(".");
package/dist/index.mjs CHANGED
@@ -16,6 +16,33 @@ function UiGrid({ options, onRegisterApi, cellRenderers, className }) {
16
16
  const optionsRef = React.useRef(options);
17
17
  optionsRef.current = options;
18
18
  const currentSlotColumnsRef = React.useRef([]);
19
+ const wrapperRenderVersionRef = React.useRef(0);
20
+ const scheduledWrapperRenderVersionRef = React.useRef(0);
21
+ const wrapperRenderWaitersRef = React.useRef([]);
22
+ const benchmarkSubscriptionUnsubscribeRef = React.useRef(null);
23
+ React.useLayoutEffect(() => {
24
+ wrapperRenderVersionRef.current += 1;
25
+ const ready = wrapperRenderWaitersRef.current.filter(
26
+ (waiter) => waiter.target <= wrapperRenderVersionRef.current
27
+ );
28
+ if (ready.length === 0) {
29
+ return;
30
+ }
31
+ wrapperRenderWaitersRef.current = wrapperRenderWaitersRef.current.filter(
32
+ (waiter) => waiter.target > wrapperRenderVersionRef.current
33
+ );
34
+ for (const waiter of ready) {
35
+ waiter.resolve();
36
+ }
37
+ });
38
+ React.useEffect(() => {
39
+ return () => {
40
+ if (benchmarkSubscriptionUnsubscribeRef.current) {
41
+ benchmarkSubscriptionUnsubscribeRef.current();
42
+ benchmarkSubscriptionUnsubscribeRef.current = null;
43
+ }
44
+ };
45
+ }, []);
19
46
  React.useEffect(() => {
20
47
  const container = containerRef.current;
21
48
  if (!container) return;
@@ -39,6 +66,10 @@ function UiGrid({ options, onRegisterApi, cellRenderers, className }) {
39
66
  void mount();
40
67
  return () => {
41
68
  disposed = true;
69
+ if (benchmarkSubscriptionUnsubscribeRef.current) {
70
+ benchmarkSubscriptionUnsubscribeRef.current();
71
+ benchmarkSubscriptionUnsubscribeRef.current = null;
72
+ }
42
73
  if (el) {
43
74
  el.removeEventListener("cellSlotsChanged", handleCellSlotsChanged);
44
75
  el.remove();
@@ -67,6 +98,7 @@ function UiGrid({ options, onRegisterApi, cellRenderers, className }) {
67
98
  const col = options.columnDefs?.find((c) => c.name === entry.columnName);
68
99
  const value = col?.field ? getNestedValue(row, col.field) : row[entry.columnName];
69
100
  if (entry.context.value !== value || entry.context.row !== row) {
101
+ markWrapperRenderScheduled();
70
102
  nextSlots.set(key, {
71
103
  ...entry,
72
104
  context: { ...entry.context, $implicit: value, value, row }
@@ -76,6 +108,67 @@ function UiGrid({ options, onRegisterApi, cellRenderers, className }) {
76
108
  }
77
109
  if (changed) setSlots(nextSlots);
78
110
  }, [options.data]);
111
+ function markWrapperRenderScheduled() {
112
+ scheduledWrapperRenderVersionRef.current = Math.max(
113
+ scheduledWrapperRenderVersionRef.current,
114
+ wrapperRenderVersionRef.current + 1
115
+ );
116
+ }
117
+ function waitForScheduledWrapperRender() {
118
+ const target = scheduledWrapperRenderVersionRef.current;
119
+ if (target <= wrapperRenderVersionRef.current) {
120
+ return Promise.resolve();
121
+ }
122
+ return new Promise((resolve) => {
123
+ wrapperRenderWaitersRef.current.push({ target, resolve });
124
+ });
125
+ }
126
+ function createWrappedGridApi(api, opts) {
127
+ const benchmarkListeners = /* @__PURE__ */ new Set();
128
+ const now = () => typeof performance === "undefined" ? Date.now() : performance.now();
129
+ if (benchmarkSubscriptionUnsubscribeRef.current) {
130
+ benchmarkSubscriptionUnsubscribeRef.current();
131
+ }
132
+ benchmarkSubscriptionUnsubscribeRef.current = api.core.on.benchmarkComplete((result) => {
133
+ for (const listener of benchmarkListeners) {
134
+ listener(result);
135
+ }
136
+ });
137
+ return {
138
+ ...api,
139
+ core: {
140
+ ...api.core,
141
+ on: {
142
+ ...api.core.on,
143
+ benchmarkComplete: (listener) => {
144
+ benchmarkListeners.add(listener);
145
+ return () => benchmarkListeners.delete(listener);
146
+ }
147
+ },
148
+ benchmark: async (iterations) => {
149
+ const loops = Math.max(1, iterations ?? opts.benchmark?.iterations ?? 25);
150
+ const started = now();
151
+ let lastResult = null;
152
+ for (let index = 0; index < loops; index += 1) {
153
+ lastResult = await api.core.benchmark(1);
154
+ await waitForScheduledWrapperRender();
155
+ }
156
+ const totalMs = now() - started;
157
+ const result = {
158
+ iterations: loops,
159
+ totalMs,
160
+ averageMs: totalMs / loops,
161
+ visibleRows: lastResult?.visibleRows ?? 0,
162
+ renderedItems: lastResult?.renderedItems ?? 0
163
+ };
164
+ for (const listener of benchmarkListeners) {
165
+ listener(result);
166
+ }
167
+ return result;
168
+ }
169
+ }
170
+ };
171
+ }
79
172
  function applyOptions(el, opts) {
80
173
  const renderers2 = cellRenderersRef.current;
81
174
  const cellSlotColumns = [];
@@ -89,8 +182,9 @@ function UiGrid({ options, onRegisterApi, cellRenderers, className }) {
89
182
  const wrappedOptions = {
90
183
  ...opts,
91
184
  onRegisterApi: (api) => {
92
- onRegisterApiRef.current?.(api);
93
- opts.onRegisterApi?.(api);
185
+ const wrappedApi = createWrappedGridApi(api, opts);
186
+ onRegisterApiRef.current?.(wrappedApi);
187
+ opts.onRegisterApi?.(wrappedApi);
94
188
  }
95
189
  };
96
190
  el.options = wrappedOptions;
@@ -105,6 +199,9 @@ function UiGrid({ options, onRegisterApi, cellRenderers, className }) {
105
199
  const detail = event.detail;
106
200
  const el = elementRef.current;
107
201
  if (!el) return;
202
+ if (detail.removed.length > 0 || detail.added.length > 0) {
203
+ markWrapperRenderScheduled();
204
+ }
108
205
  setSlots((prev) => {
109
206
  const next = new Map(prev);
110
207
  for (const slot of detail.removed) {
@@ -143,7 +240,15 @@ function UiGrid({ options, onRegisterApi, cellRenderers, className }) {
143
240
  }
144
241
  }
145
242
  }
146
- return /* @__PURE__ */ jsx("div", { ref: containerRef, className, style: { display: "block", height: "100%", minHeight: 0 }, children: portals });
243
+ return /* @__PURE__ */ jsx(
244
+ "div",
245
+ {
246
+ ref: containerRef,
247
+ className,
248
+ style: { display: "block", height: "100%", minHeight: 0 },
249
+ children: portals
250
+ }
251
+ );
147
252
  }
148
253
  function getNestedValue(obj, field) {
149
254
  const parts = field.split(".");
@@ -1,8 +1,7 @@
1
- import type { PipelineResult } from '@ornery/ui-grid-core';
2
1
  type UiGridWasmModule = {
3
- build_pipeline_js(context: unknown): PipelineResult;
2
+ default(input?: unknown): Promise<unknown>;
4
3
  };
5
- export declare function registerReactUiGridWasmEngineFromModule(module: UiGridWasmModule): void;
4
+ export declare function registerReactUiGridWasmEngineFromModule(_module: UiGridWasmModule): void;
6
5
  export declare function enableReactUiGridWasmEngine(): Promise<void>;
7
6
  export {};
8
7
  //# sourceMappingURL=rustWasmGridEngine.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rustWasmGridEngine.d.ts","sourceRoot":"","sources":["../src/rustWasmGridEngine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA4B,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAMrF,KAAK,gBAAgB,GAAG;IACtB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,cAAc,CAAC;CACrD,CAAC;AAEF,wBAAgB,uCAAuC,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAMtF;AAED,wBAAsB,2BAA2B,IAAI,OAAO,CAAC,IAAI,CAAC,CAIjE"}
1
+ {"version":3,"file":"rustWasmGridEngine.d.ts","sourceRoot":"","sources":["../src/rustWasmGridEngine.ts"],"names":[],"mappings":"AAGA,KAAK,gBAAgB,GAAG;IACtB,OAAO,CAAC,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC5C,CAAC;AAEF,wBAAgB,uCAAuC,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,CACvF;AAED,wBAAsB,2BAA2B,IAAI,OAAO,CAAC,IAAI,CAAC,CAGjE"}
@@ -108,7 +108,7 @@ export interface UseGridStateResult {
108
108
  nextPage: () => void;
109
109
  previousPage: () => void;
110
110
  onPageSizeChange: (value: string) => void;
111
- runBenchmark: (iterations?: number) => GridBenchmarkResult;
111
+ runBenchmark: (iterations?: number) => Promise<GridBenchmarkResult>;
112
112
  exportCsv: () => void;
113
113
  onViewportScroll: (startIndex: number) => void;
114
114
  }
@@ -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":"AASA,OAAO,EAEL,SAAS,EAET,WAAW,EACX,aAAa,EACb,OAAO,EAEP,mBAAmB,EACnB,gBAAgB,EAChB,UAAU,EACV,SAAS,EA+GV,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EACV,WAAW,EACX,SAAS,EACT,cAAc,EACd,OAAO,EACP,cAAc,EACd,uBAAuB,EAEvB,uBAAuB,EACvB,6BAA6B,EAG9B,MAAM,sBAAsB,CAAC;AA+C9B,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,cAAc,CAAC;IACzB,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,MAAM,EAAE,UAAU,CAAC;IACnB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,OAAO,EAAE,SAAS,CAAC;IACnB,gBAAgB,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAGzD,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,SAAS,EAAE,SAAS,CAAC;IACrB,WAAW,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACrC,WAAW,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACrC,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAC5C,mBAAmB,EAAE,uBAAuB,CAAC;IAG7C,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,qBAAqB,EAAE,OAAO,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,0BAA0B,EAAE,MAAM,CAAC;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,MAAM,CAAC;IACzB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAGlC,WAAW,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,MAAM,CAAC;IAC/C,WAAW,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,IAAI,SAAS,CAAC;IACtD,gBAAgB,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,IAAI,cAAc,CAAC;IAChE,SAAS,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,IAAI,OAAO,CAAC;IAClD,eAAe,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC;IAChD,eAAe,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,MAAM,CAAC;IACnD,YAAY,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,MAAM,CAAC;IAChD,aAAa,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,MAAM,CAAC;IACjD,mBAAmB,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,MAAM,CAAC;IACvD,WAAW,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAC;IAC5C,iBAAiB,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,MAAM,CAAC;IACrD,qBAAqB,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC;IAC1D,oBAAoB,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,MAAM,CAAC;IAClD,YAAY,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,KAAK,MAAM,CAAC;IAC9D,aAAa,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC;IAChE,YAAY,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC;IACxC,aAAa,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC;IAChE,eAAe,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,MAAM,CAAC;IACnD,WAAW,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,KAAK,uBAAuB,CAAC;IAC9E,eAAe,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,6BAA6B,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC3F,WAAW,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,MAAM,CAAC;IAC/C,gBAAgB,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC;IACrD,kBAAkB,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC;IACvD,UAAU,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,KAAK,MAAM,CAAC;IAC5D,eAAe,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,CAAC;IAC1C,iBAAiB,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC;IAC7C,iBAAiB,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,CAAC;IAC5C,SAAS,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC;IAC9C,cAAc,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC;IACjE,gBAAgB,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC;IACnE,sBAAsB,EAAE,MAAM,OAAO,CAAC;IACtC,iBAAiB,EAAE,MAAM,MAAM,CAAC;IAChC,eAAe,EAAE,MAAM,MAAM,EAAE,CAAC;IAChC,cAAc,EAAE,CACd,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,aAAa,EACrB,YAAY,CAAC,EAAE,KAAK,GAAG,aAAa,GAAG,IAAI,KACxC,OAAO,CAAC;IACb,iBAAiB,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC;IAGtD,QAAQ,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC;IAC7C,YAAY,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK;QAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC3F,gBAAgB,EAAE,MAAM,OAAO,CAAC;IAChC,gBAAgB,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC;IACrD,SAAS,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;IAC3C,cAAc,EAAE,OAAO,CAAC;IAGxB,cAAc,EAAE,OAAO,CAAC;IACxB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,eAAe,EAAE,OAAO,CAAC;IACzB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,eAAe,EAAE,OAAO,CAAC;IACzB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,eAAe,EAAE,OAAO,CAAC;IACzB,qBAAqB,EAAE,OAAO,CAAC;IAC/B,mBAAmB,EAAE,OAAO,CAAC;IAC7B,gBAAgB,EAAE,OAAO,CAAC;IAG1B,iBAAiB,EAAE,MAAM,OAAO,CAAC;IACjC,kBAAkB,EAAE,MAAM,OAAO,CAAC;IAGlC,UAAU,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;IAC5C,YAAY,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1D,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,cAAc,EAAE,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,CAAC,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IAC1E,WAAW,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IACvC,SAAS,EAAE,CACT,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,aAAa,EACrB,YAAY,CAAC,EAAE,KAAK,GAAG,aAAa,GAAG,IAAI,KACxC,IAAI,CAAC;IACV,iBAAiB,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,KAAK,CAAC,aAAa,KAAK,IAAI,CAAC;IAC7F,qBAAqB,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IAC9F,kBAAkB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,mBAAmB,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,aAAa,KAAK,IAAI,CAAC;IAC1D,gBAAgB,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IACpD,kBAAkB,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IACrE,aAAa,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IAChE,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACzD,iBAAiB,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1E,gBAAgB,EAAE,MAAM,OAAO,CAAC;IAChC,2BAA2B,EAAE,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IACtF,cAAc,EAAE,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IACzE,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,YAAY,EAAE,CAAC,UAAU,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACpE,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,gBAAgB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;CAChD;AAED,wBAAgB,YAAY,CAC1B,OAAO,EAAE,WAAW,EACpB,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,SAAS,KAAK,IAAI,GACvC,kBAAkB,CA4iDpB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ornery/ui-grid-react",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "React wrapper for @ornery/ui-grid-core",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -14,8 +14,8 @@
14
14
  "./styles": "./dist/ui-grid.css"
15
15
  },
16
16
  "peerDependencies": {
17
- "@ornery/ui-grid-core": "1.0.5",
18
- "@ornery/ui-grid-vanilla": "1.0.5",
17
+ "@ornery/ui-grid-core": "1.0.6",
18
+ "@ornery/ui-grid-vanilla": "1.0.6",
19
19
  "react": "^18.0.0 || ^19.0.0",
20
20
  "react-dom": "^18.0.0 || ^19.0.0"
21
21
  },
@@ -36,8 +36,9 @@
36
36
  },
37
37
  "scripts": {
38
38
  "start": "vite serve demo --config demo/vite.config.ts",
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')\"",
39
+ "build": "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')\"",
40
40
  "test": "vitest run",
41
+ "test:coverage": "vitest run --coverage",
41
42
  "test:watch": "vitest"
42
43
  }
43
44
  }
@@ -274,4 +274,53 @@ describe('UiGrid React component', () => {
274
274
 
275
275
  expect(gridApi.core.getVisibleRows()).toHaveLength(3);
276
276
  });
277
+
278
+ it('row selection + expandable rows: selection toggle only repaints the affected row', async () => {
279
+ // Regression test for the selection+expand performance issue. The patch
280
+ // path's per-row fingerprint cache (in the vanilla layer) skips cells
281
+ // whose visual state didn't change. The React wrapper inherits this fix
282
+ // automatically because it mounts the same vanilla element. This test
283
+ // pins the end-to-end behaviour: clicking the row-selection checkbox
284
+ // for one row must only flip ui-grid-row-selected on that row's cells —
285
+ // not invalidate any visible state on the unselected rows.
286
+ const expandableTemplate = { createEmbeddedView: () => undefined };
287
+ const { gridApi, shadowRoot } = await renderGrid({
288
+ enableRowSelection: true,
289
+ enableExpandable: true,
290
+ expandableRowTemplate:
291
+ expandableTemplate as unknown as GridOptions['expandableRowTemplate'],
292
+ });
293
+
294
+ const cellFor = (rowId: string, column: string): HTMLElement => {
295
+ const el = shadowRoot.querySelector<HTMLElement>(
296
+ `.body-cell[data-row="${rowId}"][data-column="${column}"]`,
297
+ );
298
+ if (!el) throw new Error(`Cell ${rowId}/${column} not rendered`);
299
+ return el;
300
+ };
301
+
302
+ expect(cellFor('row-1', 'name').classList.contains('ui-grid-row-selected')).toBe(false);
303
+ expect(cellFor('row-2', 'name').classList.contains('ui-grid-row-selected')).toBe(false);
304
+ expect(cellFor('row-3', 'name').classList.contains('ui-grid-row-selected')).toBe(false);
305
+
306
+ await act(async () => {
307
+ gridApi.selection.selectRow(baseData[1] as Record<string, unknown>);
308
+ });
309
+
310
+ // row-2 must be selected, the others untouched.
311
+ expect(cellFor('row-1', 'name').classList.contains('ui-grid-row-selected')).toBe(false);
312
+ expect(cellFor('row-2', 'name').classList.contains('ui-grid-row-selected')).toBe(true);
313
+ expect(cellFor('row-2', 'status').classList.contains('ui-grid-row-selected')).toBe(true);
314
+ expect(cellFor('row-3', 'name').classList.contains('ui-grid-row-selected')).toBe(false);
315
+
316
+ // Toggling expand on row-1 must insert exactly one expandable detail
317
+ // row and leave selection on row-2 intact.
318
+ await act(async () => {
319
+ gridApi.expandable.toggleRowExpansion(baseData[0] as Record<string, unknown>);
320
+ });
321
+ await waitFor(() => {
322
+ expect(shadowRoot.querySelectorAll('.expandable-row').length).toBe(1);
323
+ });
324
+ expect(cellFor('row-2', 'name').classList.contains('ui-grid-row-selected')).toBe(true);
325
+ });
277
326
  });
package/src/UiGrid.tsx CHANGED
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import { createPortal } from 'react-dom';
3
3
  import type {
4
+ GridBenchmarkResult,
4
5
  GridOptions,
5
6
  GridCellTemplateContext,
6
7
  GridRecord,
@@ -46,6 +47,36 @@ export function UiGrid({ options, onRegisterApi, cellRenderers, className }: UiG
46
47
  const optionsRef = React.useRef(options);
47
48
  optionsRef.current = options;
48
49
  const currentSlotColumnsRef = React.useRef<string[]>([]);
50
+ const wrapperRenderVersionRef = React.useRef(0);
51
+ const scheduledWrapperRenderVersionRef = React.useRef(0);
52
+ const wrapperRenderWaitersRef = React.useRef<Array<{ target: number; resolve: () => void }>>([]);
53
+ const benchmarkSubscriptionUnsubscribeRef = React.useRef<(() => void) | null>(null);
54
+
55
+ React.useLayoutEffect(() => {
56
+ wrapperRenderVersionRef.current += 1;
57
+ const ready = wrapperRenderWaitersRef.current.filter(
58
+ (waiter) => waiter.target <= wrapperRenderVersionRef.current,
59
+ );
60
+ if (ready.length === 0) {
61
+ return;
62
+ }
63
+ wrapperRenderWaitersRef.current = wrapperRenderWaitersRef.current.filter(
64
+ (waiter) => waiter.target > wrapperRenderVersionRef.current,
65
+ );
66
+ for (const waiter of ready) {
67
+ waiter.resolve();
68
+ }
69
+ });
70
+
71
+ // Clean up underlying benchmark subscription if it's still active
72
+ React.useEffect(() => {
73
+ return () => {
74
+ if (benchmarkSubscriptionUnsubscribeRef.current) {
75
+ benchmarkSubscriptionUnsubscribeRef.current();
76
+ benchmarkSubscriptionUnsubscribeRef.current = null;
77
+ }
78
+ };
79
+ }, []);
49
80
 
50
81
  // Mount the vanilla element once
51
82
  React.useEffect(() => {
@@ -77,6 +108,10 @@ export function UiGrid({ options, onRegisterApi, cellRenderers, className }: UiG
77
108
 
78
109
  return () => {
79
110
  disposed = true;
111
+ if (benchmarkSubscriptionUnsubscribeRef.current) {
112
+ benchmarkSubscriptionUnsubscribeRef.current();
113
+ benchmarkSubscriptionUnsubscribeRef.current = null;
114
+ }
80
115
  if (el) {
81
116
  el.removeEventListener('cellSlotsChanged', handleCellSlotsChanged);
82
117
  el.remove();
@@ -115,6 +150,7 @@ export function UiGrid({ options, onRegisterApi, cellRenderers, className }: UiG
115
150
  const value = col?.field ? getNestedValue(row, col.field) : row[entry.columnName];
116
151
 
117
152
  if (entry.context.value !== value || entry.context.row !== row) {
153
+ markWrapperRenderScheduled();
118
154
  nextSlots.set(key, {
119
155
  ...entry,
120
156
  context: { ...entry.context, $implicit: value, value, row },
@@ -127,6 +163,77 @@ export function UiGrid({ options, onRegisterApi, cellRenderers, className }: UiG
127
163
  // eslint-disable-next-line react-hooks/exhaustive-deps
128
164
  }, [options.data]);
129
165
 
166
+ function markWrapperRenderScheduled() {
167
+ scheduledWrapperRenderVersionRef.current = Math.max(
168
+ scheduledWrapperRenderVersionRef.current,
169
+ wrapperRenderVersionRef.current + 1,
170
+ );
171
+ }
172
+
173
+ function waitForScheduledWrapperRender(): Promise<void> {
174
+ const target = scheduledWrapperRenderVersionRef.current;
175
+ if (target <= wrapperRenderVersionRef.current) {
176
+ return Promise.resolve();
177
+ }
178
+ return new Promise<void>((resolve) => {
179
+ wrapperRenderWaitersRef.current.push({ target, resolve });
180
+ });
181
+ }
182
+
183
+ function createWrappedGridApi(api: UiGridApi, opts: GridOptions): UiGridApi {
184
+ const benchmarkListeners = new Set<(result: GridBenchmarkResult) => void>();
185
+ const now = () => (typeof performance === 'undefined' ? Date.now() : performance.now());
186
+ // Clean up previous underlying subscription if it exists
187
+ if (benchmarkSubscriptionUnsubscribeRef.current) {
188
+ benchmarkSubscriptionUnsubscribeRef.current();
189
+ }
190
+ // Subscribe to underlying benchmarkComplete and forward to wrapper listeners
191
+ benchmarkSubscriptionUnsubscribeRef.current = api.core.on.benchmarkComplete((result) => {
192
+ for (const listener of benchmarkListeners) {
193
+ listener(result);
194
+ }
195
+ });
196
+
197
+ return {
198
+ ...api,
199
+ core: {
200
+ ...api.core,
201
+ on: {
202
+ ...api.core.on,
203
+ benchmarkComplete: (listener) => {
204
+ benchmarkListeners.add(listener);
205
+ return () => benchmarkListeners.delete(listener);
206
+ },
207
+ },
208
+ benchmark: async (iterations?: number) => {
209
+ const loops = Math.max(1, iterations ?? opts.benchmark?.iterations ?? 25);
210
+ const started = now();
211
+ let lastResult: GridBenchmarkResult | null = null;
212
+
213
+ for (let index = 0; index < loops; index += 1) {
214
+ lastResult = await api.core.benchmark(1);
215
+ await waitForScheduledWrapperRender();
216
+ }
217
+
218
+ const totalMs = now() - started;
219
+
220
+ const result: GridBenchmarkResult = {
221
+ iterations: loops,
222
+ totalMs,
223
+ averageMs: totalMs / loops,
224
+ visibleRows: lastResult?.visibleRows ?? 0,
225
+ renderedItems: lastResult?.renderedItems ?? 0,
226
+ };
227
+
228
+ for (const listener of benchmarkListeners) {
229
+ listener(result);
230
+ }
231
+ return result;
232
+ },
233
+ },
234
+ };
235
+ }
236
+
130
237
  function applyOptions(el: UiGridStandaloneElement, opts: GridOptions) {
131
238
  const renderers = cellRenderersRef.current;
132
239
  const cellSlotColumns: string[] = [];
@@ -142,8 +249,9 @@ export function UiGrid({ options, onRegisterApi, cellRenderers, className }: UiG
142
249
  const wrappedOptions: GridOptions = {
143
250
  ...opts,
144
251
  onRegisterApi: (api) => {
145
- onRegisterApiRef.current?.(api as UiGridApi);
146
- opts.onRegisterApi?.(api);
252
+ const wrappedApi = createWrappedGridApi(api as UiGridApi, opts);
253
+ onRegisterApiRef.current?.(wrappedApi);
254
+ opts.onRegisterApi?.(wrappedApi);
147
255
  },
148
256
  };
149
257
 
@@ -151,8 +259,7 @@ export function UiGrid({ options, onRegisterApi, cellRenderers, className }: UiG
151
259
 
152
260
  const prev = currentSlotColumnsRef.current;
153
261
  const columnsChanged =
154
- cellSlotColumns.length !== prev.length ||
155
- cellSlotColumns.some((name, i) => name !== prev[i]);
262
+ cellSlotColumns.length !== prev.length || cellSlotColumns.some((name, i) => name !== prev[i]);
156
263
 
157
264
  if (columnsChanged) {
158
265
  currentSlotColumnsRef.current = cellSlotColumns;
@@ -164,6 +271,9 @@ export function UiGrid({ options, onRegisterApi, cellRenderers, className }: UiG
164
271
  const detail = (event as CustomEvent<FrameworkSlotDelta<FrameworkCellSlot>>).detail;
165
272
  const el = elementRef.current;
166
273
  if (!el) return;
274
+ if (detail.removed.length > 0 || detail.added.length > 0) {
275
+ markWrapperRenderScheduled();
276
+ }
167
277
 
168
278
  setSlots((prev) => {
169
279
  const next = new Map(prev);
@@ -212,7 +322,11 @@ export function UiGrid({ options, onRegisterApi, cellRenderers, className }: UiG
212
322
  }
213
323
 
214
324
  return (
215
- <div ref={containerRef} className={className} style={{ display: 'block', height: '100%', minHeight: 0 }}>
325
+ <div
326
+ ref={containerRef}
327
+ className={className}
328
+ style={{ display: 'block', height: '100%', minHeight: 0 }}
329
+ >
216
330
  {portals}
217
331
  </div>
218
332
  );
@@ -1,12 +1,8 @@
1
1
  import { beforeEach, describe, expect, it } from 'vitest';
2
- import {
3
- activeGridEngineBackend,
4
- clearRustWasmGridEngine,
5
- defaultGridEngine,
6
- } from '@ornery/ui-grid-core';
2
+ import { activeGridEngineBackend, clearRustWasmGridEngine, defaultGridEngine } from '@ornery/ui-grid-core';
7
3
  import { registerReactUiGridWasmEngineFromModule } from './rustWasmGridEngine';
8
4
  import { SORT_DIRECTIONS } from '@ornery/ui-grid-core';
9
- import type { BuildGridPipelineContext, PipelineResult } from '@ornery/ui-grid-core';
5
+ import type { BuildGridPipelineContext } from '@ornery/ui-grid-core';
10
6
 
11
7
  function createContext(): BuildGridPipelineContext {
12
8
  return {
@@ -37,20 +33,15 @@ describe('rustWasmGridEngine', () => {
37
33
  clearRustWasmGridEngine();
38
34
  });
39
35
 
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
-
36
+ it('does not install a wasm pipeline engine through the React helper', () => {
49
37
  registerReactUiGridWasmEngineFromModule({
50
- build_pipeline_js: () => sentinel,
38
+ default: async () => undefined,
51
39
  });
52
40
 
53
- expect(defaultGridEngine.buildPipeline(createContext())).toBe(sentinel);
54
- expect(activeGridEngineBackend()).toBe('rust-wasm');
41
+ expect(defaultGridEngine.buildPipeline(createContext()).visibleRows.map((row) => row.id)).toEqual([
42
+ 'react-engine-spec-0',
43
+ 'react-engine-spec-1',
44
+ ]);
45
+ expect(activeGridEngineBackend()).toBe('typescript');
55
46
  });
56
47
  });
@@ -1,23 +1,14 @@
1
- import type { BuildGridPipelineContext, PipelineResult } from '@ornery/ui-grid-core';
2
- import { registerRustWasmGridEngine } from '@ornery/ui-grid-core';
3
-
4
1
  const uiGridWasmModulePath = '../../../dist/ui-grid-wasm-web/ui_grid_wasm.js';
5
2
  const uiGridWasmBinaryPath = '/dist/ui-grid-wasm-web/ui_grid_wasm_bg.wasm';
6
3
 
7
4
  type UiGridWasmModule = {
8
- build_pipeline_js(context: unknown): PipelineResult;
5
+ default(input?: unknown): Promise<unknown>;
9
6
  };
10
7
 
11
- export function registerReactUiGridWasmEngineFromModule(module: UiGridWasmModule): void {
12
- registerRustWasmGridEngine({
13
- buildPipeline(context: BuildGridPipelineContext): PipelineResult {
14
- return module.build_pipeline_js(context);
15
- },
16
- });
8
+ export function registerReactUiGridWasmEngineFromModule(_module: UiGridWasmModule): void {
17
9
  }
18
10
 
19
11
  export async function enableReactUiGridWasmEngine(): Promise<void> {
20
12
  const module = await import(/* @vite-ignore */ uiGridWasmModulePath);
21
13
  await module.default(uiGridWasmBinaryPath);
22
- registerReactUiGridWasmEngineFromModule(module);
23
14
  }
@@ -316,7 +316,7 @@ export interface UseGridStateResult {
316
316
  nextPage: () => void;
317
317
  previousPage: () => void;
318
318
  onPageSizeChange: (value: string) => void;
319
- runBenchmark: (iterations?: number) => GridBenchmarkResult;
319
+ runBenchmark: (iterations?: number) => Promise<GridBenchmarkResult>;
320
320
  exportCsv: () => void;
321
321
  onViewportScroll: (startIndex: number) => void;
322
322
  }
@@ -1070,11 +1070,19 @@ export function useGridState(
1070
1070
  [focusRenderedCell, isCellEditable, shouldEditOnFocusFn, startCellEditFn],
1071
1071
  );
1072
1072
 
1073
- const runBenchmarkFn = useCallback((iterations?: number): GridBenchmarkResult => {
1073
+ const runBenchmarkFn = useCallback(async (iterations?: number): Promise<GridBenchmarkResult> => {
1074
1074
  const safeIterations = resolveBenchmarkIterations(
1075
1075
  iterations,
1076
1076
  optionsRef.current.benchmark?.iterations,
1077
1077
  );
1078
+ const nextFrame = () =>
1079
+ new Promise<void>((resolve) => {
1080
+ if (typeof requestAnimationFrame === 'undefined') {
1081
+ setTimeout(resolve, 0);
1082
+ return;
1083
+ }
1084
+ requestAnimationFrame(() => resolve());
1085
+ });
1078
1086
  const startedAt = performance.now();
1079
1087
  let lastResult = defaultGridEngine.buildPipeline({
1080
1088
  options: optionsRef.current,
@@ -1118,6 +1126,7 @@ export function useGridState(
1118
1126
  };
1119
1127
 
1120
1128
  setBenchmarkResult(result);
1129
+ await nextFrame();
1121
1130
  raiseGridBenchmarkComplete(gridApiRef.current!, result);
1122
1131
  return result;
1123
1132
  }, []);
package/tsconfig.json CHANGED
@@ -11,5 +11,5 @@
11
11
  "@ornery/ui-grid-vanilla": ["../ui-grid-vanilla/src/index.ts"]
12
12
  }
13
13
  },
14
- "include": ["src"]
14
+ "include": ["src", "../ui-grid-vanilla/src/scss.d.ts"]
15
15
  }
package/vitest.config.ts CHANGED
@@ -9,8 +9,15 @@ export default defineConfig({
9
9
  },
10
10
  },
11
11
  test: {
12
- environment: 'jsdom',
12
+ environment: 'happy-dom',
13
13
  globals: true,
14
14
  setupFiles: ['./src/test-setup.ts'],
15
+ coverage: {
16
+ provider: 'v8',
17
+ include: ['src/**/*.ts', 'src/**/*.tsx'],
18
+ exclude: ['src/**/*.test.ts', 'src/**/*.test.tsx', 'src/**/*.spec.ts', 'src/test-setup.ts'],
19
+ reporter: ['text', 'lcov', 'html'],
20
+ reportsDirectory: './coverage',
21
+ },
15
22
  },
16
23
  });