@handsontable/angular-wrapper 17.1.0-rc9 → 18.0.0-rc1

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,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { ViewContainerRef, ViewChild, Input, ChangeDetectionStrategy, Component, createComponent, EventEmitter, Output, HostBinding, Directive, Injectable, InjectionToken, Inject, ViewEncapsulation, NgModule } from '@angular/core';
2
+ import { ViewContainerRef, ViewChild, Input, ChangeDetectionStrategy, Component, createComponent, Directive, EventEmitter, Output, HostBinding, Injectable, InjectionToken, Inject, inject, DestroyRef, ViewEncapsulation, NgModule } from '@angular/core';
3
3
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
4
  import Handsontable from 'handsontable/base';
5
5
  import { take, skip } from 'rxjs/operators';
@@ -43,8 +43,8 @@ class CustomEditorPlaceholderComponent {
43
43
  detachEditor() {
44
44
  this.container.detach();
45
45
  }
46
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: CustomEditorPlaceholderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
47
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.21", type: CustomEditorPlaceholderComponent, isStandalone: true, selector: "ng-component", inputs: { top: "top", left: "left", height: "height", width: "width", isVisible: "isVisible", placeholderCustomClass: "placeholderCustomClass", componentRef: "componentRef" }, viewQueries: [{ propertyName: "container", first: true, predicate: ["inputPlaceholder"], descendants: true, read: ViewContainerRef, static: true }], ngImport: i0, template: ` <div
46
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: CustomEditorPlaceholderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
47
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.25", type: CustomEditorPlaceholderComponent, isStandalone: true, selector: "ng-component", inputs: { top: "top", left: "left", height: "height", width: "width", isVisible: "isVisible", placeholderCustomClass: "placeholderCustomClass", componentRef: "componentRef" }, viewQueries: [{ propertyName: "container", first: true, predicate: ["inputPlaceholder"], descendants: true, read: ViewContainerRef, static: true }], ngImport: i0, template: ` <div
48
48
  [class]="placeholderCustomClass"
49
49
  [style.display]="display"
50
50
  [style.width.px]="width"
@@ -57,7 +57,7 @@ class CustomEditorPlaceholderComponent {
57
57
  <ng-template #inputPlaceholder></ng-template>
58
58
  </div>`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
59
59
  }
60
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: CustomEditorPlaceholderComponent, decorators: [{
60
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: CustomEditorPlaceholderComponent, decorators: [{
61
61
  type: Component,
62
62
  args: [{
63
63
  template: ` <div
@@ -136,14 +136,7 @@ class BaseEditorAdapter extends Handsontable.editors.BaseEditor {
136
136
  this._isPlaceholderReady = true;
137
137
  }
138
138
  this._componentRef = columnMeta._editorComponentReference;
139
- if (this._finishEditSubscription) {
140
- this._finishEditSubscription.unsubscribe();
141
- this._finishEditSubscription = undefined;
142
- }
143
- if (this._cancelEditSubscription) {
144
- this._cancelEditSubscription.unsubscribe();
145
- this._cancelEditSubscription = undefined;
146
- }
139
+ this.cleanupSubscriptions();
147
140
  this._finishEditSubscription = this._componentRef.instance.finishEdit
148
141
  .pipe(take(1))
149
142
  .subscribe(() => {
@@ -162,23 +155,23 @@ class BaseEditorAdapter extends Handsontable.editors.BaseEditor {
162
155
  close() {
163
156
  if (this.isOpened()) {
164
157
  this.resetEditorState();
165
- this._editorPlaceHolderRef.changeDetectorRef.detectChanges();
166
- this._editorPlaceHolderRef.instance.detachEditor();
167
- this._componentRef.instance.onClose();
158
+ this._editorPlaceHolderRef?.changeDetectorRef.detectChanges();
159
+ this._editorPlaceHolderRef?.instance.detachEditor();
160
+ this._componentRef?.instance.onClose();
168
161
  }
169
162
  }
170
163
  /**
171
164
  * Focuses the editor. This event is triggered by Handsontable.
172
165
  */
173
166
  focus() {
174
- this._componentRef.instance.onFocus();
167
+ this._componentRef?.instance.onFocus();
175
168
  }
176
169
  /**
177
170
  * Gets the value from the editor.
178
171
  * @returns The value from the editor.
179
172
  */
180
173
  getValue() {
181
- return this._componentRef.instance?.getValue();
174
+ return this._componentRef?.instance?.getValue();
182
175
  }
183
176
  /**
184
177
  * Opens the editor. This event is triggered by Handsontable.
@@ -190,20 +183,23 @@ class BaseEditorAdapter extends Handsontable.editors.BaseEditor {
190
183
  open(event) {
191
184
  this.hot.getShortcutManager().setActiveContextName('editor');
192
185
  this.applyPropsToEditor();
193
- this._componentRef.instance.onOpen(event);
186
+ this._componentRef?.instance.onOpen(event);
194
187
  }
195
188
  /**
196
189
  * Sets the value for the custom editor.
197
190
  * @param newValue The value to set.
198
191
  */
199
192
  setValue(newValue) {
200
- this._componentRef.instance?.setValue(newValue);
201
- this._componentRef.changeDetectorRef.detectChanges();
193
+ this._componentRef?.instance?.setValue(newValue);
194
+ this._componentRef?.changeDetectorRef.detectChanges();
202
195
  }
203
196
  /**
204
197
  * Applies properties to the custom editor and editor placeholder.
205
198
  */
206
199
  applyPropsToEditor() {
200
+ if (!this._componentRef || !this._editorPlaceHolderRef) {
201
+ return;
202
+ }
207
203
  const rect = this.getEditedCellRect();
208
204
  if (!this.isInFullEditMode()) {
209
205
  this._componentRef.instance.setValue(null);
@@ -253,14 +249,33 @@ class BaseEditorAdapter extends Handsontable.editors.BaseEditor {
253
249
  * Handles the after destroy event.
254
250
  */
255
251
  onAfterDestroy() {
252
+ this.cleanupSubscriptions();
256
253
  this._editorPlaceHolderRef?.destroy();
257
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
+ }
258
270
  /**
259
271
  * Resets the editor placeholder state.
260
272
  * We need to reset the editor placeholder state because we use it
261
273
  * to store multiple references to the custom editor.
262
274
  */
263
275
  resetEditorState() {
276
+ if (!this._editorPlaceHolderRef) {
277
+ return;
278
+ }
264
279
  this._editorPlaceHolderRef.setInput('top', undefined);
265
280
  this._editorPlaceHolderRef.setInput('left', undefined);
266
281
  this._editorPlaceHolderRef.setInput('height', undefined);
@@ -271,44 +286,28 @@ class BaseEditorAdapter extends Handsontable.editors.BaseEditor {
271
286
  }
272
287
 
273
288
  /**
274
- * Abstract base component for creating custom cell renderer components for Handsontable.
289
+ * Shared base directive for HotCellRendererComponent and HotCellRendererAdvancedComponent.
290
+ * Holds all @Input() properties and getProps() that both renderer variants share.
275
291
  *
276
- * This class provides a common interface and properties required by any custom cell renderer.
277
- *
278
- * @template TValue - The type of the component renderer.
292
+ * @template TValue - The type of the rendered cell value.
279
293
  * @template TProps - The type of additional renderer properties.
280
294
  */
281
- class HotCellRendererComponent {
282
- static RENDERER_MARKER = Symbol('HotCellRendererComponent');
295
+ class HotCellRendererBase {
283
296
  value = '';
284
297
  instance;
285
298
  td;
286
299
  row;
287
300
  col;
288
301
  prop;
289
- /**
290
- * The cell properties provided by Handsontable, extended with optional renderer-specific properties.
291
- */
292
302
  cellProperties;
293
- /**
294
- * Retrieves the renderer-specific properties from the cell properties.
295
- *
296
- * @returns The additional properties for the renderer.
297
- */
298
303
  getProps() {
299
304
  return this.cellProperties?.rendererProps ?? {};
300
305
  }
301
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: HotCellRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
302
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.21", type: HotCellRendererComponent, isStandalone: true, selector: "hot-cell-renderer", 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 });
306
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: HotCellRendererBase, deps: [], target: i0.ɵɵFactoryTarget.Directive });
307
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.25", type: HotCellRendererBase, isStandalone: true, inputs: { value: "value", instance: "instance", td: "td", row: "row", col: "col", prop: "prop", cellProperties: "cellProperties" }, ngImport: i0 });
303
308
  }
304
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: HotCellRendererComponent, decorators: [{
305
- type: Component,
306
- args: [{
307
- selector: 'hot-cell-renderer',
308
- template: `<!-- This is an abstract component. Extend this component and provide your own template. -->`,
309
- standalone: true,
310
- changeDetection: ChangeDetectionStrategy.OnPush,
311
- }]
309
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: HotCellRendererBase, decorators: [{
310
+ type: Directive
312
311
  }], propDecorators: { value: [{
313
312
  type: Input
314
313
  }], instance: [{
@@ -326,77 +325,59 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
326
325
  }] } });
327
326
 
328
327
  /**
329
- * Abstract class representing a Handsontable editor in angular.
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.
330
336
  */
331
- class HotCellEditorComponent {
332
- static EDITOR_MARKER = Symbol('HotCellEditorComponent');
333
- /** The tabindex attribute for the editor. */
334
- tabindex = -1;
335
- /** The data-hot-input attribute for the editor. */
336
- dataHotInput = '';
337
- /** The handsontableInput class for the editor. */
338
- handsontableInputClass = true;
339
- /** The height of the editor as a percentage of the parent container. */
337
+ class HotCellRendererComponent extends HotCellRendererBase {
338
+ static RENDERER_MARKER = Symbol('HotCellRendererComponent');
339
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: HotCellRendererComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
340
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.25", 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.25", 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 {
340
359
  heightFitParentContainer = 100;
341
- /** The width of the editor as a percentage of the parent container. */
342
360
  widthFitParentContainer = 100;
343
- /** The row index of the cell being edited. */
344
361
  row;
345
- /** The column index of the cell being edited. */
346
362
  column;
347
- /** The property name of the cell being edited. */
348
363
  prop;
349
- /** The original value of the cell being edited. */
350
364
  originalValue;
351
- /** The cell properties of the cell being edited. */
352
365
  cellProperties;
353
- /** Event emitted when the edit is finished.
354
- * The data will be saved to the model.
355
- */
356
366
  finishEdit = new EventEmitter();
357
- /** Event emitted when the edit is canceled.
358
- * The entered data will be reverted to the original value.
359
- */
360
367
  cancelEdit = new EventEmitter();
361
- /** The current value of the editor. */
362
- _value;
363
- /** Event triggered by Handsontable on closing the editor.
364
- * The user can define their own actions for
365
- * the custom editor to be called after the base logic. */
366
- onClose() { }
367
- /** Event triggered by Handsontable on open the editor.
368
- * The user can define their own actions for
369
- * the custom editor to be called after the base logic. */
370
- onOpen(event) { }
371
- /**
372
- * Gets the current value of the editor.
373
- * @returns The current value of the editor.
374
- */
368
+ value;
375
369
  getValue() {
376
- return this._value;
370
+ return this.value;
377
371
  }
378
- /**
379
- * Sets the current value of the editor.
380
- * @param value The value to set.
381
- */
382
372
  setValue(value) {
383
- this._value = value;
373
+ this.value = value;
384
374
  }
385
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: HotCellEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
386
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.21", type: HotCellEditorComponent, isStandalone: true, inputs: { row: "row", column: "column", prop: "prop", originalValue: "originalValue", cellProperties: "cellProperties" }, outputs: { finishEdit: "finishEdit", cancelEdit: "cancelEdit" }, host: { properties: { "attr.tabindex": "this.tabindex", "attr.data-hot-input": "this.dataHotInput", "class.handsontableInput": "this.handsontableInputClass", "style.height.%": "this.heightFitParentContainer", "style.width.%": "this.widthFitParentContainer" } }, ngImport: i0 });
375
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: HotCellEditorBase, deps: [], target: i0.ɵɵFactoryTarget.Directive });
376
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.25", 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 });
387
377
  }
388
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: HotCellEditorComponent, decorators: [{
378
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: HotCellEditorBase, decorators: [{
389
379
  type: Directive
390
- }], propDecorators: { tabindex: [{
391
- type: HostBinding,
392
- args: ['attr.tabindex']
393
- }], dataHotInput: [{
394
- type: HostBinding,
395
- args: ['attr.data-hot-input']
396
- }], handsontableInputClass: [{
397
- type: HostBinding,
398
- args: ['class.handsontableInput']
399
- }], heightFitParentContainer: [{
380
+ }], propDecorators: { heightFitParentContainer: [{
400
381
  type: HostBinding,
401
382
  args: ['style.height.%']
402
383
  }], widthFitParentContainer: [{
@@ -418,6 +399,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
418
399
  type: Output
419
400
  }] } });
420
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.25", ngImport: i0, type: HotCellEditorComponent, deps: null, target: i0.ɵɵFactoryTarget.Directive });
422
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.25", 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.25", 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
+
421
437
  /**
422
438
  * Factory function to create a custom Handsontable editor adapter for Angular components.
423
439
  *
@@ -438,7 +454,7 @@ const FactoryEditorAdapter = (componentRef) => editorFactory({
438
454
  editor._finishEditSubscription = undefined;
439
455
  editor._cancelEditSubscription = undefined;
440
456
  createEditorPlaceholder(editor, editor.hot._angularEnvironmentInjector);
441
- editor.input = editor._editorPlaceHolderRef.location.nativeElement;
457
+ editor.input = editor._editorPlaceHolderRef?.location.nativeElement ?? document.createElement('div');
442
458
  editor._afterRowResizeCallback = () => {
443
459
  if (editor.isOpened()) {
444
460
  applyPropsToEditor(editor);
@@ -450,6 +466,7 @@ const FactoryEditorAdapter = (componentRef) => editorFactory({
450
466
  }
451
467
  };
452
468
  editor._afterDestroyCallback = () => {
469
+ cleanupSubscriptions(editor);
453
470
  if (editor._editorPlaceHolderRef) {
454
471
  editor._editorPlaceHolderRef.destroy();
455
472
  }
@@ -477,8 +494,8 @@ const FactoryEditorAdapter = (componentRef) => editorFactory({
477
494
  onFocus: (editor) => editor._componentRef.instance.onFocus?.(editor),
478
495
  afterClose: (editor) => {
479
496
  resetEditorState(editor);
480
- editor._editorPlaceHolderRef.changeDetectorRef.detectChanges();
481
- editor._editorPlaceHolderRef.instance.detachEditor();
497
+ editor._editorPlaceHolderRef?.changeDetectorRef.detectChanges();
498
+ editor._editorPlaceHolderRef?.instance.detachEditor();
482
499
  editor._componentRef.instance.afterClose?.(editor);
483
500
  },
484
501
  getValue: (editor) => editor._componentRef.instance.getValue(),
@@ -557,35 +574,18 @@ function cleanupSubscriptions(editor) {
557
574
  /**
558
575
  * Abstract base component for creating advanced custom cell renderer components for Handsontable.
559
576
  *
560
- * This class provides a common interface and properties required by any custom cell renderer.
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.
561
579
  *
562
580
  * @template TValue - The type of the component renderer.
563
581
  * @template TProps - The type of additional renderer properties.
564
582
  */
565
- class HotCellRendererAdvancedComponent {
583
+ class HotCellRendererAdvancedComponent extends HotCellRendererBase {
566
584
  static RENDERER_MARKER = Symbol('HotCellRendererAdvancedComponent');
567
- value = '';
568
- instance;
569
- td;
570
- row;
571
- col;
572
- prop;
573
- /**
574
- * The cell properties provided by Handsontable, extended with optional renderer-specific properties.
575
- */
576
- cellProperties;
577
- /**
578
- * Retrieves the renderer-specific properties from the cell properties.
579
- *
580
- * @returns The additional properties for the renderer.
581
- */
582
- getProps() {
583
- return this.cellProperties?.rendererProps ?? {};
584
- }
585
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: HotCellRendererAdvancedComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
586
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.21", 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.25", ngImport: i0, type: HotCellRendererAdvancedComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
586
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.25", 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 });
587
587
  }
588
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: HotCellRendererAdvancedComponent, decorators: [{
588
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: HotCellRendererAdvancedComponent, decorators: [{
589
589
  type: Component,
590
590
  args: [{
591
591
  selector: 'hot-cell-renderer-advanced',
@@ -593,111 +593,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
593
593
  standalone: true,
594
594
  changeDetection: ChangeDetectionStrategy.OnPush,
595
595
  }]
596
- }], propDecorators: { value: [{
597
- type: Input
598
- }], instance: [{
599
- type: Input
600
- }], td: [{
601
- type: Input
602
- }], row: [{
603
- type: Input
604
- }], col: [{
605
- type: Input
606
- }], prop: [{
607
- type: Input
608
- }], cellProperties: [{
609
- type: Input
610
- }] } });
596
+ }] });
611
597
 
612
598
  /**
613
- * Abstract class representing a Handsontable editor in angular.
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.
614
604
  */
615
- class HotCellEditorAdvancedComponent {
605
+ class HotCellEditorAdvancedComponent extends HotCellEditorBase {
616
606
  static EDITOR_MARKER = Symbol('HotCellEditorAdvancedComponent');
617
- /** The height of the editor as a percentage of the parent container. */
618
- heightFitParentContainer = 100;
619
- /** The width of the editor as a percentage of the parent container. */
620
- widthFitParentContainer = 100;
621
- /** The row index of the cell being edited. */
622
- row;
623
- /** The column index of the cell being edited. */
624
- column;
625
- /** The property name of the cell being edited. */
626
- prop;
627
- /** The original value of the cell being edited. */
628
- originalValue;
629
- /** The cell properties of the cell being edited. */
630
- cellProperties;
631
- /** Event emitted when the edit is finished.
632
- * The data will be saved to the model.
633
- */
634
- finishEdit = new EventEmitter();
635
- /** Event emitted when the edit is canceled.
636
- * The entered data will be reverted to the original value.
637
- */
638
- cancelEdit = new EventEmitter();
639
- /** The current value of the editor. */
640
- value;
641
- /** Event triggered by Handsontable on focus the editor.
642
- * The user have to define focus logic.
643
- */
607
+ /** Event triggered by Handsontable on focusing the editor. Available in advanced mode. */
644
608
  onFocus(editor) { }
645
- /**
646
- * Gets the current value of the editor.
647
- * @returns The current value of the editor.
648
- */
649
- getValue() {
650
- return this.value;
651
- }
652
- /**
653
- * Sets the current value of the editor.
654
- * @param value The value to set.
655
- */
656
- setValue(value) {
657
- this.value = value;
658
- }
659
- /** 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. */
660
610
  position = 'container';
661
611
  /** The shortcuts available for the editor. Available in advanced mode. */
662
612
  shortcuts;
663
- /** The group name for the shortcuts. Available in advanced mode.*/
613
+ /** The group name for the shortcuts. Available in advanced mode. */
664
614
  shortcutsGroup;
665
- /** Configuration. Available in advanced mode. */
615
+ /** Configuration object. Available in advanced mode. */
666
616
  config;
667
- /** 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. */
668
618
  afterOpen(editor, event) { }
669
619
  /** Lifecycle hook called after the editor is closed. Available in advanced mode. */
670
620
  afterClose(editor) { }
671
- /** 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. */
672
622
  afterInit(editor) { }
673
623
  /** Lifecycle hook called before the editor is opened. Available in advanced mode. */
674
624
  beforeOpen(editor, { row, col, prop, td, originalValue, cellProperties, }) { }
675
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: HotCellEditorAdvancedComponent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
676
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.21", type: HotCellEditorAdvancedComponent, 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 });
625
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: HotCellEditorAdvancedComponent, deps: null, target: i0.ɵɵFactoryTarget.Directive });
626
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.25", type: HotCellEditorAdvancedComponent, isStandalone: true, usesInheritance: true, ngImport: i0 });
677
627
  }
678
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: HotCellEditorAdvancedComponent, decorators: [{
628
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: HotCellEditorAdvancedComponent, decorators: [{
679
629
  type: Directive
680
- }], propDecorators: { heightFitParentContainer: [{
681
- type: HostBinding,
682
- args: ['style.height.%']
683
- }], widthFitParentContainer: [{
684
- type: HostBinding,
685
- args: ['style.width.%']
686
- }], row: [{
687
- type: Input
688
- }], column: [{
689
- type: Input
690
- }], prop: [{
691
- type: Input
692
- }], originalValue: [{
693
- type: Input
694
- }], cellProperties: [{
695
- type: Input
696
- }], finishEdit: [{
697
- type: Output
698
- }], cancelEdit: [{
699
- type: Output
700
- }] } });
630
+ }] });
701
631
 
702
632
  const INVALID_RENDERER_WARNING = 'The provided renderer component was not recognized as a valid custom renderer. ' +
703
633
  'It must either extend HotCellRendererComponent or be a valid TemplateRef. ' +
@@ -705,6 +635,11 @@ const INVALID_RENDERER_WARNING = 'The provided renderer component was not recogn
705
635
  const INVALID_ADVANCED_RENDERER_WARNING = 'The provided renderer component was not recognized as a valid custom renderer. ' +
706
636
  'It must either extend HotCellRendererAdvancedComponent. ' +
707
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
+ ];
708
643
  /**
709
644
  * Type guard that checks if the given object is a TemplateRef.
710
645
  *
@@ -712,7 +647,7 @@ const INVALID_ADVANCED_RENDERER_WARNING = 'The provided renderer component was n
712
647
  * @returns True if the object is a TemplateRef; otherwise, false.
713
648
  */
