slickgrid-react 5.12.1 → 5.13.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.
- package/README.md +48 -7
- package/dist/cjs/components/slickgrid-react.js +18 -10
- package/dist/cjs/components/slickgrid-react.js.map +1 -1
- package/dist/cjs/extensions/slickRowDetailView.js +122 -95
- package/dist/cjs/extensions/slickRowDetailView.js.map +1 -1
- package/dist/cjs/global-grid-options.js +0 -1
- package/dist/cjs/global-grid-options.js.map +1 -1
- package/dist/cjs/services/reactUtils.js +9 -3
- package/dist/cjs/services/reactUtils.js.map +1 -1
- package/dist/esm/components/slickgrid-react.js +18 -10
- package/dist/esm/components/slickgrid-react.js.map +1 -1
- package/dist/esm/extensions/slickRowDetailView.js +123 -96
- package/dist/esm/extensions/slickRowDetailView.js.map +1 -1
- package/dist/esm/global-grid-options.js +0 -1
- package/dist/esm/global-grid-options.js.map +1 -1
- package/dist/esm/services/reactUtils.js +8 -3
- package/dist/esm/services/reactUtils.js.map +1 -1
- package/dist/types/components/slickgrid-react.d.ts.map +1 -1
- package/dist/types/components/slickgridReactProps.d.ts +8 -0
- package/dist/types/components/slickgridReactProps.d.ts.map +1 -1
- package/dist/types/extensions/slickRowDetailView.d.ts +3 -0
- package/dist/types/extensions/slickRowDetailView.d.ts.map +1 -1
- package/dist/types/global-grid-options.d.ts.map +1 -1
- package/dist/types/services/reactUtils.d.ts +4 -0
- package/dist/types/services/reactUtils.d.ts.map +1 -1
- package/package.json +39 -42
- package/src/slickgrid-react/components/slickgrid-react.tsx +19 -12
- package/src/slickgrid-react/components/slickgridReactProps.ts +2 -0
- package/src/slickgrid-react/extensions/slickRowDetailView.ts +129 -99
- package/src/slickgrid-react/global-grid-options.ts +0 -1
- package/src/slickgrid-react/services/reactUtils.ts +11 -3
|
@@ -14,7 +14,7 @@ import { SlickRowDetailView as UniversalSlickRowDetailView } from '@slickgrid-un
|
|
|
14
14
|
import type { Root } from 'react-dom/client';
|
|
15
15
|
|
|
16
16
|
import type { GridOption, RowDetailView, ViewModelBindableInputData } from '../models/index';
|
|
17
|
-
import {
|
|
17
|
+
import { createReactComponentDynamically } from '../services/reactUtils';
|
|
18
18
|
|
|
19
19
|
const ROW_DETAIL_CONTAINER_PREFIX = 'container_';
|
|
20
20
|
const PRELOAD_CONTAINER_PREFIX = 'container_loading';
|
|
@@ -23,12 +23,14 @@ export interface CreatedView {
|
|
|
23
23
|
id: string | number;
|
|
24
24
|
dataContext: any;
|
|
25
25
|
root: Root | null;
|
|
26
|
+
rendered?: boolean;
|
|
26
27
|
}
|
|
27
28
|
// interface SRDV extends React.Component<Props, State>, UniversalSlickRowDetailView {}s
|
|
28
29
|
|
|
29
30
|
export class SlickRowDetailView extends UniversalSlickRowDetailView {
|
|
30
31
|
protected _component?: any;
|
|
31
32
|
protected _preloadComponent?: any;
|
|
33
|
+
protected _preloadRoot?: Root;
|
|
32
34
|
protected _views: CreatedView[] = [];
|
|
33
35
|
protected _subscriptions: EventSubscription[] = [];
|
|
34
36
|
protected _userProcessFn?: (item: any) => Promise<any>;
|
|
@@ -73,10 +75,12 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
|
|
|
73
75
|
|
|
74
76
|
/** Dispose of all the opened Row Detail Panels Components */
|
|
75
77
|
disposeAllViewComponents() {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
do {
|
|
79
|
+
const view = this._views.pop();
|
|
80
|
+
if (view) {
|
|
81
|
+
this.disposeView(view);
|
|
82
|
+
}
|
|
83
|
+
} while (this._views.length > 0);
|
|
80
84
|
}
|
|
81
85
|
|
|
82
86
|
/** Get the instance of the SlickGrid addon (control or plugin). */
|
|
@@ -114,9 +118,7 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
|
|
|
114
118
|
}
|
|
115
119
|
if (!this.gridOptions.rowDetailView.postTemplate) {
|
|
116
120
|
this._component = this.gridOptions?.rowDetailView?.viewComponent;
|
|
117
|
-
this.addonOptions.postTemplate = (itemDetail: any) => {
|
|
118
|
-
return this._grid.sanitizeHtmlString(`<div class="${ROW_DETAIL_CONTAINER_PREFIX}${itemDetail[this.datasetIdPropName]}"></div>`) as string
|
|
119
|
-
};
|
|
121
|
+
this.addonOptions.postTemplate = (itemDetail: any) => this._grid.sanitizeHtmlString(`<div class="${ROW_DETAIL_CONTAINER_PREFIX}${itemDetail[this.datasetIdPropName]}"></div>`) as string;
|
|
120
122
|
}
|
|
121
123
|
|
|
122
124
|
if (this._grid && this.gridOptions) {
|
|
@@ -132,68 +134,65 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
|
|
|
132
134
|
this.rowDetailViewOptions.onExtensionRegistered(this);
|
|
133
135
|
}
|
|
134
136
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
this.
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
}
|
|
137
|
+
this._eventHandler.subscribe(this.onAsyncResponse, (event, args) => {
|
|
138
|
+
if (typeof this.rowDetailViewOptions?.onAsyncResponse === 'function') {
|
|
139
|
+
this.rowDetailViewOptions.onAsyncResponse(event, args);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
this._eventHandler.subscribe(this.onAsyncEndUpdate, async (event, args) => {
|
|
144
|
+
// dispose preload if exists
|
|
145
|
+
this._preloadRoot?.unmount();
|
|
146
|
+
|
|
147
|
+
// triggers after backend called "onAsyncResponse.notify()"
|
|
148
|
+
await this.renderViewModel(args?.item);
|
|
149
|
+
|
|
150
|
+
if (typeof this.rowDetailViewOptions?.onAsyncEndUpdate === 'function') {
|
|
151
|
+
this.rowDetailViewOptions.onAsyncEndUpdate(event, args);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
this._eventHandler.subscribe(this.onAfterRowDetailToggle, async (event, args) => {
|
|
156
|
+
// display preload template & re-render all the other Detail Views after toggling
|
|
157
|
+
// the preload View will eventually go away once the data gets loaded after the "onAsyncEndUpdate" event
|
|
158
|
+
await this.renderPreloadView(args.item);
|
|
159
|
+
|
|
160
|
+
if (typeof this.rowDetailViewOptions?.onAfterRowDetailToggle === 'function') {
|
|
161
|
+
this.rowDetailViewOptions.onAfterRowDetailToggle(event, args);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
this._eventHandler.subscribe(this.onBeforeRowDetailToggle, (event, args) => {
|
|
166
|
+
// before toggling row detail, we need to create View Component if it doesn't exist
|
|
167
|
+
this.handleOnBeforeRowDetailToggle(event, args);
|
|
168
|
+
|
|
169
|
+
if (typeof this.rowDetailViewOptions?.onBeforeRowDetailToggle === 'function') {
|
|
170
|
+
return this.rowDetailViewOptions.onBeforeRowDetailToggle(event, args);
|
|
171
|
+
}
|
|
172
|
+
return true;
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
this._eventHandler.subscribe(this.onRowBackToViewportRange, async (event, args) => {
|
|
176
|
+
// when row is back to viewport range, we will re-render the View Component(s)
|
|
177
|
+
await this.handleOnRowBackToViewportRange(event, args);
|
|
178
|
+
|
|
179
|
+
if (typeof this.rowDetailViewOptions?.onRowBackToViewportRange === 'function') {
|
|
180
|
+
this.rowDetailViewOptions.onRowBackToViewportRange(event, args);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
this._eventHandler.subscribe(this.onBeforeRowOutOfViewportRange, (event, args) => {
|
|
185
|
+
if (typeof this.rowDetailViewOptions?.onBeforeRowOutOfViewportRange === 'function') {
|
|
186
|
+
this.rowDetailViewOptions.onBeforeRowOutOfViewportRange(event, args);
|
|
187
|
+
}
|
|
188
|
+
this.disposeView(args.item);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
this._eventHandler.subscribe(this.onRowOutOfViewportRange, (event, args) => {
|
|
192
|
+
if (typeof this.rowDetailViewOptions?.onRowOutOfViewportRange === 'function') {
|
|
193
|
+
this.rowDetailViewOptions.onRowOutOfViewportRange(event, args);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
197
196
|
|
|
198
197
|
// --
|
|
199
198
|
// hook some events needed by the Plugin itself
|
|
@@ -223,18 +222,29 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
|
|
|
223
222
|
|
|
224
223
|
/** Redraw (re-render) all the expanded row detail View Components */
|
|
225
224
|
async redrawAllViewComponents() {
|
|
226
|
-
|
|
225
|
+
this.resetRenderedRows();
|
|
226
|
+
const promises: Promise<void>[] = [];
|
|
227
|
+
this._views.forEach((view) => {
|
|
228
|
+
if (!view.rendered) {
|
|
229
|
+
promises.push(this.redrawViewComponent(view))
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
await Promise.all(promises);
|
|
227
233
|
}
|
|
228
234
|
|
|
229
235
|
/** Render all the expanded row detail View Components */
|
|
230
236
|
async renderAllViewModels() {
|
|
231
|
-
|
|
237
|
+
const promises: Promise<void>[] = [];
|
|
238
|
+
Array.from(this._views)
|
|
239
|
+
.filter((x) => x?.dataContext)
|
|
240
|
+
.forEach((x) => promises.push(this.renderViewModel(x.dataContext)));
|
|
241
|
+
await Promise.all(promises);
|
|
232
242
|
}
|
|
233
243
|
|
|
234
244
|
/** Redraw the necessary View Component */
|
|
235
245
|
async redrawViewComponent(view: CreatedView) {
|
|
236
|
-
const
|
|
237
|
-
if (
|
|
246
|
+
const containerElements = this.gridContainerElement.getElementsByClassName(`${ROW_DETAIL_CONTAINER_PREFIX}${view.id}`);
|
|
247
|
+
if (containerElements?.length >= 0) {
|
|
238
248
|
await this.renderViewModel(view.dataContext);
|
|
239
249
|
}
|
|
240
250
|
}
|
|
@@ -243,15 +253,19 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
|
|
|
243
253
|
async renderPreloadView(item: any) {
|
|
244
254
|
const containerElements = this.gridContainerElement.getElementsByClassName(`${PRELOAD_CONTAINER_PREFIX}`);
|
|
245
255
|
if (this._preloadComponent && containerElements?.length) {
|
|
256
|
+
// render row detail
|
|
257
|
+
const bindableData = {
|
|
258
|
+
model: item,
|
|
259
|
+
addon: this,
|
|
260
|
+
grid: this._grid,
|
|
261
|
+
dataView: this.dataView,
|
|
262
|
+
parent: this.rowDetailViewOptions?.parent,
|
|
263
|
+
} as ViewModelBindableInputData;
|
|
246
264
|
const detailContainer = document.createElement('section');
|
|
247
265
|
containerElements[containerElements.length - 1]!.appendChild(detailContainer);
|
|
248
266
|
|
|
249
|
-
const { root } = await
|
|
250
|
-
|
|
251
|
-
this._root = root;
|
|
252
|
-
if (viewObj) {
|
|
253
|
-
viewObj.root = root;
|
|
254
|
-
}
|
|
267
|
+
const { root } = await createReactComponentDynamically(this._preloadComponent, detailContainer as HTMLElement, bindableData);
|
|
268
|
+
this._preloadRoot = root;
|
|
255
269
|
}
|
|
256
270
|
}
|
|
257
271
|
|
|
@@ -259,6 +273,7 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
|
|
|
259
273
|
async renderViewModel(item: any) {
|
|
260
274
|
const containerElements = this.gridContainerElement.getElementsByClassName(`${ROW_DETAIL_CONTAINER_PREFIX}${item[this.datasetIdPropName]}`);
|
|
261
275
|
if (this._component && containerElements?.length) {
|
|
276
|
+
// render row detail
|
|
262
277
|
const bindableData = {
|
|
263
278
|
model: item,
|
|
264
279
|
addon: this,
|
|
@@ -268,11 +283,21 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
|
|
|
268
283
|
} as ViewModelBindableInputData;
|
|
269
284
|
const viewObj = this._views.find(obj => obj.id === item[this.datasetIdPropName]);
|
|
270
285
|
|
|
286
|
+
// remove any previous mounted views, if found then unmount them and delete them from our references array
|
|
287
|
+
// const viewIdx = this._views.findIndex((obj) => obj.id === item[this.datasetIdPropName]);
|
|
288
|
+
// if (this._views[viewIdx]?.root) {
|
|
289
|
+
// this._views[viewIdx].root.unmount();
|
|
290
|
+
// this._views.splice(viewIdx, 1);
|
|
291
|
+
// }
|
|
292
|
+
|
|
271
293
|
// load our Row Detail React Component dynamically, typically we would want to use `root.render()` after the preload component (last argument below)
|
|
272
294
|
// BUT the root render doesn't seem to work and shows a blank element, so we'll use `createRoot()` every time even though it shows a console log in Dev
|
|
273
295
|
// that is the only way I got it working so let's use it anyway and console warnings are removed in production anyway
|
|
274
|
-
const { root } =
|
|
275
|
-
if (
|
|
296
|
+
const { root } = createReactComponentDynamically(this._component, containerElements[containerElements.length - 1] as HTMLElement, bindableData /*, viewObj?.root */);
|
|
297
|
+
if (viewObj) {
|
|
298
|
+
viewObj.root = root;
|
|
299
|
+
viewObj.rendered = true;
|
|
300
|
+
} else {
|
|
276
301
|
this.addViewInfoToViewsRef(item, root);
|
|
277
302
|
}
|
|
278
303
|
}
|
|
@@ -286,21 +311,28 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
|
|
|
286
311
|
const viewInfo: CreatedView = {
|
|
287
312
|
id: item[this.datasetIdPropName],
|
|
288
313
|
dataContext: item,
|
|
289
|
-
root
|
|
314
|
+
root,
|
|
315
|
+
rendered: !!root,
|
|
290
316
|
};
|
|
291
|
-
|
|
292
|
-
|
|
317
|
+
addToArrayWhenNotExists(this._views, viewInfo, this.datasetIdPropName);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
protected disposeView(item: any, removeFromArray = false): void {
|
|
321
|
+
const foundViewIdx = this._views.findIndex((view: CreatedView) => view.id === item[this.datasetIdPropName]);
|
|
322
|
+
if (foundViewIdx >= 0 && this.disposeViewComponent(this._views[foundViewIdx])) {
|
|
323
|
+
if (removeFromArray) {
|
|
324
|
+
this._views.splice(foundViewIdx, 1);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
293
327
|
}
|
|
294
328
|
|
|
295
329
|
protected disposeViewComponent(expandedView: CreatedView): CreatedView | void {
|
|
296
|
-
if (expandedView) {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
return expandedView;
|
|
303
|
-
}
|
|
330
|
+
if (expandedView?.root) {
|
|
331
|
+
const container = this.gridContainerElement.getElementsByClassName(`${ROW_DETAIL_CONTAINER_PREFIX}${expandedView.id}`);
|
|
332
|
+
if (container?.length) {
|
|
333
|
+
expandedView.root.unmount();
|
|
334
|
+
container[0].textContent = '';
|
|
335
|
+
return expandedView;
|
|
304
336
|
}
|
|
305
337
|
}
|
|
306
338
|
}
|
|
@@ -318,10 +350,7 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
|
|
|
318
350
|
this.addViewInfoToViewsRef(args.item, null);
|
|
319
351
|
} else {
|
|
320
352
|
// collapsing, so dispose of the View
|
|
321
|
-
|
|
322
|
-
if (foundViewIdx >= 0 && this.disposeViewComponent(this._views[foundViewIdx])) {
|
|
323
|
-
this._views.splice(foundViewIdx, 1);
|
|
324
|
-
}
|
|
353
|
+
this.disposeView(args.item, true);
|
|
325
354
|
}
|
|
326
355
|
}
|
|
327
356
|
|
|
@@ -334,8 +363,9 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
|
|
|
334
363
|
rowIdsOutOfViewport: (string | number)[];
|
|
335
364
|
grid: SlickGrid;
|
|
336
365
|
}) {
|
|
337
|
-
|
|
338
|
-
|
|
366
|
+
const viewModel = Array.from(this._views).find((x) => x.id === args.rowId);
|
|
367
|
+
if (viewModel) {
|
|
368
|
+
this.redrawViewComponent(viewModel);
|
|
339
369
|
}
|
|
340
370
|
}
|
|
341
371
|
|
|
@@ -2,14 +2,22 @@ import React from 'react';
|
|
|
2
2
|
import { createRef } from 'react';
|
|
3
3
|
import { createRoot, type Root } from 'react-dom/client';
|
|
4
4
|
|
|
5
|
+
// these 2 functions are the same except that 1 is synch and the promise is delayed by a CPU cycle before resolving
|
|
6
|
+
|
|
7
|
+
export function createReactComponentDynamically<T = any>(customComponent: any, targetElm: HTMLElement, props?: any, root?: Root | null): { component: T, root: Root } {
|
|
8
|
+
const compRef = createRef();
|
|
9
|
+
root ??= createRoot(targetElm);
|
|
10
|
+
root.render(React.createElement(customComponent, { ...props, ref: compRef }));
|
|
11
|
+
|
|
12
|
+
return { component: compRef.current as T, root: root as Root };
|
|
13
|
+
}
|
|
14
|
+
|
|
5
15
|
export function loadReactComponentDynamically<T = any>(customComponent: any, targetElm: HTMLElement, props?: any, root?: Root | null): Promise<{ component: T, root: Root }> {
|
|
6
16
|
return new Promise(resolve => {
|
|
7
17
|
const compRef = createRef();
|
|
8
18
|
root ??= createRoot(targetElm);
|
|
9
19
|
root.render(React.createElement(customComponent, { ...props, ref: compRef }));
|
|
10
20
|
|
|
11
|
-
queueMicrotask(() => {
|
|
12
|
-
resolve({ component: compRef.current as T, root: root as Root });
|
|
13
|
-
});
|
|
21
|
+
queueMicrotask(() => resolve({ component: compRef.current as T, root: root as Root }));
|
|
14
22
|
});
|
|
15
23
|
}
|