UNPKG

@handsontable/angular-wrapper

Version:

Best Data Grid for Angular with Spreadsheet Look and Feel.

968 lines (955 loc) 41.7 kB
import * as i0 from '@angular/core'; import { ViewContainerRef, Component, ChangeDetectionStrategy, Input, ViewChild, createComponent, Injectable, InjectionToken, Inject, ViewEncapsulation, NgModule, EventEmitter, Directive, HostBinding, Output } from '@angular/core'; import Handsontable from 'handsontable/base'; import { take } from 'rxjs/operators'; import { baseRenderer } from 'handsontable/renderers'; import { BehaviorSubject } from 'rxjs'; /** * Component representing a placeholder for a custom editor in Handsontable. * It is used only within the wrapper. */ class CustomEditorPlaceholderComponent { /** The top position of the editor. */ top; /** The left position of the editor. */ left; /** The height of the editor. */ height; /** The width of the editor. */ width; set isVisible(value) { this._isVisible = value; } /** The reference to the component instance of the editor. */ set componentRef(hotEditorComponentRef) { if (hotEditorComponentRef) { this.container.insert(hotEditorComponentRef.hostView); } } /** The container for the editor's input placeholder. */ container; /** The display style of the editor. */ get display() { return this._isVisible ? 'block' : 'none'; } _isVisible = false; /** * Detaches the container from the Handsontable. */ detachEditor() { this.container.detach(); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CustomEditorPlaceholderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: CustomEditorPlaceholderComponent, selector: "ng-component", inputs: { top: "top", left: "left", height: "height", width: "width", isVisible: "isVisible", componentRef: "componentRef" }, viewQueries: [{ propertyName: "container", first: true, predicate: ["inputPlaceholder"], descendants: true, read: ViewContainerRef, static: true }], ngImport: i0, template: ` <div class="handsontableInputHolder ht_clone_master" [style.display]="display" [style.width.px]="width" [style.height.px]="height" [style.maxWidth.px]="width" [style.maxHeight.px]="height" [style.top.px]="top" [style.left.px]="left" > <ng-template #inputPlaceholder></ng-template> </div>`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CustomEditorPlaceholderComponent, decorators: [{ type: Component, args: [{ template: ` <div class="handsontableInputHolder ht_clone_master" [style.display]="display" [style.width.px]="width" [style.height.px]="height" [style.maxWidth.px]="width" [style.maxHeight.px]="height" [style.top.px]="top" [style.left.px]="left" > <ng-template #inputPlaceholder></ng-template> </div>`, changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, }] }], propDecorators: { top: [{ type: Input }], left: [{ type: Input }], height: [{ type: Input }], width: [{ type: Input }], isVisible: [{ type: Input }], componentRef: [{ type: Input }], container: [{ type: ViewChild, args: ['inputPlaceholder', { read: ViewContainerRef, static: true }] }] } }); /** * Adapter for BaseEditor from Handsontable. */ class BaseEditorAdapter extends Handsontable.editors.BaseEditor { /** Reference to the custom editor component. */ _componentRef; /** Reference to the editor placeholder component. */ _editorPlaceHolderRef; /** Flag indicating whether the placeholder is ready. */ _isPlaceholderReady = false; /** Subscription for the finish edit event. */ _finishEditSubscription; /** Subscription for the cancel edit event. */ _cancelEditSubscription; /** * Creates an instance of BaseEditorAdapter. * @param instance The Handsontable instance. */ constructor(instance) { super(instance); this.hot.addHook('afterRowResize', this.onAfterRowResize.bind(this)); this.hot.addHook('afterColumnResize', this.onAfterColumnResize.bind(this)); this.hot.addHook('afterDestroy', this.onAfterDestroy.bind(this)); } /** * Prepares the editor for editing. Parameters are passed from Handsontable. * @param row The row index. * @param column The column index. * @param prop The property name. * @param TD The table cell element. * @param originalValue The original value of the cell. * @param cellProperties The cell properties. */ prepare(row, column, prop, TD, originalValue, cellProperties) { if (!this.isOpened()) { super.prepare(row, column, prop, TD, originalValue, cellProperties); const columnMeta = this.hot.getColumnMeta(column); if (!this._isPlaceholderReady) { this.createEditorPlaceholder(columnMeta._environmentInjector); this._isPlaceholderReady = true; } this._componentRef = columnMeta._editorComponentReference; if (this._finishEditSubscription) { this._finishEditSubscription.unsubscribe(); this._finishEditSubscription = undefined; } if (this._cancelEditSubscription) { this._cancelEditSubscription.unsubscribe(); this._cancelEditSubscription = undefined; } this._finishEditSubscription = this._componentRef.instance.finishEdit .pipe(take(1)) .subscribe(() => { this.finishEditing(); }); this._cancelEditSubscription = this._componentRef.instance.cancelEdit .pipe(take(1)) .subscribe(() => { this.cancelChanges(); }); } } /** * Closes the editor. This event is triggered by Handsontable. */ close() { if (this.isOpened()) { this.resetEditorState(); this._editorPlaceHolderRef.changeDetectorRef.detectChanges(); this._editorPlaceHolderRef.instance.detachEditor(); this._componentRef.instance.onClose(); } } /** * Focuses the editor. This event is triggered by Handsontable. */ focus() { this._componentRef.instance.onFocus(); } /** * Gets the value from the editor. * @returns The value from the editor. */ getValue() { return this._componentRef.instance?.getValue(); } /** * Opens the editor. This event is triggered by Handsontable. * When opening, we set the shortcut context to 'editor'. * This allows the built-in keyboard shortcuts to operate within the editor. * @param event The event that triggered the opening of the editor. * @remarks When entering edit mode using double-click, keyboard shortcuts do not work. */ open(event) { this.hot.getShortcutManager().setActiveContextName('editor'); this.applyPropsToEditor(); this._componentRef.instance.onOpen(event); } /** * Sets the value for the custom editor. * @param newValue The value to set. */ setValue(newValue) { this._componentRef.instance?.setValue(newValue); this._componentRef.changeDetectorRef.detectChanges(); } /** * Applies properties to the custom editor and editor placeholder. */ applyPropsToEditor() { const rect = this.getEditedCellRect(); if (!this.isInFullEditMode()) { this._componentRef.instance.setValue(null); } this._componentRef.setInput('originalValue', this.originalValue); this._componentRef.setInput('row', this.row); this._componentRef.setInput('column', this.col); this._componentRef.setInput('prop', this.prop); this._componentRef.setInput('cellProperties', this.cellProperties); this._editorPlaceHolderRef.setInput('top', rect.top); this._editorPlaceHolderRef.setInput('left', rect.start); this._editorPlaceHolderRef.setInput('height', rect.height); this._editorPlaceHolderRef.setInput('width', rect.width); this._editorPlaceHolderRef.setInput('isVisible', true); this._editorPlaceHolderRef.setInput('componentRef', this._componentRef); this._editorPlaceHolderRef.changeDetectorRef.detectChanges(); } /** * Creates the editor placeholder and append it to hot rootElement. * @param injector The environment injector. */ createEditorPlaceholder(injector) { this._editorPlaceHolderRef = createComponent(CustomEditorPlaceholderComponent, { environmentInjector: injector, }); this.hot.rootElement.appendChild(this._editorPlaceHolderRef.location.nativeElement); } /** * Handles the after column resize event. * Helps adjust the editor size to the column size and update its position. */ onAfterColumnResize() { if (this.isOpened()) { this.applyPropsToEditor(); } } /** * Handles the after row resize event. * Helps adjust the editor size to the column size and update its position. */ onAfterRowResize() { if (this.isOpened()) { this.applyPropsToEditor(); } } /** * Handles the after destroy event. */ onAfterDestroy() { this._editorPlaceHolderRef?.destroy(); } /** * Resets the editor placeholder state. * We need to reset the editor placeholder state because we use it * to store multiple references to the custom editor. */ resetEditorState() { this._editorPlaceHolderRef.setInput('top', undefined); this._editorPlaceHolderRef.setInput('left', undefined); this._editorPlaceHolderRef.setInput('height', undefined); this._editorPlaceHolderRef.setInput('width', undefined); this._editorPlaceHolderRef.setInput('isVisible', false); this._editorPlaceHolderRef.setInput('componentRef', undefined); } } /** * Abstract base component for creating custom cell renderer components for Handsontable. * * This class provides a common interface and properties required by any custom cell renderer. * * @template TValue - The type of the component renderer. * @template TProps - The type of additional renderer properties. */ class HotCellRendererComponent { static RENDERER_MARKER = Symbol('HotCellRendererComponent'); value = ''; instance; td; row; col; prop; /** * The cell properties provided by Handsontable, extended with optional renderer-specific properties. */ cellProperties; /** * Retrieves the renderer-specific properties from the cell properties. * * @returns The additional properties for the renderer. */ getProps() { return this.cellProperties?.rendererProps ?? {}; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HotCellRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: HotCellRendererComponent, 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 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HotCellRendererComponent, decorators: [{ type: Component, args: [{ selector: 'hot-cell-renderer', template: `<!-- This is an abstract component. Extend this component and provide your own template. -->` }] }], propDecorators: { value: [{ type: Input }], instance: [{ type: Input }], td: [{ type: Input }], row: [{ type: Input }], col: [{ type: Input }], prop: [{ type: Input }], cellProperties: [{ type: Input }] } }); const INVALID_RENDERER_WARNING = 'The provided renderer component was not recognized as a valid custom renderer. ' + 'It must either extend HotCellRendererComponent or be a valid TemplateRef. ' + 'Please ensure that your custom renderer is implemented correctly and imported from the proper source.'; /** * Type guard that checks if the given object is a TemplateRef. * * @param obj - The object to check. * @returns True if the object is a TemplateRef; otherwise, false. */ function isTemplateRef(obj) { return obj && typeof obj.createEmbeddedView === 'function'; } /** * Type guard to check if an object is an instance of HotCellRendererComponent. * * @param obj - The object to check. * @returns True if the object is a HotCellRendererComponent, false otherwise. */ function isHotCellRendererComponent(obj) { return obj?.RENDERER_MARKER === HotCellRendererComponent.RENDERER_MARKER; } /** * Service for dynamically creating Angular components or templates as custom renderers for Handsontable. * * This service allows you to create a renderer function that wraps a given Angular component or TemplateRef * so that it can be used as a Handsontable renderer. * * @example * const customRenderer = dynamicComponentService.createRendererFromComponent(MyRendererComponent, { someProp: value }); * // Use customRenderer in your Handsontable configuration */ class DynamicComponentService { appRef; environmentInjector; constructor(appRef, environmentInjector) { this.appRef = appRef; this.environmentInjector = environmentInjector; } /** * Creates a custom renderer function for Handsontable from an Angular component or TemplateRef. * The generated renderer function will be used by Handsontable to render cell content. * * @param component - The Angular component type or TemplateRef to use as renderer. * @param componentProps - An object containing additional properties to use by the renderer. * @param register - If true, registers the renderer with Handsontable using the component's name. * @returns A renderer function that can be used in Handsontable's configuration. */ createRendererFromComponent(component, componentProps = {}, register = false) { return (instance, td, row, col, prop, value, cellProperties) => { const properties = { value, instance, td, row, col, prop, cellProperties }; if (componentProps) { Object.assign(cellProperties, { rendererProps: componentProps }); } const rendererParameters = [ instance, td, row, col, prop, value, cellProperties ]; baseRenderer.apply(this, rendererParameters); td.innerHTML = ''; if (isTemplateRef(component)) { this.attachTemplateToElement(component, td, properties); } else if (isHotCellRendererComponent(component)) { const componentRef = this.createComponent(component, properties); this.attachComponentToElement(componentRef, td); } else { console.warn(INVALID_RENDERER_WARNING); } if (register && isHotCellRendererComponent(component)) { Handsontable.renderers.registerRenderer(component.constructor.name, component); } return td; }; } /** * Attaches an embedded view created from a TemplateRef to a given DOM element. * * @param template - The TemplateRef to create an embedded view from. * @param tdEl - The target DOM element (a table cell) to which the view will be appended. * @param properties - Context object providing properties to be used within the template. */ attachTemplateToElement(template, tdEl, properties) { const embeddedView = template.createEmbeddedView({ $implicit: properties.value, ...properties, }); embeddedView.detectChanges(); embeddedView.rootNodes.forEach((node) => { tdEl.appendChild(node); }); } /** * Dynamically creates an Angular component of the given type. * * @param component - The Angular component type to be created. * @param rendererParameters - An object containing input properties to assign to the component instance. * @returns The ComponentRef of the dynamically created component. */ createComponent(component, rendererParameters) { const componentRef = createComponent(component, { environmentInjector: this.environmentInjector }); Object.keys(rendererParameters).forEach(key => { if (rendererParameters.hasOwnProperty(key)) { componentRef.setInput(key, rendererParameters[key]); } else { console.warn(`Input property "${key}" does not exist on component instance: ${component?.name}.`); } }); componentRef.changeDetectorRef.detectChanges(); this.appRef.attachView(componentRef.hostView); return componentRef; } /** * Attaches a dynamically created component's view to a specified DOM container element. * * @param componentRef - The reference to the dynamically created component. * @param container - The target DOM element to which the component's root node will be appended. */ attachComponentToElement(componentRef, container) { const domElem = componentRef.hostView .rootNodes[0]; container.appendChild(domElem); } /** * Destroys a dynamically created component and detaches its view from the Angular application. * * @param componentRef - The reference to the component to be destroyed. */ destroyComponent(componentRef) { this.appRef.detachView(componentRef.hostView); componentRef.destroy(); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: DynamicComponentService, deps: [{ token: i0.ApplicationRef }, { token: i0.EnvironmentInjector }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: DynamicComponentService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: DynamicComponentService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }], ctorParameters: function () { return [{ type: i0.ApplicationRef }, { type: i0.EnvironmentInjector }]; } }); const AVAILABLE_OPTIONS = Object.keys(Handsontable.DefaultSettings); const AVAILABLE_HOOKS = Handsontable.hooks.getRegistered(); /** * Service to resolve and apply custom settings for Handsontable settings object. */ class HotSettingsResolver { dynamicComponentService; environmentInjector; constructor(dynamicComponentService, environmentInjector) { this.dynamicComponentService = dynamicComponentService; this.environmentInjector = environmentInjector; } /** * Applies custom settings to the provided GridSettings. * @param settings The original grid settings. * @param ngZone The NgZone instance to run hooks inside the zone context. * @returns The merged grid settings with custom settings applied. */ applyCustomSettings(settings, ngZone) { const mergedSettings = settings; this.updateColumnRendererForGivenCustomRenderer(mergedSettings); this.updateColumnEditorForGivenCustomEditor(mergedSettings); this.updateColumnValidatorForGivenCustomValidator(mergedSettings); this.wrapHooksInNgZone(mergedSettings, ngZone); return mergedSettings ?? {}; } /** * Ensures that hook callbacks in the provided grid settings run inside Angular's zone. * * @param settings The original grid settings. * @param ngZone The NgZone instance to run hooks inside the zone context. */ wrapHooksInNgZone(settings, ngZone) { const options = AVAILABLE_HOOKS.concat(AVAILABLE_OPTIONS); options.forEach(key => { const isHook = AVAILABLE_HOOKS.indexOf(key) > -1; let option; if (isHook) { option = settings[key]; } if (option === void 0) { return; } else if (!!ngZone && (typeof option === 'function' && isHook)) { settings[key] = function (...args) { return ngZone.run(() => option.apply(this, args)); }; } else { settings[key] = option; } }); } /** * Updates the column renderer for columns with a custom renderer. * @param mergedSettings The merged grid settings. */ updateColumnRendererForGivenCustomRenderer(mergedSettings) { if (!Array.isArray(mergedSettings?.columns)) { return; } mergedSettings?.columns ?.filter((settings) => this.isRendererComponentRefType(settings.renderer) || this.isTemplateRef(settings.renderer)) ?.forEach((cellSettings) => { const renderer = this.isTemplateRef(cellSettings.renderer) ? cellSettings.renderer : cellSettings.renderer; const props = cellSettings.rendererProps ?? {}; cellSettings.renderer = this.dynamicComponentService.createRendererFromComponent(renderer, props); }); } /** * Updates the column editor for columns with a custom editor. * @param mergedSettings The merged grid settings. */ updateColumnEditorForGivenCustomEditor(mergedSettings) { if (!Array.isArray(mergedSettings?.columns)) { return; } mergedSettings?.columns ?.filter((settings) => this.isEditorComponentRefType(settings.editor)) ?.forEach((cellSettings) => { const customEditor = cellSettings.editor; cellSettings['_editorComponentReference'] = createComponent(customEditor, { environmentInjector: this.environmentInjector, }); cellSettings['_environmentInjector'] = this.environmentInjector; cellSettings.editor = BaseEditorAdapter; }); } /** * Updates the column validator for columns with a custom validator. * @param mergedSettings The merged grid settings. */ updateColumnValidatorForGivenCustomValidator(mergedSettings) { if (!Array.isArray(mergedSettings?.columns)) { return; } mergedSettings?.columns ?.filter((settings) => this.isCustomValidatorFn(settings.validator)) ?.forEach((cellSettings) => { const customValidatorFn = cellSettings.validator; cellSettings.validator = (value, callback) => { callback(customValidatorFn(value)); }; }); } isCustomValidatorFn(validator) { return typeof validator === 'function' && validator.length === 1; } isEditorComponentRefType(editor) { // ecmp - we need it to check if the editor is a component return typeof editor === 'function' && !!editor?.ɵcmp; } isRendererComponentRefType(renderer) { // ecmp - we need it to check if the renderer is a component return typeof renderer === 'function' && !!renderer?.ɵcmp; } isTemplateRef(renderer) { return renderer && typeof renderer.createEmbeddedView === 'function'; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HotSettingsResolver, deps: [{ token: DynamicComponentService }, { token: i0.EnvironmentInjector }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HotSettingsResolver }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HotSettingsResolver, decorators: [{ type: Injectable }], ctorParameters: function () { return [{ type: DynamicComponentService }, { type: i0.EnvironmentInjector }]; } }); /** * A constant representing the non-commercial and evaluation license. * */ const NON_COMMERCIAL_LICENSE = 'non-commercial-and-evaluation'; /** * Type representing a theme name. * Possible values include predefined themes and any custom string. */ var PredefinedTheme; (function (PredefinedTheme) { PredefinedTheme["Main"] = "ht-theme-main"; PredefinedTheme["MainDark"] = "ht-theme-main-dark"; PredefinedTheme["MainDarkAuto"] = "ht-theme-main-dark-auto"; PredefinedTheme["Horizon"] = "ht-theme-horizon"; PredefinedTheme["HorizonDark"] = "ht-theme-horizon-dark"; PredefinedTheme["HorizonDarkAuto"] = "ht-theme-horizon-dark-auto"; })(PredefinedTheme || (PredefinedTheme = {})); /** * Injection token for providing a global default configuration. */ const HOT_GLOBAL_CONFIG = new InjectionToken('HOT_GLOBAL_CONFIG', { providedIn: 'root', factory: () => ({}) }); /** * Service for configuring Handsontable settings. * This service allows setting and retrieving global configuration. */ class HotGlobalConfigService { /** * The default configuration object for Handsontable. * * This object is used as the initial value for the configuration BehaviorSubject. * It can be overridden by a global configuration provided through the * {@link HOT_GLOBAL_CONFIG} injection token. * * @private * @type {HotGlobalConfig} */ defaultConfig = { license: undefined, themeName: '' }; /** * A BehaviorSubject that holds the current Handsontable configuration. * * New configuration values can be emitted by calling next() on this subject. * This allows subscribers to react to configuration changes dynamically. * * @private * @type {BehaviorSubject<HotGlobalConfig>} */ configSubject = new BehaviorSubject(this.defaultConfig); /** * An Observable stream of the current Handsontable configuration. * * Components can subscribe to this observable to receive updates whenever the configuration changes. * * @returns The configuration as an observable stream. */ get config$() { return this.configSubject.asObservable(); } constructor(globalConfig) { // Merge global configuration (if provided) into defaultConfig immutably. this.defaultConfig = { ...this.defaultConfig, ...globalConfig }; this.configSubject.next(this.defaultConfig); } /** * Sets the global configuration for Handsontable. * * @param config - An object containing configuration options. * Each Handsontable instance can override this configuration by providing its own settings. */ setConfig(config) { this.configSubject.next({ ...this.defaultConfig, ...config }); } /** * Retrieves the current Handsontable configuration. * * @returns An object with the current settings. */ getConfig() { return this.configSubject.value; } /** * Resets the configuration to the default settings. * This method updates the configuration BehaviorSubject with the default configuration. */ resetConfig() { this.configSubject.next({ ...this.defaultConfig }); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HotGlobalConfigService, deps: [{ token: HOT_GLOBAL_CONFIG }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HotGlobalConfigService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HotGlobalConfigService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }], ctorParameters: function () { return [{ type: undefined, decorators: [{ type: Inject, args: [HOT_GLOBAL_CONFIG] }] }]; } }); const HOT_DESTROYED_WARNING = 'The Handsontable instance bound to this component was destroyed and cannot be' + ' used properly.'; class HotTableComponent { _hotSettingsResolver; _hotConfig; ngZone; // component inputs /** The data for the Handsontable instance. */ data = null; /** The settings for the Handsontable instance. */ settings = {}; /** The container element for the Handsontable instance. */ container; /** The Handsontable instance. */ __hotInstance = null; configSubscription; constructor(_hotSettingsResolver, _hotConfig, ngZone) { this._hotSettingsResolver = _hotSettingsResolver; this._hotConfig = _hotConfig; this.ngZone = ngZone; } /** * Gets the Handsontable instance. * @returns The Handsontable instance or `null` if it's not yet been created or has been destroyed. */ get hotInstance() { if (!this.__hotInstance || (this.__hotInstance && !this.__hotInstance.isDestroyed)) { // Will return the Handsontable instance or `null` if it's not yet been created. return this.__hotInstance; } else { console.warn(HOT_DESTROYED_WARNING); return null; } } /** * Sets the Handsontable instance. * @param hotInstance The Handsontable instance to set. */ set hotInstance(hotInstance) { this.__hotInstance = hotInstance; } /** * Initializes the Handsontable instance after the view has been initialized. * The initial settings of the table are also prepared here */ ngAfterViewInit() { let options = this._hotSettingsResolver.applyCustomSettings(this.settings, this.ngZone); const negotiatedSettings = this.getNegotiatedSettings(options); options = { ...options, ...negotiatedSettings, data: this.data }; this.ngZone.runOutsideAngular(() => { this.hotInstance = new Handsontable.Core(this.container.nativeElement, options); this.hotInstance.init(); }); this.configSubscription = this._hotConfig.config$.subscribe((config) => { if (this.hotInstance) { const negotiatedSettings = this.getNegotiatedSettings(this.settings); this.updateHotTable(negotiatedSettings); } }); } ngOnChanges(changes) { if (this.hotInstance === null) { return; } if (changes.settings && !changes.settings.firstChange) { const newOptions = this._hotSettingsResolver.applyCustomSettings(changes.settings.currentValue, this.ngZone); this.updateHotTable(newOptions); } if (changes.data && !changes.data.firstChange) { this.hotInstance?.updateData(changes.data.currentValue); } } /** * Destroys the Handsontable instance and clears the columns from custom editors. */ ngOnDestroy() { this.ngZone.runOutsideAngular(() => { if (!this.hotInstance) { return; } const columns = this.hotInstance.getSettings().columns; if (columns && Array.isArray(columns)) { columns.forEach((column) => { if (column._editorComponentReference) { column._editorComponentReference.destroy(); } }); } this.hotInstance.destroy(); }); this.configSubscription.unsubscribe(); } /** * Updates the Handsontable instance with new settings. * @param newSettings The new settings to apply to the Handsontable instance. */ updateHotTable(newSettings) { if (!this.hotInstance) { return; } this.ngZone.runOutsideAngular(() => { this.hotInstance?.updateSettings(newSettings, false); }); } /** * Merges the provided Handsontable grid settings with the global configuration. * * This method retrieves the global configuration from the HotGlobalConfigService and negotiates the final * Handsontable settings by giving precedence to the provided settings. * Additionally, the `layoutDirection` is only merged if the Handsontable instance has not yet been initialized. * * @param settings - The grid settings provided by the user or component. * @returns The final negotiated grid settings after merging with global defaults. */ getNegotiatedSettings(settings) { const hotConfig = this._hotConfig.getConfig(); const negotiatedSettings = {}; negotiatedSettings.licenseKey = settings.licenseKey ?? hotConfig.license; negotiatedSettings.themeName = settings.themeName ?? hotConfig.themeName; negotiatedSettings.language = settings.language ?? hotConfig.language; // settings that can be set only before the Handsontable instance is initialized if (!this.__hotInstance) { negotiatedSettings.layoutDirection = settings.layoutDirection ?? hotConfig.layoutDirection; } return negotiatedSettings; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HotTableComponent, deps: [{ token: HotSettingsResolver }, { token: HotGlobalConfigService }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: HotTableComponent, 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 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HotTableComponent, decorators: [{ type: Component, args: [{ selector: 'hot-table', template: '<div #container></div>', encapsulation: ViewEncapsulation.None, providers: [HotSettingsResolver], styles: [":host{display:block}\n"] }] }], ctorParameters: function () { return [{ type: HotSettingsResolver }, { type: HotGlobalConfigService }, { type: i0.NgZone }]; }, propDecorators: { data: [{ type: Input }], settings: [{ type: Input }], container: [{ type: ViewChild, args: ['container', { static: false }] }] } }); class HotTableModule { /** * Placeholder for the library version. * Replaced automatically during the pre-build/post-build process. */ static version = '16.1.1'; constructor() { } static forRoot() { return { ngModule: HotTableModule, }; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HotTableModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.2.12", ngImport: i0, type: HotTableModule, declarations: [HotTableComponent, CustomEditorPlaceholderComponent], exports: [HotTableComponent] }); static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HotTableModule }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HotTableModule, decorators: [{ type: NgModule, args: [{ declarations: [HotTableComponent, CustomEditorPlaceholderComponent], imports: [], exports: [HotTableComponent], }] }], ctorParameters: function () { return []; } }); /** * Abstract class representing a Handsontable editor in angular. */ class HotCellEditorComponent { /** The tabindex attribute for the editor. */ tabindex = -1; /** The data-hot-input attribute for the editor. */ dataHotInput = ''; /** The handsontableInput class for the editor. */ handsontableInputClass = true; /** The height of the editor as a percentage of the parent container. */ heightFitParentContainer = 100; /** The width of the editor as a percentage of the parent container. */ widthFitParentContainer = 100; /** The row index of the cell being edited. */ row; /** The column index of the cell being edited. */ column; /** The property name of the cell being edited. */ prop; /** The original value of the cell being edited. */ originalValue; /** The cell properties of the cell being edited. */ cellProperties; /** Event emitted when the edit is finished. * The data will be saved to the model. */ finishEdit = new EventEmitter(); /** Event emitted when the edit is canceled. * The entered data will be reverted to the original value. */ cancelEdit = new EventEmitter(); /** The current value of the editor. */ _value; /** Event triggered by Handsontable on closing the editor. * The user can define their own actions for * the custom editor to be called after the base logic. */ onClose() { } /** Event triggered by Handsontable on open the editor. * The user can define their own actions for * the custom editor to be called after the base logic. */ onOpen(event) { } /** * Gets the current value of the editor. * @returns The current value of the editor. */ getValue() { return this._value; } /** * Sets the current value of the editor. * @param value The value to set. */ setValue(value) { this._value = value; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HotCellEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: HotCellEditorComponent, 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 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HotCellEditorComponent, decorators: [{ type: Directive }], propDecorators: { tabindex: [{ type: HostBinding, args: ['attr.tabindex'] }], dataHotInput: [{ type: HostBinding, args: ['attr.data-hot-input'] }], handsontableInputClass: [{ type: HostBinding, args: ['class.handsontableInput'] }], heightFitParentContainer: [{ type: HostBinding, args: ['style.height.%'] }], widthFitParentContainer: [{ type: HostBinding, args: ['style.width.%'] }], row: [{ type: Input }], column: [{ type: Input }], prop: [{ type: Input }], originalValue: [{ type: Input }], cellProperties: [{ type: Input }], finishEdit: [{ type: Output }], cancelEdit: [{ type: Output }] } }); /* * Public API Surface of hot-table */ /** * Generated bundle index. Do not edit. */ export { DynamicComponentService, HOT_DESTROYED_WARNING, HOT_GLOBAL_CONFIG, HotCellEditorComponent, HotCellRendererComponent, HotGlobalConfigService, HotSettingsResolver, HotTableComponent, HotTableModule, INVALID_RENDERER_WARNING, NON_COMMERCIAL_LICENSE, PredefinedTheme, isHotCellRendererComponent, isTemplateRef }; //# sourceMappingURL=handsontable-angular-wrapper.mjs.map