714
649
  function isTemplateRef(obj) {
715
- return obj && typeof obj.createEmbeddedView === 'function';
650
+ return !!obj && typeof obj.createEmbeddedView === 'function';
716
651
  }
717
652
  /**
718
653
  * Type guard to check if an object is an instance of HotCellRendererComponent.
@@ -745,6 +680,23 @@ function isAdvancedHotCellRendererComponent(obj) {
745
680
  class DynamicComponentService {
746
681
  appRef;
747
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();
748
700
  constructor(appRef, environmentInjector) {
749
701
  this.appRef = appRef;
750
702
  this.environmentInjector = environmentInjector;
@@ -759,6 +711,7 @@ class DynamicComponentService {
759
711
  * @returns A renderer function that can be used in Handsontable's configuration.
760
712
  */
761
713
  createRendererFromComponent(component, componentProps = {}, register = false) {
714
+ let registered = false;
762
715
  return (instance, td, row, col, prop, value, cellProperties) => {
763
716
  const properties = {
764
717
  value,
@@ -769,24 +722,24 @@ class DynamicComponentService {
769
722
  prop,
770
723
  cellProperties,
771
724
  };
772
- if (componentProps) {
773
- Object.assign(cellProperties, { rendererProps: componentProps });
774
- }
775
- const rendererParameters = [instance, td, row, col, prop, value, cellProperties];
776
- baseRenderer.apply(this, rendererParameters);
777
- td.innerHTML = '';
725
+ cellProperties.rendererProps = componentProps;
726
+ baseRenderer.call(this, instance, td, row, col, prop, value, cellProperties);
727
+ this.registerSweepHook(instance);
778
728
  if (isTemplateRef(component)) {
779
- this.attachTemplateToElement(component, td, properties);
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);
780
733
  }
781
734
  else if (isHotCellRendererComponent(component)) {
782
- const componentRef = this.createComponent(component, properties);
783
- this.attachComponentToElement(componentRef, td);
735
+ this.renderComponent(td, component, properties);
784
736
  }
785
737
  else {
786
738
  console.warn(INVALID_RENDERER_WARNING);
787
739
  }
788
- if (register && isHotCellRendererComponent(component)) {
789
- Handsontable.renderers.registerRenderer(component.constructor.name, component);
740
+ if (register && !registered && isHotCellRendererComponent(component)) {
741
+ Handsontable.renderers.registerRenderer(component.name, component);
742
+ registered = true;
790
743
  }
791
744
  return td;
792
745
  };
@@ -801,6 +754,7 @@ class DynamicComponentService {
801
754
  * @returns A renderer function that can be used in Handsontable's configuration.
802
755
  */
803
756
  createRendererWithFactory(component, componentProps = {}, register = false) {
757
+ let registered = false;
804
758
  return rendererFactory(({ instance, td, row, column, prop, value, cellProperties }) => {
805
759
  const properties = {
806
760
  value,
@@ -811,21 +765,153 @@ class DynamicComponentService {
811
765
  prop,
812
766
  cellProperties,
813
767
  };
814
- if (componentProps) {
815
- Object.assign(cellProperties, { rendererProps: componentProps });
816
- }
817
- td.innerHTML = '';
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);
818
773
  if (isAdvancedHotCellRendererComponent(component)) {
819
- const componentRef = this.createComponent(component, properties);
820
- this.attachComponentToElement(componentRef, td);
774
+ this.renderComponent(td, component, properties);
821
775
  }
822
776
  else {
823
777
  console.warn(INVALID_ADVANCED_RENDERER_WARNING);
824
778
  }
825
- if (register && isAdvancedHotCellRendererComponent(component)) {
826
- registerRenderer(component.constructor.name, 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);
827
808
  }
828
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);
858
+ }
859
+ });
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;
829
915
  }
830
916
  /**
831
917
  * Attaches an embedded view created from a TemplateRef to a given DOM element.
@@ -833,6 +919,7 @@ class DynamicComponentService {
833
919
  * @param template - The TemplateRef to create an embedded view from.
834
920
  * @param tdEl - The target DOM element (a table cell) to which the view will be appended.
835
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.
836
923
  */
837
924
  attachTemplateToElement(template, tdEl, properties) {
838
925
  const embeddedView = template.createEmbeddedView({
@@ -843,6 +930,7 @@ class DynamicComponentService {
843
930
  embeddedView.rootNodes.forEach((node) => {
844
931
  tdEl.appendChild(node);
845
932
  });
933
+ return embeddedView;
846
934
  }
847
935
  /**
848
936
  * Dynamically creates an Angular component of the given type.
@@ -855,18 +943,46 @@ class DynamicComponentService {
855
943
  const componentRef = createComponent(component, {
856
944
  environmentInjector: this.environmentInjector,
857
945
  });
858
- Object.keys(rendererParameters).forEach((key) => {
859
- if (Object.prototype.hasOwnProperty.call(rendererParameters, key)) {
860
- componentRef.setInput(key, rendererParameters[key]);
861
- }
862
- else {
863
- console.warn(`Input property "${key}" does not exist on component instance: ${component?.name}.`);
864
- }
865
- });
946
+ this.applyInputs(componentRef, rendererParameters);
866
947
  componentRef.changeDetectorRef.detectChanges();
867
948
  this.appRef.attachView(componentRef.hostView);
868
949
  return componentRef;
869
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
+ }
870
986
  /**
871
987
  * Attaches a dynamically created component's view to a specified DOM container element.
872
988
  *
@@ -874,22 +990,44 @@ class DynamicComponentService {
874
990
  * @param container - The target DOM element to which the component's root node will be appended.
875
991
  */
876
992
  attachComponentToElement(componentRef, container) {
877
- const domElem = componentRef.hostView.rootNodes[0];
878
- container.appendChild(domElem);
993
+ componentRef.hostView.rootNodes.forEach((node) => {
994
+ container.appendChild(node);
995
+ });
879
996
  }
880
997
  /**
881
998
  * Destroys a dynamically created component and detaches its view from the Angular application.
882
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
+ *
883
1005
  * @param componentRef - The reference to the component to be destroyed.
884
1006
  */
885
1007
  destroyComponent(componentRef) {
886
- this.appRef.detachView(componentRef.hostView);
1008
+ const hostView = componentRef.hostView;
1009
+ if (hostView.destroyed) {
1010
+ return;
1011
+ }
1012
+ this.appRef.detachView(hostView);
887
1013
  componentRef.destroy();
888
1014
  }
889
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: DynamicComponentService, deps: [{ token: i0.ApplicationRef }, { token: i0.EnvironmentInjector }], target: i0.ɵɵFactoryTarget.Injectable });
890
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: DynamicComponentService, providedIn: 'root' });
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
+ }
1027
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: DynamicComponentService, deps: [{ token: i0.ApplicationRef }, { token: i0.EnvironmentInjector }], target: i0.ɵɵFactoryTarget.Injectable });
1028
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: DynamicComponentService, providedIn: 'root' });
891
1029
  }
