@handsontable/angular-wrapper 0.0.0-next-7c3a081-20260608 → 0.0.0-next-c78aa8c-20260608
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/fesm2022/handsontable-angular-wrapper.mjs +494 -270
- package/fesm2022/handsontable-angular-wrapper.mjs.map +1 -1
- package/lib/editor/base-editor-adapter.d.ts +6 -0
- package/lib/editor/hot-cell-editor-advanced.component.d.ts +14 -46
- package/lib/editor/hot-cell-editor-base.directive.d.ts +25 -0
- package/lib/editor/hot-cell-editor.component.d.ts +16 -55
- package/lib/editor/models/factory-editor-properties.d.ts +9 -1
- package/lib/hot-table.component.d.ts +4 -2
- package/lib/renderer/hot-cell-renderer-advanced.component.d.ts +5 -22
- package/lib/renderer/hot-cell-renderer-base.directive.d.ts +23 -0
- package/lib/renderer/hot-cell-renderer.component.d.ts +6 -22
- package/lib/renderer/hot-dynamic-renderer-component.service.d.ts +89 -0
- package/lib/services/hot-settings-resolver.service.d.ts +29 -1
- package/package.json +1 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { ViewContainerRef, ViewChild, Input, ChangeDetectionStrategy, Component, createComponent, EventEmitter, Output, HostBinding,
|
|
2
|
+
import { ViewContainerRef, ViewChild, Input, ChangeDetectionStrategy, Component, createComponent, Directive, EventEmitter, Output, HostBinding, Injectable, InjectionToken, Inject, inject, DestroyRef, ViewEncapsulation, NgModule } from '@angular/core';
|
|
3
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
3
4
|
import Handsontable from 'handsontable/base';
|
|
4
|
-
import { take } from 'rxjs/operators';
|
|
5
|
+
import { take, skip } from 'rxjs/operators';
|
|
5
6
|
import { editorFactory } from 'handsontable/editors/factory';
|
|
6
7
|
import { baseRenderer, rendererFactory, registerRenderer } from 'handsontable/renderers';
|
|
7
8
|
import { BehaviorSubject } from 'rxjs';
|
|
@@ -135,14 +136,7 @@ class BaseEditorAdapter extends Handsontable.editors.BaseEditor {
|
|
|
135
136
|
this._isPlaceholderReady = true;
|
|
136
137
|
}
|
|
137
138
|
this._componentRef = columnMeta._editorComponentReference;
|
|
138
|
-
|
|
139
|
-
this._finishEditSubscription.unsubscribe();
|
|
140
|
-
this._finishEditSubscription = undefined;
|
|
141
|
-
}
|
|
142
|
-
if (this._cancelEditSubscription) {
|
|
143
|
-
this._cancelEditSubscription.unsubscribe();
|
|
144
|
-
this._cancelEditSubscription = undefined;
|
|
145
|
-
}
|
|
139
|
+
this.cleanupSubscriptions();
|
|
146
140
|
this._finishEditSubscription = this._componentRef.instance.finishEdit
|
|
147
141
|
.pipe(take(1))
|
|
148
142
|
.subscribe(() => {
|
|
@@ -161,23 +155,23 @@ class BaseEditorAdapter extends Handsontable.editors.BaseEditor {
|
|
|
161
155
|
close() {
|
|
162
156
|
if (this.isOpened()) {
|
|
163
157
|
this.resetEditorState();
|
|
164
|
-
this._editorPlaceHolderRef
|
|
165
|
-
this._editorPlaceHolderRef
|
|
166
|
-
this._componentRef
|
|
158
|
+
this._editorPlaceHolderRef?.changeDetectorRef.detectChanges();
|
|
159
|
+
this._editorPlaceHolderRef?.instance.detachEditor();
|
|
160
|
+
this._componentRef?.instance.onClose();
|
|
167
161
|
}
|
|
168
162
|
}
|
|
169
163
|
/**
|
|
170
164
|
* Focuses the editor. This event is triggered by Handsontable.
|
|
171
165
|
*/
|
|
172
166
|
focus() {
|
|
173
|
-
this._componentRef
|
|
167
|
+
this._componentRef?.instance.onFocus();
|
|
174
168
|
}
|
|
175
169
|
/**
|
|
176
170
|
* Gets the value from the editor.
|
|
177
171
|
* @returns The value from the editor.
|
|
178
172
|
*/
|
|
179
173
|
getValue() {
|
|
180
|
-
return this._componentRef
|
|
174
|
+
return this._componentRef?.instance?.getValue();
|
|
181
175
|
}
|
|
182
176
|
/**
|
|
183
177
|
* Opens the editor. This event is triggered by Handsontable.
|
|
@@ -189,20 +183,23 @@ class BaseEditorAdapter extends Handsontable.editors.BaseEditor {
|
|
|
189
183
|
open(event) {
|
|
190
184
|
this.hot.getShortcutManager().setActiveContextName('editor');
|
|
191
185
|
this.applyPropsToEditor();
|
|
192
|
-
this._componentRef
|
|
186
|
+
this._componentRef?.instance.onOpen(event);
|
|
193
187
|
}
|
|
194
188
|
/**
|
|
195
189
|
* Sets the value for the custom editor.
|
|
196
190
|
* @param newValue The value to set.
|
|
197
191
|
*/
|
|
198
192
|
setValue(newValue) {
|
|
199
|
-
this._componentRef
|
|
200
|
-
this._componentRef
|
|
193
|
+
this._componentRef?.instance?.setValue(newValue);
|
|
194
|
+
this._componentRef?.changeDetectorRef.detectChanges();
|
|
201
195
|
}
|
|
202
196
|
/**
|
|
203
197
|
* Applies properties to the custom editor and editor placeholder.
|
|
204
198
|
*/
|
|
205
199
|
applyPropsToEditor() {
|
|
200
|
+
if (!this._componentRef || !this._editorPlaceHolderRef) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
206
203
|
const rect = this.getEditedCellRect();
|
|
207
204
|
if (!this.isInFullEditMode()) {
|
|
208
205
|
this._componentRef.instance.setValue(null);
|
|
@@ -252,14 +249,33 @@ class BaseEditorAdapter extends Handsontable.editors.BaseEditor {
|
|
|
252
249
|
* Handles the after destroy event.
|
|
253
250
|
*/
|
|
254
251
|
onAfterDestroy() {
|
|
252
|
+
this.cleanupSubscriptions();
|
|
255
253
|
this._editorPlaceHolderRef?.destroy();
|
|
256
254
|
}
|
|
255
|
+
/**
|
|
256
|
+
* Unsubscribes the finish/cancel edit subscriptions if they are still active.
|
|
257
|
+
* Without this, destroying the table while an editor was prepared but never
|
|
258
|
+
* finished/cancelled leaves the `take(1)` subscriptions hanging (they never emit).
|
|
259
|
+
*/
|
|
260
|
+
cleanupSubscriptions() {
|
|
261
|
+
if (this._finishEditSubscription) {
|
|
262
|
+
this._finishEditSubscription.unsubscribe();
|
|
263
|
+
this._finishEditSubscription = undefined;
|
|
264
|
+
}
|
|
265
|
+
if (this._cancelEditSubscription) {
|
|
266
|
+
this._cancelEditSubscription.unsubscribe();
|
|
267
|
+
this._cancelEditSubscription = undefined;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
257
270
|
/**
|
|
258
271
|
* Resets the editor placeholder state.
|
|
259
272
|
* We need to reset the editor placeholder state because we use it
|
|
260
273
|
* to store multiple references to the custom editor.
|
|
261
274
|
*/
|
|
262
275
|
resetEditorState() {
|
|
276
|
+
if (!this._editorPlaceHolderRef) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
263
279
|
this._editorPlaceHolderRef.setInput('top', undefined);
|
|
264
280
|
this._editorPlaceHolderRef.setInput('left', undefined);
|
|
265
281
|
this._editorPlaceHolderRef.setInput('height', undefined);
|
|
@@ -270,44 +286,28 @@ class BaseEditorAdapter extends Handsontable.editors.BaseEditor {
|
|
|
270
286
|
}
|
|
271
287
|
|
|
272
288
|
/**
|
|
273
|
-
*
|
|
289
|
+
* Shared base directive for HotCellRendererComponent and HotCellRendererAdvancedComponent.
|
|
290
|
+
* Holds all @Input() properties and getProps() that both renderer variants share.
|
|
274
291
|
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
* @template TValue - The type of the component renderer.
|
|
292
|
+
* @template TValue - The type of the rendered cell value.
|
|
278
293
|
* @template TProps - The type of additional renderer properties.
|
|
279
294
|
*/
|
|
280
|
-
class
|
|
281
|
-
static RENDERER_MARKER = Symbol('HotCellRendererComponent');
|
|
295
|
+
class HotCellRendererBase {
|
|
282
296
|
value = '';
|
|
283
297
|
instance;
|
|
284
298
|
td;
|
|
285
299
|
row;
|
|
286
300
|
col;
|
|
287
301
|
prop;
|
|
288
|
-
/**
|
|
289
|
-
* The cell properties provided by Handsontable, extended with optional renderer-specific properties.
|
|
290
|
-
*/
|
|
291
302
|
cellProperties;
|
|
292
|
-
/**
|
|
293
|
-
* Retrieves the renderer-specific properties from the cell properties.
|
|
294
|
-
*
|
|
295
|
-
* @returns The additional properties for the renderer.
|
|
296
|
-
*/
|
|
297
303
|
getProps() {
|
|
298
304
|
return this.cellProperties?.rendererProps ?? {};
|
|
299
305
|
}
|
|
300
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.24", ngImport: i0, type:
|
|
301
|
-
static
|
|
306
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.24", ngImport: i0, type: HotCellRendererBase, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
307
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.24", type: HotCellRendererBase, isStandalone: true, inputs: { value: "value", instance: "instance", td: "td", row: "row", col: "col", prop: "prop", cellProperties: "cellProperties" }, ngImport: i0 });
|
|
302
308
|
}
|
|
303
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.24", ngImport: i0, type:
|
|
304
|
-
type:
|
|
305
|
-
args: [{
|
|
306
|
-
selector: 'hot-cell-renderer',
|
|
307
|
-
template: `<!-- This is an abstract component. Extend this component and provide your own template. -->`,
|
|
308
|
-
standalone: true,
|
|
309
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
310
|
-
}]
|
|
309
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.24", ngImport: i0, type: HotCellRendererBase, decorators: [{
|
|
310
|
+
type: Directive
|
|
311
311
|
}], propDecorators: { value: [{
|
|
312
312
|
type: Input
|
|
313
313
|
}], instance: [{
|
|
@@ -325,77 +325,59 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.24", ngImpo
|
|
|
325
325
|
}] } });
|
|
326
326
|
|
|
327
327
|
/**
|
|
328
|
-
* Abstract
|
|
328
|
+
* Abstract base component for creating custom cell renderer components for Handsontable.
|
|
329
|
+
*
|
|
330
|
+
* Extend this component and provide your own template to implement a custom renderer.
|
|
331
|
+
* Value type is limited to primitives (`string | number | boolean`).
|
|
332
|
+
* For object and array values use {@link HotCellRendererAdvancedComponent}.
|
|
333
|
+
*
|
|
334
|
+
* @template TValue - The type of the component renderer.
|
|
335
|
+
* @template TProps - The type of additional renderer properties.
|
|
329
336
|
*/
|
|
330
|
-
class
|
|
331
|
-
static
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
337
|
+
class HotCellRendererComponent extends HotCellRendererBase {
|
|
338
|
+
static RENDERER_MARKER = Symbol('HotCellRendererComponent');
|
|
339
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.24", ngImport: i0, type: HotCellRendererComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
340
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.24", type: HotCellRendererComponent, isStandalone: true, selector: "hot-cell-renderer", usesInheritance: true, ngImport: i0, template: `<!-- This is an abstract component. Extend this component and provide your own template. -->`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
341
|
+
}
|
|
342
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.24", ngImport: i0, type: HotCellRendererComponent, decorators: [{
|
|
343
|
+
type: Component,
|
|
344
|
+
args: [{
|
|
345
|
+
selector: 'hot-cell-renderer',
|
|
346
|
+
template: `<!-- This is an abstract component. Extend this component and provide your own template. -->`,
|
|
347
|
+
standalone: true,
|
|
348
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
349
|
+
}]
|
|
350
|
+
}] });
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Shared base directive for HotCellEditorComponent and HotCellEditorAdvancedComponent.
|
|
354
|
+
* Holds all @Input(), @Output() and @HostBinding() declarations that both editor variants share.
|
|
355
|
+
*
|
|
356
|
+
* @template T - The type of the edited cell value.
|
|
357
|
+
*/
|
|
358
|
+
class HotCellEditorBase {
|
|
339
359
|
heightFitParentContainer = 100;
|
|
340
|
-
/** The width of the editor as a percentage of the parent container. */
|
|
341
360
|
widthFitParentContainer = 100;
|
|
342
|
-
/** The row index of the cell being edited. */
|
|
343
361
|
row;
|
|
344
|
-
/** The column index of the cell being edited. */
|
|
345
362
|
column;
|
|
346
|
-
/** The property name of the cell being edited. */
|
|
347
363
|
prop;
|
|
348
|
-
/** The original value of the cell being edited. */
|
|
349
364
|
originalValue;
|
|
350
|
-
/** The cell properties of the cell being edited. */
|
|
351
365
|
cellProperties;
|
|
352
|
-
/** Event emitted when the edit is finished.
|
|
353
|
-
* The data will be saved to the model.
|
|
354
|
-
*/
|
|
355
366
|
finishEdit = new EventEmitter();
|
|
356
|
-
/** Event emitted when the edit is canceled.
|
|
357
|
-
* The entered data will be reverted to the original value.
|
|
358
|
-
*/
|
|
359
367
|
cancelEdit = new EventEmitter();
|
|
360
|
-
|
|
361
|
-
_value;
|
|
362
|
-
/** Event triggered by Handsontable on closing the editor.
|
|
363
|
-
* The user can define their own actions for
|
|
364
|
-
* the custom editor to be called after the base logic. */
|
|
365
|
-
onClose() { }
|
|
366
|
-
/** Event triggered by Handsontable on open the editor.
|
|
367
|
-
* The user can define their own actions for
|
|
368
|
-
* the custom editor to be called after the base logic. */
|
|
369
|
-
onOpen(event) { }
|
|
370
|
-
/**
|
|
371
|
-
* Gets the current value of the editor.
|
|
372
|
-
* @returns The current value of the editor.
|
|
373
|
-
*/
|
|
368
|
+
value;
|
|
374
369
|
getValue() {
|
|
375
|
-
return this.
|
|
370
|
+
return this.value;
|
|
376
371
|
}
|
|
377
|
-
/**
|
|
378
|
-
* Sets the current value of the editor.
|
|
379
|
-
* @param value The value to set.
|
|
380
|
-
*/
|
|
381
372
|
setValue(value) {
|
|
382
|
-
this.
|
|
373
|
+
this.value = value;
|
|
383
374
|
}
|
|
384
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.24", ngImport: i0, type:
|
|
385
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.24", type:
|
|
375
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.24", ngImport: i0, type: HotCellEditorBase, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
376
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.24", type: HotCellEditorBase, isStandalone: true, inputs: { row: "row", column: "column", prop: "prop", originalValue: "originalValue", cellProperties: "cellProperties" }, outputs: { finishEdit: "finishEdit", cancelEdit: "cancelEdit" }, host: { properties: { "style.height.%": "this.heightFitParentContainer", "style.width.%": "this.widthFitParentContainer" } }, ngImport: i0 });
|
|
386
377
|
}
|
|
387
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.24", ngImport: i0, type:
|
|
378
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.24", ngImport: i0, type: HotCellEditorBase, decorators: [{
|
|
388
379
|
type: Directive
|
|
389
|
-
}], propDecorators: {
|
|
390
|
-
type: HostBinding,
|
|
391
|
-
args: ['attr.tabindex']
|
|
392
|
-
}], dataHotInput: [{
|
|
393
|
-
type: HostBinding,
|
|
394
|
-
args: ['attr.data-hot-input']
|
|
395
|
-
}], handsontableInputClass: [{
|
|
396
|
-
type: HostBinding,
|
|
397
|
-
args: ['class.handsontableInput']
|
|
398
|
-
}], heightFitParentContainer: [{
|
|
380
|
+
}], propDecorators: { heightFitParentContainer: [{
|
|
399
381
|
type: HostBinding,
|
|
400
382
|
args: ['style.height.%']
|
|
401
383
|
}], widthFitParentContainer: [{
|
|
@@ -417,6 +399,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.24", ngImpo
|
|
|
417
399
|
type: Output
|
|
418
400
|
}] } });
|
|
419
401
|
|
|
402
|
+
/**
|
|
403
|
+
* Abstract class representing a basic Handsontable cell editor in Angular.
|
|
404
|
+
*
|
|
405
|
+
* Extend this class and decorate the subclass with `@Component()` to implement a custom editor.
|
|
406
|
+
* Value type is limited to primitives (`string | number | boolean`).
|
|
407
|
+
* For object and array values use {@link HotCellEditorAdvancedComponent}.
|
|
408
|
+
*/
|
|
409
|
+
class HotCellEditorComponent extends HotCellEditorBase {
|
|
410
|
+
static EDITOR_MARKER = Symbol('HotCellEditorComponent');
|
|
411
|
+
/** The tabindex attribute for the editor. */
|
|
412
|
+
tabindex = -1;
|
|
413
|
+
/** The data-hot-input attribute for the editor. */
|
|
414
|
+
dataHotInput = '';
|
|
415
|
+
/** The handsontableInput class for the editor. */
|
|
416
|
+
handsontableInputClass = true;
|
|
417
|
+
/** Event triggered by Handsontable on closing the editor. */
|
|
418
|
+
onClose() { }
|
|
419
|
+
/** Event triggered by Handsontable on opening the editor. */
|
|
420
|
+
onOpen(event) { }
|
|
421
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.24", ngImport: i0, type: HotCellEditorComponent, deps: null, target: i0.ɵɵFactoryTarget.Directive });
|
|
422
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.24", type: HotCellEditorComponent, isStandalone: true, host: { properties: { "attr.tabindex": "this.tabindex", "attr.data-hot-input": "this.dataHotInput", "class.handsontableInput": "this.handsontableInputClass" } }, usesInheritance: true, ngImport: i0 });
|
|
423
|
+
}
|
|
424
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.24", ngImport: i0, type: HotCellEditorComponent, decorators: [{
|
|
425
|
+
type: Directive
|
|
426
|
+
}], propDecorators: { tabindex: [{
|
|
427
|
+
type: HostBinding,
|
|
428
|
+
args: ['attr.tabindex']
|
|
429
|
+
}], dataHotInput: [{
|
|
430
|
+
type: HostBinding,
|
|
431
|
+
args: ['attr.data-hot-input']
|
|
432
|
+
}], handsontableInputClass: [{
|
|
433
|
+
type: HostBinding,
|
|
434
|
+
args: ['class.handsontableInput']
|
|
435
|
+
}] } });
|
|
436
|
+
|
|
420
437
|
/**
|
|
421
438
|
* Factory function to create a custom Handsontable editor adapter for Angular components.
|
|
422
439
|
*
|
|
@@ -437,7 +454,7 @@ const FactoryEditorAdapter = (componentRef) => editorFactory({
|
|
|
437
454
|
editor._finishEditSubscription = undefined;
|
|
438
455
|
editor._cancelEditSubscription = undefined;
|
|
439
456
|
createEditorPlaceholder(editor, editor.hot._angularEnvironmentInjector);
|
|
440
|
-
editor.input = editor._editorPlaceHolderRef
|
|
457
|
+
editor.input = editor._editorPlaceHolderRef?.location.nativeElement ?? document.createElement('div');
|
|
441
458
|
editor._afterRowResizeCallback = () => {
|
|
442
459
|
if (editor.isOpened()) {
|
|
443
460
|
applyPropsToEditor(editor);
|
|
@@ -449,6 +466,7 @@ const FactoryEditorAdapter = (componentRef) => editorFactory({
|
|
|
449
466
|
}
|
|
450
467
|
};
|
|
451
468
|
editor._afterDestroyCallback = () => {
|
|
469
|
+
cleanupSubscriptions(editor);
|
|
452
470
|
if (editor._editorPlaceHolderRef) {
|
|
453
471
|
editor._editorPlaceHolderRef.destroy();
|
|
454
472
|
}
|
|
@@ -476,8 +494,8 @@ const FactoryEditorAdapter = (componentRef) => editorFactory({
|
|
|
476
494
|
onFocus: (editor) => editor._componentRef.instance.onFocus?.(editor),
|
|
477
495
|
afterClose: (editor) => {
|
|
478
496
|
resetEditorState(editor);
|
|
479
|
-
editor._editorPlaceHolderRef
|
|
480
|
-
editor._editorPlaceHolderRef
|
|
497
|
+
editor._editorPlaceHolderRef?.changeDetectorRef.detectChanges();
|
|
498
|
+
editor._editorPlaceHolderRef?.instance.detachEditor();
|
|
481
499
|
editor._componentRef.instance.afterClose?.(editor);
|
|
482
500
|
},
|
|
483
501
|
getValue: (editor) => editor._componentRef.instance.getValue(),
|
|
@@ -556,33 +574,16 @@ function cleanupSubscriptions(editor) {
|
|
|
556
574
|
/**
|
|
557
575
|
* Abstract base component for creating advanced custom cell renderer components for Handsontable.
|
|
558
576
|
*
|
|
559
|
-
*
|
|
577
|
+
* Extend this component and provide your own template to implement a custom renderer.
|
|
578
|
+
* Unlike {@link HotCellRendererComponent}, this variant also accepts object and array values.
|
|
560
579
|
*
|
|
561
580
|
* @template TValue - The type of the component renderer.
|
|
562
581
|
* @template TProps - The type of additional renderer properties.
|
|
563
582
|
*/
|
|
564
|
-
class HotCellRendererAdvancedComponent {
|
|
583
|
+
class HotCellRendererAdvancedComponent extends HotCellRendererBase {
|
|
565
584
|
static RENDERER_MARKER = Symbol('HotCellRendererAdvancedComponent');
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
td;
|
|
569
|
-
row;
|
|
570
|
-
col;
|
|
571
|
-
prop;
|
|
572
|
-
/**
|
|
573
|
-
* The cell properties provided by Handsontable, extended with optional renderer-specific properties.
|
|
574
|
-
*/
|
|
575
|
-
cellProperties;
|
|
576
|
-
/**
|
|
577
|
-
* Retrieves the renderer-specific properties from the cell properties.
|
|
578
|
-
*
|
|
579
|
-
* @returns The additional properties for the renderer.
|
|
580
|
-
*/
|
|
581
|
-
getProps() {
|
|
582
|
-
return this.cellProperties?.rendererProps ?? {};
|
|
583
|
-
}
|
|
584
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.24", ngImport: i0, type: HotCellRendererAdvancedComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
585
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.24", type: HotCellRendererAdvancedComponent, isStandalone: true, selector: "hot-cell-renderer-advanced", inputs: { value: "value", instance: "instance", td: "td", row: "row", col: "col", prop: "prop", cellProperties: "cellProperties" }, ngImport: i0, template: `<!-- This is an abstract component. Extend this component and provide your own template. -->`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
585
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.24", ngImport: i0, type: HotCellRendererAdvancedComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
586
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.24", type: HotCellRendererAdvancedComponent, isStandalone: true, selector: "hot-cell-renderer-advanced", usesInheritance: true, ngImport: i0, template: `<!-- This is an abstract component. Extend this component and provide your own template. -->`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
586
587
|
}
|
|
587
588
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.24", ngImport: i0, type: HotCellRendererAdvancedComponent, decorators: [{
|
|
588
589
|
type: Component,
|
|
@@ -592,111 +593,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.24", ngImpo
|
|
|
592
593
|
standalone: true,
|
|
593
594
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
594
595
|
}]
|
|
595
|
-
}]
|
|
596
|
-
type: Input
|
|
597
|
-
}], instance: [{
|
|
598
|
-
type: Input
|
|
599
|
-
}], td: [{
|
|
600
|
-
type: Input
|
|
601
|
-
}], row: [{
|
|
602
|
-
type: Input
|
|
603
|
-
}], col: [{
|
|
604
|
-
type: Input
|
|
605
|
-
}], prop: [{
|
|
606
|
-
type: Input
|
|
607
|
-
}], cellProperties: [{
|
|
608
|
-
type: Input
|
|
609
|
-
}] } });
|
|
596
|
+
}] });
|
|
610
597
|
|
|
611
598
|
/**
|
|
612
|
-
* Abstract class representing
|
|
599
|
+
* Abstract class representing an advanced Handsontable cell editor in Angular.
|
|
600
|
+
*
|
|
601
|
+
* Extend this class and decorate the subclass with `@Component()` to implement a custom editor.
|
|
602
|
+
* Unlike {@link HotCellEditorComponent}, this variant also accepts object and array values
|
|
603
|
+
* and provides additional lifecycle hooks and positioning options.
|
|
613
604
|
*/
|
|
614
|
-
class HotCellEditorAdvancedComponent {
|
|
605
|
+
class HotCellEditorAdvancedComponent extends HotCellEditorBase {
|
|
615
606
|
static EDITOR_MARKER = Symbol('HotCellEditorAdvancedComponent');
|
|
616
|
-
/**
|
|
617
|
-
heightFitParentContainer = 100;
|
|
618
|
-
/** The width of the editor as a percentage of the parent container. */
|
|
619
|
-
widthFitParentContainer = 100;
|
|
620
|
-
/** The row index of the cell being edited. */
|
|
621
|
-
row;
|
|
622
|
-
/** The column index of the cell being edited. */
|
|
623
|
-
column;
|
|
624
|
-
/** The property name of the cell being edited. */
|
|
625
|
-
prop;
|
|
626
|
-
/** The original value of the cell being edited. */
|
|
627
|
-
originalValue;
|
|
628
|
-
/** The cell properties of the cell being edited. */
|
|
629
|
-
cellProperties;
|
|
630
|
-
/** Event emitted when the edit is finished.
|
|
631
|
-
* The data will be saved to the model.
|
|
632
|
-
*/
|
|
633
|
-
finishEdit = new EventEmitter();
|
|
634
|
-
/** Event emitted when the edit is canceled.
|
|
635
|
-
* The entered data will be reverted to the original value.
|
|
636
|
-
*/
|
|
637
|
-
cancelEdit = new EventEmitter();
|
|
638
|
-
/** The current value of the editor. */
|
|
639
|
-
value;
|
|
640
|
-
/** Event triggered by Handsontable on focus the editor.
|
|
641
|
-
* The user have to define focus logic.
|
|
642
|
-
*/
|
|
607
|
+
/** Event triggered by Handsontable on focusing the editor. Available in advanced mode. */
|
|
643
608
|
onFocus(editor) { }
|
|
644
|
-
/**
|
|
645
|
-
* Gets the current value of the editor.
|
|
646
|
-
* @returns The current value of the editor.
|
|
647
|
-
*/
|
|
648
|
-
getValue() {
|
|
649
|
-
return this.value;
|
|
650
|
-
}
|
|
651
|
-
/**
|
|
652
|
-
* Sets the current value of the editor.
|
|
653
|
-
* @param value The value to set.
|
|
654
|
-
*/
|
|
655
|
-
setValue(value) {
|
|
656
|
-
this.value = value;
|
|
657
|
-
}
|
|
658
|
-
/** The position of the editor in the DOM. Used by Handsontable API. Available in advanced mode. */
|
|
609
|
+
/** The position of the editor in the DOM. Available in advanced mode. */
|
|
659
610
|
position = 'container';
|
|
660
611
|
/** The shortcuts available for the editor. Available in advanced mode. */
|
|
661
612
|
shortcuts;
|
|
662
|
-
/** The group name for the shortcuts. Available in advanced mode
|
|
613
|
+
/** The group name for the shortcuts. Available in advanced mode. */
|
|
663
614
|
shortcutsGroup;
|
|
664
|
-
/** Configuration. Available in advanced mode. */
|
|
615
|
+
/** Configuration object. Available in advanced mode. */
|
|
665
616
|
config;
|
|
666
|
-
/** Lifecycle hook called after the editor is opened. Available in advanced mode
|
|
617
|
+
/** Lifecycle hook called after the editor is opened. Available in advanced mode. */
|
|
667
618
|
afterOpen(editor, event) { }
|
|
668
619
|
/** Lifecycle hook called after the editor is closed. Available in advanced mode. */
|
|
669
620
|
afterClose(editor) { }
|
|
670
|
-
/** Lifecycle hook called after the editor is initialized. Available in advanced mode
|
|
621
|
+
/** Lifecycle hook called after the editor is initialized. Available in advanced mode. */
|
|
671
622
|
afterInit(editor) { }
|
|
672
623
|
/** Lifecycle hook called before the editor is opened. Available in advanced mode. */
|
|
673
624
|
beforeOpen(editor, { row, col, prop, td, originalValue, cellProperties, }) { }
|
|
674
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.24", ngImport: i0, type: HotCellEditorAdvancedComponent, deps:
|
|
675
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.24", type: HotCellEditorAdvancedComponent, isStandalone: true,
|
|
625
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.24", ngImport: i0, type: HotCellEditorAdvancedComponent, deps: null, target: i0.ɵɵFactoryTarget.Directive });
|
|
626
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.24", type: HotCellEditorAdvancedComponent, isStandalone: true, usesInheritance: true, ngImport: i0 });
|
|
676
627
|
}
|
|
677
628
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.24", ngImport: i0, type: HotCellEditorAdvancedComponent, decorators: [{
|
|
678
629
|
type: Directive
|
|
679
|
-
}]
|
|
680
|
-
type: HostBinding,
|
|
681
|
-
args: ['style.height.%']
|
|
682
|
-
}], widthFitParentContainer: [{
|
|
683
|
-
type: HostBinding,
|
|
684
|
-
args: ['style.width.%']
|
|
685
|
-
}], row: [{
|
|
686
|
-
type: Input
|
|
687
|
-
}], column: [{
|
|
688
|
-
type: Input
|
|
689
|
-
}], prop: [{
|
|
690
|
-
type: Input
|
|
691
|
-
}], originalValue: [{
|
|
692
|
-
type: Input
|
|
693
|
-
}], cellProperties: [{
|
|
694
|
-
type: Input
|
|
695
|
-
}], finishEdit: [{
|
|
696
|
-
type: Output
|
|
697
|
-
}], cancelEdit: [{
|
|
698
|
-
type: Output
|
|
699
|
-
}] } });
|
|
630
|
+
}] });
|
|
700
631
|
|
|
701
632
|
const INVALID_RENDERER_WARNING = 'The provided renderer component was not recognized as a valid custom renderer. ' +
|
|
702
633
|
'It must either extend HotCellRendererComponent or be a valid TemplateRef. ' +
|
|
@@ -704,6 +635,11 @@ const INVALID_RENDERER_WARNING = 'The provided renderer component was not recogn
|
|
|
704
635
|
const INVALID_ADVANCED_RENDERER_WARNING = 'The provided renderer component was not recognized as a valid custom renderer. ' +
|
|
705
636
|
'It must either extend HotCellRendererAdvancedComponent. ' +
|
|
706
637
|
'Please ensure that your custom renderer is implemented correctly and imported from the proper source.';
|
|
638
|
+
// Renderer component inputs, listed once at module scope so the per-cell render path can set them
|
|
639
|
+
// without allocating a fresh key array (via Object.keys) on every cell of every render frame.
|
|
640
|
+
const RENDERER_INPUT_KEYS = [
|
|
641
|
+
'instance', 'td', 'row', 'col', 'prop', 'value', 'cellProperties',
|
|
642
|
+
];
|
|
707
643
|
/**
|
|
708
644
|
* Type guard that checks if the given object is a TemplateRef.
|
|
709
645
|
*
|
|
@@ -711,7 +647,7 @@ const INVALID_ADVANCED_RENDERER_WARNING = 'The provided renderer component was n
|
|
|
711
647
|
* @returns True if the object is a TemplateRef; otherwise, false.
|
|
712
648
|
*/
|
|
713
649
|
function isTemplateRef(obj) {
|
|
714
|
-
return obj && typeof obj.createEmbeddedView === 'function';
|
|
650
|
+
return !!obj && typeof obj.createEmbeddedView === 'function';
|
|
715
651
|
}
|
|
716
652
|
/**
|
|
717
653
|
* Type guard to check if an object is an instance of HotCellRendererComponent.
|
|
@@ -744,6 +680,23 @@ function isAdvancedHotCellRendererComponent(obj) {
|
|
|
744
680
|
class DynamicComponentService {
|
|
745
681
|
appRef;
|
|
746
682
|
environmentInjector;
|
|
683
|
+
// Track Angular component refs and embedded views keyed by the TD element they are attached to.
|
|
684
|
+
// When a cell is re-rendered the previous component is destroyed before a new one is created.
|
|
685
|
+
_tdComponentRefs = new WeakMap();
|
|
686
|
+
_tdEmbeddedViews = new WeakMap();
|
|
687
|
+
// Per-instance registries of every renderer ref/view currently attached to the application.
|
|
688
|
+
// The WeakMaps above only allow per-TD lookup; these sets let us sweep refs whose TD was dropped
|
|
689
|
+
// from Handsontable's virtual viewport (scrolling, updateData), which would otherwise stay
|
|
690
|
+
// attached to ApplicationRef forever and leak both memory and change-detection work.
|
|
691
|
+
//
|
|
692
|
+
// The registries are scoped per Handsontable instance (not one global set) because this service
|
|
693
|
+
// is a root singleton shared by every <hot-table>. A global sweep would scan the cells of every
|
|
694
|
+
// table on the page on each `afterViewRender`, i.e. on every scroll frame of any one of them.
|
|
695
|
+
// Keying by instance bounds each sweep to the cells of the table that actually re-rendered.
|
|
696
|
+
_instanceComponentRefs = new WeakMap();
|
|
697
|
+
_instanceEmbeddedViews = new WeakMap();
|
|
698
|
+
// Instances we already wired the sweep hook into, so each instance is hooked at most once.
|
|
699
|
+
_hookedInstances = new WeakSet();
|
|
747
700
|
constructor(appRef, environmentInjector) {
|
|
748
701
|
this.appRef = appRef;
|
|
749
702
|
this.environmentInjector = environmentInjector;
|
|
@@ -758,6 +711,7 @@ class DynamicComponentService {
|
|
|
758
711
|
* @returns A renderer function that can be used in Handsontable's configuration.
|
|
759
712
|
*/
|
|
760
713
|
createRendererFromComponent(component, componentProps = {}, register = false) {
|
|
714
|
+
let registered = false;
|
|
761
715
|
return (instance, td, row, col, prop, value, cellProperties) => {
|
|
762
716
|
const properties = {
|
|
763
717
|
value,
|
|
@@ -768,24 +722,24 @@ class DynamicComponentService {
|
|
|
768
722
|
prop,
|
|
769
723
|
cellProperties,
|
|
770
724
|
};
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
const rendererParameters = [instance, td, row, col, prop, value, cellProperties];
|
|
775
|
-
baseRenderer.apply(this, rendererParameters);
|
|
776
|
-
td.innerHTML = '';
|
|
725
|
+
cellProperties.rendererProps = componentProps;
|
|
726
|
+
baseRenderer.call(this, instance, td, row, col, prop, value, cellProperties);
|
|
727
|
+
this.registerSweepHook(instance);
|
|
777
728
|
if (isTemplateRef(component)) {
|
|
778
|
-
|
|
729
|
+
// Embedded views carry a per-render context, so they are always rebuilt.
|
|
730
|
+
this.replaceCellContent(instance, td);
|
|
731
|
+
const embeddedView = this.attachTemplateToElement(component, td, properties);
|
|
732
|
+
this.trackEmbeddedView(instance, td, embeddedView);
|
|
779
733
|
}
|
|
780
734
|
else if (isHotCellRendererComponent(component)) {
|
|
781
|
-
|
|
782
|
-
this.attachComponentToElement(componentRef, td);
|
|
735
|
+
this.renderComponent(td, component, properties);
|
|
783
736
|
}
|
|
784
737
|
else {
|
|
785
738
|
console.warn(INVALID_RENDERER_WARNING);
|
|
786
739
|
}
|
|
787
|
-
if (register && isHotCellRendererComponent(component)) {
|
|
788
|
-
Handsontable.renderers.registerRenderer(component.
|
|
740
|
+
if (register && !registered && isHotCellRendererComponent(component)) {
|
|
741
|
+
Handsontable.renderers.registerRenderer(component.name, component);
|
|
742
|
+
registered = true;
|
|
789
743
|
}
|
|
790
744
|
return td;
|
|
791
745
|
};
|
|
@@ -800,6 +754,7 @@ class DynamicComponentService {
|
|
|
800
754
|
* @returns A renderer function that can be used in Handsontable's configuration.
|
|
801
755
|
*/
|
|
802
756
|
createRendererWithFactory(component, componentProps = {}, register = false) {
|
|
757
|
+
let registered = false;
|
|
803
758
|
return rendererFactory(({ instance, td, row, column, prop, value, cellProperties }) => {
|
|
804
759
|
const properties = {
|
|
805
760
|
value,
|
|
@@ -810,28 +765,161 @@ class DynamicComponentService {
|
|
|
810
765
|
prop,
|
|
811
766
|
cellProperties,
|
|
812
767
|
};
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
768
|
+
cellProperties.rendererProps = componentProps;
|
|
769
|
+
// Apply the base renderer so the TD gets the same base classes/attributes as the
|
|
770
|
+
// createRendererFromComponent path (rendererFactory itself does not call it).
|
|
771
|
+
baseRenderer.call(this, instance, td, row, column, prop, value, cellProperties);
|
|
772
|
+
this.registerSweepHook(instance);
|
|
817
773
|
if (isAdvancedHotCellRendererComponent(component)) {
|
|
818
|
-
|
|
819
|
-
this.attachComponentToElement(componentRef, td);
|
|
774
|
+
this.renderComponent(td, component, properties);
|
|
820
775
|
}
|
|
821
776
|
else {
|
|
822
777
|
console.warn(INVALID_ADVANCED_RENDERER_WARNING);
|
|
823
778
|
}
|
|
824
|
-
if (register && isAdvancedHotCellRendererComponent(component)) {
|
|
825
|
-
registerRenderer(component.
|
|
779
|
+
if (register && !registered && isAdvancedHotCellRendererComponent(component)) {
|
|
780
|
+
registerRenderer(component.name, component);
|
|
781
|
+
registered = true;
|
|
782
|
+
}
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Destroys all renderer components and embedded views attached to cells within a container element.
|
|
787
|
+
* Must be called before destroying the Handsontable instance to prevent Angular component leaks.
|
|
788
|
+
*
|
|
789
|
+
* @param container - The root DOM element of the Handsontable instance.
|
|
790
|
+
* @param instance - The Handsontable instance whose registries should be torn down. When omitted
|
|
791
|
+
* (e.g. test stubs), only refs reachable through TDs still in the container are destroyed.
|
|
792
|
+
*/
|
|
793
|
+
cleanupContainer(container, instance) {
|
|
794
|
+
const compRefs = instance ? this._instanceComponentRefs.get(instance) : undefined;
|
|
795
|
+
const embViews = instance ? this._instanceEmbeddedViews.get(instance) : undefined;
|
|
796
|
+
container.querySelectorAll('td').forEach((td) => {
|
|
797
|
+
const compRef = this._tdComponentRefs.get(td);
|
|
798
|
+
if (compRef) {
|
|
799
|
+
this.destroyComponent(compRef);
|
|
800
|
+
this._tdComponentRefs.delete(td);
|
|
801
|
+
compRefs?.delete(compRef);
|
|
802
|
+
}
|
|
803
|
+
const embView = this._tdEmbeddedViews.get(td);
|
|
804
|
+
if (embView) {
|
|
805
|
+
this.destroyEmbeddedView(embView);
|
|
806
|
+
this._tdEmbeddedViews.delete(td);
|
|
807
|
+
embViews?.delete(embView);
|
|
808
|
+
}
|
|
809
|
+
});
|
|
810
|
+
// The loop above only reaches TDs still present in the container. Refs for cells already
|
|
811
|
+
// dropped from the viewport (but not yet swept) would otherwise be orphaned once this
|
|
812
|
+
// instance's afterViewRender hook is gone after destroy. Tear down whatever is left and drop
|
|
813
|
+
// the per-instance registries so a repeated cleanup call is a no-op. (Entries removed in the
|
|
814
|
+
// loop above are already gone from these sets, so nothing is destroyed twice.)
|
|
815
|
+
compRefs?.forEach((ref) => this.destroyComponent(ref));
|
|
816
|
+
embViews?.forEach((view) => this.destroyEmbeddedView(view));
|
|
817
|
+
if (instance) {
|
|
818
|
+
this._instanceComponentRefs.delete(instance);
|
|
819
|
+
this._instanceEmbeddedViews.delete(instance);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* Registers a one-time `afterViewRender` hook on the given Handsontable instance that sweeps
|
|
824
|
+
* renderer refs whose TD is no longer connected to the document. Handsontable recycles a pool of
|
|
825
|
+
* TD elements while virtualizing rows; cells that leave the viewport are never re-rendered, so
|
|
826
|
+
* without this sweep their Angular components stay attached to ApplicationRef and leak.
|
|
827
|
+
*
|
|
828
|
+
* Guarded against instances that do not expose `addHook` (e.g. test stubs).
|
|
829
|
+
*
|
|
830
|
+
* @param instance - The Handsontable instance whose render cycle drives the sweep.
|
|
831
|
+
*/
|
|
832
|
+
registerSweepHook(instance) {
|
|
833
|
+
if (this._hookedInstances.has(instance) || typeof instance?.addHook !== 'function') {
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
this._hookedInstances.add(instance);
|
|
837
|
+
instance.addHook('afterViewRender', () => this.sweepDetachedViews(instance));
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Destroys every renderer ref/view tracked for the given instance whose root node is no longer
|
|
841
|
+
* attached to the document.
|
|
842
|
+
*
|
|
843
|
+
* @param instance - The Handsontable instance whose registries should be swept.
|
|
844
|
+
*/
|
|
845
|
+
sweepDetachedViews(instance) {
|
|
846
|
+
const compRefs = this._instanceComponentRefs.get(instance);
|
|
847
|
+
compRefs?.forEach((ref) => {
|
|
848
|
+
if (!this.isViewConnected(ref.hostView)) {
|
|
849
|
+
this.destroyComponent(ref);
|
|
850
|
+
compRefs.delete(ref);
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
const embViews = this._instanceEmbeddedViews.get(instance);
|
|
854
|
+
embViews?.forEach((view) => {
|
|
855
|
+
if (!this.isViewConnected(view)) {
|
|
856
|
+
this.destroyEmbeddedView(view);
|
|
857
|
+
embViews.delete(view);
|
|
826
858
|
}
|
|
827
859
|
});
|
|
828
860
|
}
|
|
861
|
+
/**
|
|
862
|
+
* @returns True if any of the view's root nodes is still connected to the document.
|
|
863
|
+
*/
|
|
864
|
+
isViewConnected(view) {
|
|
865
|
+
return view.rootNodes.some((node) => !!node?.isConnected);
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Destroys the renderer ref/view previously attached to a cell and clears the cell content,
|
|
869
|
+
* so a fresh renderer can take over the TD without leaking the old one.
|
|
870
|
+
*
|
|
871
|
+
* @param instance - The Handsontable instance owning the cell, used to update its registries.
|
|
872
|
+
* @param td - The table cell whose previous content should be torn down.
|
|
873
|
+
*/
|
|
874
|
+
replaceCellContent(instance, td) {
|
|
875
|
+
const prevRef = this._tdComponentRefs.get(td);
|
|
876
|
+
if (prevRef) {
|
|
877
|
+
this.destroyComponent(prevRef);
|
|
878
|
+
this._tdComponentRefs.delete(td);
|
|
879
|
+
this._instanceComponentRefs.get(instance)?.delete(prevRef);
|
|
880
|
+
}
|
|
881
|
+
const prevView = this._tdEmbeddedViews.get(td);
|
|
882
|
+
if (prevView) {
|
|
883
|
+
this.destroyEmbeddedView(prevView);
|
|
884
|
+
this._tdEmbeddedViews.delete(td);
|
|
885
|
+
this._instanceEmbeddedViews.get(instance)?.delete(prevView);
|
|
886
|
+
}
|
|
887
|
+
td.innerHTML = '';
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Tracks a component ref both by its TD (for fast replacement) and in the instance registry
|
|
891
|
+
* (for sweeping and full teardown).
|
|
892
|
+
*/
|
|
893
|
+
trackComponentRef(instance, td, ref) {
|
|
894
|
+
this._tdComponentRefs.set(td, ref);
|
|
895
|
+
this.registryFor(this._instanceComponentRefs, instance).add(ref);
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Tracks an embedded view both by its TD (for fast replacement) and in the instance registry
|
|
899
|
+
* (for sweeping and full teardown).
|
|
900
|
+
*/
|
|
901
|
+
trackEmbeddedView(instance, td, view) {
|
|
902
|
+
this._tdEmbeddedViews.set(td, view);
|
|
903
|
+
this.registryFor(this._instanceEmbeddedViews, instance).add(view);
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* Returns the per-instance registry set for the given instance, creating it on first use.
|
|
907
|
+
*/
|
|
908
|
+
registryFor(registry, instance) {
|
|
909
|
+
let set = registry.get(instance);
|
|
910
|
+
if (!set) {
|
|
911
|
+
set = new Set();
|
|
912
|
+
registry.set(instance, set);
|
|
913
|
+
}
|
|
914
|
+
return set;
|
|
915
|
+
}
|
|
829
916
|
/**
|
|
830
917
|
* Attaches an embedded view created from a TemplateRef to a given DOM element.
|
|
831
918
|
*
|
|
832
919
|
* @param template - The TemplateRef to create an embedded view from.
|
|
833
920
|
* @param tdEl - The target DOM element (a table cell) to which the view will be appended.
|
|
834
921
|
* @param properties - Context object providing properties to be used within the template.
|
|
922
|
+
* @returns The created EmbeddedViewRef so the caller can track and destroy it later.
|
|
835
923
|
*/
|
|
836
924
|
attachTemplateToElement(template, tdEl, properties) {
|
|
837
925
|
const embeddedView = template.createEmbeddedView({
|
|
@@ -842,6 +930,7 @@ class DynamicComponentService {
|
|
|
842
930
|
embeddedView.rootNodes.forEach((node) => {
|
|
843
931
|
tdEl.appendChild(node);
|
|
844
932
|
});
|
|
933
|
+
return embeddedView;
|
|
845
934
|
}
|
|
846
935
|
/**
|
|
847
936
|
* Dynamically creates an Angular component of the given type.
|
|
@@ -854,18 +943,46 @@ class DynamicComponentService {
|
|
|
854
943
|
const componentRef = createComponent(component, {
|
|
855
944
|
environmentInjector: this.environmentInjector,
|
|
856
945
|
});
|
|
857
|
-
|
|
858
|
-
if (Object.prototype.hasOwnProperty.call(rendererParameters, key)) {
|
|
859
|
-
componentRef.setInput(key, rendererParameters[key]);
|
|
860
|
-
}
|
|
861
|
-
else {
|
|
862
|
-
console.warn(`Input property "${key}" does not exist on component instance: ${component?.name}.`);
|
|
863
|
-
}
|
|
864
|
-
});
|
|
946
|
+
this.applyInputs(componentRef, rendererParameters);
|
|
865
947
|
componentRef.changeDetectorRef.detectChanges();
|
|
866
948
|
this.appRef.attachView(componentRef.hostView);
|
|
867
949
|
return componentRef;
|
|
868
950
|
}
|
|
951
|
+
/**
|
|
952
|
+
* Renders an Angular component into the given cell, recycling the component already attached to
|
|
953
|
+
* the TD when it is of the same type. Handsontable recycles its pool of TD elements heavily while
|
|
954
|
+
* virtualizing rows, so recreating an Angular component on every re-render would cause needless
|
|
955
|
+
* teardown/instantiation churn and GC pressure. When the type matches we only refresh the inputs.
|
|
956
|
+
*
|
|
957
|
+
* @param td - The target table cell.
|
|
958
|
+
* @param component - The renderer component type to render.
|
|
959
|
+
* @param properties - The renderer parameters to feed as component inputs.
|
|
960
|
+
*/
|
|
961
|
+
renderComponent(td, component, properties) {
|
|
962
|
+
const prevRef = this._tdComponentRefs.get(td);
|
|
963
|
+
if (prevRef &&
|
|
964
|
+
prevRef.componentType === component &&
|
|
965
|
+
this.isViewConnected(prevRef.hostView)) {
|
|
966
|
+
this.applyInputs(prevRef, properties);
|
|
967
|
+
prevRef.changeDetectorRef.detectChanges();
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
this.replaceCellContent(properties.instance, td);
|
|
971
|
+
const componentRef = this.createComponent(component, properties);
|
|
972
|
+
this.attachComponentToElement(componentRef, td);
|
|
973
|
+
this.trackComponentRef(properties.instance, td, componentRef);
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
976
|
+
* Assigns every renderer parameter as an input on the given component ref.
|
|
977
|
+
*
|
|
978
|
+
* @param componentRef - The component ref whose inputs should be set.
|
|
979
|
+
* @param rendererParameters - The renderer parameters to assign.
|
|
980
|
+
*/
|
|
981
|
+
applyInputs(componentRef, rendererParameters) {
|
|
982
|
+
RENDERER_INPUT_KEYS.forEach((key) => {
|
|
983
|
+
componentRef.setInput(key, rendererParameters[key]);
|
|
984
|
+
});
|
|
985
|
+
}
|
|
869
986
|
/**
|
|
870
987
|
* Attaches a dynamically created component's view to a specified DOM container element.
|
|
871
988
|
*
|
|
@@ -873,18 +990,40 @@ class DynamicComponentService {
|
|
|
873
990
|
* @param container - The target DOM element to which the component's root node will be appended.
|
|
874
991
|
*/
|
|
875
992
|
attachComponentToElement(componentRef, container) {
|
|
876
|
-
|
|
877
|
-
|
|
993
|
+
componentRef.hostView.rootNodes.forEach((node) => {
|
|
994
|
+
container.appendChild(node);
|
|
995
|
+
});
|
|
878
996
|
}
|
|
879
997
|
/**
|
|
880
998
|
* Destroys a dynamically created component and detaches its view from the Angular application.
|
|
881
999
|
*
|
|
1000
|
+
* Idempotent: a TD recycled after `sweepDetachedViews` already destroyed its ref still maps to that
|
|
1001
|
+
* stale ref in `_tdComponentRefs`, so the next render path may reach this with an already-destroyed
|
|
1002
|
+
* ref. Guarding on `destroyed` skips the redundant detach/destroy instead of relying on Angular's
|
|
1003
|
+
* internal no-op behaviour.
|
|
1004
|
+
*
|
|
882
1005
|
* @param componentRef - The reference to the component to be destroyed.
|
|
883
1006
|
*/
|
|
884
1007
|
destroyComponent(componentRef) {
|
|
885
|
-
|
|
1008
|
+
const hostView = componentRef.hostView;
|
|
1009
|
+
if (hostView.destroyed) {
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
this.appRef.detachView(hostView);
|
|
886
1013
|
componentRef.destroy();
|
|
887
1014
|
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Destroys an embedded view. Idempotent for the same reason as {@link destroyComponent}: a recycled
|
|
1017
|
+
* TD can still map to an already-destroyed view in `_tdEmbeddedViews`.
|
|
1018
|
+
*
|
|
1019
|
+
* @param view - The embedded view to destroy.
|
|
1020
|
+
*/
|
|
1021
|
+
destroyEmbeddedView(view) {
|
|
1022
|
+
if (view.destroyed) {
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
view.destroy();
|
|
1026
|
+
}
|
|
888
1027
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.24", ngImport: i0, type: DynamicComponentService, deps: [{ token: i0.ApplicationRef }, { token: i0.EnvironmentInjector }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
889
1028
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.24", ngImport: i0, type: DynamicComponentService, providedIn: 'root' });
|
|
890
1029
|
}
|
|
@@ -896,6 +1035,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.24", ngImpo
|
|
|
896
1035
|
}], ctorParameters: () => [{ type: i0.ApplicationRef }, { type: i0.EnvironmentInjector }] });
|
|
897
1036
|
|
|
898
1037
|
const AVAILABLE_HOOKS_SET = new Set(Handsontable.hooks.getRegistered());
|
|
1038
|
+
const HOT_ZONE_WRAPPED = Symbol('hotZoneWrapped');
|
|
899
1039
|
/**
|
|
900
1040
|
* Service to resolve and apply custom settings for Handsontable settings object.
|
|
901
1041
|
*/
|
|
@@ -911,15 +1051,26 @@ class HotSettingsResolver {
|
|
|
911
1051
|
/**
|
|
912
1052
|
* Applies custom settings to the provided GridSettings.
|
|
913
1053
|
* @param settings The original grid settings.
|
|
1054
|
+
* @param previousColumns The previously resolved columns (from the prior settings cycle). When
|
|
1055
|
+
* supplied, an editor component already created for a column whose editor type is unchanged is
|
|
1056
|
+
* recycled instead of being recreated, avoiding needless Angular component teardown/rebuild.
|
|
914
1057
|
* @returns The merged grid settings with custom settings applied.
|
|
915
1058
|
*/
|
|
916
|
-
applyCustomSettings(settings) {
|
|
917
|
-
|
|
1059
|
+
applyCustomSettings(settings, previousColumns) {
|
|
1060
|
+
// Shallow-clone the user settings (and each column) before mutating. Otherwise we would
|
|
1061
|
+
// write generated renderers/editors and `_editorComponentReference` straight onto the
|
|
1062
|
+
// caller's objects. When the same settings/columns are shared across two <hot-table>
|
|
1063
|
+
// instances, the second resolution would overwrite the first instance's editor refs,
|
|
1064
|
+
// leaking them and cross-wiring a single editor component between tables.
|
|
1065
|
+
const mergedSettings = { ...settings };
|
|
1066
|
+
if (Array.isArray(mergedSettings.columns)) {
|
|
1067
|
+
mergedSettings.columns = mergedSettings.columns.map((column) => ({ ...column }));
|
|
1068
|
+
}
|
|
918
1069
|
this.updateColumnRendererForGivenCustomRenderer(mergedSettings);
|
|
919
|
-
this.updateColumnEditorForGivenCustomEditor(mergedSettings);
|
|
1070
|
+
this.updateColumnEditorForGivenCustomEditor(mergedSettings, previousColumns);
|
|
920
1071
|
this.updateColumnValidatorForGivenCustomValidator(mergedSettings);
|
|
921
1072
|
this.wrapHooksInNgZone(mergedSettings);
|
|
922
|
-
return mergedSettings
|
|
1073
|
+
return mergedSettings;
|
|
923
1074
|
}
|
|
924
1075
|
/**
|
|
925
1076
|
* Ensures that hook callbacks in the provided grid settings run inside Angular's zone.
|
|
@@ -928,12 +1079,18 @@ class HotSettingsResolver {
|
|
|
928
1079
|
*/
|
|
929
1080
|
wrapHooksInNgZone(settings) {
|
|
930
1081
|
const ngZone = this.ngZone;
|
|
931
|
-
|
|
1082
|
+
// Iterate only the keys actually present in settings instead of all ~100 registered HOT hooks.
|
|
1083
|
+
Object.keys(settings).forEach((key) => {
|
|
1084
|
+
if (!AVAILABLE_HOOKS_SET.has(key)) {
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
932
1087
|
const option = settings[key];
|
|
933
|
-
if (typeof option === 'function') {
|
|
934
|
-
|
|
1088
|
+
if (typeof option === 'function' && !option[HOT_ZONE_WRAPPED]) {
|
|
1089
|
+
const wrapped = function (...args) {
|
|
935
1090
|
return ngZone.run(() => option.apply(this, args));
|
|
936
1091
|
};
|
|
1092
|
+
wrapped[HOT_ZONE_WRAPPED] = true;
|
|
1093
|
+
settings[key] = wrapped;
|
|
937
1094
|
}
|
|
938
1095
|
});
|
|
939
1096
|
}
|
|
@@ -960,31 +1117,73 @@ class HotSettingsResolver {
|
|
|
960
1117
|
}
|
|
961
1118
|
/**
|
|
962
1119
|
* Updates the column editor for columns with a custom editor.
|
|
1120
|
+
*
|
|
1121
|
+
* Iterates by original column index (not a filtered subset) so each column can be matched against
|
|
1122
|
+
* the column at the same index in `previousColumns` for editor-component recycling.
|
|
1123
|
+
*
|
|
963
1124
|
* @param mergedSettings The merged grid settings.
|
|
1125
|
+
* @param previousColumns The previously resolved columns, used to recycle editor components.
|
|
964
1126
|
*/
|
|
965
|
-
updateColumnEditorForGivenCustomEditor(mergedSettings) {
|
|
1127
|
+
updateColumnEditorForGivenCustomEditor(mergedSettings, previousColumns) {
|
|
966
1128
|
if (!Array.isArray(mergedSettings?.columns)) {
|
|
967
1129
|
return;
|
|
968
1130
|
}
|
|
969
|
-
mergedSettings
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
if (
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
1131
|
+
mergedSettings.columns.forEach((cellSettings, index) => {
|
|
1132
|
+
const isAdvanced = this.isAdvancedEditorComponentRefType(cellSettings.editor);
|
|
1133
|
+
const isBasic = this.isEditorComponentRefType(cellSettings.editor);
|
|
1134
|
+
if (!isAdvanced && !isBasic) {
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
const editorType = cellSettings.editor;
|
|
1138
|
+
const reusableRef = this.reusableEditorRef(previousColumns?.[index], cellSettings, editorType);
|
|
1139
|
+
const internalSettings = cellSettings;
|
|
1140
|
+
// Recycle the editor component from the previous settings cycle when the same editor type
|
|
1141
|
+
// sits at the same column index AND the same logical column (by `data`) still occupies it.
|
|
1142
|
+
// Recreating it on every settings change would tear down and rebuild an Angular component
|
|
1143
|
+
// (and its DOM/internal state) for no reason. The reused ref is carried into the new column;
|
|
1144
|
+
// HotTableComponent.ngOnChanges detects it by identity and skips destroying it.
|
|
1145
|
+
const component = reusableRef ?? createComponent(editorType, {
|
|
1146
|
+
environmentInjector: this.environmentInjector,
|
|
1147
|
+
});
|
|
1148
|
+
internalSettings._editorComponentReference = component;
|
|
1149
|
+
if (isAdvanced) {
|
|
976
1150
|
cellSettings.editor = FactoryEditorAdapter(component);
|
|
977
1151
|
}
|
|
978
1152
|
else {
|
|
979
|
-
|
|
980
|
-
environmentInjector: this.environmentInjector,
|
|
981
|
-
});
|
|
982
|
-
cellSettings['_editorComponentReference'] = component;
|
|
983
|
-
cellSettings['_environmentInjector'] = this.environmentInjector;
|
|
1153
|
+
internalSettings._environmentInjector = this.environmentInjector;
|
|
984
1154
|
cellSettings.editor = BaseEditorAdapter;
|
|
985
1155
|
}
|
|
986
1156
|
});
|
|
987
1157
|
}
|
|
1158
|
+
/**
|
|
1159
|
+
* Returns the previous column's editor component ref when it can be reused for the new column, or
|
|
1160
|
+
* `undefined` to signal a fresh component is needed.
|
|
1161
|
+
*
|
|
1162
|
+
* A ref is only recycled when, at the same index, both the editor component type AND the logical
|
|
1163
|
+
* column identity (its `data` binding) are unchanged. The component-type check alone would already
|
|
1164
|
+
* be functionally safe — a Handsontable editor is not per-cell rendered state but a single
|
|
1165
|
+
* on-demand component that `BaseEditorAdapter`/`FactoryEditorAdapter` re-prepare on every edit
|
|
1166
|
+
* (`prepare()` re-reads the ref from the *current* column meta and `applyPropsToEditor()` re-applies
|
|
1167
|
+
* the full cell context on each `open()`). The extra `data` check is a defensive guard: when columns
|
|
1168
|
+
* are reordered/shortened so a *different* logical column lands on an index, we build a fresh editor
|
|
1169
|
+
* rather than carry the previous column's instance over, so no custom editor that caches
|
|
1170
|
+
* column-specific config at construction can leak stale state into the new cell.
|
|
1171
|
+
*
|
|
1172
|
+
* @param previousColumn The column at the same index in the previous settings cycle.
|
|
1173
|
+
* @param currentColumn The column now occupying this index.
|
|
1174
|
+
* @param editorType The editor component type requested for the new column.
|
|
1175
|
+
*/
|
|
1176
|
+
reusableEditorRef(previousColumn, currentColumn, editorType) {
|
|
1177
|
+
const previousRef = previousColumn?._editorComponentReference;
|
|
1178
|
+
if (!previousRef || previousRef.componentType !== editorType) {
|
|
1179
|
+
return undefined;
|
|
1180
|
+
}
|
|
1181
|
+
// Same logical column still occupies this index. Columns without a `data` binding are identified
|
|
1182
|
+
// purely by position, so two `undefined` data values compare equal and recycle as before.
|
|
1183
|
+
const sameLogicalColumn = previousColumn?.data ===
|
|
1184
|
+
currentColumn.data;
|
|
1185
|
+
return sameLogicalColumn ? previousRef : undefined;
|
|
1186
|
+
}
|
|
988
1187
|
/**
|
|
989
1188
|
* Updates the column validator for columns with a custom validator.
|
|
990
1189
|
* @param mergedSettings The merged grid settings.
|
|
@@ -1152,6 +1351,7 @@ class HotTableComponent {
|
|
|
1152
1351
|
_hotConfig;
|
|
1153
1352
|
ngZone;
|
|
1154
1353
|
environmentInjector;
|
|
1354
|
+
_dynamicComponentService;
|
|
1155
1355
|
// component inputs
|
|
1156
1356
|
/** The data for the Handsontable instance. */
|
|
1157
1357
|
data = null;
|
|
@@ -1161,19 +1361,20 @@ class HotTableComponent {
|
|
|
1161
1361
|
container;
|
|
1162
1362
|
/** The Handsontable instance. */
|
|
1163
1363
|
__hotInstance = null;
|
|
1164
|
-
|
|
1165
|
-
constructor(_hotSettingsResolver, _hotConfig, ngZone, environmentInjector) {
|
|
1364
|
+
_destroyRef = inject(DestroyRef);
|
|
1365
|
+
constructor(_hotSettingsResolver, _hotConfig, ngZone, environmentInjector, _dynamicComponentService) {
|
|
1166
1366
|
this._hotSettingsResolver = _hotSettingsResolver;
|
|
1167
1367
|
this._hotConfig = _hotConfig;
|
|
1168
1368
|
this.ngZone = ngZone;
|
|
1169
1369
|
this.environmentInjector = environmentInjector;
|
|
1370
|
+
this._dynamicComponentService = _dynamicComponentService;
|
|
1170
1371
|
}
|
|
1171
1372
|
/**
|
|
1172
1373
|
* Gets the Handsontable instance.
|
|
1173
1374
|
* @returns The Handsontable instance or `null` if it's not yet been created or has been destroyed.
|
|
1174
1375
|
*/
|
|
1175
1376
|
get hotInstance() {
|
|
1176
|
-
if (!this.__hotInstance ||
|
|
1377
|
+
if (!this.__hotInstance || !this.__hotInstance.isDestroyed) {
|
|
1177
1378
|
// Will return the Handsontable instance or `null` if it's not yet been created.
|
|
1178
1379
|
return this.__hotInstance;
|
|
1179
1380
|
}
|
|
@@ -1202,7 +1403,7 @@ class HotTableComponent {
|
|
|
1202
1403
|
this.hotInstance._angularEnvironmentInjector = this.environmentInjector;
|
|
1203
1404
|
this.hotInstance.init();
|
|
1204
1405
|
});
|
|
1205
|
-
this.
|
|
1406
|
+
this._hotConfig.config$.pipe(skip(1), takeUntilDestroyed(this._destroyRef)).subscribe((config) => {
|
|
1206
1407
|
if (this.hotInstance) {
|
|
1207
1408
|
const negotiatedSettings = this.getNegotiatedSettings(this.settings);
|
|
1208
1409
|
this.updateHotTable(negotiatedSettings);
|
|
@@ -1214,8 +1415,28 @@ class HotTableComponent {
|
|
|
1214
1415
|
return;
|
|
1215
1416
|
}
|
|
1216
1417
|
if (changes.settings && !changes.settings.firstChange) {
|
|
1217
|
-
|
|
1418
|
+
// Capture old editor refs before applying new settings so HOT can close any active editor first.
|
|
1419
|
+
const prevColumns = this.__hotInstance?.getSettings().columns;
|
|
1420
|
+
const prevColumnsArray = Array.isArray(prevColumns) ? prevColumns : undefined;
|
|
1421
|
+
// Pass the previous columns so unchanged editor types recycle their existing component
|
|
1422
|
+
// instead of creating a fresh one on every settings change.
|
|
1423
|
+
const newOptions = this._hotSettingsResolver.applyCustomSettings(changes.settings.currentValue, prevColumnsArray);
|
|
1424
|
+
// updateHotTable closes any active editor via HOT.updateSettings before we destroy old refs.
|
|
1218
1425
|
this.updateHotTable(newOptions);
|
|
1426
|
+
// Only destroy old editor refs when new settings actually replace columns.
|
|
1427
|
+
// If newOptions has no columns, HOT keeps the old column objects active — destroying
|
|
1428
|
+
// their refs would crash FactoryEditorAdapter / BaseEditorAdapter on next edit.
|
|
1429
|
+
if (prevColumnsArray && Array.isArray(newOptions.columns)) {
|
|
1430
|
+
// Refs recycled into the new columns must survive — destroy only the ones left behind.
|
|
1431
|
+
const reusedRefs = new Set(newOptions.columns
|
|
1432
|
+
.map((column) => column._editorComponentReference)
|
|
1433
|
+
.filter((ref) => !!ref));
|
|
1434
|
+
prevColumnsArray.forEach((column) => {
|
|
1435
|
+
if (column._editorComponentReference && !reusedRefs.has(column._editorComponentReference)) {
|
|
1436
|
+
column._editorComponentReference.destroy();
|
|
1437
|
+
}
|
|
1438
|
+
});
|
|
1439
|
+
}
|
|
1219
1440
|
}
|
|
1220
1441
|
if (changes.data && !changes.data.firstChange) {
|
|
1221
1442
|
this.hotInstance?.updateData(changes.data.currentValue);
|
|
@@ -1226,10 +1447,14 @@ class HotTableComponent {
|
|
|
1226
1447
|
*/
|
|
1227
1448
|
ngOnDestroy() {
|
|
1228
1449
|
this.ngZone.runOutsideAngular(() => {
|
|
1229
|
-
if (!this.
|
|
1450
|
+
if (!this.__hotInstance || this.__hotInstance.isDestroyed) {
|
|
1230
1451
|
return;
|
|
1231
1452
|
}
|
|
1232
|
-
|
|
1453
|
+
// Destroy renderer Angular components attached to table cells before HOT removes the DOM.
|
|
1454
|
+
if (this.container) {
|
|
1455
|
+
this._dynamicComponentService.cleanupContainer(this.container.nativeElement, this.__hotInstance);
|
|
1456
|
+
}
|
|
1457
|
+
const columns = this.__hotInstance.getSettings().columns;
|
|
1233
1458
|
if (columns && Array.isArray(columns)) {
|
|
1234
1459
|
columns.forEach((column) => {
|
|
1235
1460
|
if (column._editorComponentReference) {
|
|
@@ -1237,9 +1462,8 @@ class HotTableComponent {
|
|
|
1237
1462
|
}
|
|
1238
1463
|
});
|
|
1239
1464
|
}
|
|
1240
|
-
this.
|
|
1465
|
+
this.__hotInstance.destroy();
|
|
1241
1466
|
});
|
|
1242
|
-
this.configSubscription.unsubscribe();
|
|
1243
1467
|
}
|
|
1244
1468
|
/**
|
|
1245
1469
|
* Updates the Handsontable instance with new settings.
|
|
@@ -1289,13 +1513,13 @@ class HotTableComponent {
|
|
|
1289
1513
|
}
|
|
1290
1514
|
return negotiatedSettings;
|
|
1291
1515
|
}
|
|
1292
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.24", ngImport: i0, type: HotTableComponent, deps: [{ token: HotSettingsResolver }, { token: HotGlobalConfigService }, { token: i0.NgZone }, { token: i0.EnvironmentInjector }], target: i0.ɵɵFactoryTarget.Component });
|
|
1516
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.24", ngImport: i0, type: HotTableComponent, deps: [{ token: HotSettingsResolver }, { token: HotGlobalConfigService }, { token: i0.NgZone }, { token: i0.EnvironmentInjector }, { token: DynamicComponentService }], target: i0.ɵɵFactoryTarget.Component });
|
|
1293
1517
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.24", type: HotTableComponent, isStandalone: true, selector: "hot-table", inputs: { data: "data", settings: "settings" }, providers: [HotSettingsResolver], viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true }], usesOnChanges: true, ngImport: i0, template: '<div #container></div>', isInline: true, styles: [":host{display:block}\n"], encapsulation: i0.ViewEncapsulation.None });
|
|
1294
1518
|
}
|
|
1295
1519
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.24", ngImport: i0, type: HotTableComponent, decorators: [{
|
|
1296
1520
|
type: Component,
|
|
1297
1521
|
args: [{ selector: 'hot-table', template: '<div #container></div>', encapsulation: ViewEncapsulation.None, providers: [HotSettingsResolver], styles: [":host{display:block}\n"] }]
|
|
1298
|
-
}], ctorParameters: () => [{ type: HotSettingsResolver }, { type: HotGlobalConfigService }, { type: i0.NgZone }, { type: i0.EnvironmentInjector }], propDecorators: { data: [{
|
|
1522
|
+
}], ctorParameters: () => [{ type: HotSettingsResolver }, { type: HotGlobalConfigService }, { type: i0.NgZone }, { type: i0.EnvironmentInjector }, { type: DynamicComponentService }], propDecorators: { data: [{
|
|
1299
1523
|
type: Input
|
|
1300
1524
|
}], settings: [{
|
|
1301
1525
|
type: Input
|