@handsontable/angular-wrapper
Version:
Best Data Grid for Angular with Spreadsheet Look and Feel.
968 lines (955 loc) • 41.7 kB
JavaScript
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