892
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: DynamicComponentService, decorators: [{
1030
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: DynamicComponentService, decorators: [{
893
1031
  type: Injectable,
894
1032
  args: [{
895
1033
  providedIn: 'root',
@@ -897,6 +1035,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
897
1035
  }], ctorParameters: () => [{ type: i0.ApplicationRef }, { type: i0.EnvironmentInjector }] });
898
1036
 
899
1037
  const AVAILABLE_HOOKS_SET = new Set(Handsontable.hooks.getRegistered());
1038
+ const HOT_ZONE_WRAPPED = Symbol('hotZoneWrapped');
900
1039
  /**
901
1040
  * Service to resolve and apply custom settings for Handsontable settings object.
902
1041
  */
@@ -912,15 +1051,26 @@ class HotSettingsResolver {
912
1051
  /**
913
1052
  * Applies custom settings to the provided GridSettings.
914
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.
915
1057
  * @returns The merged grid settings with custom settings applied.
916
1058
  */
917
- applyCustomSettings(settings) {
918
- const mergedSettings = settings;
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
+ }
919
1069
  this.updateColumnRendererForGivenCustomRenderer(mergedSettings);
920
- this.updateColumnEditorForGivenCustomEditor(mergedSettings);
1070
+ this.updateColumnEditorForGivenCustomEditor(mergedSettings, previousColumns);
921
1071
  this.updateColumnValidatorForGivenCustomValidator(mergedSettings);
922
1072
  this.wrapHooksInNgZone(mergedSettings);
923
- return mergedSettings ?? {};
1073
+ return mergedSettings;
924
1074
  }
925
1075
  /**
926
1076
  * Ensures that hook callbacks in the provided grid settings run inside Angular's zone.
@@ -929,12 +1079,18 @@ class HotSettingsResolver {
929
1079
  */
930
1080
  wrapHooksInNgZone(settings) {
931
1081
  const ngZone = this.ngZone;
932
- AVAILABLE_HOOKS_SET.forEach((key) => {
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
+ }
933
1087
  const option = settings[key];
934
- if (typeof option === 'function') {
935
- settings[key] = function (...args) {
1088
+ if (typeof option === 'function' && !option[HOT_ZONE_WRAPPED]) {
1089
+ const wrapped = function (...args) {
936
1090
  return ngZone.run(() => option.apply(this, args));
937
1091
  };
1092
+ wrapped[HOT_ZONE_WRAPPED] = true;
1093
+ settings[key] = wrapped;
938
1094
  }
939
1095
  });
940
1096
  }
@@ -961,31 +1117,73 @@ class HotSettingsResolver {
961
1117
  }
962
1118
  /**
963
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
+ *
964
1124
  * @param mergedSettings The merged grid settings.
1125
+ * @param previousColumns The previously resolved columns, used to recycle editor components.
965
1126
  */
966
- updateColumnEditorForGivenCustomEditor(mergedSettings) {
1127
+ updateColumnEditorForGivenCustomEditor(mergedSettings, previousColumns) {
967
1128
  if (!Array.isArray(mergedSettings?.columns)) {
968
1129
  return;
969
1130
  }
970
- mergedSettings?.columns
971
- ?.filter((settings) => this.isEditorComponentRefType(settings.editor) || this.isAdvancedEditorComponentRefType(settings.editor))
972
- ?.forEach((cellSettings) => {
973
- if (this.isAdvancedEditorComponentRefType(cellSettings.editor)) {
974
- const component = createComponent(cellSettings.editor, {
975
- environmentInjector: this.environmentInjector,
976
- });
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) {
977
1150
  cellSettings.editor = FactoryEditorAdapter(component);
978
1151
  }
979
1152
  else {
980
- const component = createComponent(cellSettings.editor, {
981
- environmentInjector: this.environmentInjector,
982
- });
983
- cellSettings['_editorComponentReference'] = component;
984
- cellSettings['_environmentInjector'] = this.environmentInjector;
1153
+ internalSettings._environmentInjector = this.environmentInjector;
985
1154
  cellSettings.editor = BaseEditorAdapter;
986
1155
  }
987
1156
  });
988
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
+ }
989
1187
  /**
990
1188
  * Updates the column validator for columns with a custom validator.
991
1189
  * @param mergedSettings The merged grid settings.
@@ -1038,10 +1236,10 @@ class HotSettingsResolver {
1038
1236
  this.isTemplateRef(renderer) ||
1039
1237
  this.isAdvancedRendererComponentRefType(renderer);
1040
1238
  }
1041
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: HotSettingsResolver, deps: [{ token: DynamicComponentService }, { token: i0.EnvironmentInjector }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable });
1042
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: HotSettingsResolver });
1239
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: HotSettingsResolver, deps: [{ token: DynamicComponentService }, { token: i0.EnvironmentInjector }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable });
1240
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: HotSettingsResolver });
1043
1241
  }
1044
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: HotSettingsResolver, decorators: [{
1242
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: HotSettingsResolver, decorators: [{
1045
1243
  type: Injectable
1046
1244
  }], ctorParameters: () => [{ type: DynamicComponentService }, { type: i0.EnvironmentInjector }, { type: i0.NgZone }] });
1047
1245
 
@@ -1134,10 +1332,10 @@ class HotGlobalConfigService {
1134
1332
  resetConfig() {
1135
1333
  this.configSubject.next({ ...this.defaultConfig });
1136
1334
  }
1137
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: HotGlobalConfigService, deps: [{ token: HOT_GLOBAL_CONFIG }], target: i0.ɵɵFactoryTarget.Injectable });
1138
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: HotGlobalConfigService, providedIn: 'root' });
1335
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: HotGlobalConfigService, deps: [{ token: HOT_GLOBAL_CONFIG }], target: i0.ɵɵFactoryTarget.Injectable });
1336
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: HotGlobalConfigService, providedIn: 'root' });
1139
1337
  }
1140
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: HotGlobalConfigService, decorators: [{
1338
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: HotGlobalConfigService, decorators: [{
1141
1339
  type: Injectable,
1142
1340
  args: [{
1143
1341
  providedIn: 'root',
@@ -1147,37 +1345,43 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
1147
1345
  args: [HOT_GLOBAL_CONFIG]
1148
1346
  }] }] });
1149
1347
 
1150
- const HOT_DESTROYED_WARNING = 'The Handsontable instance bound to this component was destroyed and cannot be used properly.';
1348
+ const HOT_DESTROYED_WARNING = 'The Handsontable instance bound to this component was destroyed and cannot be' + ' used properly.';
1151
1349
  class HotTableComponent {
1152
1350
  _hotSettingsResolver;
1153
1351
  _hotConfig;
1154
1352
  ngZone;
1155
1353
  environmentInjector;
1156
- destroyRef;
1354
+ _dynamicComponentService;
1355
+ // component inputs
1157
1356
  /** The data for the Handsontable instance. */
1158
1357
  data = null;
1159
1358
  /** The settings for the Handsontable instance. */
1160
1359
  settings = {};
1360
+ /** The container element for the Handsontable instance. */
1161
1361
  container;
1162
1362
  /** The Handsontable instance. */
1163
1363
  __hotInstance = null;
1164
- constructor(_hotSettingsResolver, _hotConfig, ngZone, environmentInjector, destroyRef) {
1364
+ _destroyRef = inject(DestroyRef);
1365
+ constructor(_hotSettingsResolver, _hotConfig, ngZone, environmentInjector, _dynamicComponentService) {
1165
1366
  this._hotSettingsResolver = _hotSettingsResolver;
1166
1367
  this._hotConfig = _hotConfig;
1167
1368
  this.ngZone = ngZone;
1168
1369
  this.environmentInjector = environmentInjector;
1169
- this.destroyRef = destroyRef;
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?.isDestroyed) {
1377
+ if (!this.__hotInstance || !this.__hotInstance.isDestroyed) {
1378
+ // Will return the Handsontable instance or `null` if it's not yet been created.
1379
+ return this.__hotInstance;
1380
+ }
1381
+ else {
1177
1382
  console.warn(HOT_DESTROYED_WARNING);
1178
1383
  return null;
1179
1384
  }
1180
- return this.__hotInstance;
1181
1385
  }
1182
1386
  /**
1183
1387
  * Sets the Handsontable instance.
@@ -1193,13 +1397,13 @@ class HotTableComponent {
1193
1397
  ngAfterViewInit() {
1194
1398
  let options = this._hotSettingsResolver.applyCustomSettings(this.settings);
1195
1399
  const negotiatedSettings = this.getNegotiatedSettings(options);
1196
- options = { ...options, ...negotiatedSettings, data: this.data };
1400
+ options = { ...options, ...negotiatedSettings, ...(this.data != null ? { data: this.data } : {}) };
1197
1401
  this.ngZone.runOutsideAngular(() => {
1198
1402
  this.hotInstance = new Handsontable.Core(this.container.nativeElement, options);
1199
1403
  this.hotInstance._angularEnvironmentInjector = this.environmentInjector;
1200
1404
  this.hotInstance.init();
1201
1405
  });
1202
- this._hotConfig.config$.pipe(skip(1), takeUntilDestroyed(this.destroyRef)).subscribe(() => {
1406
+ this._hotConfig.config$.pipe(skip(1), takeUntilDestroyed(this._destroyRef)).subscribe((config) => {
1203
1407
  if (this.hotInstance) {
1204
1408
  const negotiatedSettings = this.getNegotiatedSettings(this.settings);
1205
1409
  this.updateHotTable(negotiatedSettings);
@@ -1211,37 +1415,56 @@ class HotTableComponent {
1211
1415
  return;
1212
1416
  }
1213
1417
  if (changes.settings && !changes.settings.firstChange) {
1214
- this.destroyEditorComponentRefs();
1215
- const newOptions = this._hotSettingsResolver.applyCustomSettings(changes.settings.currentValue);
1216
- const dataChanged = changes.data && !changes.data.firstChange;
1217
- this.updateHotTable(dataChanged ? { ...newOptions, data: changes.data.currentValue } : newOptions);
1218
- return;
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.
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
- this.ngZone.runOutsideAngular(() => {
1222
- this.hotInstance.updateData(changes.data.currentValue);
1223
- });
1442
+ this.hotInstance?.updateData(changes.data.currentValue);
1224
1443
  }
1225
1444
  }
1226
1445
  /**
1227
1446
  * Destroys the Handsontable instance and clears the columns from custom editors.
1228
1447
  */
1229
1448
  ngOnDestroy() {
1230
- if (!this.hotInstance) {
1231
- return;
1232
- }
1233
- this.destroyEditorComponentRefs();
1234
1449
  this.ngZone.runOutsideAngular(() => {
1235
- this.hotInstance.destroy();
1236
- this.hotInstance = null;
1450
+ if (!this.__hotInstance || this.__hotInstance.isDestroyed) {
1451
+ return;
1452
+ }
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;
1458
+ if (columns && Array.isArray(columns)) {
1459
+ columns.forEach((column) => {
1460
+ if (column._editorComponentReference) {
1461
+ column._editorComponentReference.destroy();
1462
+ }
1463
+ });
1464
+ }
1465
+ this.__hotInstance.destroy();
1237
1466
  });
1238
1467
  }
1239
- destroyEditorComponentRefs() {
1240
- const columns = this.hotInstance.getSettings().columns;
1241
- if (Array.isArray(columns)) {
1242
- columns.forEach((column) => column._editorComponentReference?.destroy());
1243
- }
1244
- }
1245
1468
  /**
1246
1469
  * Updates the Handsontable instance with new settings.
1247
1470
  * @param newSettings The new settings to apply to the Handsontable instance.
@@ -1258,7 +1481,7 @@ class HotTableComponent {
1258
1481
  }
1259
1482
  }
1260
1483
  this.ngZone.runOutsideAngular(() => {
1261
- this.hotInstance.updateSettings(filteredSettings, false);
1484
+ this.hotInstance?.updateSettings(filteredSettings, false);
1262
1485
  });
1263
1486
  }
1264
1487
  /**
@@ -1290,32 +1513,28 @@ class HotTableComponent {
1290
1513
  }
1291
1514
  return negotiatedSettings;
1292
1515
  }
1293
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: HotTableComponent, deps: [{ token: HotSettingsResolver }, { token: HotGlobalConfigService }, { token: i0.NgZone }, { token: i0.EnvironmentInjector }, { token: i0.DestroyRef }], target: i0.ɵɵFactoryTarget.Component });
1294
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.21", 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"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1516
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: HotTableComponent, deps: [{ token: HotSettingsResolver }, { token: HotGlobalConfigService }, { token: i0.NgZone }, { token: i0.EnvironmentInjector }, { token: DynamicComponentService }], target: i0.ɵɵFactoryTarget.Component });
1517
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.25", 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 });
1295
1518
  }
1296
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: HotTableComponent, decorators: [{
1519
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: HotTableComponent, decorators: [{
1297
1520
  type: Component,
1298
- args: [{ selector: 'hot-table', template: '<div #container></div>', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, providers: [HotSettingsResolver], styles: [":host{display:block}\n"] }]
1299
- }], ctorParameters: () => [{ type: HotSettingsResolver }, { type: HotGlobalConfigService }, { type: i0.NgZone }, { type: i0.EnvironmentInjector }, { type: i0.DestroyRef }], propDecorators: { data: [{
1521
+ args: [{ selector: 'hot-table', template: '<div #container></div>', encapsulation: ViewEncapsulation.None, providers: [HotSettingsResolver], styles: [":host{display:block}\n"] }]
1522
+ }], ctorParameters: () => [{ type: HotSettingsResolver }, { type: HotGlobalConfigService }, { type: i0.NgZone }, { type: i0.EnvironmentInjector }, { type: DynamicComponentService }], propDecorators: { data: [{
1300
1523
  type: Input
1301
1524
  }], settings: [{
1302
1525
  type: Input
1303
1526
  }], container: [{
1304
1527
  type: ViewChild,
1305
- args: ['container']
1528
+ args: ['container', { static: false }]
1306
1529
  }] } });
1307
1530
 
1308
1531
  class HotTableModule {
1309
- /**
1310
- * Placeholder for the library version.
1311
- * Replaced automatically during the pre-build/post-build process.
1312
- */
1313
- static version = '17.1.0-rc9';
1314
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: HotTableModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1315
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.21", ngImport: i0, type: HotTableModule, imports: [HotTableComponent], exports: [HotTableComponent] });
1316
- static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: HotTableModule });
1532
+ static version = '18.0.0-rc1';
1533
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: HotTableModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1534
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.25", ngImport: i0, type: HotTableModule, imports: [HotTableComponent], exports: [HotTableComponent] });
1535
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: HotTableModule });
1317
1536
  }
1318
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: HotTableModule, decorators: [{
1537
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: HotTableModule, decorators: [{
1319
1538
  type: NgModule,
1320
1539
  args: [{
1321
1540
  imports: [HotTableComponent],