@spike-rabbit/dashboards-ng 49.0.0 → 49.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,26 +1,31 @@
1
- import { NgClass, AsyncPipe, NgTemplateOutlet } from '@angular/common';
1
+ import { NgTemplateOutlet, isPlatformBrowser, AsyncPipe } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
- import { InjectionToken, inject, input, model, output, computed, ViewChild, Component, Injectable, Renderer2, DOCUMENT, ElementRef, Input, EventEmitter, createNgModule, Injector, EnvironmentInjector, viewChild, ViewContainerRef, isSignal, NgZone, signal, NgModule } from '@angular/core';
3
+ import { InjectionToken, inject, input, model, output, viewChild, computed, ChangeDetectionStrategy, Component, Injectable, ElementRef, Renderer2, DOCUMENT, Directive, EventEmitter, createNgModule, Injector, EnvironmentInjector, DestroyRef, signal, ViewContainerRef, isSignal, viewChildren, NgZone, PLATFORM_ID, afterNextRender, inputBinding, outputBinding, NgModule } from '@angular/core';
4
4
  import { SiDashboardCardComponent, SiDashboardComponent } from '@spike-rabbit/element-ng/dashboard';
5
- import * as i1 from '@spike-rabbit/element-translate-ng/translate';
6
- import { t, SiTranslateModule, SiTranslatePipe } from '@spike-rabbit/element-translate-ng/translate';
7
- import { BehaviorSubject, of, Subject, throwError, ReplaySubject, combineLatest } from 'rxjs';
8
- import { takeUntil, take, map } from 'rxjs/operators';
5
+ import { t, SiTranslatePipe, injectSiTranslateService } from '@spike-rabbit/element-translate-ng/translate';
6
+ import { BehaviorSubject, of, Subject, throwError, ReplaySubject, first, combineLatest } from 'rxjs';
7
+ import { take, takeUntil } from 'rxjs/operators';
9
8
  import { BOOTSTRAP_BREAKPOINTS, SiResponsiveContainerDirective } from '@spike-rabbit/element-ng/resize-observer';
10
9
  import { ActivatedRoute, RouterLink } from '@angular/router';
10
+ import { elementEdit } from '@siemens/element-icons';
11
11
  import { SiContentActionBarComponent } from '@spike-rabbit/element-ng/content-action-bar';
12
+ import { addIcons, SiIconComponent } from '@spike-rabbit/element-ng/icon';
12
13
  import { SiLinkDirective } from '@spike-rabbit/element-ng/link';
13
- import { SiLoadingSpinnerComponent, SiLoadingService, SiLoadingSpinnerDirective } from '@spike-rabbit/element-ng/loading-spinner';
14
+ import { SiLoadingButtonComponent, SiLoadingSpinnerDirective } from '@spike-rabbit/element-ng/loading-spinner';
15
+ import { SiTooltipDirective } from '@spike-rabbit/element-ng/tooltip';
14
16
  import { SiModalService } from '@spike-rabbit/element-ng/modal';
15
17
  import { GridStack } from 'gridstack';
18
+ import { LiveAnnouncer } from '@angular/cdk/a11y';
19
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
16
20
  import { SiActionDialogService } from '@spike-rabbit/element-ng/action-modal';
21
+ import { CdkListbox, CdkOption } from '@angular/cdk/listbox';
17
22
  import { SiCircleStatusComponent } from '@spike-rabbit/element-ng/circle-status';
18
23
  import { SiEmptyStateComponent } from '@spike-rabbit/element-ng/empty-state';
19
24
  import { SiSearchBarComponent } from '@spike-rabbit/element-ng/search-bar';
20
25
  export * from '@spike-rabbit/dashboards-ng/translate';
21
26
 
22
27
  /**
23
- * Copyright (c) Siemens 2016 - 2025
28
+ * Copyright (c) Siemens 2016 - 2026
24
29
  * SPDX-License-Identifier: MIT
25
30
  */
26
31
  /**
@@ -36,7 +41,7 @@ const DEFAULT_GRIDSTACK_OPTIONS = {
36
41
  };
37
42
 
38
43
  /**
39
- * Copyright (c) Siemens 2016 - 2025
44
+ * Copyright (c) Siemens 2016 - 2026
40
45
  * SPDX-License-Identifier: MIT
41
46
  */
42
47
  /**
@@ -48,12 +53,21 @@ const SI_DASHBOARD_CONFIGURATION = new InjectionToken('Injection token to config
48
53
  factory: () => ({ grid: { gridStackOptions: DEFAULT_GRIDSTACK_OPTIONS } })
49
54
  });
50
55
  /**
51
- * @deprecated Use SI_DASHBOARD_CONFIGURATION instead.
56
+ * Injection token to configure global toolbar menu items.
52
57
  */
53
- const CONFIG_TOKEN = SI_DASHBOARD_CONFIGURATION;
58
+ const SI_DASHBOARD_TOOLBAR_ITEMS = new InjectionToken('Injection token to configure global toolbar menu items.');
59
+ /**
60
+ * provider function to configure global toolbar menu items.
61
+ * @param toolbarItems - primary and secondary edit actions which are common to all
62
+ * dashboard instances on application.
63
+ */
64
+ const provideDashboardToolbarItems = (toolbarItems) => ({
65
+ provide: SI_DASHBOARD_TOOLBAR_ITEMS,
66
+ useValue: { primary: toolbarItems?.primary ?? [], secondary: toolbarItems?.secondary ?? [] }
67
+ });
54
68
 
55
69
  /**
56
- * Copyright (c) Siemens 2016 - 2025
70
+ * Copyright (c) Siemens 2016 - 2026
57
71
  * SPDX-License-Identifier: MIT
58
72
  */
59
73
  /**
@@ -71,6 +85,9 @@ class SiWidgetStorage {
71
85
  /**
72
86
  * Optional method to provide primary and secondary toolbar menu items.
73
87
  * @param dashboardId - The id of the dashboard if present to allow dashboard specific menu items.
88
+ *
89
+ * @deprecated use {@link provideDashboardToolbarItems} to provide common toolbar items.
90
+ * Additionally configure individual dashboard actions via {@link SiFlexibleDashboardComponent.primaryEditActions} and {@link SiFlexibleDashboardComponent.secondaryEditActions} respectively.
74
91
  */
75
92
  getToolbarMenuItems;
76
93
  }
@@ -116,26 +133,19 @@ class SiDefaultWidgetStorage extends SiWidgetStorage {
116
133
  }
117
134
  return this.map.get(dashboardId);
118
135
  }
119
- save(widgets, removedWidgets, dashboardId) {
120
- const newWidgets = widgets.map(widget => {
121
- if (widget.id === undefined) {
122
- return { ...widget, id: Math.random().toString(36).substring(2, 9) };
123
- }
124
- else {
125
- return widget;
126
- }
127
- });
136
+ save(modifiedWidgets, addedWidgets, removedWidgets, dashboardId) {
137
+ const newWidgets = [...addedWidgets, ...modifiedWidgets];
128
138
  this.update(newWidgets, dashboardId);
129
139
  return of(newWidgets);
130
140
  }
131
- update(newConfigs, dashboardId) {
141
+ update(widgetConfigs, dashboardId) {
132
142
  const widgets$ = this.load(dashboardId);
133
- widgets$.next(newConfigs);
143
+ widgets$.next(widgetConfigs);
134
144
  if (!dashboardId) {
135
- this.storage.setItem(STORAGE_KEY, JSON.stringify(newConfigs));
145
+ this.storage.setItem(STORAGE_KEY, JSON.stringify(widgetConfigs));
136
146
  }
137
147
  else {
138
- this.storage.setItem(`${STORAGE_KEY}-${dashboardId}`, JSON.stringify(newConfigs));
148
+ this.storage.setItem(`${STORAGE_KEY}-${dashboardId}`, JSON.stringify(widgetConfigs));
139
149
  }
140
150
  }
141
151
  loadFromStorage(dashboardId) {
@@ -155,7 +165,7 @@ class SiDefaultWidgetStorage extends SiWidgetStorage {
155
165
  }
156
166
 
157
167
  /**
158
- * Copyright (c) Siemens 2016 - 2025
168
+ * Copyright (c) Siemens 2016 - 2026
159
169
  * SPDX-License-Identifier: MIT
160
170
  */
161
171
  /**
@@ -166,69 +176,84 @@ class SiDefaultWidgetStorage extends SiWidgetStorage {
166
176
  * the cancel and save buttons, as well as displaying primary and secondary actions.
167
177
  */
168
178
  class SiDashboardToolbarComponent {
179
+ icons = addIcons({ elementEdit });
169
180
  /**
170
181
  * Set primary actions that are in `editable` mode first visible or in
171
182
  * the expanded content action bar of the toolbar.
172
183
  *
173
184
  * @defaultValue []
174
185
  */
175
- primaryEditActions = input([]);
186
+ primaryEditActions = input([], /* @ts-ignore */
187
+ ...(ngDevMode ? [{ debugName: "primaryEditActions" }] : /* istanbul ignore next */ []));
176
188
  /**
177
189
  * Set secondary actions that are in `editable` mode second visible or in
178
190
  * the dropdown part of the content action bar of the toolbar.
179
191
  *
180
192
  * @defaultValue [] */
181
- secondaryEditActions = input([]);
193
+ secondaryEditActions = input([], /* @ts-ignore */
194
+ ...(ngDevMode ? [{ debugName: "secondaryEditActions" }] : /* istanbul ignore next */ []));
182
195
  /**
183
196
  * Input to disable the save button. Note, the input `disabled` disables all
184
197
  * actions and the save button of the toolbar.
185
198
  *
186
199
  * @defaultValue true
187
200
  */
188
- disableSaveButton = model(true);
201
+ disableSaveButton = model(true, /* @ts-ignore */
202
+ ...(ngDevMode ? [{ debugName: "disableSaveButton" }] : /* istanbul ignore next */ []));
189
203
  /**
190
204
  * Input to disable all actions and buttons of the toolbar.
191
205
  *
192
206
  * @defaultValue false */
193
- disabled = input(false);
207
+ disabled = input(false, /* @ts-ignore */
208
+ ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
194
209
  /**
195
210
  * Input option to set the `editable` mode. When editable
196
211
  * the toolbar shows a cancel and save button. Otherwise,
197
212
  * it displays an edit button.
198
213
  *
199
214
  * @defaultValue false */
200
- editable = model(false);
215
+ editable = model(false, /* @ts-ignore */
216
+ ...(ngDevMode ? [{ debugName: "editable" }] : /* istanbul ignore next */ []));
201
217
  /**
202
218
  * Option to hide the dashboard edit button.
203
219
  *
204
220
  * @defaultValue false
205
221
  */
206
- hideEditButton = input(false);
222
+ hideEditButton = input(false, /* @ts-ignore */
223
+ ...(ngDevMode ? [{ debugName: "hideEditButton" }] : /* istanbul ignore next */ []));
207
224
  /**
208
225
  * Option to display the edit button as a text button instead, only if the window is larger than xs {@link SiResponsiveContainerDirective}.
209
226
  *
210
227
  * @defaultValue false
211
228
  */
212
- showEditButtonLabel = input(false);
229
+ showEditButtonLabel = input(false, /* @ts-ignore */
230
+ ...(ngDevMode ? [{ debugName: "showEditButtonLabel" }] : /* istanbul ignore next */ []));
231
+ grid = input.required(/* @ts-ignore */
232
+ ...(ngDevMode ? [{ debugName: "grid" }] : /* istanbul ignore next */ []));
213
233
  /**
214
234
  * Emits on save button click.
215
235
  */
216
236
  save = output();
217
- /**
218
- * Emits on cancel button click.
219
- */
220
- // eslint-disable-next-line @angular-eslint/no-output-native
221
- cancel = output();
222
237
  labelEdit = t(() => $localize `:@@DASHBOARD.EDIT:Edit`);
223
238
  labelCancel = t(() => $localize `:@@DASHBOARD.CANCEL:Cancel`);
224
239
  labelSave = t(() => $localize `:@@DASHBOARD.SAVE:Save`);
225
240
  activatedRoute = inject(ActivatedRoute, { optional: true });
226
- dashboardToolbarContainer;
227
- showContentActionBar = computed(() => this.primaryEditActions()?.length + this.secondaryEditActions()?.length > 3);
241
+ dashboardToolbarContainer = viewChild.required(SiResponsiveContainerDirective);
242
+ showContentActionBar = computed(() => this.primaryEditActions()?.length + this.secondaryEditActions()?.length > 3, /* @ts-ignore */
243
+ ...(ngDevMode ? [{ debugName: "showContentActionBar" }] : /* istanbul ignore next */ []));
228
244
  editActions = computed(() => [
229
245
  ...this.primaryEditActions(),
230
246
  ...this.secondaryEditActions()
231
- ]);
247
+ ], /* @ts-ignore */
248
+ ...(ngDevMode ? [{ debugName: "editActions" }] : /* istanbul ignore next */ []));
249
+ primaryEditActionsComputed = computed(() => {
250
+ return this.primaryEditActions().map(item => this.proxyMenuItemAction(item));
251
+ }, /* @ts-ignore */
252
+ ...(ngDevMode ? [{ debugName: "primaryEditActionsComputed" }] : /* istanbul ignore next */ []));
253
+ secondaryEditActionsComputed = computed(() => {
254
+ return this.secondaryEditActions().map(item => this.proxyMenuItemAction(item));
255
+ }, /* @ts-ignore */
256
+ ...(ngDevMode ? [{ debugName: "secondaryEditActionsComputed" }] : /* istanbul ignore next */ []));
232
257
  onEdit() {
233
258
  this.editable.set(true);
234
259
  }
@@ -239,113 +264,137 @@ class SiDashboardToolbarComponent {
239
264
  }
240
265
  onCancel() {
241
266
  if (this.editable()) {
242
- this.cancel.emit();
267
+ this.editable.set(false);
243
268
  }
244
269
  }
245
270
  isToolbarItem(item) {
246
271
  return 'label' in item;
247
272
  }
248
- showEditButtonLabelDesktop() {
249
- return this.showEditButtonLabel() && !this.dashboardToolbarContainer?.xs();
273
+ showEditButtonLabelDesktop = computed(() => {
274
+ return this.showEditButtonLabel() && !this.dashboardToolbarContainer()?.xs();
275
+ }, /* @ts-ignore */
276
+ ...(ngDevMode ? [{ debugName: "showEditButtonLabelDesktop" }] : /* istanbul ignore next */ []));
277
+ proxyMenuItemAction(item) {
278
+ if (item.type === 'action' && typeof item.action === 'function') {
279
+ item.action = item.action.bind(this, this.grid());
280
+ }
281
+ return item;
250
282
  }
251
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiDashboardToolbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
252
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.6", type: SiDashboardToolbarComponent, isStandalone: true, selector: "si-dashboard-toolbar", inputs: { primaryEditActions: { classPropertyName: "primaryEditActions", publicName: "primaryEditActions", isSignal: true, isRequired: false, transformFunction: null }, secondaryEditActions: { classPropertyName: "secondaryEditActions", publicName: "secondaryEditActions", isSignal: true, isRequired: false, transformFunction: null }, disableSaveButton: { classPropertyName: "disableSaveButton", publicName: "disableSaveButton", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, hideEditButton: { classPropertyName: "hideEditButton", publicName: "hideEditButton", isSignal: true, isRequired: false, transformFunction: null }, showEditButtonLabel: { classPropertyName: "showEditButtonLabel", publicName: "showEditButtonLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { disableSaveButton: "disableSaveButtonChange", editable: "editableChange", save: "save", cancel: "cancel" }, viewQueries: [{ propertyName: "dashboardToolbarContainer", first: true, predicate: SiResponsiveContainerDirective, descendants: true }], ngImport: i0, template: "<div class=\"row mb-3\" siResponsiveContainer>\n <div class=\"col-12 d-flex justify-content-between\">\n @if (!editable()) {\n <ng-content select=\"[filters-slot]\" />\n @if (!hideEditButton()) {\n <button\n type=\"button\"\n class=\"btn ms-auto\"\n [ngClass]=\"\n showEditButtonLabelDesktop()\n ? 'btn-secondary'\n : 'btn-circle btn-sm btn-tertiary element-edit'\n \"\n [attr.aria-label]=\"labelEdit | translate\"\n [attr.title]=\"!showEditButtonLabelDesktop() ? (labelEdit | translate) : undefined\"\n [disabled]=\"disabled()\"\n (click)=\"onEdit()\"\n >\n @if (showEditButtonLabelDesktop()) {\n {{ labelEdit | translate }}\n }\n </button>\n }\n } @else {\n @if (showContentActionBar()) {\n <si-content-action-bar\n toggleItemLabel=\"toggle\"\n viewType=\"expanded\"\n [disabled]=\"disabled()\"\n [primaryActions]=\"primaryEditActions()\"\n [secondaryActions]=\"secondaryEditActions()\"\n />\n } @else {\n <div>\n @for (action of editActions(); track $index) {\n @if (isToolbarItem(action)) {\n @if (disabled()) {\n <!-- Links do not support disabled, so we need to render a button instead in this case. -->\n <button type=\"button\" class=\"btn btn-secondary me-4\" disabled>\n {{ action.label! | translate }}\n </button>\n } @else {\n @switch (action.type) {\n @case ('action') {\n <!-- eslint-disable @angular-eslint/template/no-any -->\n <!-- This item has a patched action which will inject the grid. But our types do not reflect this. -->\n <!-- TODO: make our types reflect that this item has a patched action. -->\n <button\n type=\"button\"\n class=\"btn btn-secondary me-4\"\n (click)=\"$any(action.action)()\"\n >\n {{ action.label! | translate }}\n </button>\n <!-- eslint-disable-enable @angular-eslint/template/no-any -->\n }\n @case ('router-link') {\n <a\n class=\"btn btn-secondary me-4\"\n [routerLink]=\"action.routerLink\"\n [queryParams]=\"action.extras?.queryParams\"\n [queryParamsHandling]=\"action.extras?.queryParamsHandling\"\n [fragment]=\"action.extras?.fragment\"\n [state]=\"action.extras?.state\"\n [relativeTo]=\"action.extras?.relativeTo ?? activatedRoute\"\n [preserveFragment]=\"action.extras?.preserveFragment\"\n [skipLocationChange]=\"action.extras?.skipLocationChange\"\n [replaceUrl]=\"action.extras?.replaceUrl\"\n >\n {{ action.label! | translate }}\n </a>\n }\n @case ('link') {\n <a class=\"btn btn-secondary me-4\" [href]=\"action.href\" [target]=\"action.target\">\n {{ action.label! | translate }}\n </a>\n }\n }\n }\n } @else {\n <button\n type=\"button\"\n class=\"btn btn-secondary me-4\"\n [disabled]=\"disabled()\"\n [siLink]=\"action\"\n >\n {{ action.title! | translate }}\n </button>\n }\n }\n </div>\n }\n <div>\n <button\n type=\"button\"\n class=\"btn btn-secondary me-4\"\n [disabled]=\"disabled()\"\n (click)=\"onCancel()\"\n >\n {{ labelCancel | translate }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-primary\"\n [disabled]=\"disableSaveButton() || disabled()\"\n (click)=\"onSave()\"\n >\n @if (disabled()) {\n <si-loading-spinner />\n } @else {\n <span>{{ labelSave | translate }}</span>\n }\n </button>\n </div>\n }\n </div>\n</div>\n", styles: [":host{display:flex;flex:1 1 0;flex-direction:column}si-loading-spinner{--loading-spinner-size: 1rem;--loading-spinner-color: var(--element-text-inverse)}\n"], dependencies: [{ kind: "component", type: SiContentActionBarComponent, selector: "si-content-action-bar", inputs: ["primaryActions", "secondaryActions", "actionParam", "viewType", "toggleItemLabel", "preventIconsInDropdownMenus", "disabled"] }, { kind: "directive", type: SiLinkDirective, selector: "[siLink]", inputs: ["siLink", "siLinkDefaultTarget", "actionParam", "activeClass", "exactMatch", "ariaCurrent"], outputs: ["activeChange"], exportAs: ["siLink"] }, { kind: "component", type: SiLoadingSpinnerComponent, selector: "si-loading-spinner", inputs: ["isBlockingSpinner", "isSpinnerOverlay", "ariaLabel"] }, { kind: "ngmodule", type: SiTranslateModule }, { kind: "pipe", type: i1.SiTranslatePipe, name: "translate" }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: SiResponsiveContainerDirective, selector: "[siResponsiveContainer]", inputs: ["resizeThrottle", "breakpoints"], exportAs: ["siResponsiveContainer"] }] });
283
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: SiDashboardToolbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
284
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: SiDashboardToolbarComponent, isStandalone: true, selector: "si-dashboard-toolbar", inputs: { primaryEditActions: { classPropertyName: "primaryEditActions", publicName: "primaryEditActions", isSignal: true, isRequired: false, transformFunction: null }, secondaryEditActions: { classPropertyName: "secondaryEditActions", publicName: "secondaryEditActions", isSignal: true, isRequired: false, transformFunction: null }, disableSaveButton: { classPropertyName: "disableSaveButton", publicName: "disableSaveButton", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, hideEditButton: { classPropertyName: "hideEditButton", publicName: "hideEditButton", isSignal: true, isRequired: false, transformFunction: null }, showEditButtonLabel: { classPropertyName: "showEditButtonLabel", publicName: "showEditButtonLabel", isSignal: true, isRequired: false, transformFunction: null }, grid: { classPropertyName: "grid", publicName: "grid", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { disableSaveButton: "disableSaveButtonChange", editable: "editableChange", save: "save" }, host: { classAttribute: "d-flex flex-column flex-grow-1" }, viewQueries: [{ propertyName: "dashboardToolbarContainer", first: true, predicate: SiResponsiveContainerDirective, descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"row mb-3\" siResponsiveContainer>\n <div class=\"col-12 d-flex justify-content-between\">\n @if (!editable()) {\n <ng-content select=\"[filters-slot]\" />\n @if (!hideEditButton()) {\n @let showEditButtonLabelDesktop = this.showEditButtonLabelDesktop();\n <button\n type=\"button\"\n class=\"btn ms-auto\"\n [class]=\"showEditButtonLabelDesktop ? 'btn-secondary' : 'btn-icon btn-tertiary'\"\n [attr.aria-label]=\"labelEdit | translate\"\n [siTooltip]=\"labelEdit\"\n [isDisabled]=\"showEditButtonLabelDesktop\"\n [disabled]=\"disabled()\"\n (click)=\"onEdit()\"\n >\n @if (showEditButtonLabelDesktop) {\n {{ labelEdit | translate }}\n } @else {\n <si-icon [icon]=\"icons.elementEdit\" />\n }\n </button>\n }\n } @else {\n @if (showContentActionBar()) {\n <si-content-action-bar\n toggleItemLabel=\"toggle\"\n viewType=\"expanded\"\n [disabled]=\"disabled()\"\n [primaryActions]=\"primaryEditActionsComputed()\"\n [secondaryActions]=\"secondaryEditActionsComputed()\"\n />\n } @else {\n <div>\n @for (action of editActions(); track $index) {\n @if (isToolbarItem(action)) {\n @if (disabled()) {\n <!-- Links do not support disabled, so we need to render a button instead in this case. -->\n <button type=\"button\" class=\"btn btn-secondary me-4\" disabled>\n {{ action.label! | translate }}\n </button>\n } @else {\n @switch (action.type) {\n @case ('action') {\n <button\n type=\"button\"\n class=\"btn btn-secondary me-4\"\n (click)=\"action.action(grid())\"\n >\n {{ action.label! | translate }}\n </button>\n }\n @case ('router-link') {\n <a\n class=\"btn btn-secondary me-4\"\n [routerLink]=\"action.routerLink\"\n [queryParams]=\"action.extras?.queryParams\"\n [queryParamsHandling]=\"action.extras?.queryParamsHandling\"\n [fragment]=\"action.extras?.fragment\"\n [state]=\"action.extras?.state\"\n [relativeTo]=\"action.extras?.relativeTo ?? activatedRoute\"\n [preserveFragment]=\"action.extras?.preserveFragment\"\n [skipLocationChange]=\"action.extras?.skipLocationChange\"\n [replaceUrl]=\"action.extras?.replaceUrl\"\n >\n {{ action.label! | translate }}\n </a>\n }\n @case ('link') {\n <a class=\"btn btn-secondary me-4\" [href]=\"action.href\" [target]=\"action.target\">\n {{ action.label! | translate }}\n </a>\n }\n }\n }\n } @else {\n <button\n type=\"button\"\n class=\"btn btn-secondary me-4\"\n [disabled]=\"disabled()\"\n [siLink]=\"action\"\n >\n {{ action.title! | translate }}\n </button>\n }\n }\n </div>\n }\n <div>\n <button\n type=\"button\"\n class=\"btn btn-secondary me-4\"\n [disabled]=\"disabled()\"\n (click)=\"onCancel()\"\n >\n {{ labelCancel | translate }}\n </button>\n <si-loading-button\n class=\"align-top\"\n buttonClass=\"btn btn-primary\"\n [disabled]=\"disableSaveButton()\"\n [loading]=\"disabled()\"\n (click)=\"onSave()\"\n >\n {{ labelSave | translate }}\n </si-loading-button>\n </div>\n }\n </div>\n</div>\n", dependencies: [{ kind: "component", type: SiContentActionBarComponent, selector: "si-content-action-bar", inputs: ["primaryActions", "secondaryActions", "actionParam", "viewType", "toggleItemLabel", "preventIconsInDropdownMenus", "disabled"] }, { kind: "directive", type: SiLinkDirective, selector: "[siLink]", inputs: ["siLink", "siLinkDefaultTarget", "actionParam", "activeClass", "exactMatch", "ariaCurrent"], outputs: ["activeChange"], exportAs: ["siLink"] }, { kind: "component", type: SiLoadingButtonComponent, selector: "si-loading-button", inputs: ["disabled", "loading", "type", "ariaLabel", "ariaLabelledBy", "buttonClass"] }, { kind: "component", type: SiIconComponent, selector: "si-icon", inputs: ["icon"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "directive", type: SiResponsiveContainerDirective, selector: "[siResponsiveContainer]", inputs: ["resizeThrottle", "breakpoints"], exportAs: ["siResponsiveContainer"] }, { kind: "directive", type: SiTooltipDirective, selector: "[siTooltip]", inputs: ["siTooltip", "placement", "isDisabled", "tooltipScrollStrategy", "tooltipContext"] }, { kind: "pipe", type: SiTranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.Eager });
253
285
  }
254
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiDashboardToolbarComponent, decorators: [{
286
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: SiDashboardToolbarComponent, decorators: [{
255
287
  type: Component,
256
288
  args: [{ selector: 'si-dashboard-toolbar', imports: [
257
289
  SiContentActionBarComponent,
258
290
  SiLinkDirective,
259
- SiLoadingSpinnerComponent,
260
- SiTranslateModule,
291
+ SiLoadingButtonComponent,
292
+ SiIconComponent,
261
293
  RouterLink,
262
- NgClass,
263
- SiResponsiveContainerDirective
264
- ], template: "<div class=\"row mb-3\" siResponsiveContainer>\n <div class=\"col-12 d-flex justify-content-between\">\n @if (!editable()) {\n <ng-content select=\"[filters-slot]\" />\n @if (!hideEditButton()) {\n <button\n type=\"button\"\n class=\"btn ms-auto\"\n [ngClass]=\"\n showEditButtonLabelDesktop()\n ? 'btn-secondary'\n : 'btn-circle btn-sm btn-tertiary element-edit'\n \"\n [attr.aria-label]=\"labelEdit | translate\"\n [attr.title]=\"!showEditButtonLabelDesktop() ? (labelEdit | translate) : undefined\"\n [disabled]=\"disabled()\"\n (click)=\"onEdit()\"\n >\n @if (showEditButtonLabelDesktop()) {\n {{ labelEdit | translate }}\n }\n </button>\n }\n } @else {\n @if (showContentActionBar()) {\n <si-content-action-bar\n toggleItemLabel=\"toggle\"\n viewType=\"expanded\"\n [disabled]=\"disabled()\"\n [primaryActions]=\"primaryEditActions()\"\n [secondaryActions]=\"secondaryEditActions()\"\n />\n } @else {\n <div>\n @for (action of editActions(); track $index) {\n @if (isToolbarItem(action)) {\n @if (disabled()) {\n <!-- Links do not support disabled, so we need to render a button instead in this case. -->\n <button type=\"button\" class=\"btn btn-secondary me-4\" disabled>\n {{ action.label! | translate }}\n </button>\n } @else {\n @switch (action.type) {\n @case ('action') {\n <!-- eslint-disable @angular-eslint/template/no-any -->\n <!-- This item has a patched action which will inject the grid. But our types do not reflect this. -->\n <!-- TODO: make our types reflect that this item has a patched action. -->\n <button\n type=\"button\"\n class=\"btn btn-secondary me-4\"\n (click)=\"$any(action.action)()\"\n >\n {{ action.label! | translate }}\n </button>\n <!-- eslint-disable-enable @angular-eslint/template/no-any -->\n }\n @case ('router-link') {\n <a\n class=\"btn btn-secondary me-4\"\n [routerLink]=\"action.routerLink\"\n [queryParams]=\"action.extras?.queryParams\"\n [queryParamsHandling]=\"action.extras?.queryParamsHandling\"\n [fragment]=\"action.extras?.fragment\"\n [state]=\"action.extras?.state\"\n [relativeTo]=\"action.extras?.relativeTo ?? activatedRoute\"\n [preserveFragment]=\"action.extras?.preserveFragment\"\n [skipLocationChange]=\"action.extras?.skipLocationChange\"\n [replaceUrl]=\"action.extras?.replaceUrl\"\n >\n {{ action.label! | translate }}\n </a>\n }\n @case ('link') {\n <a class=\"btn btn-secondary me-4\" [href]=\"action.href\" [target]=\"action.target\">\n {{ action.label! | translate }}\n </a>\n }\n }\n }\n } @else {\n <button\n type=\"button\"\n class=\"btn btn-secondary me-4\"\n [disabled]=\"disabled()\"\n [siLink]=\"action\"\n >\n {{ action.title! | translate }}\n </button>\n }\n }\n </div>\n }\n <div>\n <button\n type=\"button\"\n class=\"btn btn-secondary me-4\"\n [disabled]=\"disabled()\"\n (click)=\"onCancel()\"\n >\n {{ labelCancel | translate }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-primary\"\n [disabled]=\"disableSaveButton() || disabled()\"\n (click)=\"onSave()\"\n >\n @if (disabled()) {\n <si-loading-spinner />\n } @else {\n <span>{{ labelSave | translate }}</span>\n }\n </button>\n </div>\n }\n </div>\n</div>\n", styles: [":host{display:flex;flex:1 1 0;flex-direction:column}si-loading-spinner{--loading-spinner-size: 1rem;--loading-spinner-color: var(--element-text-inverse)}\n"] }]
265
- }], propDecorators: { dashboardToolbarContainer: [{
266
- type: ViewChild,
267
- args: [SiResponsiveContainerDirective]
268
- }] } });
294
+ SiResponsiveContainerDirective,
295
+ SiTranslatePipe,
296
+ SiTooltipDirective
297
+ ], changeDetection: ChangeDetectionStrategy.Eager, host: {
298
+ class: 'd-flex flex-column flex-grow-1'
299
+ }, template: "<div class=\"row mb-3\" siResponsiveContainer>\n <div class=\"col-12 d-flex justify-content-between\">\n @if (!editable()) {\n <ng-content select=\"[filters-slot]\" />\n @if (!hideEditButton()) {\n @let showEditButtonLabelDesktop = this.showEditButtonLabelDesktop();\n <button\n type=\"button\"\n class=\"btn ms-auto\"\n [class]=\"showEditButtonLabelDesktop ? 'btn-secondary' : 'btn-icon btn-tertiary'\"\n [attr.aria-label]=\"labelEdit | translate\"\n [siTooltip]=\"labelEdit\"\n [isDisabled]=\"showEditButtonLabelDesktop\"\n [disabled]=\"disabled()\"\n (click)=\"onEdit()\"\n >\n @if (showEditButtonLabelDesktop) {\n {{ labelEdit | translate }}\n } @else {\n <si-icon [icon]=\"icons.elementEdit\" />\n }\n </button>\n }\n } @else {\n @if (showContentActionBar()) {\n <si-content-action-bar\n toggleItemLabel=\"toggle\"\n viewType=\"expanded\"\n [disabled]=\"disabled()\"\n [primaryActions]=\"primaryEditActionsComputed()\"\n [secondaryActions]=\"secondaryEditActionsComputed()\"\n />\n } @else {\n <div>\n @for (action of editActions(); track $index) {\n @if (isToolbarItem(action)) {\n @if (disabled()) {\n <!-- Links do not support disabled, so we need to render a button instead in this case. -->\n <button type=\"button\" class=\"btn btn-secondary me-4\" disabled>\n {{ action.label! | translate }}\n </button>\n } @else {\n @switch (action.type) {\n @case ('action') {\n <button\n type=\"button\"\n class=\"btn btn-secondary me-4\"\n (click)=\"action.action(grid())\"\n >\n {{ action.label! | translate }}\n </button>\n }\n @case ('router-link') {\n <a\n class=\"btn btn-secondary me-4\"\n [routerLink]=\"action.routerLink\"\n [queryParams]=\"action.extras?.queryParams\"\n [queryParamsHandling]=\"action.extras?.queryParamsHandling\"\n [fragment]=\"action.extras?.fragment\"\n [state]=\"action.extras?.state\"\n [relativeTo]=\"action.extras?.relativeTo ?? activatedRoute\"\n [preserveFragment]=\"action.extras?.preserveFragment\"\n [skipLocationChange]=\"action.extras?.skipLocationChange\"\n [replaceUrl]=\"action.extras?.replaceUrl\"\n >\n {{ action.label! | translate }}\n </a>\n }\n @case ('link') {\n <a class=\"btn btn-secondary me-4\" [href]=\"action.href\" [target]=\"action.target\">\n {{ action.label! | translate }}\n </a>\n }\n }\n }\n } @else {\n <button\n type=\"button\"\n class=\"btn btn-secondary me-4\"\n [disabled]=\"disabled()\"\n [siLink]=\"action\"\n >\n {{ action.title! | translate }}\n </button>\n }\n }\n </div>\n }\n <div>\n <button\n type=\"button\"\n class=\"btn btn-secondary me-4\"\n [disabled]=\"disabled()\"\n (click)=\"onCancel()\"\n >\n {{ labelCancel | translate }}\n </button>\n <si-loading-button\n class=\"align-top\"\n buttonClass=\"btn btn-primary\"\n [disabled]=\"disableSaveButton()\"\n [loading]=\"disabled()\"\n (click)=\"onSave()\"\n >\n {{ labelSave | translate }}\n </si-loading-button>\n </div>\n }\n </div>\n</div>\n" }]
300
+ }], propDecorators: { primaryEditActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "primaryEditActions", required: false }] }], secondaryEditActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "secondaryEditActions", required: false }] }], disableSaveButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "disableSaveButton", required: false }] }, { type: i0.Output, args: ["disableSaveButtonChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], editable: [{ type: i0.Input, args: [{ isSignal: true, alias: "editable", required: false }] }, { type: i0.Output, args: ["editableChange"] }], hideEditButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideEditButton", required: false }] }], showEditButtonLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "showEditButtonLabel", required: false }] }], grid: [{ type: i0.Input, args: [{ isSignal: true, alias: "grid", required: true }] }], save: [{ type: i0.Output, args: ["save"] }], dashboardToolbarContainer: [{ type: i0.ViewChild, args: [i0.forwardRef(() => SiResponsiveContainerDirective), { isSignal: true }] }] } });
269
301
 
270
302
  /**
271
- * Copyright (c) Siemens 2016 - 2025
303
+ * Copyright (c) Siemens 2016 - 2026
272
304
  * SPDX-License-Identifier: MIT
273
305
  */
274
- class SiGridService {
275
- /** @defaultValue [] */
276
- widgetCatalog = [];
306
+ /**
307
+ * Injection token to optionally inject the {@link SiWidgetIdProvider} implementation
308
+ * to provide custom widget id generation logic. The default implementation
309
+ * is {@link SiWidgetDefaultIdProvider}.
310
+ *
311
+ * * @example
312
+ * The following shows how to provide your own widget id provider.
313
+ * ```
314
+ * providers: [..., { provide: SI_WIDGET_ID_PROVIDER, useClass: CustomWidgetIdProvider }]
315
+ * ```
316
+ *
317
+ */
318
+ const SI_WIDGET_ID_PROVIDER = new InjectionToken('Injection token to configure your widget id provider.', { providedIn: 'root', factory: () => inject(SiWidgetDefaultIdProvider) });
319
+ /**
320
+ * Abstract class to provide widget id generation logic.
321
+ */
322
+ class SiWidgetIdProvider {
323
+ }
324
+ /**
325
+ * The default implementation of the {@link SiWidgetIdProvider} which
326
+ * generates random widget ids.
327
+ */
328
+ class SiWidgetDefaultIdProvider extends SiWidgetIdProvider {
277
329
  /**
278
- * Observable that emits true if si-grid is set to editable.
279
- * The owner of the editable state is the si-grid component.
280
- * This subject is used to propagate these state changes via
281
- * the si-widget-host components to the widgets.
330
+ * Generates a unique widget id.
331
+ *
332
+ * The method uses `crypto.randomUUID()` which generates a RFC4122 version 4 UUID
333
+ * (a cryptographically secure random identifier with 122 bits of entropy).
334
+ *
335
+ * @param widget - The widget instance config without any id.
336
+ * @param dashboardId - The id of the dashboard where the widget is added.
337
+ * @returns A unique widget id string in the format `crypto.randomUUID()`.
282
338
  */
283
- editable$ = new BehaviorSubject(false);
284
- getWidget(id) {
285
- return this.widgetCatalog.find(widget => widget.id === id);
339
+ generateWidgetId(widget, dashboardId) {
340
+ return crypto.randomUUID();
286
341
  }
287
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiGridService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
288
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiGridService });
342
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: SiWidgetDefaultIdProvider, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
343
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: SiWidgetDefaultIdProvider, providedIn: 'root' });
289
344
  }
290
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiGridService, decorators: [{
291
- type: Injectable
345
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: SiWidgetDefaultIdProvider, decorators: [{
346
+ type: Injectable,
347
+ args: [{ providedIn: 'root' }]
292
348
  }] });
293
349
 
294
350
  /**
295
- * Copyright (c) Siemens 2016 - 2025
351
+ * Copyright (c) Siemens 2016 - 2026
296
352
  * SPDX-License-Identifier: MIT
297
353
  */
298
354
  class SiWebComponentWrapperBaseComponent {
299
- _config;
300
- get config() {
301
- return this._config;
302
- }
303
- set config(config) {
304
- this._config = config;
305
- if (this.webComponentHost.nativeElement.children.length > 0) {
306
- this.webComponentHost.nativeElement.children[0].config = config;
307
- }
308
- }
309
- elementTagName;
310
- url;
311
- webComponentHost;
355
+ config = input.required(/* @ts-ignore */
356
+ ...(ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []));
357
+ elementTagName = input.required(/* @ts-ignore */
358
+ ...(ngDevMode ? [{ debugName: "elementTagName" }] : /* istanbul ignore next */ []));
359
+ url = input.required(/* @ts-ignore */
360
+ ...(ngDevMode ? [{ debugName: "url" }] : /* istanbul ignore next */ []));
361
+ webComponentHost = viewChild.required('webComponentHost', {
362
+ read: ElementRef
363
+ });
312
364
  webComponent;
313
365
  renderer2 = inject(Renderer2);
314
366
  document = inject(DOCUMENT);
367
+ ngOnChanges(changes) {
368
+ if (changes.config && this.webComponent) {
369
+ this.webComponent.config = this.config();
370
+ }
371
+ }
315
372
  ngAfterViewInit() {
316
- if (!this.isScriptLoaded(this.url)) {
373
+ if (!this.isScriptLoaded(this.url())) {
317
374
  const script = this.renderer2.createElement('script');
318
- script.src = this.url;
375
+ script.src = this.url();
319
376
  this.renderer2.appendChild(this.document.body, script);
320
377
  }
321
- this.webComponent = this.renderer2.createElement(this.elementTagName);
322
- this.webComponent.config = this.config;
378
+ this.webComponent = this.renderer2.createElement(this.elementTagName());
379
+ if (this.webComponent) {
380
+ this.webComponent.config = this.config();
381
+ }
323
382
  }
324
383
  isScriptLoaded(url) {
325
384
  const script = document.querySelector(`script[src='${url}']`);
326
385
  return script ? true : false;
327
386
  }
328
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiWebComponentWrapperBaseComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
329
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.6", type: SiWebComponentWrapperBaseComponent, isStandalone: true, selector: "ng-component", inputs: { config: "config", elementTagName: "elementTagName", url: "url" }, viewQueries: [{ propertyName: "webComponentHost", first: true, predicate: ["webComponentHost"], descendants: true, read: ElementRef, static: true }], ngImport: i0, template: '', isInline: true });
387
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: SiWebComponentWrapperBaseComponent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
388
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "22.0.5", type: SiWebComponentWrapperBaseComponent, isStandalone: true, inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null }, elementTagName: { classPropertyName: "elementTagName", publicName: "elementTagName", isSignal: true, isRequired: true, transformFunction: null }, url: { classPropertyName: "url", publicName: "url", isSignal: true, isRequired: true, transformFunction: null } }, viewQueries: [{ propertyName: "webComponentHost", first: true, predicate: ["webComponentHost"], descendants: true, read: ElementRef, isSignal: true }], usesOnChanges: true, ngImport: i0 });
330
389
  }
331
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiWebComponentWrapperBaseComponent, decorators: [{
332
- type: Component,
333
- args: [{
334
- template: ''
335
- }]
336
- }], propDecorators: { config: [{
337
- type: Input
338
- }], elementTagName: [{
339
- type: Input
340
- }], url: [{
341
- type: Input
342
- }], webComponentHost: [{
343
- type: ViewChild,
344
- args: ['webComponentHost', { static: true, read: ElementRef }]
345
- }] } });
390
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: SiWebComponentWrapperBaseComponent, decorators: [{
391
+ type: Directive
392
+ }], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: true }] }], elementTagName: [{ type: i0.Input, args: [{ isSignal: true, alias: "elementTagName", required: true }] }], url: [{ type: i0.Input, args: [{ isSignal: true, alias: "url", required: true }] }], webComponentHost: [{ type: i0.ViewChild, args: ['webComponentHost', { ...{
393
+ read: ElementRef
394
+ }, isSignal: true }] }] } });
346
395
 
347
396
  /**
348
- * Copyright (c) Siemens 2016 - 2025
397
+ * Copyright (c) Siemens 2016 - 2026
349
398
  * SPDX-License-Identifier: MIT
350
399
  */
351
400
  class SiWebComponentEditorWrapperComponent extends SiWebComponentWrapperBaseComponent {
@@ -356,20 +405,24 @@ class SiWebComponentEditorWrapperComponent extends SiWebComponentWrapperBaseComp
356
405
  * This will be set when setupWidgetInstanceEditor is executed
357
406
  */
358
407
  statusChangesHandler;
359
- webComponentEventListener = (event) => this.configChange.next(event.detail);
408
+ webComponentEventListener = (event) => event.detail && this.configChange.next(event.detail);
360
409
  webComponentStateChangeListener = (event) => {
361
- this.state = event.detail;
362
- this.stateChange.next(event.detail);
410
+ if (event.detail) {
411
+ this.state = event.detail;
412
+ this.stateChange.next(event.detail);
413
+ }
363
414
  };
364
415
  webComponentStatusChangesListener = (event) => {
365
- this.statusChangesHandler?.(event.detail);
416
+ if (event.detail) {
417
+ this.statusChangesHandler?.(event.detail);
418
+ }
366
419
  };
367
420
  ngAfterViewInit() {
368
421
  super.ngAfterViewInit();
369
422
  this.webComponent?.addEventListener('stateChange', this.webComponentStateChangeListener);
370
423
  this.webComponent?.addEventListener('statusChanges', this.webComponentStatusChangesListener);
371
424
  this.webComponent?.addEventListener('configChange', this.webComponentEventListener);
372
- this.webComponentHost.nativeElement.appendChild(this.webComponent);
425
+ this.webComponentHost().nativeElement.appendChild(this.webComponent);
373
426
  }
374
427
  ngOnDestroy() {
375
428
  this.webComponent?.removeEventListener('stateChange', this.webComponentStateChangeListener);
@@ -382,16 +435,16 @@ class SiWebComponentEditorWrapperComponent extends SiWebComponentWrapperBaseComp
382
435
  previous() {
383
436
  this.webComponent?.dispatchEvent(new CustomEvent('previous'));
384
437
  }
385
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiWebComponentEditorWrapperComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
386
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.6", type: SiWebComponentEditorWrapperComponent, isStandalone: true, selector: "si-web-component-editor-wrapper", usesInheritance: true, ngImport: i0, template: "<div #webComponentHost class=\"h-100\"></div>\n" });
438
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: SiWebComponentEditorWrapperComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
439
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.5", type: SiWebComponentEditorWrapperComponent, isStandalone: true, selector: "si-web-component-editor-wrapper", usesInheritance: true, ngImport: i0, template: "<div #webComponentHost class=\"h-100\"></div>\n", changeDetection: i0.ChangeDetectionStrategy.Eager });
387
440
  }
388
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiWebComponentEditorWrapperComponent, decorators: [{
441
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: SiWebComponentEditorWrapperComponent, decorators: [{
389
442
  type: Component,
390
- args: [{ selector: 'si-web-component-editor-wrapper', template: "<div #webComponentHost class=\"h-100\"></div>\n" }]
443
+ args: [{ selector: 'si-web-component-editor-wrapper', changeDetection: ChangeDetectionStrategy.Eager, template: "<div #webComponentHost class=\"h-100\"></div>\n" }]
391
444
  }] });
392
445
 
393
446
  /**
394
- * Copyright (c) Siemens 2016 - 2025
447
+ * Copyright (c) Siemens 2016 - 2026
395
448
  * SPDX-License-Identifier: MIT
396
449
  */
397
450
  class SiWebComponentWrapperComponent extends SiWebComponentWrapperBaseComponent {
@@ -401,8 +454,8 @@ class SiWebComponentWrapperComponent extends SiWebComponentWrapperBaseComponent
401
454
  }
402
455
  set editable(editable) {
403
456
  this._editable = editable;
404
- if (this.webComponentHost.nativeElement.children.length > 0) {
405
- this.webComponentHost.nativeElement.children[0].editable = editable;
457
+ if (this.webComponent) {
458
+ this.webComponent.editable = editable;
406
459
  }
407
460
  }
408
461
  primaryActions;
@@ -410,11 +463,11 @@ class SiWebComponentWrapperComponent extends SiWebComponentWrapperBaseComponent
410
463
  primaryEditActions;
411
464
  secondaryEditActions;
412
465
  configChange = new EventEmitter();
413
- webComponentEventListener = (event) => this.configChanged(event.detail);
466
+ webComponentEventListener = (event) => event.detail && this.configChanged(event.detail);
414
467
  ngAfterViewInit() {
415
468
  super.ngAfterViewInit();
416
469
  this.webComponent?.addEventListener('configChange', this.webComponentEventListener);
417
- this.webComponentHost.nativeElement.appendChild(this.webComponent);
470
+ this.webComponentHost().nativeElement.appendChild(this.webComponent);
418
471
  }
419
472
  ngOnDestroy() {
420
473
  this.webComponent?.removeEventListener('configChange', this.webComponentEventListener);
@@ -426,20 +479,19 @@ class SiWebComponentWrapperComponent extends SiWebComponentWrapperBaseComponent
426
479
  this.secondaryEditActions = event.secondaryEditActions ?? this.secondaryEditActions;
427
480
  this.configChange.emit(event);
428
481
  }
429
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiWebComponentWrapperComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
430
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.6", type: SiWebComponentWrapperComponent, isStandalone: true, selector: "si-web-component-wrapper", inputs: { editable: "editable" }, usesInheritance: true, ngImport: i0, template: "<div #webComponentHost class=\"h-100\"></div>\n" });
482
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: SiWebComponentWrapperComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
483
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.5", type: SiWebComponentWrapperComponent, isStandalone: true, selector: "si-web-component-wrapper", usesInheritance: true, ngImport: i0, template: "<div #webComponentHost class=\"h-100\"></div>\n", changeDetection: i0.ChangeDetectionStrategy.Eager });
431
484
  }
432
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiWebComponentWrapperComponent, decorators: [{
485
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: SiWebComponentWrapperComponent, decorators: [{
433
486
  type: Component,
434
- args: [{ selector: 'si-web-component-wrapper', template: "<div #webComponentHost class=\"h-100\"></div>\n" }]
435
- }], propDecorators: { editable: [{
436
- type: Input
437
- }] } });
487
+ args: [{ selector: 'si-web-component-wrapper', changeDetection: ChangeDetectionStrategy.Eager, template: "<div #webComponentHost class=\"h-100\"></div>\n" }]
488
+ }] });
438
489
 
439
490
  /**
440
- * Copyright (c) Siemens 2016 - 2025
491
+ * Copyright (c) Siemens 2016 - 2026
441
492
  * SPDX-License-Identifier: MIT
442
493
  */
494
+ /** @internal */
443
495
  const widgetFactoryRegistry = {
444
496
  _factories: {},
445
497
  register(name, factoryFn) {
@@ -469,29 +521,50 @@ const setupComponent = (widgetComponentFactory, host, injector, envInjector, com
469
521
  return throwError(() => new Error(`Unknown widget factory type ${widgetComponentFactory.factoryType}.`));
470
522
  }
471
523
  };
524
+ const loadModuleBasedComponent = (factory, componentName, host, injector, envInjector, result) => {
525
+ const loader = factory.moduleLoader;
526
+ const componentKey = factory[componentName];
527
+ loader(componentKey).then(moduleExports => {
528
+ const ngModuleRef = createNgModule(moduleExports[factory.moduleName], envInjector);
529
+ const componentType = moduleExports[componentKey];
530
+ const widgetInstanceRef = host.createComponent(componentType, { injector, ngModuleRef });
531
+ result.next(widgetInstanceRef);
532
+ result.complete();
533
+ }, rejection => {
534
+ const errorMsg = `Loading widget module ${factory.moduleName} failed`;
535
+ const msg = rejection ? `${errorMsg} with ${JSON.stringify(rejection.toString())}` : errorMsg;
536
+ result.error(msg);
537
+ result.complete();
538
+ });
539
+ };
540
+ const loadStandaloneComponent = (factory, componentName, host, injector, result) => {
541
+ const loader = factory.componentLoader;
542
+ const componentKey = factory[componentName];
543
+ loader(componentKey).then(componentType => {
544
+ const widgetInstanceRef = host.createComponent(componentType, { injector });
545
+ result.next(widgetInstanceRef);
546
+ result.complete();
547
+ }, rejection => {
548
+ const errorMsg = `Loading widget component ${componentKey} failed`;
549
+ const msg = rejection ? `${errorMsg} with ${JSON.stringify(rejection.toString())}` : errorMsg;
550
+ result.error(msg);
551
+ result.complete();
552
+ });
553
+ };
472
554
  const loadAndAttachComponent = (factory, componentName, host, injector, envInjector) => {
473
555
  const result = new Subject();
474
- if (factory[componentName]) {
475
- factory.moduleLoader(factory[componentName]).then(module => {
476
- const ngModuleRef = createNgModule(module[factory.moduleName], envInjector);
477
- const componentKey = factory[componentName];
478
- if (!componentKey) {
479
- throw new Error(`Component configuration for ${componentName} is undefined.`);
480
- }
481
- const componentType = module[componentKey];
482
- const widgetInstanceRef = host.createComponent(componentType, { injector, ngModuleRef });
483
- result.next(widgetInstanceRef);
484
- result.complete();
485
- }, rejection => {
486
- const msg = rejection
487
- ? `Loading widget module ${factory.moduleName} failed with ${JSON.stringify(rejection.toString())}`
488
- : `Loading widget module ${factory.moduleName} failed`;
489
- result.error(msg);
490
- result.complete();
491
- });
556
+ if (!factory[componentName]) {
557
+ result.error(`Provided component factory has no ${componentName} component configuration`);
558
+ return result;
559
+ }
560
+ if (factory.moduleName) {
561
+ loadModuleBasedComponent(factory, componentName, host, injector, envInjector, result);
562
+ }
563
+ else if (factory.componentLoader) {
564
+ loadStandaloneComponent(factory, componentName, host, injector, result);
492
565
  }
493
566
  else {
494
- result.error(`Provided component factory has no ${componentName} component configuration`);
567
+ result.error('Invalid factory configuration: missing moduleLoader or componentLoader');
495
568
  }
496
569
  return result;
497
570
  };
@@ -505,8 +578,8 @@ const loadAndAttachWebComponentWrapper = (factory, componentName, host) => {
505
578
  else {
506
579
  widgetInstanceRef = host.createComponent(SiWebComponentEditorWrapperComponent);
507
580
  }
508
- widgetInstanceRef.instance.elementTagName = factory[componentName];
509
- widgetInstanceRef.instance.url = factory.url;
581
+ widgetInstanceRef.setInput('elementTagName', factory[componentName]);
582
+ widgetInstanceRef.setInput('url', factory.url);
510
583
  result.next(widgetInstanceRef);
511
584
  result.complete();
512
585
  }
@@ -517,19 +590,40 @@ const loadAndAttachWebComponentWrapper = (factory, componentName, host) => {
517
590
  };
518
591
 
519
592
  /**
520
- * Copyright (c) Siemens 2016 - 2025
593
+ * Copyright (c) Siemens 2016 - 2026
521
594
  * SPDX-License-Identifier: MIT
522
595
  */
523
596
  class SiWidgetHostComponent {
524
597
  siModal = inject(SiActionDialogService);
525
- gridService = inject(SiGridService);
526
598
  injector = inject(Injector);
527
599
  envInjector = inject(EnvironmentInjector);
528
- unsubscribe = new Subject();
529
- widgetConfig = input.required();
600
+ destroyRef = inject(DestroyRef);
601
+ translateService = injectSiTranslateService();
602
+ liveAnnouncer = inject(LiveAnnouncer);
603
+ /** @internal */
604
+ elementRef = inject(ElementRef).nativeElement;
605
+ /** @defaultValue false */
606
+ keyboardActive = signal(false, /* @ts-ignore */
607
+ ...(ngDevMode ? [{ debugName: "keyboardActive" }] : /* istanbul ignore next */ []));
608
+ widgetConfig = input.required(/* @ts-ignore */
609
+ ...(ngDevMode ? [{ debugName: "widgetConfig" }] : /* istanbul ignore next */ []));
610
+ grid = input(/* @ts-ignore */
611
+ ...(ngDevMode ? [undefined, { debugName: "grid" }] : /* istanbul ignore next */ []));
612
+ /**
613
+ * The component factory for this widget's type.
614
+ */
615
+ componentFactory = input(/* @ts-ignore */
616
+ ...(ngDevMode ? [undefined, { debugName: "componentFactory" }] : /* istanbul ignore next */ []));
617
+ /**
618
+ * Sets the widget host into editable mode.
619
+ *
620
+ * @defaultValue false
621
+ */
622
+ editable = input(false, /* @ts-ignore */
623
+ ...(ngDevMode ? [{ debugName: "editable" }] : /* istanbul ignore next */ []));
530
624
  remove = output();
531
625
  edit = output();
532
- initCompleted = output();
626
+ gridEvent = output();
533
627
  card = viewChild.required('card');
534
628
  widgetHost = viewChild.required('widgetHost', { read: ViewContainerRef });
535
629
  labelEdit = t(() => $localize `:@@DASHBOARD.WIDGET.EDIT:Edit`);
@@ -540,19 +634,36 @@ class SiWidgetHostComponent {
540
634
  labelDialogHeading = t(() => $localize `:@@DASHBOARD.REMOVE_WIDGET_CONFIRMATION_DIALOG.HEADING:Remove widget`);
541
635
  labelDialogRemove = t(() => $localize `:@@DASHBOARD.REMOVE_WIDGET_CONFIRMATION_DIALOG.REMOVE:Remove`);
542
636
  labelDialogCancel = t(() => $localize `:@@DASHBOARD.REMOVE_WIDGET_CONFIRMATION_DIALOG.CANCEL:Cancel`);
637
+ a11yWidgetMovedMessage = t(() => $localize `:@@DASHBOARD.WIDGET.A11Y.MOVED:Widget moved to column {{column}}, row {{row}}.`);
638
+ a11yWidgetResizedMessage = t(() => $localize `:@@DASHBOARD.WIDGET.A11Y.RESIZED:Widget resized to {{columns}} columns wide, {{rows}} rows tall.`);
639
+ a11yWidgetActivatedMessage = t(() => $localize `:@@DASHBOARD.WIDGET.A11Y.ACTIVATED:Widget activated. Use arrow keys to move, Shift+arrow keys to resize, Escape to exit.`);
640
+ a11yWidgetDeactivatedMessage = t(() => $localize `:@@DASHBOARD.WIDGET.A11Y.DEACTIVATED:Widget deactivated.`);
641
+ a11yWidgetDescription = t(() => $localize `:@@DASHBOARD.WIDGET.A11Y.DESCRIPTION:Press Enter or Space to activate. Then use arrow keys to move, Shift+arrow keys to resize, Escape to exit.`);
642
+ widgetAriaLabel = computed(() => {
643
+ const widgetHeading = this.widgetConfig().heading;
644
+ if (widgetHeading) {
645
+ return this.translateService.translateSync(widgetHeading);
646
+ }
647
+ return null;
648
+ }, /* @ts-ignore */
649
+ ...(ngDevMode ? [{ debugName: "widgetAriaLabel" }] : /* istanbul ignore next */ []));
543
650
  widgetInstance;
544
651
  widgetRef;
545
652
  /** @defaultValue [] */
546
- primaryActions = [];
653
+ primaryActions = signal([], /* @ts-ignore */
654
+ ...(ngDevMode ? [{ debugName: "primaryActions" }] : /* istanbul ignore next */ []));
547
655
  /** @defaultValue [] */
548
- secondaryActions = [];
656
+ secondaryActions = signal([], /* @ts-ignore */
657
+ ...(ngDevMode ? [{ debugName: "secondaryActions" }] : /* istanbul ignore next */ []));
549
658
  /** @defaultValue 'expanded' */
550
- actionBarViewType = 'expanded';
551
- editable$ = this.gridService.editable$;
659
+ actionBarViewType = signal('expanded', /* @ts-ignore */
660
+ ...(ngDevMode ? [{ debugName: "actionBarViewType" }] : /* istanbul ignore next */ []));
552
661
  /** @defaultValue [] */
553
- editablePrimaryActions = [];
662
+ editablePrimaryActions = signal([], /* @ts-ignore */
663
+ ...(ngDevMode ? [{ debugName: "editablePrimaryActions" }] : /* istanbul ignore next */ []));
554
664
  /** @defaultValue [] */
555
- editableSecondaryActions = [];
665
+ editableSecondaryActions = signal([], /* @ts-ignore */
666
+ ...(ngDevMode ? [{ debugName: "editableSecondaryActions" }] : /* istanbul ignore next */ []));
556
667
  /**
557
668
  * @defaultValue
558
669
  * ```
@@ -591,13 +702,30 @@ class SiWidgetHostComponent {
591
702
  iconOnly: true,
592
703
  action: () => this.onRemove()
593
704
  };
594
- widgetInstanceFooter;
595
- get accentLine() {
596
- const widgetConfig = this.widgetConfig();
597
- return widgetConfig.accentLine ? 'accent-' + widgetConfig.accentLine : '';
598
- }
705
+ widgetInstanceFooter = signal(undefined, /* @ts-ignore */
706
+ ...(ngDevMode ? [{ debugName: "widgetInstanceFooter" }] : /* istanbul ignore next */ []));
707
+ accentLine = computed(() => {
708
+ const { accentLine } = this.widgetConfig();
709
+ return accentLine ? 'accent-' + accentLine : '';
710
+ }, /* @ts-ignore */
711
+ ...(ngDevMode ? [{ debugName: "accentLine" }] : /* istanbul ignore next */ []));
599
712
  ngOnChanges(changes) {
600
713
  if (changes.widgetConfig) {
714
+ const options = {
715
+ ...this.widgetConfig(),
716
+ w: this.widgetConfig().width,
717
+ h: this.widgetConfig().height,
718
+ x: this.widgetConfig().x,
719
+ y: this.widgetConfig().y,
720
+ minW: this.widgetConfig().minWidth,
721
+ minH: this.widgetConfig().minHeight
722
+ };
723
+ if (!changes.widgetConfig.firstChange) {
724
+ this.grid()?.update(this.elementRef, options);
725
+ }
726
+ else {
727
+ this.grid()?.makeWidget(this.elementRef, options);
728
+ }
601
729
  if (this.widgetRef) {
602
730
  if (isSignal(this.widgetRef.instance.config)) {
603
731
  this.widgetRef.setInput('config', this.widgetConfig());
@@ -607,21 +735,148 @@ class SiWidgetHostComponent {
607
735
  }
608
736
  }
609
737
  }
738
+ if (changes.editable && !changes.editable.firstChange) {
739
+ this.setupEditable(this.editable());
740
+ }
610
741
  }
611
742
  ngOnInit() {
612
743
  this.attachWidgetInstance();
613
- this.editable$
614
- .pipe(takeUntil(this.unsubscribe))
615
- .subscribe(editable => this.setupEditable(editable));
616
744
  }
617
- ngOnDestroy() {
618
- this.unsubscribe.next();
619
- this.unsubscribe.complete();
745
+ onToggleActive(event) {
746
+ if (!this.editable() || event.target !== event.currentTarget) {
747
+ return;
748
+ }
749
+ event.preventDefault();
750
+ const active = !this.keyboardActive();
751
+ this.keyboardActive.set(active);
752
+ this.announceTranslated(active ? this.a11yWidgetActivatedMessage : this.a11yWidgetDeactivatedMessage, {});
753
+ }
754
+ onDeactivate(event) {
755
+ if (!this.keyboardActive()) {
756
+ return;
757
+ }
758
+ event.preventDefault();
759
+ this.keyboardActive.set(false);
760
+ this.announceTranslated(this.a11yWidgetDeactivatedMessage, {});
761
+ }
762
+ onFocusOut() {
763
+ if (this.keyboardActive()) {
764
+ this.keyboardActive.set(false);
765
+ }
766
+ }
767
+ onArrowKey(event) {
768
+ if (!this.keyboardActive()) {
769
+ return;
770
+ }
771
+ const el = this.elementRef;
772
+ const node = el?.gridstackNode;
773
+ if (!node?.grid) {
774
+ return;
775
+ }
776
+ const keyEvent = event;
777
+ if (keyEvent.shiftKey) {
778
+ this.handleResize(keyEvent, node);
779
+ }
780
+ else {
781
+ this.handleMove(keyEvent, node);
782
+ }
783
+ }
784
+ handleResize(event, node) {
785
+ const el = this.elementRef;
786
+ let { w, h } = node;
787
+ switch (event.key) {
788
+ case 'ArrowRight':
789
+ w = (w ?? 1) + 1;
790
+ break;
791
+ case 'ArrowLeft':
792
+ w = Math.max(node.minW ?? 1, (w ?? 1) - 1);
793
+ break;
794
+ case 'ArrowDown':
795
+ h = (h ?? 1) + 1;
796
+ break;
797
+ case 'ArrowUp':
798
+ h = Math.max(node.minH ?? 1, (h ?? 1) - 1);
799
+ break;
800
+ default:
801
+ return;
802
+ }
803
+ if (w === node.w && h === node.h) {
804
+ return;
805
+ }
806
+ event.preventDefault();
807
+ const grid = node.grid;
808
+ grid.update(el, { w, h });
809
+ this.gridEvent.emit(new Event('resizestop'));
810
+ this.announceTranslated(this.a11yWidgetResizedMessage, { columns: w, rows: h });
811
+ }
812
+ handleMove(event, node) {
813
+ const el = this.elementRef;
814
+ const { x, y } = node;
815
+ const grid = node.grid;
816
+ let newX = x ?? 0;
817
+ let newY = y ?? 0;
818
+ switch (event.key) {
819
+ case 'ArrowRight':
820
+ do {
821
+ newX++;
822
+ grid.update(el, { x: newX, y });
823
+ } while (node.x === x && newX < grid.getColumn());
824
+ break;
825
+ case 'ArrowLeft':
826
+ if (newX <= 0) {
827
+ break;
828
+ }
829
+ do {
830
+ newX--;
831
+ grid.update(el, { x: newX, y });
832
+ } while (node.x === x && newX > 0);
833
+ break;
834
+ case 'ArrowDown':
835
+ do {
836
+ newY++;
837
+ grid.update(el, { x, y: newY });
838
+ } while (node.y === y && newY < grid.getRow());
839
+ break;
840
+ case 'ArrowUp':
841
+ if (newY <= 0) {
842
+ break;
843
+ }
844
+ do {
845
+ newY--;
846
+ grid.update(el, { x, y: newY });
847
+ } while (node.y === y && newY > 0);
848
+ break;
849
+ default:
850
+ return;
851
+ }
852
+ event.preventDefault();
853
+ this.gridEvent.emit(new Event('dragstop'));
854
+ this.scrollIntoViewAfterTransition(el);
855
+ this.announceTranslated(this.a11yWidgetMovedMessage, {
856
+ column: (node.x ?? 0) + 1,
857
+ row: (node.y ?? 0) + 1
858
+ });
859
+ }
860
+ scrollIntoViewAfterTransition(el) {
861
+ const onEnd = () => {
862
+ el.removeEventListener('transitionend', onEnd);
863
+ el.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
864
+ };
865
+ el.addEventListener('transitionend', onEnd, { once: true });
866
+ }
867
+ announce(message) {
868
+ this.liveAnnouncer.announce(message, 'assertive');
869
+ }
870
+ announceTranslated(message, params) {
871
+ this.translateService
872
+ .translateAsync(message, params)
873
+ .pipe(first())
874
+ .subscribe(msg => this.announce(msg));
620
875
  }
621
876
  attachWidgetInstance() {
622
- const widget = this.gridService.getWidget(this.widgetConfig().widgetId);
623
- if (widget) {
624
- setupWidgetInstance(widget.componentFactory, this.widgetHost(), this.injector, this.envInjector).subscribe({
877
+ const componentFactory = this.componentFactory();
878
+ if (componentFactory) {
879
+ setupWidgetInstance(componentFactory, this.widgetHost(), this.injector, this.envInjector).subscribe({
625
880
  next: (widgetRef) => {
626
881
  this.widgetInstance = widgetRef.instance;
627
882
  this.widgetRef = widgetRef;
@@ -630,8 +885,8 @@ class SiWidgetHostComponent {
630
885
  // on web component, who pushes their configuration through an event after being attached
631
886
  // to the DOM.
632
887
  this.widgetInstance.configChange
633
- .pipe(takeUntil(this.unsubscribe))
634
- .subscribe(event => setTimeout(() => this.setupEditable(this.editable$.value, event)));
888
+ .pipe(takeUntilDestroyed(this.destroyRef))
889
+ .subscribe(event => setTimeout(() => this.setupEditable(this.editable(), event)));
635
890
  }
636
891
  if (isSignal(this.widgetInstance.config)) {
637
892
  this.widgetRef.setInput('config', this.widgetConfig());
@@ -639,19 +894,20 @@ class SiWidgetHostComponent {
639
894
  else {
640
895
  this.widgetInstance.config = this.widgetConfig();
641
896
  }
642
- this.widgetInstanceFooter = this.widgetInstance.footer;
643
- this.setupEditable(this.gridService.editable$.value);
644
- this.initCompleted.emit();
897
+ this.widgetInstanceFooter.set(this.widgetInstance.footer);
898
+ this.setupEditable(this.editable());
645
899
  },
646
900
  error: error => console.error('Error: ', error)
647
901
  });
648
902
  }
649
903
  else {
650
904
  console.error(`Cannot find widget with id ${this.widgetConfig().widgetId}`);
651
- this.initCompleted.emit();
652
905
  }
653
906
  }
654
907
  setupEditable(editable, widgetConfig) {
908
+ if (!editable) {
909
+ this.keyboardActive.set(false);
910
+ }
655
911
  widgetConfig ??= {
656
912
  primaryActions: this.widgetInstance?.primaryActions,
657
913
  secondaryActions: this.widgetInstance?.secondaryActions,
@@ -659,34 +915,37 @@ class SiWidgetHostComponent {
659
915
  secondaryEditActions: this.widgetInstance?.secondaryEditActions
660
916
  };
661
917
  if (editable) {
662
- this.editablePrimaryActions = [];
918
+ this.editablePrimaryActions.set([]);
663
919
  if (this.isEditable()) {
664
- this.editablePrimaryActions.push(this.editAction);
920
+ this.editablePrimaryActions.set([...this.editablePrimaryActions(), this.editAction]);
665
921
  }
666
922
  if (!this.widgetConfig().isNotRemovable) {
667
- this.editablePrimaryActions.push(this.removeAction);
923
+ this.editablePrimaryActions.set([...this.editablePrimaryActions(), this.removeAction]);
668
924
  }
669
925
  if (widgetConfig.primaryEditActions) {
670
- this.primaryActions = [...widgetConfig.primaryEditActions, ...this.editablePrimaryActions];
926
+ this.primaryActions.set([
927
+ ...widgetConfig.primaryEditActions,
928
+ ...this.editablePrimaryActions()
929
+ ]);
671
930
  }
672
931
  else {
673
- this.primaryActions = this.editablePrimaryActions;
932
+ this.primaryActions.set(this.editablePrimaryActions());
674
933
  }
675
934
  if (widgetConfig.secondaryEditActions) {
676
- this.secondaryActions = [
935
+ this.secondaryActions.set([
677
936
  ...widgetConfig.secondaryEditActions,
678
- ...this.editableSecondaryActions
679
- ];
937
+ ...this.editableSecondaryActions()
938
+ ]);
680
939
  }
681
940
  else {
682
- this.secondaryActions = this.editableSecondaryActions;
941
+ this.secondaryActions.set(this.editableSecondaryActions());
683
942
  }
684
- this.actionBarViewType = 'expanded';
943
+ this.actionBarViewType.set('expanded');
685
944
  }
686
945
  else {
687
- this.actionBarViewType = this.widgetConfig().actionBarViewType ?? 'expanded';
688
- this.primaryActions = widgetConfig.primaryActions ?? [];
689
- this.secondaryActions = widgetConfig.secondaryActions ?? [];
946
+ this.actionBarViewType.set(this.widgetConfig().actionBarViewType ?? 'expanded');
947
+ this.primaryActions.set(widgetConfig.primaryActions ?? []);
948
+ this.secondaryActions.set(widgetConfig.secondaryActions ?? []);
690
949
  }
691
950
  if (this.widgetInstance?.editable !== undefined) {
692
951
  if (isSignal(this.widgetInstance.editable)) {
@@ -709,8 +968,7 @@ class SiWidgetHostComponent {
709
968
  }
710
969
  isEditable() {
711
970
  const widgetConfig = this.widgetConfig();
712
- return (!widgetConfig.immutable &&
713
- !!this.gridService.getWidget(widgetConfig.widgetId)?.componentFactory?.editorComponentName);
971
+ return !widgetConfig.immutable && !!this.componentFactory()?.editorComponentName;
714
972
  }
715
973
  onEdit() {
716
974
  this.edit.emit(this.widgetConfig());
@@ -730,18 +988,24 @@ class SiWidgetHostComponent {
730
988
  }
731
989
  });
732
990
  }
733
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiWidgetHostComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
734
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.6", type: SiWidgetHostComponent, isStandalone: true, selector: "si-widget-host", inputs: { widgetConfig: { classPropertyName: "widgetConfig", publicName: "widgetConfig", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { remove: "remove", edit: "edit", initCompleted: "initCompleted" }, host: { classAttribute: "grid-stack-item" }, viewQueries: [{ propertyName: "card", first: true, predicate: ["card"], descendants: true, isSignal: true }, { propertyName: "widgetHost", first: true, predicate: ["widgetHost"], descendants: true, read: ViewContainerRef, isSignal: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"grid-stack-item-content p-4\">\n @if (editable$ | async) {\n <div class=\"resize-handle\">\n <svg\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <g>\n <path d=\"M16 16H18V18H16V16Z\" fill=\"currentColor\" />\n <path d=\"M11 16H13V18H11V16Z\" fill=\"currentColor\" />\n <path d=\"M11 11H13V13H11V11Z\" fill=\"currentColor\" />\n <path d=\"M6 16H8V18H6V16Z\" fill=\"currentColor\" />\n <path d=\"M16 11H18V13H16V11Z\" fill=\"currentColor\" />\n <path d=\"M16 6H18V8H16V6Z\" fill=\"currentColor\" />\n </g>\n </svg>\n </div>\n <div class=\"draggable-overlay\"></div>\n }\n <si-dashboard-card\n #card\n class=\"h-100 dragging-card\"\n [ngClass]=\"accentLine\"\n [expandText]=\"labelExpand\"\n [restoreText]=\"labelRestore\"\n [heading]=\"widgetConfig().heading\"\n [primaryActions]=\"primaryActions\"\n [secondaryActions]=\"secondaryActions\"\n [actionBarViewType]=\"actionBarViewType\"\n [enableExpandInteraction]=\"widgetConfig().expandable\"\n [imgAlt]=\"widgetConfig().image?.alt\"\n [imgDir]=\"widgetConfig().image?.dir\"\n [imgObjectFit]=\"widgetConfig().image?.objectFit\"\n [imgObjectPosition]=\"widgetConfig().image?.objectPosition\"\n [imgSrc]=\"widgetConfig().image?.src\"\n >\n <div class=\"card-body overflow-auto\" body>\n <ng-container #widgetHost />\n </div>\n\n @if (widgetInstanceFooter) {\n <div class=\"card-footer\" footer>\n <ng-container *ngTemplateOutlet=\"widgetInstanceFooter\" />\n </div>\n }\n </si-dashboard-card>\n</div>\n", styles: [":host{display:flex}::ng-deep si-content-action-bar{z-index:500}::ng-deep .ui-droppable.ui-droppable-over>*:not(.ui-droppable){cursor:move!important;pointer-events:inherit!important}::ng-deep .ui-draggable-dragging .dragging-card,::ng-deep .ui-resizable-resizing .dragging-card{box-shadow:0 0 8px var(--element-box-shadow-color-1),0 8px 8px var(--element-box-shadow-color-2)!important}::ng-deep .ui-draggable-dragging>.grid-stack-item-content,::ng-deep .ui-resizable-resizing>.grid-stack-item-content{box-shadow:none;opacity:1}::ng-deep .ui-resizable-handle{display:block!important}::ng-deep .ui-resizable-se{background-image:none!important;transform:none!important}.resize-handle{inset-block-end:6px;color:var(--element-ui-2);pointer-events:none;position:absolute;inset-inline-end:6px;text-align:center;z-index:10}.draggable-overlay{cursor:move;inset-block-end:24px;inset-block-start:40px;inset-inline:24px;position:absolute;z-index:100}\n"], dependencies: [{ kind: "component", type: SiDashboardCardComponent, selector: "si-dashboard-card", inputs: ["restoreText", "expandText", "enableExpandInteraction", "showMenubar"], outputs: ["expandChange"] }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
991
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: SiWidgetHostComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
992
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: SiWidgetHostComponent, isStandalone: true, selector: "si-widget-host", inputs: { widgetConfig: { classPropertyName: "widgetConfig", publicName: "widgetConfig", isSignal: true, isRequired: true, transformFunction: null }, grid: { classPropertyName: "grid", publicName: "grid", isSignal: true, isRequired: false, transformFunction: null }, componentFactory: { classPropertyName: "componentFactory", publicName: "componentFactory", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { remove: "remove", edit: "edit", gridEvent: "gridEvent" }, host: { properties: { "attr.role": "editable() ? null : 'listitem'", "attr.aria-roledescription": "editable() ? null : 'widget'", "attr.aria-label": "editable() || !widgetAriaLabel() ? null : widgetAriaLabel()" }, classAttribute: "grid-stack-item" }, viewQueries: [{ propertyName: "card", first: true, predicate: ["card"], descendants: true, isSignal: true }, { propertyName: "widgetHost", first: true, predicate: ["widgetHost"], descendants: true, read: ViewContainerRef, isSignal: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"grid-stack-item-content p-4\">\n @if (editable()) {\n <div class=\"resize-handle\">\n <svg\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <g>\n <path d=\"M16 16H18V18H16V16Z\" fill=\"currentColor\" />\n <path d=\"M11 16H13V18H11V16Z\" fill=\"currentColor\" />\n <path d=\"M11 11H13V13H11V11Z\" fill=\"currentColor\" />\n <path d=\"M6 16H8V18H6V16Z\" fill=\"currentColor\" />\n <path d=\"M16 11H18V13H16V11Z\" fill=\"currentColor\" />\n <path d=\"M16 6H18V8H16V6Z\" fill=\"currentColor\" />\n </g>\n </svg>\n </div>\n <div class=\"draggable-overlay\"></div>\n }\n <si-dashboard-card\n #card\n class=\"h-100 dragging-card\"\n [class]=\"accentLine()\"\n [class.elevation-2]=\"keyboardActive()\"\n [class.si-widget-keyboard-active]=\"keyboardActive()\"\n [attr.tabindex]=\"editable() ? 0 : null\"\n [attr.role]=\"editable() ? 'application' : null\"\n [attr.aria-roledescription]=\"editable() ? 'widget' : null\"\n [attr.aria-label]=\"editable() ? widgetConfig().heading : null\"\n [attr.aria-description]=\"editable() ? (a11yWidgetDescription | translate) : null\"\n [expandText]=\"labelExpand\"\n [restoreText]=\"labelRestore\"\n [heading]=\"widgetConfig().heading\"\n [primaryActions]=\"primaryActions()\"\n [secondaryActions]=\"secondaryActions()\"\n [actionBarViewType]=\"actionBarViewType()\"\n [enableExpandInteraction]=\"widgetConfig().expandable\"\n [imgAlt]=\"widgetConfig().image?.alt\"\n [imgDir]=\"widgetConfig().image?.dir\"\n [imgObjectFit]=\"widgetConfig().image?.objectFit\"\n [imgObjectPosition]=\"widgetConfig().image?.objectPosition\"\n [imgSrc]=\"widgetConfig().image?.src\"\n (keydown.enter)=\"onToggleActive($event)\"\n (keydown.space)=\"onToggleActive($event)\"\n (keydown.escape)=\"onDeactivate($event)\"\n (keydown.arrowRight)=\"onArrowKey($event)\"\n (keydown.arrowLeft)=\"onArrowKey($event)\"\n (keydown.arrowUp)=\"onArrowKey($event)\"\n (keydown.arrowDown)=\"onArrowKey($event)\"\n (keydown.shift.arrowRight)=\"onArrowKey($event)\"\n (keydown.shift.arrowLeft)=\"onArrowKey($event)\"\n (keydown.shift.arrowUp)=\"onArrowKey($event)\"\n (keydown.shift.arrowDown)=\"onArrowKey($event)\"\n (focusout)=\"onFocusOut()\"\n >\n <div class=\"card-body overflow-auto\" body>\n <ng-container #widgetHost />\n </div>\n @let footer = widgetInstanceFooter();\n @if (footer) {\n <div class=\"card-footer\" footer>\n <ng-container *ngTemplateOutlet=\"footer\" />\n </div>\n }\n </si-dashboard-card>\n</div>\n", styles: [":host{display:flex}::ng-deep body:not(:has(.ui-draggable-dragging)) .draggable-overlay{cursor:grab}::ng-deep si-content-action-bar{z-index:500}::ng-deep .ui-droppable.ui-droppable-over>*:not(.ui-droppable){cursor:move!important;pointer-events:inherit!important}::ng-deep .ui-draggable-dragging .dragging-card,::ng-deep .ui-resizable-resizing .dragging-card{box-shadow:0 0 8px var(--element-box-shadow-color-1),0 8px 8px var(--element-box-shadow-color-2)!important}::ng-deep .ui-draggable-dragging>.grid-stack-item-content,::ng-deep .ui-resizable-resizing>.grid-stack-item-content{box-shadow:none;opacity:1}::ng-deep .ui-resizable-handle{display:block!important}::ng-deep .ui-resizable-se{background-image:none!important;transform:none!important;inset-block-end:6px!important;inset-inline-end:6px!important}.resize-handle{inset-block-end:6px;color:var(--element-ui-2);pointer-events:none;position:absolute;inset-inline-end:6px;text-align:center;z-index:10}.draggable-overlay{inset:8px;position:absolute;z-index:100}\n"], dependencies: [{ kind: "component", type: SiDashboardCardComponent, selector: "si-dashboard-card", inputs: ["restoreText", "expandText", "enableExpandInteraction", "showMenubar"], outputs: ["expandChange"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: SiTranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
735
993
  }
736
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiWidgetHostComponent, decorators: [{
994
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: SiWidgetHostComponent, decorators: [{
737
995
  type: Component,
738
- args: [{ selector: 'si-widget-host', imports: [SiDashboardCardComponent, AsyncPipe, NgClass, NgTemplateOutlet], host: {
739
- class: 'grid-stack-item'
740
- }, template: "<div class=\"grid-stack-item-content p-4\">\n @if (editable$ | async) {\n <div class=\"resize-handle\">\n <svg\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <g>\n <path d=\"M16 16H18V18H16V16Z\" fill=\"currentColor\" />\n <path d=\"M11 16H13V18H11V16Z\" fill=\"currentColor\" />\n <path d=\"M11 11H13V13H11V11Z\" fill=\"currentColor\" />\n <path d=\"M6 16H8V18H6V16Z\" fill=\"currentColor\" />\n <path d=\"M16 11H18V13H16V11Z\" fill=\"currentColor\" />\n <path d=\"M16 6H18V8H16V6Z\" fill=\"currentColor\" />\n </g>\n </svg>\n </div>\n <div class=\"draggable-overlay\"></div>\n }\n <si-dashboard-card\n #card\n class=\"h-100 dragging-card\"\n [ngClass]=\"accentLine\"\n [expandText]=\"labelExpand\"\n [restoreText]=\"labelRestore\"\n [heading]=\"widgetConfig().heading\"\n [primaryActions]=\"primaryActions\"\n [secondaryActions]=\"secondaryActions\"\n [actionBarViewType]=\"actionBarViewType\"\n [enableExpandInteraction]=\"widgetConfig().expandable\"\n [imgAlt]=\"widgetConfig().image?.alt\"\n [imgDir]=\"widgetConfig().image?.dir\"\n [imgObjectFit]=\"widgetConfig().image?.objectFit\"\n [imgObjectPosition]=\"widgetConfig().image?.objectPosition\"\n [imgSrc]=\"widgetConfig().image?.src\"\n >\n <div class=\"card-body overflow-auto\" body>\n <ng-container #widgetHost />\n </div>\n\n @if (widgetInstanceFooter) {\n <div class=\"card-footer\" footer>\n <ng-container *ngTemplateOutlet=\"widgetInstanceFooter\" />\n </div>\n }\n </si-dashboard-card>\n</div>\n", styles: [":host{display:flex}::ng-deep si-content-action-bar{z-index:500}::ng-deep .ui-droppable.ui-droppable-over>*:not(.ui-droppable){cursor:move!important;pointer-events:inherit!important}::ng-deep .ui-draggable-dragging .dragging-card,::ng-deep .ui-resizable-resizing .dragging-card{box-shadow:0 0 8px var(--element-box-shadow-color-1),0 8px 8px var(--element-box-shadow-color-2)!important}::ng-deep .ui-draggable-dragging>.grid-stack-item-content,::ng-deep .ui-resizable-resizing>.grid-stack-item-content{box-shadow:none;opacity:1}::ng-deep .ui-resizable-handle{display:block!important}::ng-deep .ui-resizable-se{background-image:none!important;transform:none!important}.resize-handle{inset-block-end:6px;color:var(--element-ui-2);pointer-events:none;position:absolute;inset-inline-end:6px;text-align:center;z-index:10}.draggable-overlay{cursor:move;inset-block-end:24px;inset-block-start:40px;inset-inline:24px;position:absolute;z-index:100}\n"] }]
741
- }] });
996
+ args: [{ selector: 'si-widget-host', imports: [SiDashboardCardComponent, NgTemplateOutlet, SiTranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, host: {
997
+ class: 'grid-stack-item',
998
+ // In non-edit mode, expose each widget as a labeled list item so screen
999
+ // readers group the widgets and let users jump between them. In edit mode
1000
+ // the inner card carries the interactive `application` role instead.
1001
+ '[attr.role]': "editable() ? null : 'listitem'",
1002
+ '[attr.aria-roledescription]': "editable() ? null : 'widget'",
1003
+ '[attr.aria-label]': 'editable() || !widgetAriaLabel() ? null : widgetAriaLabel()'
1004
+ }, template: "<div class=\"grid-stack-item-content p-4\">\n @if (editable()) {\n <div class=\"resize-handle\">\n <svg\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <g>\n <path d=\"M16 16H18V18H16V16Z\" fill=\"currentColor\" />\n <path d=\"M11 16H13V18H11V16Z\" fill=\"currentColor\" />\n <path d=\"M11 11H13V13H11V11Z\" fill=\"currentColor\" />\n <path d=\"M6 16H8V18H6V16Z\" fill=\"currentColor\" />\n <path d=\"M16 11H18V13H16V11Z\" fill=\"currentColor\" />\n <path d=\"M16 6H18V8H16V6Z\" fill=\"currentColor\" />\n </g>\n </svg>\n </div>\n <div class=\"draggable-overlay\"></div>\n }\n <si-dashboard-card\n #card\n class=\"h-100 dragging-card\"\n [class]=\"accentLine()\"\n [class.elevation-2]=\"keyboardActive()\"\n [class.si-widget-keyboard-active]=\"keyboardActive()\"\n [attr.tabindex]=\"editable() ? 0 : null\"\n [attr.role]=\"editable() ? 'application' : null\"\n [attr.aria-roledescription]=\"editable() ? 'widget' : null\"\n [attr.aria-label]=\"editable() ? widgetConfig().heading : null\"\n [attr.aria-description]=\"editable() ? (a11yWidgetDescription | translate) : null\"\n [expandText]=\"labelExpand\"\n [restoreText]=\"labelRestore\"\n [heading]=\"widgetConfig().heading\"\n [primaryActions]=\"primaryActions()\"\n [secondaryActions]=\"secondaryActions()\"\n [actionBarViewType]=\"actionBarViewType()\"\n [enableExpandInteraction]=\"widgetConfig().expandable\"\n [imgAlt]=\"widgetConfig().image?.alt\"\n [imgDir]=\"widgetConfig().image?.dir\"\n [imgObjectFit]=\"widgetConfig().image?.objectFit\"\n [imgObjectPosition]=\"widgetConfig().image?.objectPosition\"\n [imgSrc]=\"widgetConfig().image?.src\"\n (keydown.enter)=\"onToggleActive($event)\"\n (keydown.space)=\"onToggleActive($event)\"\n (keydown.escape)=\"onDeactivate($event)\"\n (keydown.arrowRight)=\"onArrowKey($event)\"\n (keydown.arrowLeft)=\"onArrowKey($event)\"\n (keydown.arrowUp)=\"onArrowKey($event)\"\n (keydown.arrowDown)=\"onArrowKey($event)\"\n (keydown.shift.arrowRight)=\"onArrowKey($event)\"\n (keydown.shift.arrowLeft)=\"onArrowKey($event)\"\n (keydown.shift.arrowUp)=\"onArrowKey($event)\"\n (keydown.shift.arrowDown)=\"onArrowKey($event)\"\n (focusout)=\"onFocusOut()\"\n >\n <div class=\"card-body overflow-auto\" body>\n <ng-container #widgetHost />\n </div>\n @let footer = widgetInstanceFooter();\n @if (footer) {\n <div class=\"card-footer\" footer>\n <ng-container *ngTemplateOutlet=\"footer\" />\n </div>\n }\n </si-dashboard-card>\n</div>\n", styles: [":host{display:flex}::ng-deep body:not(:has(.ui-draggable-dragging)) .draggable-overlay{cursor:grab}::ng-deep si-content-action-bar{z-index:500}::ng-deep .ui-droppable.ui-droppable-over>*:not(.ui-droppable){cursor:move!important;pointer-events:inherit!important}::ng-deep .ui-draggable-dragging .dragging-card,::ng-deep .ui-resizable-resizing .dragging-card{box-shadow:0 0 8px var(--element-box-shadow-color-1),0 8px 8px var(--element-box-shadow-color-2)!important}::ng-deep .ui-draggable-dragging>.grid-stack-item-content,::ng-deep .ui-resizable-resizing>.grid-stack-item-content{box-shadow:none;opacity:1}::ng-deep .ui-resizable-handle{display:block!important}::ng-deep .ui-resizable-se{background-image:none!important;transform:none!important;inset-block-end:6px!important;inset-inline-end:6px!important}.resize-handle{inset-block-end:6px;color:var(--element-ui-2);pointer-events:none;position:absolute;inset-inline-end:6px;text-align:center;z-index:10}.draggable-overlay{inset:8px;position:absolute;z-index:100}\n"] }]
1005
+ }], propDecorators: { widgetConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "widgetConfig", required: true }] }], grid: [{ type: i0.Input, args: [{ isSignal: true, alias: "grid", required: false }] }], componentFactory: [{ type: i0.Input, args: [{ isSignal: true, alias: "componentFactory", required: false }] }], editable: [{ type: i0.Input, args: [{ isSignal: true, alias: "editable", required: false }] }], remove: [{ type: i0.Output, args: ["remove"] }], edit: [{ type: i0.Output, args: ["edit"] }], gridEvent: [{ type: i0.Output, args: ["gridEvent"] }], card: [{ type: i0.ViewChild, args: ['card', { isSignal: true }] }], widgetHost: [{ type: i0.ViewChild, args: ['widgetHost', { ...{ read: ViewContainerRef }, isSignal: true }] }] } });
742
1006
 
743
1007
  /**
744
- * Copyright (c) Siemens 2016 - 2025
1008
+ * Copyright (c) Siemens 2016 - 2026
745
1009
  * SPDX-License-Identifier: MIT
746
1010
  */
747
1011
  class SiGridstackWrapperComponent {
@@ -750,17 +1014,27 @@ class SiGridstackWrapperComponent {
750
1014
  *
751
1015
  * @defaultValue []
752
1016
  */
753
- widgetConfigs = input([]);
1017
+ widgetConfigs = input([], /* @ts-ignore */
1018
+ ...(ngDevMode ? [{ debugName: "widgetConfigs" }] : /* istanbul ignore next */ []));
754
1019
  /**
755
1020
  * Whenever gridstack allows to drag, resize or delete the grid item
756
1021
  *
757
1022
  * @defaultValue false
758
1023
  */
759
- editable = input(false);
1024
+ editable = input(false, /* @ts-ignore */
1025
+ ...(ngDevMode ? [{ debugName: "editable" }] : /* istanbul ignore next */ []));
760
1026
  /**
761
1027
  * Module configuration
762
1028
  */
763
- gridConfig = input();
1029
+ gridConfig = input(/* @ts-ignore */
1030
+ ...(ngDevMode ? [undefined, { debugName: "gridConfig" }] : /* istanbul ignore next */ []));
1031
+ /**
1032
+ * Map of widget id to widget definition, passed through to widget hosts.
1033
+ *
1034
+ * @defaultValue new Map()
1035
+ */
1036
+ widgetCatalogMap = input(new Map(), /* @ts-ignore */
1037
+ ...(ngDevMode ? [{ debugName: "widgetCatalogMap" }] : /* istanbul ignore next */ []));
764
1038
  /**
765
1039
  * Emits dashboard grid events.
766
1040
  */
@@ -773,44 +1047,38 @@ class SiGridstackWrapperComponent {
773
1047
  * Emits the id of a widget instance that shall be edited.
774
1048
  */
775
1049
  widgetInstanceEdit = output();
776
- gridstackContainer = viewChild('gridstackContainer', { read: ViewContainerRef });
1050
+ widgetHosts = viewChildren(SiWidgetHostComponent, /* @ts-ignore */
1051
+ ...(ngDevMode ? [{ debugName: "widgetHosts" }] : /* istanbul ignore next */ []));
1052
+ widgetHostsMap = computed(() => new Map(this.widgetHosts().map(host => [host.widgetConfig().id, host])), /* @ts-ignore */
1053
+ ...(ngDevMode ? [{ debugName: "widgetHostsMap" }] : /* istanbul ignore next */ []));
1054
+ a11yWidgetsListLabel = t(() => $localize `:@@DASHBOARD.A11Y.WIDGETS_LIST:Dashboard widgets`);
777
1055
  grid;
778
- markedForRender = [];
779
- gridItems = [];
780
- itemIdAttr = 'item-id';
781
- widgetIdSubscriptionMap = new Map();
782
1056
  ngZone = inject(NgZone);
783
1057
  elementRef = inject(ElementRef);
1058
+ injector = inject(Injector);
1059
+ platformId = inject(PLATFORM_ID);
1060
+ isBrowser = isPlatformBrowser(this.platformId);
1061
+ destroyRef = inject(DestroyRef);
784
1062
  ngOnChanges(changes) {
785
- if (changes.widgetConfigs) {
786
- const { currentValue, previousValue, firstChange } = changes.widgetConfigs;
787
- this.grid?.batchUpdate(true);
788
- if (firstChange) {
789
- this.markedForRender = currentValue;
1063
+ if (changes.widgetConfigs && !changes.widgetConfigs.firstChange) {
1064
+ const configs = changes.widgetConfigs.currentValue;
1065
+ const newIds = new Set(configs.map(w => w.id));
1066
+ const hostMap = this.widgetHostsMap();
1067
+ const hasAdds = configs.some(w => !hostMap.has(w.id));
1068
+ if (hasAdds) {
1069
+ this.startBatch();
790
1070
  }
791
- else {
792
- // Get newly added items
793
- const toBeAdded = currentValue.filter((item) => !previousValue.find((i) => i.id === item.id));
794
- // Get deleted items
795
- const toBeRemoved = previousValue.filter((item) => !currentValue.find((i) => i.id === item.id));
796
- if (toBeAdded) {
797
- this.mount(toBeAdded);
798
- }
799
- if (toBeRemoved) {
800
- this.unmount(toBeRemoved);
1071
+ if (this.isBrowser) {
1072
+ for (const [id, host] of hostMap) {
1073
+ if (!newIds.has(id)) {
1074
+ this.grid?.removeWidget(host.elementRef);
1075
+ }
801
1076
  }
802
1077
  }
803
- // Detect changes
804
- this.updateViewComponents(currentValue);
805
- this.updateLayout(currentValue);
806
- this.grid?.batchUpdate(false);
807
1078
  }
808
- if (changes.editable) {
809
- const { currentValue, firstChange } = changes.editable;
810
- if (!firstChange) {
811
- this.grid.enableMove(currentValue);
812
- this.grid.enableResize(currentValue);
813
- }
1079
+ if (changes.editable && !changes.editable.firstChange) {
1080
+ this.grid?.enableMove(changes.editable.currentValue);
1081
+ this.grid?.enableResize(changes.editable.currentValue);
814
1082
  }
815
1083
  }
816
1084
  ngOnInit() {
@@ -824,107 +1092,32 @@ class SiGridstackWrapperComponent {
824
1092
  ...initialViewMode
825
1093
  };
826
1094
  this.grid = GridStack.init(options, this.elementRef.nativeElement.firstChild);
1095
+ this.startBatch();
827
1096
  this.hookEvents(this.grid);
828
- this.mount(this.markedForRender);
829
- }
830
- ngOnDestroy() {
831
- this.widgetIdSubscriptionMap.forEach(subscriptions => {
832
- subscriptions.forEach(subscription => subscription.unsubscribe());
833
- });
834
- this.widgetIdSubscriptionMap.clear();
835
- }
836
- mount(items) {
837
- if (items.length > 0) {
838
- items.forEach(item => {
839
- this.addToView(item);
840
- });
841
- }
842
- }
843
- unmount(items) {
844
- if (items.length > 0) {
845
- items.forEach(item => {
846
- this.removeFromView(item.id);
847
- });
848
- }
849
- }
850
- getLayout() {
851
- const gridItems = this.grid?.getGridItems();
852
- if (gridItems.length > 0) {
853
- const positions = gridItems.map(gridItemHTMLElement => {
854
- const id = gridItemHTMLElement.getAttribute('item-id');
855
- const x = Number(gridItemHTMLElement.getAttribute('gs-x')) || 0;
856
- const y = Number(gridItemHTMLElement.getAttribute('gs-y')) || 0;
857
- const width = Number(gridItemHTMLElement.getAttribute('gs-w')) || 0;
858
- const height = Number(gridItemHTMLElement.getAttribute('gs-h')) || 0;
859
- return { id, x, y, width, height };
860
- });
861
- return positions;
862
- }
863
- else {
864
- return [];
865
- }
866
- }
867
- updateLayout(widgets) {
868
- const tmp = widgets.map(w => ({ ...w, w: w.width, h: w.height }));
869
- if (this.grid) {
870
- const gridItems = this.grid.getGridItems();
871
- gridItems.forEach(gridItem => {
872
- const config = tmp.find(widget => widget.id === gridItem.getAttribute('item-id'));
873
- this.grid.update(gridItem, { ...config });
874
- });
875
- }
876
- }
877
- addToView(item) {
878
- const componentRef = this.gridstackContainer().createComponent(SiWidgetHostComponent);
879
- componentRef.setInput('widgetConfig', item);
880
- const subscriptions = [];
881
- const subscriptionRemove = componentRef.instance.remove.subscribe(widgetId => {
882
- const widgetSubscriptions = this.widgetIdSubscriptionMap.get(widgetId);
883
- if (widgetSubscriptions) {
884
- widgetSubscriptions.forEach(sub => sub.unsubscribe());
885
- }
886
- this.widgetInstanceRemove.emit(widgetId);
887
- });
888
- subscriptions.push(subscriptionRemove);
889
- const subscriptionEdit = componentRef.instance.edit.subscribe(widgetConfig => {
890
- this.widgetInstanceEdit.emit(widgetConfig);
891
- });
892
- subscriptions.push(subscriptionEdit);
893
- this.widgetIdSubscriptionMap.set(item.id, subscriptions);
894
- const element = componentRef.location.nativeElement;
895
- element.setAttribute(this.itemIdAttr, item.id);
896
- this.gridItems.push({ id: item.id, component: componentRef });
897
- this.grid.makeWidget(element, {
898
- w: item.width,
899
- h: item.height,
900
- x: item.x,
901
- y: item.y,
902
- minW: item.minWidth,
903
- minH: item.minHeight
904
- });
1097
+ this.destroyRef.onDestroy(() => this.grid?.destroy());
905
1098
  }
906
- removeFromView(widgetId) {
907
- const gridItemElements = this.grid.getGridItems();
908
- const toRemove = gridItemElements.find((el) => el.getAttribute(this.itemIdAttr) === widgetId);
909
- if (toRemove) {
910
- this.grid.removeWidget(toRemove);
911
- const index = this.gridItems.findIndex(i => i.id === widgetId);
912
- this.gridItems[index].component.destroy();
913
- this.gridItems.splice(index, 1);
914
- }
1099
+ startBatch() {
1100
+ this.grid?.batchUpdate(true);
1101
+ // this ensures that si-widget-host components are rendered before turning off batch mode.
1102
+ afterNextRender(() => this.grid?.batchUpdate(false), { injector: this.injector });
915
1103
  }
916
1104
  /**
917
- * GridItemComponents are created dynamically, change detection won't trigger as there is no \@Input binding.
918
- * We have to update instance and run ChangeDetectionRef manually.
1105
+ *
1106
+ * Returns the position of a specific widget in the grid.
919
1107
  */
920
- updateViewComponents(newConfigs) {
921
- // TODO: lookup by id would fasten the update
922
- for (const config of newConfigs) {
923
- const gridItem = this.gridItems.find(item => item.id === config.id);
924
- if (gridItem) {
925
- gridItem.component.setInput('widgetConfig', config);
926
- }
1108
+ getWidgetLayout(widgetId) {
1109
+ const gridItem = this.widgetHostsMap().get(widgetId);
1110
+ if (!gridItem) {
1111
+ return undefined;
927
1112
  }
1113
+ const element = gridItem.elementRef;
1114
+ return {
1115
+ id: widgetId,
1116
+ x: Number(element.getAttribute('gs-x')) || 0,
1117
+ y: Number(element.getAttribute('gs-y')) || 0,
1118
+ width: Number(element.getAttribute('gs-w')) || 0,
1119
+ height: Number(element.getAttribute('gs-h')) || 0
1120
+ };
928
1121
  }
929
1122
  hookEvents(grid) {
930
1123
  grid.on('added removed dragstop resizestop disable enable dropped resize resizestart drag dragstart change', (event) => {
@@ -936,16 +1129,96 @@ class SiGridstackWrapperComponent {
936
1129
  });
937
1130
  });
938
1131
  }
939
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiGridstackWrapperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
940
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.0.6", type: SiGridstackWrapperComponent, isStandalone: true, selector: "si-gridstack-wrapper", inputs: { widgetConfigs: { classPropertyName: "widgetConfigs", publicName: "widgetConfigs", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, gridConfig: { classPropertyName: "gridConfig", publicName: "gridConfig", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { gridEvent: "gridEvent", widgetInstanceRemove: "widgetInstanceRemove", widgetInstanceEdit: "widgetInstanceEdit" }, viewQueries: [{ propertyName: "gridstackContainer", first: true, predicate: ["gridstackContainer"], descendants: true, read: ViewContainerRef, isSignal: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"grid-stack\">\n <ng-container #gridstackContainer />\n</div>\n", styles: ["::ng-deep .si-grid-stack-placeholder{background-color:var(--element-base-information);opacity:.3}:host{display:flex;flex:1 1 0;flex-direction:column;margin-inline-start:-8px;margin-inline-end:-8px;margin-block-start:-8px}.grid-stack{display:block}\n"] });
1132
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: SiGridstackWrapperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1133
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: SiGridstackWrapperComponent, isStandalone: true, selector: "si-gridstack-wrapper", inputs: { widgetConfigs: { classPropertyName: "widgetConfigs", publicName: "widgetConfigs", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, gridConfig: { classPropertyName: "gridConfig", publicName: "gridConfig", isSignal: true, isRequired: false, transformFunction: null }, widgetCatalogMap: { classPropertyName: "widgetCatalogMap", publicName: "widgetCatalogMap", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { gridEvent: "gridEvent", widgetInstanceRemove: "widgetInstanceRemove", widgetInstanceEdit: "widgetInstanceEdit" }, viewQueries: [{ propertyName: "widgetHosts", predicate: SiWidgetHostComponent, descendants: true, isSignal: true }], usesOnChanges: true, ngImport: i0, template: "<div\n class=\"grid-stack\"\n [attr.role]=\"editable() ? null : 'list'\"\n [attr.aria-label]=\"editable() ? null : (a11yWidgetsListLabel | translate)\"\n>\n @if (grid) {\n @for (item of widgetConfigs(); track item.id) {\n <si-widget-host\n [grid]=\"grid\"\n [attr.item-id]=\"item.id\"\n [editable]=\"editable()\"\n [widgetConfig]=\"item\"\n [componentFactory]=\"widgetCatalogMap().get(item.widgetId)?.componentFactory\"\n (remove)=\"widgetInstanceRemove.emit(item.id)\"\n (edit)=\"widgetInstanceEdit.emit(item)\"\n />\n }\n }\n</div>\n", styles: ["::ng-deep .si-grid-stack-placeholder{background-color:var(--element-base-information);opacity:.3}:host{display:flex;flex:1 1 0;flex-direction:column;margin-inline-start:-8px;margin-inline-end:-8px;margin-block-start:-8px}.grid-stack{display:block}\n"], dependencies: [{ kind: "component", type: SiWidgetHostComponent, selector: "si-widget-host", inputs: ["widgetConfig", "grid", "componentFactory", "editable"], outputs: ["remove", "edit", "gridEvent"] }, { kind: "pipe", type: SiTranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.Eager });
941
1134
  }
942
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiGridstackWrapperComponent, decorators: [{
1135
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: SiGridstackWrapperComponent, decorators: [{
943
1136
  type: Component,
944
- args: [{ selector: 'si-gridstack-wrapper', template: "<div class=\"grid-stack\">\n <ng-container #gridstackContainer />\n</div>\n", styles: ["::ng-deep .si-grid-stack-placeholder{background-color:var(--element-base-information);opacity:.3}:host{display:flex;flex:1 1 0;flex-direction:column;margin-inline-start:-8px;margin-inline-end:-8px;margin-block-start:-8px}.grid-stack{display:block}\n"] }]
945
- }] });
1137
+ args: [{ selector: 'si-gridstack-wrapper', imports: [SiWidgetHostComponent, SiTranslatePipe], changeDetection: ChangeDetectionStrategy.Eager, template: "<div\n class=\"grid-stack\"\n [attr.role]=\"editable() ? null : 'list'\"\n [attr.aria-label]=\"editable() ? null : (a11yWidgetsListLabel | translate)\"\n>\n @if (grid) {\n @for (item of widgetConfigs(); track item.id) {\n <si-widget-host\n [grid]=\"grid\"\n [attr.item-id]=\"item.id\"\n [editable]=\"editable()\"\n [widgetConfig]=\"item\"\n [componentFactory]=\"widgetCatalogMap().get(item.widgetId)?.componentFactory\"\n (remove)=\"widgetInstanceRemove.emit(item.id)\"\n (edit)=\"widgetInstanceEdit.emit(item)\"\n />\n }\n }\n</div>\n", styles: ["::ng-deep .si-grid-stack-placeholder{background-color:var(--element-base-information);opacity:.3}:host{display:flex;flex:1 1 0;flex-direction:column;margin-inline-start:-8px;margin-inline-end:-8px;margin-block-start:-8px}.grid-stack{display:block}\n"] }]
1138
+ }], propDecorators: { widgetConfigs: [{ type: i0.Input, args: [{ isSignal: true, alias: "widgetConfigs", required: false }] }], editable: [{ type: i0.Input, args: [{ isSignal: true, alias: "editable", required: false }] }], gridConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "gridConfig", required: false }] }], widgetCatalogMap: [{ type: i0.Input, args: [{ isSignal: true, alias: "widgetCatalogMap", required: false }] }], gridEvent: [{ type: i0.Output, args: ["gridEvent"] }], widgetInstanceRemove: [{ type: i0.Output, args: ["widgetInstanceRemove"] }], widgetInstanceEdit: [{ type: i0.Output, args: ["widgetInstanceEdit"] }], widgetHosts: [{ type: i0.ViewChildren, args: [i0.forwardRef(() => SiWidgetHostComponent), { isSignal: true }] }] } });
1139
+
1140
+ /**
1141
+ * Copyright (c) Siemens 2016 - 2026
1142
+ * SPDX-License-Identifier: MIT
1143
+ */
1144
+ /**
1145
+ * Base class encapsulating common widget editor lifecycle management.
1146
+ * Both `SiWidgetCatalogComponent` and `SiWidgetInstanceEditorDialogComponent`
1147
+ * extend this class to share editor setup, teardown, and status handling logic.
1148
+ */
1149
+ class SiWidgetEditorBase {
1150
+ /** Indicates if the current config is valid or not. */
1151
+ invalidConfig = signal(false, /* @ts-ignore */
1152
+ ...(ngDevMode ? [{ debugName: "invalidConfig" }] : /* istanbul ignore next */ []));
1153
+ /** Marks the widget configuration as modified. */
1154
+ widgetConfigModified = signal(false, /* @ts-ignore */
1155
+ ...(ngDevMode ? [{ debugName: "widgetConfigModified" }] : /* istanbul ignore next */ []));
1156
+ editorWizardState = signal(undefined, /* @ts-ignore */
1157
+ ...(ngDevMode ? [{ debugName: "editorWizardState" }] : /* istanbul ignore next */ []));
1158
+ widgetInstanceEditor;
1159
+ subscriptions = [];
1160
+ editorHost = viewChild.required('editorHost', { read: ViewContainerRef });
1161
+ injector = inject(Injector);
1162
+ envInjector = inject(EnvironmentInjector);
1163
+ ngOnDestroy() {
1164
+ this.tearDownEditor();
1165
+ }
1166
+ loadWidgetEditor(widgetComponentFactory, host) {
1167
+ return setupWidgetEditor(widgetComponentFactory, host, this.injector, this.envInjector);
1168
+ }
1169
+ initializeEditor(componentRef, config) {
1170
+ this.widgetInstanceEditor = componentRef.instance;
1171
+ if (isSignal(this.widgetInstanceEditor.config)) {
1172
+ componentRef.setInput('config', config);
1173
+ }
1174
+ else {
1175
+ this.widgetInstanceEditor.config = config;
1176
+ }
1177
+ // To be used by webcomponent wrapper
1178
+ if ('statusChangesHandler' in this.widgetInstanceEditor) {
1179
+ this.widgetInstanceEditor.statusChangesHandler = this.handleStatusChanges.bind(this);
1180
+ }
1181
+ if (this.widgetInstanceEditor.statusChanges) {
1182
+ this.subscriptions.push(this.widgetInstanceEditor.statusChanges.subscribe(statusChanges => this.handleStatusChanges(statusChanges)));
1183
+ }
1184
+ else if (this.widgetInstanceEditor.configChange) {
1185
+ this.subscriptions.push(this.widgetInstanceEditor.configChange.subscribe(() => this.widgetConfigModified.set(true)));
1186
+ }
1187
+ if (this.isEditorWizard(this.widgetInstanceEditor)) {
1188
+ this.editorWizardState.set(this.widgetInstanceEditor.state);
1189
+ if (this.widgetInstanceEditor.stateChange) {
1190
+ this.subscriptions.push(this.widgetInstanceEditor.stateChange.subscribe(state => this.editorWizardState.set(state)));
1191
+ }
1192
+ }
1193
+ }
1194
+ tearDownEditor() {
1195
+ this.invalidConfig.set(false);
1196
+ this.widgetConfigModified.set(false);
1197
+ this.editorWizardState.set(undefined);
1198
+ this.widgetInstanceEditor = undefined;
1199
+ this.subscriptions.forEach(s => s.unsubscribe());
1200
+ this.subscriptions = [];
1201
+ }
1202
+ isEditorWizard(editor) {
1203
+ return !!editor && 'state' in editor;
1204
+ }
1205
+ handleStatusChanges(statusChanges) {
1206
+ if (statusChanges.invalid !== undefined) {
1207
+ this.invalidConfig.set(statusChanges.invalid);
1208
+ }
1209
+ if (statusChanges.modified !== undefined) {
1210
+ this.widgetConfigModified.set(statusChanges.modified);
1211
+ }
1212
+ }
1213
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: SiWidgetEditorBase, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1214
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "22.0.5", type: SiWidgetEditorBase, isStandalone: true, viewQueries: [{ propertyName: "editorHost", first: true, predicate: ["editorHost"], descendants: true, read: ViewContainerRef, isSignal: true }], ngImport: i0 });
1215
+ }
1216
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: SiWidgetEditorBase, decorators: [{
1217
+ type: Directive
1218
+ }], propDecorators: { editorHost: [{ type: i0.ViewChild, args: ['editorHost', { ...{ read: ViewContainerRef }, isSignal: true }] }] } });
946
1219
 
947
1220
  /**
948
- * Copyright (c) Siemens 2016 - 2025
1221
+ * Copyright (c) Siemens 2016 - 2026
949
1222
  * SPDX-License-Identifier: MIT
950
1223
  */
951
1224
  /**
@@ -954,17 +1227,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImpor
954
1227
  * into its content. The dialog component is accountable for interacting with the dashboard
955
1228
  * and offers options for saving changes or terminating the editing process.
956
1229
  */
957
- class SiWidgetInstanceEditorDialogComponent {
1230
+ class SiWidgetInstanceEditorDialogComponent extends SiWidgetEditorBase {
958
1231
  /**
959
1232
  * Input for the widget instance configuration. It is used to populate the
960
1233
  * widget editor.
961
1234
  */
962
- widgetConfig = model.required();
1235
+ widgetConfig = model.required(/* @ts-ignore */
1236
+ ...(ngDevMode ? [{ debugName: "widgetConfig" }] : /* istanbul ignore next */ []));
963
1237
  /**
964
1238
  * Input for the widget definition. It is required to retrieve the component
965
1239
  * factory to instantiate the widget editor.
966
1240
  */
967
- widget = input.required();
1241
+ widget = input.required(/* @ts-ignore */
1242
+ ...(ngDevMode ? [{ debugName: "widget" }] : /* istanbul ignore next */ []));
968
1243
  /**
969
1244
  * Emits the edited widget instance configuration if the user confirms by
970
1245
  * saving, or `undefined` if the user cancels the dialog.
@@ -974,10 +1249,8 @@ class SiWidgetInstanceEditorDialogComponent {
974
1249
  * Emits when the editor instantiation is completed.
975
1250
  */
976
1251
  editorSetupCompleted = output();
977
- editorHost = viewChild.required('editorHost', { read: ViewContainerRef });
978
- /** Indicates if the current config is valid or not. If invalid, the save button will be disabled. */
979
- invalidConfig = signal(false);
980
- showNextButton = computed(() => this.editorWizardState() !== undefined ? true : false);
1252
+ showNextButton = computed(() => this.editorWizardState() !== undefined ? true : false, /* @ts-ignore */
1253
+ ...(ngDevMode ? [{ debugName: "showNextButton" }] : /* istanbul ignore next */ []));
981
1254
  disableNextButton = computed(() => {
982
1255
  const wizardState = this.editorWizardState();
983
1256
  if (!wizardState) {
@@ -992,12 +1265,15 @@ class SiWidgetInstanceEditorDialogComponent {
992
1265
  else {
993
1266
  return false;
994
1267
  }
995
- });
996
- showPreviousButton = computed(() => !!this.editorWizardState());
1268
+ }, /* @ts-ignore */
1269
+ ...(ngDevMode ? [{ debugName: "disableNextButton" }] : /* istanbul ignore next */ []));
1270
+ showPreviousButton = computed(() => !!this.editorWizardState(), /* @ts-ignore */
1271
+ ...(ngDevMode ? [{ debugName: "showPreviousButton" }] : /* istanbul ignore next */ []));
997
1272
  disablePreviousButton = computed(() => {
998
1273
  const wizardState = this.editorWizardState();
999
1274
  return wizardState ? !wizardState.hasPrevious : true;
1000
- });
1275
+ }, /* @ts-ignore */
1276
+ ...(ngDevMode ? [{ debugName: "disablePreviousButton" }] : /* istanbul ignore next */ []));
1001
1277
  labelSave = t(() => $localize `:@@DASHBOARD.WIDGET_EDITOR_DIALOG.SAVE:Save`);
1002
1278
  labelCancel = t(() => $localize `:@@DASHBOARD.WIDGET_EDITOR_DIALOG.CANCEL:Cancel`);
1003
1279
  labelPrevious = t(() => $localize `:@@DASHBOARD.WIDGET_EDITOR_DIALOG.PREVIOUS:Previous`);
@@ -1007,60 +1283,13 @@ class SiWidgetInstanceEditorDialogComponent {
1007
1283
  labelDialogSave = t(() => $localize `:@@DASHBOARD.WIDGET_EDITOR_DIALOG.DISCARD_CONFIG_CHANGE_DIALOG.SAVE:Save`);
1008
1284
  labelDialogDiscard = t(() => $localize `:@@DASHBOARD.WIDGET_EDITOR_DIALOG.DISCARD_CONFIG_CHANGE_DIALOG.DISCARD:Discard`);
1009
1285
  labelDialogCancel = t(() => $localize `:@@DASHBOARD.WIDGET_EDITOR_DIALOG.DISCARD_CONFIG_CHANGE_DIALOG.CANCEL:Cancel`);
1010
- /**
1011
- * Marks the widget configuration as modified. Is set when widget editor instance
1012
- * emits configChange events. Triggers edit discard confirmation dialog when widget config
1013
- * is modified but not dialog is canceled.
1014
- * */
1015
- widgetConfigModified = signal(false);
1016
- widgetInstanceEditor;
1017
- editorWizardState = signal(undefined);
1018
- subscriptions = [];
1019
- injector = inject(Injector);
1020
- envInjector = inject(EnvironmentInjector);
1021
1286
  dialogService = inject(SiActionDialogService);
1022
1287
  ngOnInit() {
1023
- setupWidgetEditor(this.widget().componentFactory, this.editorHost(), this.injector, this.envInjector).subscribe(componentRef => {
1024
- this.widgetInstanceEditor = componentRef.instance;
1025
- if (isSignal(this.widgetInstanceEditor.config)) {
1026
- componentRef.setInput('config', this.widgetConfig());
1027
- }
1028
- else {
1029
- this.widgetInstanceEditor.config = this.widgetConfig();
1030
- }
1031
- // To be used by webcomponent wrapper
1032
- if ('statusChangesHandler' in this.widgetInstanceEditor) {
1033
- this.widgetInstanceEditor.statusChangesHandler = this.handleStatusChanges.bind(this);
1034
- }
1035
- let hasStatusChangesEmitter = false;
1036
- if (this.widgetInstanceEditor.statusChanges) {
1037
- hasStatusChangesEmitter = true;
1038
- this.subscriptions.push(this.widgetInstanceEditor.statusChanges.subscribe(statusChanges => {
1039
- this.handleStatusChanges(statusChanges);
1040
- }));
1041
- }
1042
- if (this.widgetInstanceEditor.configChange) {
1043
- this.subscriptions.push(this.widgetInstanceEditor.configChange.subscribe(config => {
1044
- if (!hasStatusChangesEmitter) {
1045
- this.invalidConfig.set(!!config.invalid);
1046
- this.widgetConfigModified.set(true);
1047
- }
1048
- }));
1049
- }
1050
- if (this.isEditorWizdard(this.widgetInstanceEditor)) {
1051
- this.editorWizardState.set(this.widgetInstanceEditor.state);
1052
- if (this.widgetInstanceEditor.stateChange) {
1053
- this.subscriptions.push(this.widgetInstanceEditor.stateChange.subscribe(state => {
1054
- this.editorWizardState.set(state);
1055
- }));
1056
- }
1057
- }
1288
+ this.loadWidgetEditor(this.widget().componentFactory, this.editorHost()).subscribe(componentRef => {
1289
+ this.initializeEditor(componentRef, this.widgetConfig());
1058
1290
  this.editorSetupCompleted.emit();
1059
1291
  });
1060
1292
  }
1061
- ngOnDestroy() {
1062
- this.tearDownEditor();
1063
- }
1064
1293
  onCancel() {
1065
1294
  if (!this.widgetConfigModified()) {
1066
1295
  this.closed.emit(undefined);
@@ -1089,13 +1318,13 @@ class SiWidgetInstanceEditorDialogComponent {
1089
1318
  }
1090
1319
  }
1091
1320
  onNext() {
1092
- if (this.isEditorWizdard(this.widgetInstanceEditor)) {
1321
+ if (this.isEditorWizard(this.widgetInstanceEditor)) {
1093
1322
  this.widgetInstanceEditor.next();
1094
1323
  this.editorWizardState.set(this.widgetInstanceEditor.state);
1095
1324
  }
1096
1325
  }
1097
1326
  onPrevious() {
1098
- if (this.isEditorWizdard(this.widgetInstanceEditor) && this.editorWizardState()?.hasPrevious) {
1327
+ if (this.isEditorWizard(this.widgetInstanceEditor) && this.editorWizardState()?.hasPrevious) {
1099
1328
  this.widgetInstanceEditor.previous();
1100
1329
  this.editorWizardState.set(this.widgetInstanceEditor.state);
1101
1330
  }
@@ -1114,80 +1343,59 @@ class SiWidgetInstanceEditorDialogComponent {
1114
1343
  }
1115
1344
  this.closed.emit(this.widgetConfig());
1116
1345
  }
1117
- tearDownEditor() {
1118
- this.editorWizardState.set(undefined);
1119
- this.widgetInstanceEditor = undefined;
1120
- this.subscriptions.forEach(s => s.unsubscribe());
1121
- this.subscriptions = [];
1122
- }
1123
- isEditorWizdard(editor) {
1124
- return !!editor && 'state' in editor;
1125
- }
1126
- handleStatusChanges(statusChanges) {
1127
- if (statusChanges.invalid !== undefined) {
1128
- this.invalidConfig.set(statusChanges.invalid);
1129
- }
1130
- if (statusChanges.modified !== undefined) {
1131
- this.widgetConfigModified.set(statusChanges.modified);
1132
- }
1133
- }
1134
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiWidgetInstanceEditorDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1135
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.6", type: SiWidgetInstanceEditorDialogComponent, isStandalone: true, selector: "si-widget-instance-editor-dialog", inputs: { widgetConfig: { classPropertyName: "widgetConfig", publicName: "widgetConfig", isSignal: true, isRequired: true, transformFunction: null }, widget: { classPropertyName: "widget", publicName: "widget", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { widgetConfig: "widgetConfigChange", closed: "closed", editorSetupCompleted: "editorSetupCompleted" }, viewQueries: [{ propertyName: "editorHost", first: true, predicate: ["editorHost"], descendants: true, read: ViewContainerRef, isSignal: true }], ngImport: i0, template: "<div class=\"si-layout-fixed-height mb-8 mx-0 overflow-auto px-2 pt-2\">\n <ng-container #editorHost />\n</div>\n\n<div class=\"editor-footer px-2\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"onCancel()\">{{\n labelCancel | translate\n }}</button>\n\n @if (showPreviousButton()) {\n <button\n type=\"button\"\n class=\"btn btn-secondary\"\n [disabled]=\"disablePreviousButton()\"\n (click)=\"onPrevious()\"\n >{{ labelPrevious | translate }}</button\n >\n }\n @if (showNextButton()) {\n <button\n type=\"button\"\n class=\"btn btn-secondary\"\n [disabled]=\"disableNextButton()\"\n (click)=\"onNext()\"\n >{{ labelNext | translate }}</button\n >\n }\n\n <button type=\"button\" class=\"btn btn-primary\" [disabled]=\"invalidConfig()\" (click)=\"onSave()\">{{\n labelSave | translate\n }}</button>\n</div>\n", styles: [":host{display:flex;flex:1 1 0;flex-direction:column;margin-inline:-4px;margin-block-start:-4px}.editor-footer{align-items:center;display:flex;flex-shrink:0;flex-wrap:wrap;justify-content:flex-end}.editor-footer>*{margin-inline-start:16px}\n"], dependencies: [{ kind: "ngmodule", type: SiTranslateModule }, { kind: "pipe", type: i1.SiTranslatePipe, name: "translate" }] });
1346
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: SiWidgetInstanceEditorDialogComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1347
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: SiWidgetInstanceEditorDialogComponent, isStandalone: true, selector: "si-widget-instance-editor-dialog", inputs: { widgetConfig: { classPropertyName: "widgetConfig", publicName: "widgetConfig", isSignal: true, isRequired: true, transformFunction: null }, widget: { classPropertyName: "widget", publicName: "widget", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { widgetConfig: "widgetConfigChange", closed: "closed", editorSetupCompleted: "editorSetupCompleted" }, usesInheritance: true, ngImport: i0, template: "<div class=\"si-layout-fixed-height mb-8 mx-0 overflow-auto px-2 pt-2\">\n <ng-container #editorHost />\n</div>\n\n<div class=\"editor-footer px-2\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"onCancel()\">{{\n labelCancel | translate\n }}</button>\n\n @if (showPreviousButton()) {\n <button\n type=\"button\"\n class=\"btn btn-secondary\"\n [disabled]=\"disablePreviousButton()\"\n (click)=\"onPrevious()\"\n >{{ labelPrevious | translate }}</button\n >\n }\n @if (showNextButton()) {\n <button\n type=\"button\"\n class=\"btn btn-secondary\"\n [disabled]=\"disableNextButton()\"\n (click)=\"onNext()\"\n >{{ labelNext | translate }}</button\n >\n }\n\n <button type=\"button\" class=\"btn btn-primary\" [disabled]=\"invalidConfig()\" (click)=\"onSave()\">{{\n labelSave | translate\n }}</button>\n</div>\n", styles: [":host{display:flex;flex:1 1 0;flex-direction:column;margin-inline:-4px;margin-block-start:-4px}.editor-footer{align-items:center;display:flex;flex-shrink:0;flex-wrap:wrap;justify-content:flex-end}.editor-footer>*{margin-inline-start:16px}\n"], dependencies: [{ kind: "pipe", type: SiTranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.Eager });
1136
1348
  }
1137
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiWidgetInstanceEditorDialogComponent, decorators: [{
1349
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: SiWidgetInstanceEditorDialogComponent, decorators: [{
1138
1350
  type: Component,
1139
- args: [{ selector: 'si-widget-instance-editor-dialog', imports: [SiTranslateModule], template: "<div class=\"si-layout-fixed-height mb-8 mx-0 overflow-auto px-2 pt-2\">\n <ng-container #editorHost />\n</div>\n\n<div class=\"editor-footer px-2\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"onCancel()\">{{\n labelCancel | translate\n }}</button>\n\n @if (showPreviousButton()) {\n <button\n type=\"button\"\n class=\"btn btn-secondary\"\n [disabled]=\"disablePreviousButton()\"\n (click)=\"onPrevious()\"\n >{{ labelPrevious | translate }}</button\n >\n }\n @if (showNextButton()) {\n <button\n type=\"button\"\n class=\"btn btn-secondary\"\n [disabled]=\"disableNextButton()\"\n (click)=\"onNext()\"\n >{{ labelNext | translate }}</button\n >\n }\n\n <button type=\"button\" class=\"btn btn-primary\" [disabled]=\"invalidConfig()\" (click)=\"onSave()\">{{\n labelSave | translate\n }}</button>\n</div>\n", styles: [":host{display:flex;flex:1 1 0;flex-direction:column;margin-inline:-4px;margin-block-start:-4px}.editor-footer{align-items:center;display:flex;flex-shrink:0;flex-wrap:wrap;justify-content:flex-end}.editor-footer>*{margin-inline-start:16px}\n"] }]
1140
- }] });
1351
+ args: [{ selector: 'si-widget-instance-editor-dialog', imports: [SiTranslatePipe], changeDetection: ChangeDetectionStrategy.Eager, template: "<div class=\"si-layout-fixed-height mb-8 mx-0 overflow-auto px-2 pt-2\">\n <ng-container #editorHost />\n</div>\n\n<div class=\"editor-footer px-2\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"onCancel()\">{{\n labelCancel | translate\n }}</button>\n\n @if (showPreviousButton()) {\n <button\n type=\"button\"\n class=\"btn btn-secondary\"\n [disabled]=\"disablePreviousButton()\"\n (click)=\"onPrevious()\"\n >{{ labelPrevious | translate }}</button\n >\n }\n @if (showNextButton()) {\n <button\n type=\"button\"\n class=\"btn btn-secondary\"\n [disabled]=\"disableNextButton()\"\n (click)=\"onNext()\"\n >{{ labelNext | translate }}</button\n >\n }\n\n <button type=\"button\" class=\"btn btn-primary\" [disabled]=\"invalidConfig()\" (click)=\"onSave()\">{{\n labelSave | translate\n }}</button>\n</div>\n", styles: [":host{display:flex;flex:1 1 0;flex-direction:column;margin-inline:-4px;margin-block-start:-4px}.editor-footer{align-items:center;display:flex;flex-shrink:0;flex-wrap:wrap;justify-content:flex-end}.editor-footer>*{margin-inline-start:16px}\n"] }]
1352
+ }], propDecorators: { widgetConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "widgetConfig", required: true }] }, { type: i0.Output, args: ["widgetConfigChange"] }], widget: [{ type: i0.Input, args: [{ isSignal: true, alias: "widget", required: true }] }], closed: [{ type: i0.Output, args: ["closed"] }], editorSetupCompleted: [{ type: i0.Output, args: ["editorSetupCompleted"] }] } });
1141
1353
 
1142
1354
  /**
1143
- * Copyright (c) Siemens 2016 - 2025
1355
+ * Copyright (c) Siemens 2016 - 2026
1144
1356
  * SPDX-License-Identifier: MIT
1145
1357
  */
1146
- const NEW_WIDGET_PREFIX = 'new-widget-';
1147
- let idCounter = 1;
1148
1358
  /**
1149
1359
  * The grid component is the actual component on which the widget instances are placed and visualized. You can think of
1150
1360
  * a headless dashboard, without a title, toolbar or edit buttons.
1151
1361
  */
1152
1362
  class SiGridComponent {
1153
1363
  storeSubscription;
1154
- gridService = inject(SiGridService);
1155
1364
  modalService = inject(SiModalService);
1156
1365
  widgetStorage = inject(SI_WIDGET_STORE);
1366
+ widgetIdProvider = inject(SI_WIDGET_ID_PROVIDER);
1157
1367
  /**
1158
1368
  * Configuration options for a grid instance. Default is the optional
1159
1369
  * value from the {@link SI_DASHBOARD_CONFIGURATION}.
1160
1370
  *
1161
1371
  * @defaultValue inject(SI_DASHBOARD_CONFIGURATION)?.grid
1162
1372
  */
1163
- gridConfig = input(inject(SI_DASHBOARD_CONFIGURATION)?.grid);
1373
+ gridConfig = input(inject(SI_DASHBOARD_CONFIGURATION)?.grid, /* @ts-ignore */
1374
+ ...(ngDevMode ? [{ debugName: "gridConfig" }] : /* istanbul ignore next */ []));
1164
1375
  /**
1165
1376
  * Sets the grid into editable mode, in which the widget instances can be moved,
1166
1377
  * resized, removed or new ones added.
1167
1378
  *
1168
1379
  * @defaultValue false
1169
1380
  */
1170
- editable = model(false);
1171
- /**
1172
- * This is the internal owner of the current editable state and is used to track if
1173
- * editable or not. Not editable can be changed by either calling the `edit()` api
1174
- * method or by setting the `editable` input. When setting the input, the `ngOnChanges(...)`
1175
- * hook is used to call the `edit()` method. Similar, to get from editable to not editable,
1176
- * `cancel()` or `save()` is used and can be triggered from `ngOnChanges(...)`.
1177
- */
1178
- editableInternal = false;
1381
+ editable = model(false, /* @ts-ignore */
1382
+ ...(ngDevMode ? [{ debugName: "editable" }] : /* istanbul ignore next */ []));
1179
1383
  /**
1180
1384
  * An optional, but recommended dashboard id that is used in persistence and passed
1181
1385
  * to the widget store for saving and loading data.
1182
1386
  */
1183
- dashboardId = input();
1387
+ dashboardId = input(/* @ts-ignore */
1388
+ ...(ngDevMode ? [undefined, { debugName: "dashboardId" }] : /* istanbul ignore next */ []));
1184
1389
  /**
1185
1390
  * Provides the available widgets to the grid. When loading the widget configurations from
1186
1391
  * the storage, we need to have the widget definition available to be able to create the widget
1187
1392
  * instances on the grid.
1188
1393
  *
1189
1394
  * @defaultValue [] */
1190
- widgetCatalog = input([]);
1395
+ widgetCatalog = input([], /* @ts-ignore */
1396
+ ...(ngDevMode ? [{ debugName: "widgetCatalog" }] : /* istanbul ignore next */ []));
1397
+ widgetCatalogMap = computed(() => new Map(this.widgetCatalog().map(widget => [widget.id, widget])), /* @ts-ignore */
1398
+ ...(ngDevMode ? [{ debugName: "widgetCatalogMap" }] : /* istanbul ignore next */ []));
1191
1399
  /**
1192
1400
  * When the user clicks edit on a widget instance, an editor need to appear and the
1193
1401
  * widget editor component need to be loaded. When the grid is used standalone, it
@@ -1198,18 +1406,21 @@ class SiGridComponent {
1198
1406
  * widget `edit` action.
1199
1407
  *
1200
1408
  * @defaultValue false */
1201
- emitWidgetInstanceEditEvents = input(false);
1409
+ emitWidgetInstanceEditEvents = input(false, /* @ts-ignore */
1410
+ ...(ngDevMode ? [{ debugName: "emitWidgetInstanceEditEvents" }] : /* istanbul ignore next */ []));
1202
1411
  /**
1203
1412
  * Option to turn off the loading spinner on save and load operations.
1204
1413
  *
1205
1414
  * @defaultValue false
1206
1415
  */
1207
- hideProgressIndicator = input(false);
1416
+ hideProgressIndicator = input(false, /* @ts-ignore */
1417
+ ...(ngDevMode ? [{ debugName: "hideProgressIndicator" }] : /* istanbul ignore next */ []));
1208
1418
  /**
1209
1419
  * Option to configure a custom widget instance editor dialog component. The component provides the
1210
1420
  * editor hosting and the buttons to save and cancel.
1211
1421
  */
1212
- widgetInstanceEditorDialogComponent = input();
1422
+ widgetInstanceEditorDialogComponent = input(/* @ts-ignore */
1423
+ ...(ngDevMode ? [undefined, { debugName: "widgetInstanceEditorDialogComponent" }] : /* istanbul ignore next */ []));
1213
1424
  /**
1214
1425
  * Emits the modification state of the grid. It is `unmodified` when the visible state
1215
1426
  * is equal to the loaded state from the widget storage. When the user modifies the dashboard by
@@ -1260,13 +1471,6 @@ class SiGridComponent {
1260
1471
  markedForRemoval = [];
1261
1472
  /** @defaultValue viewChild.required(SiGridstackWrapperComponent) */
1262
1473
  gridStackWrapper = viewChild.required(SiGridstackWrapperComponent);
1263
- /**
1264
- * Service used to indicate load and save indication.
1265
- * @deprecated Use `isLoading` instead.
1266
- *
1267
- * @defaultValue inject(SiLoadingService)
1268
- */
1269
- loadingService = inject(SiLoadingService);
1270
1474
  /**
1271
1475
  * Indication for load and save operations.
1272
1476
  */
@@ -1276,43 +1480,34 @@ class SiGridComponent {
1276
1480
  return !this.initialLoad && this.visibleWidgetInstances$.value.length === 0;
1277
1481
  }
1278
1482
  ngOnChanges(changes) {
1279
- // Reload widgets if the dashboardId changes. Do not load on inital
1483
+ // Reload widgets if the dashboardId changes. Do not load on initial
1280
1484
  // dashboardId property binding as first load will be done in ngOnInit()
1281
1485
  if (changes.dashboardId && !changes.dashboardId.firstChange) {
1282
1486
  queueMicrotask(() => this.loadAndSubscribeWidgets());
1283
1487
  }
1284
1488
  if (changes.editable) {
1285
1489
  if (changes.editable.currentValue) {
1286
- this.edit();
1490
+ this.resetEditState();
1287
1491
  }
1288
1492
  else {
1289
- this.cancel();
1493
+ this.restoreSavedState();
1290
1494
  }
1291
1495
  }
1292
- if (changes.widgetCatalog) {
1293
- this.gridService.widgetCatalog = this.widgetCatalog();
1294
- }
1295
1496
  }
1296
1497
  ngOnInit() {
1297
1498
  queueMicrotask(() => this.loadAndSubscribeWidgets());
1298
- this.gridService.widgetCatalog = this.widgetCatalog();
1299
1499
  }
1300
1500
  ngOnDestroy() {
1301
1501
  this.storeSubscription?.unsubscribe();
1302
1502
  this.isLoading.complete();
1303
- this.loadingService.counter.complete();
1304
1503
  }
1305
1504
  /**
1306
1505
  * Set dashboard grid in editable mode to modify widget instances.
1307
1506
  */
1308
1507
  edit() {
1309
- if (!this.editableInternal) {
1310
- this.transientWidgetInstances = [];
1311
- this.markedForRemoval = [];
1312
- this.setModified(false);
1313
- this.editableInternal = true;
1508
+ if (!this.editable()) {
1314
1509
  this.editable.set(true);
1315
- this.gridService.editable$.next(this.editableInternal);
1510
+ this.resetEditState();
1316
1511
  }
1317
1512
  }
1318
1513
  /**
@@ -1320,30 +1515,27 @@ class SiGridComponent {
1320
1515
  * changes the editable and isModified modes.
1321
1516
  */
1322
1517
  save() {
1323
- if (!this.editableInternal) {
1518
+ if (!this.editable()) {
1324
1519
  return;
1325
1520
  }
1326
1521
  this.isLoading.next(true);
1327
- this.loadingService.counter.next(1);
1328
- // Update position information and remove temporary ids
1329
- const widgets = this.updateWidgetPositions(this.visibleWidgetInstances$.value).map(widget => widget.id.startsWith(NEW_WIDGET_PREFIX) ? { ...widget, id: undefined } : widget);
1330
- const toRemove = this.markedForRemoval.filter(widget => !widget.id.startsWith(NEW_WIDGET_PREFIX));
1522
+ // Update position information
1523
+ const widgets = this.updateWidgetPositions(this.visibleWidgetInstances$.value);
1524
+ const transientWidgetIds = new Set(this.transientWidgetInstances.map(w => w.id));
1525
+ const modifiedWidgets = widgets.filter(widget => !transientWidgetIds.has(widget.id));
1526
+ const addedWidgets = widgets.filter(widget => transientWidgetIds.has(widget.id));
1331
1527
  this.widgetStorage
1332
- .save(widgets, toRemove, this.dashboardId())
1528
+ .save(modifiedWidgets, addedWidgets, this.markedForRemoval, this.dashboardId())
1333
1529
  .pipe(take(1))
1334
1530
  .subscribe({
1335
1531
  next: (value) => {
1336
1532
  this.setModified(false);
1337
- this.editableInternal = false;
1338
1533
  this.editable.set(false);
1339
- this.gridService.editable$.next(this.editableInternal);
1340
1534
  this.isLoading.next(false);
1341
- this.loadingService.counter.next(0);
1342
1535
  },
1343
1536
  error: (err) => {
1344
1537
  console.error('Saving dashboard configuration failed.', err);
1345
1538
  this.isLoading.next(false);
1346
- this.loadingService.counter.next(0);
1347
1539
  }
1348
1540
  });
1349
1541
  }
@@ -1351,18 +1543,10 @@ class SiGridComponent {
1351
1543
  * Cancel current changes and restore last saved state.
1352
1544
  */
1353
1545
  cancel() {
1354
- if (!this.editableInternal) {
1355
- return;
1356
- }
1357
- if (this.modified) {
1358
- this.visibleWidgetInstances$.next([...this.persistedWidgetInstances]);
1359
- this.setModified(false);
1360
- }
1361
- this.editableInternal = false;
1362
1546
  if (this.editable()) {
1363
1547
  this.editable.set(false);
1548
+ this.restoreSavedState();
1364
1549
  }
1365
- this.gridService.editable$.next(this.editableInternal);
1366
1550
  }
1367
1551
  /**
1368
1552
  * Adds a new widget instance configuration to the dashboard grid. It is not
@@ -1371,7 +1555,7 @@ class SiGridComponent {
1371
1555
  * @param widgetInstanceConfig - The new widget configuration.
1372
1556
  */
1373
1557
  addWidgetInstance(widgetInstanceConfig) {
1374
- const id = `${NEW_WIDGET_PREFIX}${idCounter++}`;
1558
+ const id = this.widgetIdProvider.generateWidgetId(widgetInstanceConfig, this.dashboardId());
1375
1559
  const newWidget = { ...widgetInstanceConfig, id };
1376
1560
  const nextWidgets = this.updateWidgetPositions([
1377
1561
  ...this.visibleWidgetInstances$.value,
@@ -1389,7 +1573,9 @@ class SiGridComponent {
1389
1573
  removeWidgetInstance(widgetInstanceId) {
1390
1574
  const widgetToRemove = this.visibleWidgetInstances$.value.find(widget => widget.id === widgetInstanceId);
1391
1575
  if (widgetToRemove) {
1392
- this.markedForRemoval.push(widgetToRemove);
1576
+ if (!this.transientWidgetInstances.some(widget => widget.id === widgetToRemove.id)) {
1577
+ this.markedForRemoval.push(widgetToRemove);
1578
+ }
1393
1579
  let nextWidgets = this.visibleWidgetInstances$.value.filter(widget => widget.id !== widgetInstanceId);
1394
1580
  nextWidgets = this.updateWidgetPositions(nextWidgets);
1395
1581
  this.visibleWidgetInstances$.next(nextWidgets);
@@ -1404,12 +1590,12 @@ class SiGridComponent {
1404
1590
  */
1405
1591
  editWidgetInstance(widgetInstanceConfig) {
1406
1592
  // Need to edit a clone to avoid runtime editing
1407
- const widgetConfigClone = JSON.parse(JSON.stringify(widgetInstanceConfig));
1593
+ const widgetConfigClone = structuredClone(widgetInstanceConfig);
1408
1594
  if (this.emitWidgetInstanceEditEvents()) {
1409
1595
  this.widgetInstanceEdit.emit(widgetConfigClone);
1410
1596
  }
1411
1597
  else {
1412
- const widget = this.gridService.getWidget(widgetConfigClone.widgetId);
1598
+ const widget = this.widgetCatalogMap().get(widgetConfigClone.widgetId);
1413
1599
  const config = {
1414
1600
  animated: true,
1415
1601
  keyboard: true,
@@ -1449,6 +1635,14 @@ class SiGridComponent {
1449
1635
  }
1450
1636
  handleGridEvent(event) {
1451
1637
  const relevantEventTypes = ['added', 'removed', 'dragstop', 'resizestop'];
1638
+ // When widgets without explicit x/y are first added, GridStack auto-positions them.
1639
+ // Capture these resolved positions so cancel() can restore the exact layout
1640
+ // instead of re-triggering auto-positioning which may produce a different result.
1641
+ if (event.event.type === 'added' &&
1642
+ !this.editable() &&
1643
+ this.persistedWidgetInstances.some(w => w.x === undefined || w.y === undefined)) {
1644
+ this.persistedWidgetInstances = this.updateWidgetPositions(this.persistedWidgetInstances);
1645
+ }
1452
1646
  if (this.editable() && relevantEventTypes.includes(event.event.type)) {
1453
1647
  // Make sure the widget config always holds the latest position information
1454
1648
  const widgets = this.updateWidgetPositions(this.visibleWidgetInstances$.value);
@@ -1468,7 +1662,6 @@ class SiGridComponent {
1468
1662
  // subscription. To handle this, we use the boolean marker `initialLoad`.
1469
1663
  this.initialLoad = true;
1470
1664
  this.isLoading.next(true);
1471
- this.loadingService.counter.next(1);
1472
1665
  this.storeSubscription = this.widgetStorage.load(this.dashboardId()).subscribe({
1473
1666
  next: widgets => {
1474
1667
  this.visibleWidgetInstances$.next(widgets);
@@ -1476,21 +1669,23 @@ class SiGridComponent {
1476
1669
  if (this.initialLoad) {
1477
1670
  this.initialLoad = false;
1478
1671
  this.isLoading.next(false);
1479
- this.loadingService.counter.next(0);
1480
1672
  }
1481
1673
  },
1482
1674
  error: err => {
1483
1675
  console.error('Loading dashboard configuration failed', err);
1484
1676
  this.isLoading.next(false);
1485
- this.loadingService.counter.next(0);
1486
1677
  }
1487
1678
  });
1488
1679
  }
1489
1680
  updateWidgetPositions(widgetConfigs) {
1490
- const layout = this.gridStackWrapper().getLayout();
1681
+ const wrapper = this.gridStackWrapper();
1491
1682
  const widgets = widgetConfigs.map(widget => {
1492
- const position = layout.find(p => p.id === widget.id);
1493
- if (position) {
1683
+ const position = wrapper.getWidgetLayout(widget.id);
1684
+ if (position &&
1685
+ (widget.x !== position.x ||
1686
+ widget.y !== position.y ||
1687
+ widget.width !== position.width ||
1688
+ widget.height !== position.height)) {
1494
1689
  return {
1495
1690
  ...widget,
1496
1691
  x: position.x,
@@ -1505,19 +1700,30 @@ class SiGridComponent {
1505
1700
  });
1506
1701
  return widgets || [];
1507
1702
  }
1703
+ resetEditState() {
1704
+ this.transientWidgetInstances = [];
1705
+ this.markedForRemoval = [];
1706
+ this.setModified(false);
1707
+ }
1708
+ restoreSavedState() {
1709
+ if (this.modified) {
1710
+ this.visibleWidgetInstances$.next([...this.persistedWidgetInstances]);
1711
+ this.setModified(false);
1712
+ }
1713
+ }
1508
1714
  setModified(modified) {
1509
1715
  if (this.modified !== modified) {
1510
1716
  this.modified = modified;
1511
1717
  this.isModified.emit(this.modified);
1512
1718
  }
1513
1719
  }
1514
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiGridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1515
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.6", type: SiGridComponent, isStandalone: true, selector: "si-grid", inputs: { gridConfig: { classPropertyName: "gridConfig", publicName: "gridConfig", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, dashboardId: { classPropertyName: "dashboardId", publicName: "dashboardId", isSignal: true, isRequired: false, transformFunction: null }, widgetCatalog: { classPropertyName: "widgetCatalog", publicName: "widgetCatalog", isSignal: true, isRequired: false, transformFunction: null }, emitWidgetInstanceEditEvents: { classPropertyName: "emitWidgetInstanceEditEvents", publicName: "emitWidgetInstanceEditEvents", isSignal: true, isRequired: false, transformFunction: null }, hideProgressIndicator: { classPropertyName: "hideProgressIndicator", publicName: "hideProgressIndicator", isSignal: true, isRequired: false, transformFunction: null }, widgetInstanceEditorDialogComponent: { classPropertyName: "widgetInstanceEditorDialogComponent", publicName: "widgetInstanceEditorDialogComponent", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { editable: "editableChange", isModified: "isModified", widgetInstanceEdit: "widgetInstanceEdit" }, providers: [SiGridService, SiLoadingService], viewQueries: [{ propertyName: "gridStackWrapper", first: true, predicate: SiGridstackWrapperComponent, descendants: true, isSignal: true }], usesOnChanges: true, ngImport: i0, template: "@if (showEmptyState) {\n <ng-content select=\"[empty-state]\" />\n}\n<si-gridstack-wrapper\n [widgetConfigs]=\"(visibleWidgetInstances$ | async) ?? []\"\n [gridConfig]=\"gridConfig()\"\n [editable]=\"editable()\"\n [siLoading]=\"!hideProgressIndicator() && initialLoad && (isLoading | async)!\"\n [blocking]=\"false\"\n (gridEvent)=\"handleGridEvent($event)\"\n (widgetInstanceRemove)=\"removeWidgetInstance($event)\"\n (widgetInstanceEdit)=\"editWidgetInstance($event)\"\n/>\n", styles: [":host{display:flex;flex:1 1 0;flex-direction:column}::ng-deep .grid-stack-animate,::ng-deep .grid-stack-animate .grid-stack-item{transition-property:inset-inline-start,inset-block-start}\n"], dependencies: [{ kind: "component", type: SiGridstackWrapperComponent, selector: "si-gridstack-wrapper", inputs: ["widgetConfigs", "editable", "gridConfig"], outputs: ["gridEvent", "widgetInstanceRemove", "widgetInstanceEdit"] }, { kind: "directive", type: SiLoadingSpinnerDirective, selector: "[siLoading]", inputs: ["siLoading", "blocking", "initialDelay"] }, { kind: "pipe", type: AsyncPipe, name: "async" }] });
1720
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: SiGridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1721
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: SiGridComponent, isStandalone: true, selector: "si-grid", inputs: { gridConfig: { classPropertyName: "gridConfig", publicName: "gridConfig", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, dashboardId: { classPropertyName: "dashboardId", publicName: "dashboardId", isSignal: true, isRequired: false, transformFunction: null }, widgetCatalog: { classPropertyName: "widgetCatalog", publicName: "widgetCatalog", isSignal: true, isRequired: false, transformFunction: null }, emitWidgetInstanceEditEvents: { classPropertyName: "emitWidgetInstanceEditEvents", publicName: "emitWidgetInstanceEditEvents", isSignal: true, isRequired: false, transformFunction: null }, hideProgressIndicator: { classPropertyName: "hideProgressIndicator", publicName: "hideProgressIndicator", isSignal: true, isRequired: false, transformFunction: null }, widgetInstanceEditorDialogComponent: { classPropertyName: "widgetInstanceEditorDialogComponent", publicName: "widgetInstanceEditorDialogComponent", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { editable: "editableChange", isModified: "isModified", widgetInstanceEdit: "widgetInstanceEdit" }, viewQueries: [{ propertyName: "gridStackWrapper", first: true, predicate: SiGridstackWrapperComponent, descendants: true, isSignal: true }], usesOnChanges: true, ngImport: i0, template: "@if (showEmptyState) {\n <ng-content select=\"[empty-state]\" />\n}\n<si-gridstack-wrapper\n [widgetConfigs]=\"(visibleWidgetInstances$ | async) ?? []\"\n [gridConfig]=\"gridConfig()\"\n [editable]=\"editable()\"\n [widgetCatalogMap]=\"widgetCatalogMap()\"\n [siLoading]=\"!hideProgressIndicator() && initialLoad && (isLoading | async)!\"\n [blocking]=\"false\"\n (gridEvent)=\"handleGridEvent($event)\"\n (widgetInstanceRemove)=\"removeWidgetInstance($event)\"\n (widgetInstanceEdit)=\"editWidgetInstance($event)\"\n/>\n", styles: [":host{display:flex;flex:1 1 0;flex-direction:column}::ng-deep .grid-stack-animate,::ng-deep .grid-stack-animate .grid-stack-item{transition-property:inset-inline-start,inset-block-start}\n"], dependencies: [{ kind: "component", type: SiGridstackWrapperComponent, selector: "si-gridstack-wrapper", inputs: ["widgetConfigs", "editable", "gridConfig", "widgetCatalogMap"], outputs: ["gridEvent", "widgetInstanceRemove", "widgetInstanceEdit"] }, { kind: "directive", type: SiLoadingSpinnerDirective, selector: "[siLoading]", inputs: ["siLoading", "blocking", "initialDelay"] }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.Eager });
1516
1722
  }
1517
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiGridComponent, decorators: [{
1723
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: SiGridComponent, decorators: [{
1518
1724
  type: Component,
1519
- args: [{ selector: 'si-grid', imports: [SiGridstackWrapperComponent, SiLoadingSpinnerDirective, AsyncPipe], providers: [SiGridService, SiLoadingService], template: "@if (showEmptyState) {\n <ng-content select=\"[empty-state]\" />\n}\n<si-gridstack-wrapper\n [widgetConfigs]=\"(visibleWidgetInstances$ | async) ?? []\"\n [gridConfig]=\"gridConfig()\"\n [editable]=\"editable()\"\n [siLoading]=\"!hideProgressIndicator() && initialLoad && (isLoading | async)!\"\n [blocking]=\"false\"\n (gridEvent)=\"handleGridEvent($event)\"\n (widgetInstanceRemove)=\"removeWidgetInstance($event)\"\n (widgetInstanceEdit)=\"editWidgetInstance($event)\"\n/>\n", styles: [":host{display:flex;flex:1 1 0;flex-direction:column}::ng-deep .grid-stack-animate,::ng-deep .grid-stack-animate .grid-stack-item{transition-property:inset-inline-start,inset-block-start}\n"] }]
1520
- }] });
1725
+ args: [{ selector: 'si-grid', imports: [SiGridstackWrapperComponent, SiLoadingSpinnerDirective, AsyncPipe], changeDetection: ChangeDetectionStrategy.Eager, template: "@if (showEmptyState) {\n <ng-content select=\"[empty-state]\" />\n}\n<si-gridstack-wrapper\n [widgetConfigs]=\"(visibleWidgetInstances$ | async) ?? []\"\n [gridConfig]=\"gridConfig()\"\n [editable]=\"editable()\"\n [widgetCatalogMap]=\"widgetCatalogMap()\"\n [siLoading]=\"!hideProgressIndicator() && initialLoad && (isLoading | async)!\"\n [blocking]=\"false\"\n (gridEvent)=\"handleGridEvent($event)\"\n (widgetInstanceRemove)=\"removeWidgetInstance($event)\"\n (widgetInstanceEdit)=\"editWidgetInstance($event)\"\n/>\n", styles: [":host{display:flex;flex:1 1 0;flex-direction:column}::ng-deep .grid-stack-animate,::ng-deep .grid-stack-animate .grid-stack-item{transition-property:inset-inline-start,inset-block-start}\n"] }]
1726
+ }], propDecorators: { gridConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "gridConfig", required: false }] }], editable: [{ type: i0.Input, args: [{ isSignal: true, alias: "editable", required: false }] }, { type: i0.Output, args: ["editableChange"] }], dashboardId: [{ type: i0.Input, args: [{ isSignal: true, alias: "dashboardId", required: false }] }], widgetCatalog: [{ type: i0.Input, args: [{ isSignal: true, alias: "widgetCatalog", required: false }] }], emitWidgetInstanceEditEvents: [{ type: i0.Input, args: [{ isSignal: true, alias: "emitWidgetInstanceEditEvents", required: false }] }], hideProgressIndicator: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideProgressIndicator", required: false }] }], widgetInstanceEditorDialogComponent: [{ type: i0.Input, args: [{ isSignal: true, alias: "widgetInstanceEditorDialogComponent", required: false }] }], isModified: [{ type: i0.Output, args: ["isModified"] }], widgetInstanceEdit: [{ type: i0.Output, args: ["widgetInstanceEdit"] }], gridStackWrapper: [{ type: i0.ViewChild, args: [i0.forwardRef(() => SiGridstackWrapperComponent), { isSignal: true }] }] } });
1521
1727
 
1522
1728
  /**
1523
1729
  * Function creates a new {@link WidgetConfig} without id from a {@link Widget} and
@@ -1538,7 +1744,7 @@ const createWidgetConfig = (widget) => {
1538
1744
  };
1539
1745
 
1540
1746
  /**
1541
- * Copyright (c) Siemens 2016 - 2025
1747
+ * Copyright (c) Siemens 2016 - 2026
1542
1748
  * SPDX-License-Identifier: MIT
1543
1749
  */
1544
1750
  /**
@@ -1547,7 +1753,7 @@ const createWidgetConfig = (widget) => {
1547
1753
  * selection, a host in which the widget specific editor is loaded. Applications can either
1548
1754
  * stay with the default catalog or implement their own by extending this class.
1549
1755
  */
1550
- class SiWidgetCatalogComponent {
1756
+ class SiWidgetCatalogComponent extends SiWidgetEditorBase {
1551
1757
  /**
1552
1758
  * Placeholder text for the search input field in the widget catalog.
1553
1759
  *
@@ -1556,7 +1762,8 @@ class SiWidgetCatalogComponent {
1556
1762
  * t(() => $localize`:@@DASHBOARD.WIDGET_LIBRARY.SEARCH_PLACEHOLDER:Search widget`)
1557
1763
  * ```
1558
1764
  */
1559
- searchPlaceholder = input(t(() => $localize `:@@DASHBOARD.WIDGET_LIBRARY.SEARCH_PLACEHOLDER:Search widget`));
1765
+ searchPlaceholder = input(t(() => $localize `:@@DASHBOARD.WIDGET_LIBRARY.SEARCH_PLACEHOLDER:Search widget`), /* @ts-ignore */
1766
+ ...(ngDevMode ? [{ debugName: "searchPlaceholder" }] : /* istanbul ignore next */ []));
1560
1767
  /**
1561
1768
  * Emits when the catalog is `closed`, either by canceling or by adding or saving
1562
1769
  * a widget configuration. On cancel `undefined` is emitted, otherwise the related
@@ -1569,8 +1776,8 @@ class SiWidgetCatalogComponent {
1569
1776
  * @internal
1570
1777
  * @defaultValue 'list'
1571
1778
  */
1572
- view = signal('list');
1573
- editorHost = viewChild.required('editorHost', { read: ViewContainerRef });
1779
+ view = signal('list', /* @ts-ignore */
1780
+ ...(ngDevMode ? [{ debugName: "view" }] : /* istanbul ignore next */ []));
1574
1781
  /**
1575
1782
  * Property to provide the available widgets to the catalog. The flexible
1576
1783
  * dashboard creates the catalog by Angular's `createComponent()` method
@@ -1587,9 +1794,12 @@ class SiWidgetCatalogComponent {
1587
1794
  * Array used to hold the search result on the widget catalog.
1588
1795
  * @defaultValue [] */
1589
1796
  filteredWidgetCatalog = [];
1590
- selected = signal(undefined);
1797
+ selected = signal(undefined, /* @ts-ignore */
1798
+ ...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
1591
1799
  widgetConfig;
1592
- hasEditor = signal(false);
1800
+ hasEditor = computed(() => !!this.selected()?.componentFactory.editorComponentName, /* @ts-ignore */
1801
+ ...(ngDevMode ? [{ debugName: "hasEditor" }] : /* istanbul ignore next */ []));
1802
+ translateService = injectSiTranslateService();
1593
1803
  labelCancel = t(() => $localize `:@@DASHBOARD.WIDGET_LIBRARY.CANCEL:Cancel`);
1594
1804
  labelPrevious = t(() => $localize `:@@DASHBOARD.WIDGET_LIBRARY.PREVIOUS:Previous`);
1595
1805
  labelNext = t(() => $localize `:@@DASHBOARD.WIDGET_LIBRARY.NEXT:Next`);
@@ -1599,10 +1809,15 @@ class SiWidgetCatalogComponent {
1599
1809
  labelDialogCancel = t(() => $localize `:@@DASHBOARD.WIDGET_LIBRARY.DISCARD_CONFIG_CHANGE_DIALOG.CANCEL:Cancel`);
1600
1810
  labelDialogMessage = t(() => $localize `:@@DASHBOARD.WIDGET_LIBRARY.DISCARD_CONFIG_CHANGE_DIALOG.MESSAGE:The widget configuration changed. Do you want to discard the changes?`);
1601
1811
  labelDialogDiscard = t(() => $localize `:@@DASHBOARD.WIDGET_LIBRARY.DISCARD_CONFIG_CHANGE_DIALOG.DISCARD:Discard`);
1602
- showAddButton = computed(() => this.view() === 'list' ? !this.hasEditor() : true);
1603
- showNextButton = computed(() => this.view() === 'list' ? this.hasEditor() : this.editorWizardState() !== undefined);
1604
- showPreviousButton = computed(() => this.view() === 'editor');
1605
- disableAddButton = computed(() => !this.selected() || this.invalidConfig());
1812
+ labelWidgetCatalogList = t(() => $localize `:@@DASHBOARD.WIDGET_LIBRARY.WIDGET_CATALOG_LIST:Widget catalog list`);
1813
+ showAddButton = computed(() => this.view() === 'list' ? !this.hasEditor() : true, /* @ts-ignore */
1814
+ ...(ngDevMode ? [{ debugName: "showAddButton" }] : /* istanbul ignore next */ []));
1815
+ showNextButton = computed(() => this.view() === 'list' ? this.hasEditor() : this.editorWizardState() !== undefined, /* @ts-ignore */
1816
+ ...(ngDevMode ? [{ debugName: "showNextButton" }] : /* istanbul ignore next */ []));
1817
+ showPreviousButton = computed(() => this.view() === 'editor', /* @ts-ignore */
1818
+ ...(ngDevMode ? [{ debugName: "showPreviousButton" }] : /* istanbul ignore next */ []));
1819
+ disableAddButton = computed(() => !this.selected() || this.invalidConfig(), /* @ts-ignore */
1820
+ ...(ngDevMode ? [{ debugName: "disableAddButton" }] : /* istanbul ignore next */ []));
1606
1821
  disableNextButton = computed(() => {
1607
1822
  const wizardState = this.editorWizardState();
1608
1823
  if (this.view() === 'list') {
@@ -1620,31 +1835,17 @@ class SiWidgetCatalogComponent {
1620
1835
  else {
1621
1836
  return false;
1622
1837
  }
1623
- });
1624
- /** Indicates if the current config is valid or not. If invalid, the add button is disabled. */
1625
- invalidConfig = signal(false);
1626
- /**
1627
- * Marks the widget configuration as modified. Is set when widget editor instance
1628
- * emits configChange events. Triggers edit discard confirmation dialog when widget config
1629
- * is modified but not added to the dashboard.
1630
- * */
1631
- widgetConfigModified = false;
1632
- widgetInstanceEditor;
1633
- editorWizardState = signal(undefined);
1634
- widgetInstanceEditorRef;
1635
- subscriptions = [];
1838
+ }, /* @ts-ignore */
1839
+ ...(ngDevMode ? [{ debugName: "disableNextButton" }] : /* istanbul ignore next */ []));
1636
1840
  dialogService = inject(SiActionDialogService);
1637
- injector = inject(Injector);
1638
- envInjector = inject(EnvironmentInjector);
1841
+ widgetCdkListbox = viewChild((CdkListbox), /* @ts-ignore */
1842
+ ...(ngDevMode ? [{ debugName: "widgetCdkListbox" }] : /* istanbul ignore next */ []));
1639
1843
  ngOnInit() {
1640
1844
  this.filteredWidgetCatalog = this.widgetCatalog;
1641
1845
  if (this.widgetCatalog.length > 0) {
1642
1846
  this.selectWidget(this.widgetCatalog[0]);
1643
1847
  }
1644
1848
  }
1645
- ngOnDestroy() {
1646
- this.tearDownEditor();
1647
- }
1648
1849
  onSearch(searchTerm) {
1649
1850
  if (!searchTerm || searchTerm.trim().length === 0) {
1650
1851
  this.searchTerm = '';
@@ -1652,7 +1853,11 @@ class SiWidgetCatalogComponent {
1652
1853
  }
1653
1854
  else {
1654
1855
  this.searchTerm = searchTerm;
1655
- this.filteredWidgetCatalog = this.widgetCatalog.filter(wd => wd.name.toLowerCase().includes(searchTerm.trim().toLowerCase()));
1856
+ const term = searchTerm.trim().toLowerCase();
1857
+ this.filteredWidgetCatalog = this.widgetCatalog.filter(wd => {
1858
+ const name = this.translateService.translateSync(wd.name);
1859
+ return name.toLowerCase().includes(term);
1860
+ });
1656
1861
  }
1657
1862
  if (this.filteredWidgetCatalog.length > 0) {
1658
1863
  this.selectWidget(this.filteredWidgetCatalog[0]);
@@ -1662,7 +1867,7 @@ class SiWidgetCatalogComponent {
1662
1867
  }
1663
1868
  }
1664
1869
  onCancel() {
1665
- if (!this.widgetConfigModified) {
1870
+ if (!this.widgetConfigModified()) {
1666
1871
  this.closed.emit(undefined);
1667
1872
  }
1668
1873
  else {
@@ -1687,18 +1892,18 @@ class SiWidgetCatalogComponent {
1687
1892
  this.setupWidgetInstanceEditor();
1688
1893
  }
1689
1894
  else {
1690
- if (this.isEditorWizdard(this.widgetInstanceEditor)) {
1895
+ if (this.isEditorWizard(this.widgetInstanceEditor)) {
1691
1896
  this.widgetInstanceEditor.next();
1692
1897
  this.editorWizardState.set(this.widgetInstanceEditor.state);
1693
1898
  }
1694
1899
  }
1695
1900
  }
1696
1901
  onPrevious() {
1697
- if (this.isEditorWizdard(this.widgetInstanceEditor) && this.editorWizardState()?.hasPrevious) {
1902
+ if (this.isEditorWizard(this.widgetInstanceEditor) && this.editorWizardState()?.hasPrevious) {
1698
1903
  this.widgetInstanceEditor.previous();
1699
1904
  this.editorWizardState.set(this.widgetInstanceEditor.state);
1700
1905
  }
1701
- else if (!this.widgetConfigModified) {
1906
+ else if (!this.widgetConfigModified()) {
1702
1907
  this.setupCatalog();
1703
1908
  }
1704
1909
  else {
@@ -1726,61 +1931,17 @@ class SiWidgetCatalogComponent {
1726
1931
  this.tearDownEditor();
1727
1932
  this.view.set('editor');
1728
1933
  this.widgetConfig = createWidgetConfig(selected);
1729
- setupWidgetEditor(selected.componentFactory, this.editorHost(), this.injector, this.envInjector).subscribe({
1934
+ this.loadWidgetEditor(selected.componentFactory, this.editorHost()).subscribe({
1730
1935
  next: componentRef => {
1731
- this.widgetInstanceEditorRef = componentRef;
1732
- this.widgetInstanceEditor = componentRef.instance;
1733
- if (isSignal(this.widgetInstanceEditor.config)) {
1734
- this.widgetInstanceEditorRef.setInput('config', this.widgetConfig);
1735
- }
1736
- else {
1737
- this.widgetInstanceEditor.config = this.widgetConfig;
1738
- }
1739
- // To be used by webcomponent wrapper
1740
- if ('statusChangesHandler' in this.widgetInstanceEditor) {
1741
- this.widgetInstanceEditor.statusChangesHandler = this.handleStatusChanges.bind(this);
1742
- }
1743
- let hasStatusChangesEmitter = false;
1744
- if (this.widgetInstanceEditor.statusChanges) {
1745
- hasStatusChangesEmitter = true;
1746
- this.subscriptions.push(this.widgetInstanceEditor.statusChanges.subscribe(statusChanges => {
1747
- this.handleStatusChanges(statusChanges);
1748
- }));
1749
- }
1750
- if (this.widgetInstanceEditor.configChange) {
1751
- this.subscriptions.push(this.widgetInstanceEditor.configChange.subscribe(config => {
1752
- if (!hasStatusChangesEmitter) {
1753
- this.invalidConfig.set(!!config.invalid);
1754
- this.widgetConfigModified = true;
1755
- }
1756
- }));
1757
- }
1758
- if (this.isEditorWizdard(this.widgetInstanceEditor)) {
1759
- this.editorWizardState.set(this.widgetInstanceEditor.state);
1760
- if (this.widgetInstanceEditor.stateChange) {
1761
- this.subscriptions.push(this.widgetInstanceEditor.stateChange.subscribe(state => {
1762
- this.editorWizardState.set(state);
1763
- }));
1764
- }
1765
- }
1936
+ this.initializeEditor(componentRef, this.widgetConfig);
1766
1937
  },
1767
1938
  error: error => {
1768
1939
  console.error(error);
1769
1940
  }
1770
1941
  });
1771
1942
  }
1772
- tearDownEditor() {
1773
- this.invalidConfig.set(false);
1774
- this.widgetConfigModified = false;
1775
- this.editorWizardState.set(undefined);
1776
- this.widgetInstanceEditor = undefined;
1777
- this.subscriptions.forEach(s => s.unsubscribe());
1778
- this.subscriptions = [];
1779
- }
1780
1943
  setupCatalog() {
1781
- if (this.editorHost().length > 0) {
1782
- this.editorHost().remove(0);
1783
- }
1944
+ this.editorHost().clear();
1784
1945
  this.tearDownEditor();
1785
1946
  this.widgetConfig = undefined;
1786
1947
  this.view.set('list');
@@ -1806,34 +1967,30 @@ class SiWidgetCatalogComponent {
1806
1967
  }
1807
1968
  selectWidget(widget) {
1808
1969
  this.selected.set(widget);
1809
- if (widget?.componentFactory?.editorComponentName) {
1810
- this.hasEditor.set(true);
1811
- }
1812
- else {
1813
- this.hasEditor.set(false);
1814
- }
1815
- }
1816
- isEditorWizdard(editor) {
1817
- return !!editor && 'state' in editor;
1818
- }
1819
- handleStatusChanges(statusChanges) {
1820
- if (statusChanges.invalid !== undefined) {
1821
- this.invalidConfig.set(statusChanges.invalid);
1822
- }
1823
- if (statusChanges.modified !== undefined) {
1824
- this.widgetConfigModified = statusChanges.modified;
1970
+ if (widget) {
1971
+ // need to keep this in setTimeout to avoid ExpressionChangedAfterItHasBeenCheckedError
1972
+ setTimeout(() => {
1973
+ this.widgetCdkListbox()?.selectValue(widget);
1974
+ });
1825
1975
  }
1826
1976
  }
1827
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiWidgetCatalogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1828
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.6", type: SiWidgetCatalogComponent, isStandalone: true, selector: "si-widget-catalog", inputs: { searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed" }, viewQueries: [{ propertyName: "editorHost", first: true, predicate: ["editorHost"], descendants: true, read: ViewContainerRef, isSignal: true }], ngImport: i0, template: "<div class=\"si-layout-fixed-height mb-8 mx-0 overflow-auto px-2 pt-2\">\n @if (view() === 'list') {\n <div class=\"si-layout-fixed-height\">\n <div class=\"mx-0\">\n <si-search-bar\n class=\"w-100\"\n colorVariant=\"base-1\"\n showIcon=\"true\"\n [placeholder]=\"searchPlaceholder() | translate\"\n [value]=\"searchTerm\"\n (searchChange)=\"onSearch($event)\"\n />\n </div>\n <div\n class=\"si-layout-fixed-height text-pre-wrap text-break overflow-y-auto widget-list mt-6 bg-base-1\"\n >\n @if (filteredWidgetCatalog.length > 0) {\n <div class=\"si-layout-fixed-height list-group\">\n @for (widget of filteredWidgetCatalog; track $index) {\n <button\n type=\"button\"\n class=\"list-group-item list-group-item-action d-flex\"\n [class.active]=\"selected() === widget\"\n (click)=\"selectWidget(widget)\"\n >\n <si-circle-status class=\"my-n4 me-5\" [icon]=\"widget.iconClass\" />\n <div class=\"d-flex flex-column align-items-start\">\n <span class=\"si-title-2\">{{ widget.name }}</span>\n <span class=\"si-body-2\">{{ widget.description?.trim() }}</span>\n </div>\n </button>\n }\n </div>\n } @else {\n <div>\n <si-empty-state icon=\"element-report\" [heading]=\"labelEmpty\" />\n </div>\n }\n </div>\n </div>\n }\n <ng-container #editorHost />\n</div>\n\n<div class=\"catalog-footer py-0 px-2\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"onCancel()\">{{\n labelCancel | translate\n }}</button>\n @if (showPreviousButton()) {\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"onPrevious()\">{{\n labelPrevious | translate\n }}</button>\n }\n @if (showNextButton()) {\n <button\n type=\"button\"\n class=\"btn\"\n [class.btn-primary]=\"view() === 'list'\"\n [class.btn-secondary]=\"view() !== 'list'\"\n [disabled]=\"disableNextButton()\"\n (click)=\"onNext()\"\n >{{ labelNext | translate }}</button\n >\n }\n @if (showAddButton()) {\n <button\n type=\"button\"\n class=\"btn btn-primary\"\n [disabled]=\"disableAddButton()\"\n (click)=\"onAddWidget()\"\n >{{ labelAdd | translate }}</button\n >\n }\n</div>\n", styles: [":host{display:flex;flex:1 1 0;flex-direction:column;margin-inline:-4px;margin-block-start:-4px}.widget-list{background-color:var(--element-base-1);border:1px solid var(--element-ui-4);border-radius:var(--element-radius-2);overflow-x:auto}.list-group-item{border-inline-color:transparent;border-radius:0}.catalog-footer{align-items:center;display:flex;flex-shrink:0;flex-wrap:wrap;justify-content:flex-end}.catalog-footer>*{margin-inline-start:16px}\n"], dependencies: [{ kind: "component", type: SiSearchBarComponent, selector: "si-search-bar", inputs: ["debounceTime", "prohibitedCharacters", "placeholder", "showIcon", "tabbable", "value", "readonly", "colorVariant", "disabled"], outputs: ["searchChange"] }, { kind: "component", type: SiCircleStatusComponent, selector: "si-circle-status", inputs: ["status", "color", "icon", "size", "eventOut", "eventIcon", "blink", "blinkPulse", "ariaLabel"] }, { kind: "component", type: SiEmptyStateComponent, selector: "si-empty-state", inputs: ["icon", "heading", "content"] }, { kind: "pipe", type: SiTranslatePipe, name: "translate" }] });
1977
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: SiWidgetCatalogComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1978
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: SiWidgetCatalogComponent, isStandalone: true, selector: "si-widget-catalog", inputs: { searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed" }, viewQueries: [{ propertyName: "widgetCdkListbox", first: true, predicate: (CdkListbox), descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"si-layout-fixed-height mb-8 mx-0 overflow-auto px-2 pt-2\">\n @if (view() === 'list') {\n <div class=\"si-layout-fixed-height\">\n <div class=\"mx-0\">\n <si-search-bar\n class=\"w-100\"\n colorVariant=\"base-1\"\n showIcon=\"true\"\n [placeholder]=\"searchPlaceholder() | translate\"\n [value]=\"searchTerm\"\n (searchChange)=\"onSearch($event)\"\n />\n </div>\n <div class=\"si-layout-fixed-height text-pre-wrap text-break mt-6 bg-base-1 rounded-2\">\n @if (filteredWidgetCatalog.length > 0) {\n <span id=\"widget-catalog-list-description\" class=\"visually-hidden\">\n {{ labelWidgetCatalogList | translate }}\n </span>\n <ul\n class=\"si-layout-fixed-height list-group overflow-auto mb-0\"\n aria-labelledby=\"widget-catalog-list-description\"\n cdkListbox\n [cdkListboxValue]=\"[]\"\n (cdkListboxValueChange)=\"selectWidget($event.value[0])\"\n >\n @for (widget of filteredWidgetCatalog; track $index) {\n <li\n class=\"list-group-item list-group-item-action d-flex\"\n [cdkOption]=\"widget\"\n [class.active]=\"selected() === widget\"\n >\n <si-circle-status class=\"my-n4 me-5\" [icon]=\"widget.iconClass\" />\n <div class=\"d-flex flex-column align-items-start align-self-center\">\n <span class=\"si-h5\">{{ widget.name | translate }}</span>\n <span class=\"si-body\">{{ widget.description?.trim() | translate }}</span>\n </div>\n </li>\n }\n </ul>\n } @else {\n <div>\n <si-empty-state icon=\"element-report\" [heading]=\"labelEmpty\" />\n </div>\n }\n </div>\n </div>\n }\n <ng-container #editorHost />\n</div>\n\n<div class=\"catalog-footer pb-4 px-2\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"onCancel()\">{{\n labelCancel | translate\n }}</button>\n @if (showPreviousButton()) {\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"onPrevious()\">{{\n labelPrevious | translate\n }}</button>\n }\n @if (showNextButton()) {\n <button\n type=\"button\"\n class=\"btn\"\n [class.btn-primary]=\"view() === 'list'\"\n [class.btn-secondary]=\"view() !== 'list'\"\n [disabled]=\"disableNextButton()\"\n (click)=\"onNext()\"\n >{{ labelNext | translate }}</button\n >\n }\n @if (showAddButton()) {\n <button\n type=\"button\"\n class=\"btn btn-primary\"\n [disabled]=\"disableAddButton()\"\n (click)=\"onAddWidget()\"\n >{{ labelAdd | translate }}</button\n >\n }\n</div>\n", styles: [":host{display:flex;flex:1 1 0;flex-direction:column;margin-inline:-4px;margin-block-start:-4px}.list-group-item{cursor:pointer}.catalog-footer{align-items:center;display:flex;flex-shrink:0;flex-wrap:wrap;justify-content:flex-end}.catalog-footer>*{margin-inline-start:16px}\n"], dependencies: [{ kind: "component", type: SiSearchBarComponent, selector: "si-search-bar", inputs: ["debounceTime", "prohibitedCharacters", "placeholder", "showIcon", "tabbable", "value", "readonly", "colorVariant", "disabled", "maxlength", "clearButtonAriaLabel"], outputs: ["searchChange"] }, { kind: "component", type: SiCircleStatusComponent, selector: "si-circle-status", inputs: ["status", "color", "icon", "size", "eventOut", "eventIcon", "blink", "blinkPulse", "ariaLabel"] }, { kind: "component", type: SiEmptyStateComponent, selector: "si-empty-state", inputs: ["icon", "heading", "content"] }, { kind: "directive", type: CdkListbox, selector: "[cdkListbox]", inputs: ["id", "tabindex", "cdkListboxValue", "cdkListboxMultiple", "cdkListboxDisabled", "cdkListboxUseActiveDescendant", "cdkListboxOrientation", "cdkListboxCompareWith", "cdkListboxNavigationWrapDisabled", "cdkListboxNavigatesDisabledOptions"], outputs: ["cdkListboxValueChange"], exportAs: ["cdkListbox"] }, { kind: "directive", type: CdkOption, selector: "[cdkOption]", inputs: ["id", "cdkOption", "cdkOptionTypeaheadLabel", "cdkOptionDisabled", "tabindex"], exportAs: ["cdkOption"] }, { kind: "pipe", type: SiTranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.Eager });
1829
1979
  }
1830
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiWidgetCatalogComponent, decorators: [{
1980
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: SiWidgetCatalogComponent, decorators: [{
1831
1981
  type: Component,
1832
- args: [{ selector: 'si-widget-catalog', imports: [SiSearchBarComponent, SiCircleStatusComponent, SiEmptyStateComponent, SiTranslatePipe], template: "<div class=\"si-layout-fixed-height mb-8 mx-0 overflow-auto px-2 pt-2\">\n @if (view() === 'list') {\n <div class=\"si-layout-fixed-height\">\n <div class=\"mx-0\">\n <si-search-bar\n class=\"w-100\"\n colorVariant=\"base-1\"\n showIcon=\"true\"\n [placeholder]=\"searchPlaceholder() | translate\"\n [value]=\"searchTerm\"\n (searchChange)=\"onSearch($event)\"\n />\n </div>\n <div\n class=\"si-layout-fixed-height text-pre-wrap text-break overflow-y-auto widget-list mt-6 bg-base-1\"\n >\n @if (filteredWidgetCatalog.length > 0) {\n <div class=\"si-layout-fixed-height list-group\">\n @for (widget of filteredWidgetCatalog; track $index) {\n <button\n type=\"button\"\n class=\"list-group-item list-group-item-action d-flex\"\n [class.active]=\"selected() === widget\"\n (click)=\"selectWidget(widget)\"\n >\n <si-circle-status class=\"my-n4 me-5\" [icon]=\"widget.iconClass\" />\n <div class=\"d-flex flex-column align-items-start\">\n <span class=\"si-title-2\">{{ widget.name }}</span>\n <span class=\"si-body-2\">{{ widget.description?.trim() }}</span>\n </div>\n </button>\n }\n </div>\n } @else {\n <div>\n <si-empty-state icon=\"element-report\" [heading]=\"labelEmpty\" />\n </div>\n }\n </div>\n </div>\n }\n <ng-container #editorHost />\n</div>\n\n<div class=\"catalog-footer py-0 px-2\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"onCancel()\">{{\n labelCancel | translate\n }}</button>\n @if (showPreviousButton()) {\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"onPrevious()\">{{\n labelPrevious | translate\n }}</button>\n }\n @if (showNextButton()) {\n <button\n type=\"button\"\n class=\"btn\"\n [class.btn-primary]=\"view() === 'list'\"\n [class.btn-secondary]=\"view() !== 'list'\"\n [disabled]=\"disableNextButton()\"\n (click)=\"onNext()\"\n >{{ labelNext | translate }}</button\n >\n }\n @if (showAddButton()) {\n <button\n type=\"button\"\n class=\"btn btn-primary\"\n [disabled]=\"disableAddButton()\"\n (click)=\"onAddWidget()\"\n >{{ labelAdd | translate }}</button\n >\n }\n</div>\n", styles: [":host{display:flex;flex:1 1 0;flex-direction:column;margin-inline:-4px;margin-block-start:-4px}.widget-list{background-color:var(--element-base-1);border:1px solid var(--element-ui-4);border-radius:var(--element-radius-2);overflow-x:auto}.list-group-item{border-inline-color:transparent;border-radius:0}.catalog-footer{align-items:center;display:flex;flex-shrink:0;flex-wrap:wrap;justify-content:flex-end}.catalog-footer>*{margin-inline-start:16px}\n"] }]
1833
- }] });
1982
+ args: [{ selector: 'si-widget-catalog', imports: [
1983
+ SiSearchBarComponent,
1984
+ SiCircleStatusComponent,
1985
+ SiEmptyStateComponent,
1986
+ SiTranslatePipe,
1987
+ CdkListbox,
1988
+ CdkOption
1989
+ ], changeDetection: ChangeDetectionStrategy.Eager, template: "<div class=\"si-layout-fixed-height mb-8 mx-0 overflow-auto px-2 pt-2\">\n @if (view() === 'list') {\n <div class=\"si-layout-fixed-height\">\n <div class=\"mx-0\">\n <si-search-bar\n class=\"w-100\"\n colorVariant=\"base-1\"\n showIcon=\"true\"\n [placeholder]=\"searchPlaceholder() | translate\"\n [value]=\"searchTerm\"\n (searchChange)=\"onSearch($event)\"\n />\n </div>\n <div class=\"si-layout-fixed-height text-pre-wrap text-break mt-6 bg-base-1 rounded-2\">\n @if (filteredWidgetCatalog.length > 0) {\n <span id=\"widget-catalog-list-description\" class=\"visually-hidden\">\n {{ labelWidgetCatalogList | translate }}\n </span>\n <ul\n class=\"si-layout-fixed-height list-group overflow-auto mb-0\"\n aria-labelledby=\"widget-catalog-list-description\"\n cdkListbox\n [cdkListboxValue]=\"[]\"\n (cdkListboxValueChange)=\"selectWidget($event.value[0])\"\n >\n @for (widget of filteredWidgetCatalog; track $index) {\n <li\n class=\"list-group-item list-group-item-action d-flex\"\n [cdkOption]=\"widget\"\n [class.active]=\"selected() === widget\"\n >\n <si-circle-status class=\"my-n4 me-5\" [icon]=\"widget.iconClass\" />\n <div class=\"d-flex flex-column align-items-start align-self-center\">\n <span class=\"si-h5\">{{ widget.name | translate }}</span>\n <span class=\"si-body\">{{ widget.description?.trim() | translate }}</span>\n </div>\n </li>\n }\n </ul>\n } @else {\n <div>\n <si-empty-state icon=\"element-report\" [heading]=\"labelEmpty\" />\n </div>\n }\n </div>\n </div>\n }\n <ng-container #editorHost />\n</div>\n\n<div class=\"catalog-footer pb-4 px-2\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"onCancel()\">{{\n labelCancel | translate\n }}</button>\n @if (showPreviousButton()) {\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"onPrevious()\">{{\n labelPrevious | translate\n }}</button>\n }\n @if (showNextButton()) {\n <button\n type=\"button\"\n class=\"btn\"\n [class.btn-primary]=\"view() === 'list'\"\n [class.btn-secondary]=\"view() !== 'list'\"\n [disabled]=\"disableNextButton()\"\n (click)=\"onNext()\"\n >{{ labelNext | translate }}</button\n >\n }\n @if (showAddButton()) {\n <button\n type=\"button\"\n class=\"btn btn-primary\"\n [disabled]=\"disableAddButton()\"\n (click)=\"onAddWidget()\"\n >{{ labelAdd | translate }}</button\n >\n }\n</div>\n", styles: [":host{display:flex;flex:1 1 0;flex-direction:column;margin-inline:-4px;margin-block-start:-4px}.list-group-item{cursor:pointer}.catalog-footer{align-items:center;display:flex;flex-shrink:0;flex-wrap:wrap;justify-content:flex-end}.catalog-footer>*{margin-inline-start:16px}\n"] }]
1990
+ }], propDecorators: { searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], widgetCdkListbox: [{ type: i0.ViewChild, args: [i0.forwardRef(() => CdkListbox), { isSignal: true }] }] } });
1834
1991
 
1835
1992
  /**
1836
- * Copyright (c) Siemens 2016 - 2025
1993
+ * Copyright (c) Siemens 2016 - 2026
1837
1994
  * SPDX-License-Identifier: MIT
1838
1995
  */
1839
1996
  /**
@@ -1846,62 +2003,73 @@ class SiFlexibleDashboardComponent {
1846
2003
  *
1847
2004
  * @defaultValue false
1848
2005
  */
1849
- editable = model(false);
2006
+ editable = model(false, /* @ts-ignore */
2007
+ ...(ngDevMode ? [{ debugName: "editable" }] : /* istanbul ignore next */ []));
1850
2008
  /**
1851
2009
  * Heading for the dashboard.
1852
2010
  */
1853
- heading = input();
2011
+ heading = input(/* @ts-ignore */
2012
+ ...(ngDevMode ? [undefined, { debugName: "heading" }] : /* istanbul ignore next */ []));
1854
2013
  /**
1855
2014
  * Optionally, provide a custom subclass of the SiWidgetCatalogComponent
1856
2015
  * to use your own catalog component.
1857
2016
  */
1858
- widgetCatalogComponent = input();
2017
+ widgetCatalogComponent = input(/* @ts-ignore */
2018
+ ...(ngDevMode ? [undefined, { debugName: "widgetCatalogComponent" }] : /* istanbul ignore next */ []));
1859
2019
  /**
1860
2020
  * Optionally, provide a custom subclass of the {@link SiWidgetInstanceEditorDialogComponent}
1861
2021
  * to use your own implementation.
1862
2022
  */
1863
- widgetInstanceEditorDialogComponent = input();
2023
+ widgetInstanceEditorDialogComponent = input(/* @ts-ignore */
2024
+ ...(ngDevMode ? [undefined, { debugName: "widgetInstanceEditorDialogComponent" }] : /* istanbul ignore next */ []));
1864
2025
  /**
1865
2026
  * Optionally, but it is recommended to include an id for a dashboard.
1866
2027
  * The id is utilized in the persistence calls on the {@link SiWidgetStorage}.
1867
2028
  */
1868
- dashboardId = input();
2029
+ dashboardId = input(/* @ts-ignore */
2030
+ ...(ngDevMode ? [undefined, { debugName: "dashboardId" }] : /* istanbul ignore next */ []));
1869
2031
  /**
1870
2032
  * Sets the available widgets for the widget catalog to the dashboard.
1871
2033
  *
1872
2034
  * @defaultValue [] */
1873
- widgetCatalog = input([]);
2035
+ widgetCatalog = input([], /* @ts-ignore */
2036
+ ...(ngDevMode ? [{ debugName: "widgetCatalog" }] : /* istanbul ignore next */ []));
1874
2037
  /**
1875
2038
  * Option to remove the add widget instance button from the primary toolbar.
1876
2039
  *
1877
2040
  * @defaultValue false
1878
2041
  */
1879
- hideAddWidgetInstanceButton = input(false);
2042
+ hideAddWidgetInstanceButton = input(false, /* @ts-ignore */
2043
+ ...(ngDevMode ? [{ debugName: "hideAddWidgetInstanceButton" }] : /* istanbul ignore next */ []));
1880
2044
  /**
1881
2045
  * Option to hide the dashboard edit button.
1882
2046
  *
1883
2047
  * @defaultValue false
1884
2048
  */
1885
- hideEditButton = input(false);
2049
+ hideEditButton = input(false, /* @ts-ignore */
2050
+ ...(ngDevMode ? [{ debugName: "hideEditButton" }] : /* istanbul ignore next */ []));
1886
2051
  /**
1887
2052
  * Option to display the edit button as a text button instead, only if the window is larger than xs {@link SiResponsiveContainerDirective}.
1888
2053
  *
1889
2054
  * @defaultValue false
1890
2055
  */
1891
- showEditButtonLabel = input(false);
2056
+ showEditButtonLabel = input(false, /* @ts-ignore */
2057
+ ...(ngDevMode ? [{ debugName: "showEditButtonLabel" }] : /* istanbul ignore next */ []));
1892
2058
  /**
1893
2059
  * Option to turn off the loading spinner on save and load operations.
1894
2060
  *
1895
2061
  * @defaultValue false
1896
2062
  */
1897
- hideProgressIndicator = input(false);
2063
+ hideProgressIndicator = input(false, /* @ts-ignore */
2064
+ ...(ngDevMode ? [{ debugName: "hideProgressIndicator" }] : /* istanbul ignore next */ []));
1898
2065
  /**
1899
2066
  * Option to configure a dashboard instance. Default is the optional value from
1900
2067
  * the {@link SI_DASHBOARD_CONFIGURATION}.
1901
2068
  *
1902
2069
  * @defaultValue inject(SI_DASHBOARD_CONFIGURATION)
1903
2070
  */
1904
- config = input(inject(SI_DASHBOARD_CONFIGURATION));
2071
+ config = input(inject(SI_DASHBOARD_CONFIGURATION), /* @ts-ignore */
2072
+ ...(ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []));
1905
2073
  /**
1906
2074
  * Placeholder text for the search input field in the widget catalog.
1907
2075
  *
@@ -1910,7 +2078,20 @@ class SiFlexibleDashboardComponent {
1910
2078
  * t(() => $localize`:@@DASHBOARD.WIDGET_LIBRARY.SEARCH_PLACEHOLDER:Search widget`)
1911
2079
  * ```
1912
2080
  */
1913
- searchPlaceholder = input(t(() => $localize `:@@DASHBOARD.WIDGET_LIBRARY.SEARCH_PLACEHOLDER:Search widget`));
2081
+ searchPlaceholder = input(t(() => $localize `:@@DASHBOARD.WIDGET_LIBRARY.SEARCH_PLACEHOLDER:Search widget`), /* @ts-ignore */
2082
+ ...(ngDevMode ? [{ debugName: "searchPlaceholder" }] : /* istanbul ignore next */ []));
2083
+ /**
2084
+ * Primary action menu items shown in the edit mode of the dashboard.
2085
+ * @defaultValue []
2086
+ */
2087
+ primaryEditActions = input([], /* @ts-ignore */
2088
+ ...(ngDevMode ? [{ debugName: "primaryEditActions" }] : /* istanbul ignore next */ []));
2089
+ /**
2090
+ * Secondary action menu items shown in the edit mode of the dashboard.
2091
+ * @defaultValue []
2092
+ */
2093
+ secondaryEditActions = input([], /* @ts-ignore */
2094
+ ...(ngDevMode ? [{ debugName: "secondaryEditActions" }] : /* istanbul ignore next */ []));
1914
2095
  /**
1915
2096
  * The grid component is the actual container for the widgets.
1916
2097
  */
@@ -1945,13 +2126,14 @@ class SiFlexibleDashboardComponent {
1945
2126
  ]);
1946
2127
  /**
1947
2128
  * The secondary action menu items shown in the edit mode of the dashboard. When all menu items are more than
1948
- * three, they will be places in the secondary menu of the content action bar.
2129
+ * three, they will be placed in the secondary menu of the content action bar.
1949
2130
  */
1950
2131
  secondaryEditActions$ = new BehaviorSubject([]);
1951
2132
  /**
1952
2133
  * @returns True, if the dashboard shows its widgets and not a catalog or and editor.
1953
2134
  */
1954
- isDashboardVisible = computed(() => this.viewState() === 'dashboard');
2135
+ isDashboardVisible = computed(() => this.viewState() === 'dashboard', /* @ts-ignore */
2136
+ ...(ngDevMode ? [{ debugName: "isDashboardVisible" }] : /* istanbul ignore next */ []));
1955
2137
  /**
1956
2138
  * The page title of the dashboard, which is either {@link SiFlexibleDashboardComponent.heading} for the
1957
2139
  * default widget view or `DASHBOARD.WIDGET_EDITOR_DIALOG.TITLE` or 'DASHBOARD.WIDGET_LIBRARY.TITLE' when
@@ -1967,14 +2149,18 @@ class SiFlexibleDashboardComponent {
1967
2149
  default:
1968
2150
  return this.heading();
1969
2151
  }
1970
- });
1971
- viewState = signal('dashboard');
1972
- subscriptions = [];
2152
+ }, /* @ts-ignore */
2153
+ ...(ngDevMode ? [{ debugName: "pageTitle" }] : /* istanbul ignore next */ []));
2154
+ viewState = signal('dashboard', /* @ts-ignore */
2155
+ ...(ngDevMode ? [{ debugName: "viewState" }] : /* istanbul ignore next */ []));
1973
2156
  widgetStorage = inject(SI_WIDGET_STORE);
1974
2157
  hideAddWidgetInstanceButton$ = new BehaviorSubject(this.hideAddWidgetInstanceButton());
1975
2158
  dashboardId$ = new Subject();
1976
- outputRefSubscription = [];
1977
2159
  toolbar = viewChild.required('toolbar');
2160
+ globalEditActions = inject(SI_DASHBOARD_TOOLBAR_ITEMS, { optional: true }) ?? {
2161
+ primary: [],
2162
+ secondary: []
2163
+ };
1978
2164
  ngOnChanges(changes) {
1979
2165
  if (changes.dashboardId) {
1980
2166
  const dashboard = this.dashboard();
@@ -1982,39 +2168,22 @@ class SiFlexibleDashboardComponent {
1982
2168
  dashboard.restore();
1983
2169
  }
1984
2170
  if (this.editable()) {
1985
- this.grid().cancel();
2171
+ this.editable.set(false);
1986
2172
  this.viewState.set('dashboard');
1987
2173
  this.catalogHost().clear();
1988
2174
  }
1989
2175
  this.dashboardId$.next(changes.dashboardId.currentValue);
1990
2176
  this.setupMenuItems();
1991
2177
  }
1992
- if (changes.editable) {
1993
- if (changes.editable.currentValue) {
1994
- this.grid().edit();
1995
- }
1996
- else {
1997
- this.grid().cancel();
1998
- }
1999
- }
2000
2178
  if (changes.hideAddWidgetInstanceButton) {
2001
2179
  this.hideAddWidgetInstanceButton$.next(changes.hideAddWidgetInstanceButton.currentValue);
2002
2180
  }
2003
2181
  }
2004
2182
  ngOnInit() {
2005
2183
  this.setupMenuItems();
2006
- const grid = this.grid();
2007
- this.outputRefSubscription.push(grid.widgetInstanceEdit.subscribe(widgetConfig => {
2008
- this.editWidgetInstance(widgetConfig);
2009
- }));
2010
- this.outputRefSubscription.push(grid.editable.subscribe(editable => {
2011
- this.editable.set(editable);
2012
- }));
2013
2184
  }
2014
2185
  ngOnDestroy() {
2015
2186
  this.dashboardId$.next(undefined);
2016
- this.subscriptions?.forEach(sub => sub.unsubscribe());
2017
- this.outputRefSubscription?.forEach(sub => sub.unsubscribe());
2018
2187
  }
2019
2188
  /**
2020
2189
  * Shows the widget catalog of the dashboard. If a widget instance is expanded
@@ -2026,39 +2195,50 @@ class SiFlexibleDashboardComponent {
2026
2195
  dashboard.restore();
2027
2196
  }
2028
2197
  if (!this.editable()) {
2029
- this.grid().edit();
2198
+ this.editable.set(true);
2030
2199
  }
2031
2200
  this.viewState.set('catalog');
2032
2201
  const componentType = this.widgetCatalogComponent() ?? SiWidgetCatalogComponent;
2033
- const catalogRef = this.catalogHost().createComponent(componentType);
2034
- catalogRef.setInput('searchPlaceholder', this.searchPlaceholder());
2035
- catalogRef.instance.widgetCatalog = this.widgetCatalog();
2036
- const subscription = catalogRef.instance.closed.subscribe(widgetConfig => {
2037
- subscription.unsubscribe();
2038
- this.viewState.set('dashboard');
2039
- this.catalogHost().clear();
2040
- if (widgetConfig) {
2041
- this.grid().addWidgetInstance(widgetConfig);
2042
- }
2202
+ const catalogRef = this.catalogHost().createComponent(componentType, {
2203
+ bindings: [
2204
+ inputBinding('searchPlaceholder', this.searchPlaceholder),
2205
+ outputBinding('closed', widgetConfig => {
2206
+ this.viewState.set('dashboard');
2207
+ this.catalogHost().clear();
2208
+ if (widgetConfig) {
2209
+ this.grid().addWidgetInstance(widgetConfig);
2210
+ }
2211
+ })
2212
+ ]
2043
2213
  });
2214
+ catalogRef.instance.widgetCatalog = this.widgetCatalog();
2044
2215
  }
2045
2216
  onModified(event) {
2046
2217
  this.isModified.emit(event);
2047
2218
  this.toolbar().disableSaveButton.set(!event);
2048
2219
  }
2049
2220
  setupMenuItems() {
2050
- const primaryMenuItems = this.widgetStorage.getToolbarMenuItems
2051
- ? this.widgetStorage.getToolbarMenuItems(this.dashboardId()).primary
2052
- : of([]);
2221
+ const primaryEditActions = [...this.primaryEditActions(), ...this.globalEditActions.primary];
2222
+ const secondaryEditActions = [
2223
+ ...this.secondaryEditActions(),
2224
+ ...this.globalEditActions.secondary
2225
+ ];
2226
+ const primaryMenuItems = primaryEditActions.length
2227
+ ? of(primaryEditActions)
2228
+ : this.widgetStorage.getToolbarMenuItems
2229
+ ? this.widgetStorage.getToolbarMenuItems(this.dashboardId()).primary
2230
+ : of([]);
2053
2231
  combineLatest([primaryMenuItems, this.hideAddWidgetInstanceButton$])
2054
2232
  .pipe(takeUntil(this.dashboardId$))
2055
2233
  .subscribe(([items, hideAddButton]) => this.setupPrimaryMenuItems(items, hideAddButton));
2056
- const secondaryMenuItems = this.widgetStorage.getToolbarMenuItems
2057
- ? this.widgetStorage.getToolbarMenuItems(this.dashboardId()).secondary
2058
- : undefined;
2234
+ const secondaryMenuItems = secondaryEditActions.length
2235
+ ? of(secondaryEditActions)
2236
+ : this.widgetStorage.getToolbarMenuItems
2237
+ ? this.widgetStorage.getToolbarMenuItems(this.dashboardId()).secondary
2238
+ : undefined;
2059
2239
  if (secondaryMenuItems) {
2060
2240
  secondaryMenuItems
2061
- .pipe(takeUntil(this.dashboardId$), map(items => items.map(item => this.proxyMenuItemAction(item))))
2241
+ .pipe(takeUntil(this.dashboardId$))
2062
2242
  .subscribe(items => this.secondaryEditActions$.next(items));
2063
2243
  }
2064
2244
  }
@@ -2066,7 +2246,7 @@ class SiFlexibleDashboardComponent {
2066
2246
  const next = hideAddWidgetInstanceButton
2067
2247
  ? []
2068
2248
  : [this.addWidgetInstanceAction];
2069
- next.push(...items.map(item => this.proxyMenuItemAction(item)));
2249
+ next.push(...items);
2070
2250
  this.primaryEditActions$.next(next);
2071
2251
  }
2072
2252
  editWidgetInstance(widgetConfig) {
@@ -2078,82 +2258,121 @@ class SiFlexibleDashboardComponent {
2078
2258
  const widgetInstanceEditorDialogComponent = this.widgetInstanceEditorDialogComponent();
2079
2259
  const componentType = widgetInstanceEditorDialogComponent ?? SiWidgetInstanceEditorDialogComponent;
2080
2260
  this.viewState.set('editor');
2081
- const catalogRef = this.catalogHost().createComponent(componentType);
2082
- catalogRef.setInput('widgetConfig', widgetConfig);
2083
- catalogRef.setInput('widget', widget);
2084
- const subscription = catalogRef.instance.closed.subscribe(editedWidgetConfig => {
2085
- subscription.unsubscribe();
2086
- this.viewState.set('dashboard');
2087
- this.catalogHost().clear();
2088
- if (editedWidgetConfig) {
2089
- this.grid().updateWidgetInstance(editedWidgetConfig);
2090
- }
2261
+ this.catalogHost().createComponent(componentType, {
2262
+ bindings: [
2263
+ inputBinding('widgetConfig', () => widgetConfig),
2264
+ inputBinding('widget', () => widget),
2265
+ outputBinding('closed', editedWidgetConfig => {
2266
+ this.viewState.set('dashboard');
2267
+ this.catalogHost().clear();
2268
+ if (editedWidgetConfig) {
2269
+ this.grid().updateWidgetInstance(editedWidgetConfig);
2270
+ }
2271
+ })
2272
+ ]
2091
2273
  });
2092
2274
  }
2093
- proxyMenuItemAction(item) {
2094
- if ('action' in item) {
2095
- if (!item.action || item.action instanceof String) {
2096
- return item;
2097
- }
2098
- else {
2099
- const realAction = item.action;
2100
- item.action = () => {
2101
- realAction(this.grid());
2102
- };
2103
- }
2104
- }
2105
- return item;
2106
- }
2107
2275
  getWidget(id) {
2108
2276
  return this.widgetCatalog().find(widget => widget.id === id);
2109
2277
  }
2110
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiFlexibleDashboardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2111
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.0.6", type: SiFlexibleDashboardComponent, isStandalone: true, selector: "si-flexible-dashboard", inputs: { editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null }, widgetCatalogComponent: { classPropertyName: "widgetCatalogComponent", publicName: "widgetCatalogComponent", isSignal: true, isRequired: false, transformFunction: null }, widgetInstanceEditorDialogComponent: { classPropertyName: "widgetInstanceEditorDialogComponent", publicName: "widgetInstanceEditorDialogComponent", isSignal: true, isRequired: false, transformFunction: null }, dashboardId: { classPropertyName: "dashboardId", publicName: "dashboardId", isSignal: true, isRequired: false, transformFunction: null }, widgetCatalog: { classPropertyName: "widgetCatalog", publicName: "widgetCatalog", isSignal: true, isRequired: false, transformFunction: null }, hideAddWidgetInstanceButton: { classPropertyName: "hideAddWidgetInstanceButton", publicName: "hideAddWidgetInstanceButton", isSignal: true, isRequired: false, transformFunction: null }, hideEditButton: { classPropertyName: "hideEditButton", publicName: "hideEditButton", isSignal: true, isRequired: false, transformFunction: null }, showEditButtonLabel: { classPropertyName: "showEditButtonLabel", publicName: "showEditButtonLabel", isSignal: true, isRequired: false, transformFunction: null }, hideProgressIndicator: { classPropertyName: "hideProgressIndicator", publicName: "hideProgressIndicator", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { editable: "editableChange", isModified: "isModified" }, viewQueries: [{ propertyName: "grid", first: true, predicate: ["grid"], descendants: true, isSignal: true }, { propertyName: "dashboard", first: true, predicate: ["dashboard"], descendants: true, isSignal: true }, { propertyName: "catalogHost", first: true, predicate: ["catalogHost"], descendants: true, read: ViewContainerRef, isSignal: true }, { propertyName: "toolbar", first: true, predicate: ["toolbar"], descendants: true, isSignal: true }], usesOnChanges: true, ngImport: i0, template: "<si-dashboard #dashboard [heading]=\"pageTitle()\" [sticky]=\"true\">\n <si-dashboard-toolbar\n #toolbar\n menubar\n [class.d-none]=\"!isDashboardVisible()\"\n [primaryEditActions]=\"(primaryEditActions$ | async) ?? []\"\n [secondaryEditActions]=\"(secondaryEditActions$ | async) ?? []\"\n [editable]=\"editable()\"\n [disabled]=\"(grid.isLoading | async) ?? false\"\n [hideEditButton]=\"hideEditButton()\"\n [showEditButtonLabel]=\"showEditButtonLabel()\"\n (editableChange)=\"grid.edit()\"\n (save)=\"grid.save()\"\n (cancel)=\"grid.cancel()\"\n >\n <ng-content select=\"[filters-slot]\" filters-slot />\n </si-dashboard-toolbar>\n\n <div dashboard class=\"d-contents\">\n <si-grid\n #grid\n [class.d-none]=\"!isDashboardVisible()\"\n [gridConfig]=\"config()?.grid\"\n [dashboardId]=\"dashboardId()\"\n [widgetCatalog]=\"widgetCatalog()\"\n [hideProgressIndicator]=\"hideProgressIndicator()\"\n [widgetInstanceEditorDialogComponent]=\"widgetInstanceEditorDialogComponent()\"\n [emitWidgetInstanceEditEvents]=\"true\"\n (isModified)=\"onModified($event)\"\n >\n <ng-content select=\"[empty-state]\" empty-state />\n </si-grid>\n <ng-container #catalogHost />\n </div>\n</si-dashboard>\n", styles: [":host{display:flex;flex:1 1 0;flex-direction:column}\n"], dependencies: [{ kind: "component", type: SiDashboardComponent, selector: "si-dashboard", inputs: ["heading", "enableExpandInteractions", "sticky", "hideMenubar"] }, { kind: "component", type: SiDashboardToolbarComponent, selector: "si-dashboard-toolbar", inputs: ["primaryEditActions", "secondaryEditActions", "disableSaveButton", "disabled", "editable", "hideEditButton", "showEditButtonLabel"], outputs: ["disableSaveButtonChange", "editableChange", "save", "cancel"] }, { kind: "component", type: SiGridComponent, selector: "si-grid", inputs: ["gridConfig", "editable", "dashboardId", "widgetCatalog", "emitWidgetInstanceEditEvents", "hideProgressIndicator", "widgetInstanceEditorDialogComponent"], outputs: ["editableChange", "isModified", "widgetInstanceEdit"] }, { kind: "pipe", type: AsyncPipe, name: "async" }] });
2278
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: SiFlexibleDashboardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2279
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.0.5", type: SiFlexibleDashboardComponent, isStandalone: true, selector: "si-flexible-dashboard", inputs: { editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null }, widgetCatalogComponent: { classPropertyName: "widgetCatalogComponent", publicName: "widgetCatalogComponent", isSignal: true, isRequired: false, transformFunction: null }, widgetInstanceEditorDialogComponent: { classPropertyName: "widgetInstanceEditorDialogComponent", publicName: "widgetInstanceEditorDialogComponent", isSignal: true, isRequired: false, transformFunction: null }, dashboardId: { classPropertyName: "dashboardId", publicName: "dashboardId", isSignal: true, isRequired: false, transformFunction: null }, widgetCatalog: { classPropertyName: "widgetCatalog", publicName: "widgetCatalog", isSignal: true, isRequired: false, transformFunction: null }, hideAddWidgetInstanceButton: { classPropertyName: "hideAddWidgetInstanceButton", publicName: "hideAddWidgetInstanceButton", isSignal: true, isRequired: false, transformFunction: null }, hideEditButton: { classPropertyName: "hideEditButton", publicName: "hideEditButton", isSignal: true, isRequired: false, transformFunction: null }, showEditButtonLabel: { classPropertyName: "showEditButtonLabel", publicName: "showEditButtonLabel", isSignal: true, isRequired: false, transformFunction: null }, hideProgressIndicator: { classPropertyName: "hideProgressIndicator", publicName: "hideProgressIndicator", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, primaryEditActions: { classPropertyName: "primaryEditActions", publicName: "primaryEditActions", isSignal: true, isRequired: false, transformFunction: null }, secondaryEditActions: { classPropertyName: "secondaryEditActions", publicName: "secondaryEditActions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { editable: "editableChange", isModified: "isModified" }, viewQueries: [{ propertyName: "grid", first: true, predicate: ["grid"], descendants: true, isSignal: true }, { propertyName: "dashboard", first: true, predicate: ["dashboard"], descendants: true, isSignal: true }, { propertyName: "catalogHost", first: true, predicate: ["catalogHost"], descendants: true, read: ViewContainerRef, isSignal: true }, { propertyName: "toolbar", first: true, predicate: ["toolbar"], descendants: true, isSignal: true }], usesOnChanges: true, ngImport: i0, template: "<si-dashboard #dashboard [heading]=\"pageTitle()\" [sticky]=\"true\">\n <si-dashboard-toolbar\n #toolbar\n menubar\n [class.d-none]=\"!isDashboardVisible()\"\n [primaryEditActions]=\"(primaryEditActions$ | async) ?? []\"\n [secondaryEditActions]=\"(secondaryEditActions$ | async) ?? []\"\n [disabled]=\"(grid.isLoading | async) ?? false\"\n [hideEditButton]=\"hideEditButton()\"\n [showEditButtonLabel]=\"showEditButtonLabel()\"\n [grid]=\"grid\"\n [(editable)]=\"editable\"\n (save)=\"grid.save()\"\n >\n <ng-content select=\"[filters-slot]\" filters-slot />\n </si-dashboard-toolbar>\n\n <div dashboard class=\"d-contents\">\n <si-grid\n #grid\n [class.d-none]=\"!isDashboardVisible()\"\n [gridConfig]=\"config()?.grid\"\n [dashboardId]=\"dashboardId()\"\n [widgetCatalog]=\"widgetCatalog()\"\n [hideProgressIndicator]=\"hideProgressIndicator()\"\n [widgetInstanceEditorDialogComponent]=\"widgetInstanceEditorDialogComponent()\"\n [emitWidgetInstanceEditEvents]=\"true\"\n [(editable)]=\"editable\"\n (isModified)=\"onModified($event)\"\n (widgetInstanceEdit)=\"editWidgetInstance($event)\"\n >\n <ng-content select=\"[empty-state]\" empty-state />\n </si-grid>\n <ng-container #catalogHost />\n </div>\n</si-dashboard>\n", styles: [":host{display:flex;flex:1 1 0;flex-direction:column}\n"], dependencies: [{ kind: "component", type: SiDashboardComponent, selector: "si-dashboard", inputs: ["heading", "enableExpandInteractions", "sticky", "hideMenubar"] }, { kind: "component", type: SiDashboardToolbarComponent, selector: "si-dashboard-toolbar", inputs: ["primaryEditActions", "secondaryEditActions", "disableSaveButton", "disabled", "editable", "hideEditButton", "showEditButtonLabel", "grid"], outputs: ["disableSaveButtonChange", "editableChange", "save"] }, { kind: "component", type: SiGridComponent, selector: "si-grid", inputs: ["gridConfig", "editable", "dashboardId", "widgetCatalog", "emitWidgetInstanceEditEvents", "hideProgressIndicator", "widgetInstanceEditorDialogComponent"], outputs: ["editableChange", "isModified", "widgetInstanceEdit"] }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.Eager });
2112
2280
  }
2113
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiFlexibleDashboardComponent, decorators: [{
2281
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: SiFlexibleDashboardComponent, decorators: [{
2114
2282
  type: Component,
2115
- args: [{ selector: 'si-flexible-dashboard', imports: [SiDashboardComponent, SiDashboardToolbarComponent, SiGridComponent, AsyncPipe], template: "<si-dashboard #dashboard [heading]=\"pageTitle()\" [sticky]=\"true\">\n <si-dashboard-toolbar\n #toolbar\n menubar\n [class.d-none]=\"!isDashboardVisible()\"\n [primaryEditActions]=\"(primaryEditActions$ | async) ?? []\"\n [secondaryEditActions]=\"(secondaryEditActions$ | async) ?? []\"\n [editable]=\"editable()\"\n [disabled]=\"(grid.isLoading | async) ?? false\"\n [hideEditButton]=\"hideEditButton()\"\n [showEditButtonLabel]=\"showEditButtonLabel()\"\n (editableChange)=\"grid.edit()\"\n (save)=\"grid.save()\"\n (cancel)=\"grid.cancel()\"\n >\n <ng-content select=\"[filters-slot]\" filters-slot />\n </si-dashboard-toolbar>\n\n <div dashboard class=\"d-contents\">\n <si-grid\n #grid\n [class.d-none]=\"!isDashboardVisible()\"\n [gridConfig]=\"config()?.grid\"\n [dashboardId]=\"dashboardId()\"\n [widgetCatalog]=\"widgetCatalog()\"\n [hideProgressIndicator]=\"hideProgressIndicator()\"\n [widgetInstanceEditorDialogComponent]=\"widgetInstanceEditorDialogComponent()\"\n [emitWidgetInstanceEditEvents]=\"true\"\n (isModified)=\"onModified($event)\"\n >\n <ng-content select=\"[empty-state]\" empty-state />\n </si-grid>\n <ng-container #catalogHost />\n </div>\n</si-dashboard>\n", styles: [":host{display:flex;flex:1 1 0;flex-direction:column}\n"] }]
2116
- }] });
2283
+ args: [{ selector: 'si-flexible-dashboard', imports: [SiDashboardComponent, SiDashboardToolbarComponent, SiGridComponent, AsyncPipe], changeDetection: ChangeDetectionStrategy.Eager, template: "<si-dashboard #dashboard [heading]=\"pageTitle()\" [sticky]=\"true\">\n <si-dashboard-toolbar\n #toolbar\n menubar\n [class.d-none]=\"!isDashboardVisible()\"\n [primaryEditActions]=\"(primaryEditActions$ | async) ?? []\"\n [secondaryEditActions]=\"(secondaryEditActions$ | async) ?? []\"\n [disabled]=\"(grid.isLoading | async) ?? false\"\n [hideEditButton]=\"hideEditButton()\"\n [showEditButtonLabel]=\"showEditButtonLabel()\"\n [grid]=\"grid\"\n [(editable)]=\"editable\"\n (save)=\"grid.save()\"\n >\n <ng-content select=\"[filters-slot]\" filters-slot />\n </si-dashboard-toolbar>\n\n <div dashboard class=\"d-contents\">\n <si-grid\n #grid\n [class.d-none]=\"!isDashboardVisible()\"\n [gridConfig]=\"config()?.grid\"\n [dashboardId]=\"dashboardId()\"\n [widgetCatalog]=\"widgetCatalog()\"\n [hideProgressIndicator]=\"hideProgressIndicator()\"\n [widgetInstanceEditorDialogComponent]=\"widgetInstanceEditorDialogComponent()\"\n [emitWidgetInstanceEditEvents]=\"true\"\n [(editable)]=\"editable\"\n (isModified)=\"onModified($event)\"\n (widgetInstanceEdit)=\"editWidgetInstance($event)\"\n >\n <ng-content select=\"[empty-state]\" empty-state />\n </si-grid>\n <ng-container #catalogHost />\n </div>\n</si-dashboard>\n", styles: [":host{display:flex;flex:1 1 0;flex-direction:column}\n"] }]
2284
+ }], propDecorators: { editable: [{ type: i0.Input, args: [{ isSignal: true, alias: "editable", required: false }] }, { type: i0.Output, args: ["editableChange"] }], heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: false }] }], widgetCatalogComponent: [{ type: i0.Input, args: [{ isSignal: true, alias: "widgetCatalogComponent", required: false }] }], widgetInstanceEditorDialogComponent: [{ type: i0.Input, args: [{ isSignal: true, alias: "widgetInstanceEditorDialogComponent", required: false }] }], dashboardId: [{ type: i0.Input, args: [{ isSignal: true, alias: "dashboardId", required: false }] }], widgetCatalog: [{ type: i0.Input, args: [{ isSignal: true, alias: "widgetCatalog", required: false }] }], hideAddWidgetInstanceButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideAddWidgetInstanceButton", required: false }] }], hideEditButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideEditButton", required: false }] }], showEditButtonLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "showEditButtonLabel", required: false }] }], hideProgressIndicator: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideProgressIndicator", required: false }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], primaryEditActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "primaryEditActions", required: false }] }], secondaryEditActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "secondaryEditActions", required: false }] }], grid: [{ type: i0.ViewChild, args: ['grid', { isSignal: true }] }], dashboard: [{ type: i0.ViewChild, args: ['dashboard', { isSignal: true }] }], catalogHost: [{ type: i0.ViewChild, args: ['catalogHost', { ...{ read: ViewContainerRef }, isSignal: true }] }], isModified: [{ type: i0.Output, args: ["isModified"] }], toolbar: [{ type: i0.ViewChild, args: ['toolbar', { isSignal: true }] }] } });
2117
2285
 
2118
2286
  /**
2119
- * Copyright (c) Siemens 2016 - 2025
2287
+ * Copyright (c) Siemens 2016 - 2026
2288
+ * SPDX-License-Identifier: MIT
2289
+ */
2290
+ /**
2291
+ * A presentational (dumb) component that renders a single widget instance from a
2292
+ * {@link WidgetConfig} using the provided {@link Widget} definition. It delegates
2293
+ * the actual rendering to the {@link SiWidgetHostComponent}.
2294
+ *
2295
+ * **Intended Usage**
2296
+ *
2297
+ * This component renders an individual widget outside of a dashboard or grid,
2298
+ * without requiring manual host wiring. It is a self-contained widget cell that
2299
+ * can be placed anywhere a single widget needs to be shown, for example in kiosk
2300
+ * mode or within a custom layout such as a carousel.
2301
+ *
2302
+ * **Example**
2303
+ *
2304
+ * ```html
2305
+ * <!-- Render a single widget instance -->
2306
+ * <si-widget-renderer [widgetConfig]="widgetConfig" [widget]="widget" />
2307
+ * ```
2308
+ */
2309
+ class SiWidgetRendererComponent {
2310
+ /** The configuration of the widget instance to render. */
2311
+ widgetConfig = input.required(/* @ts-ignore */
2312
+ ...(ngDevMode ? [{ debugName: "widgetConfig" }] : /* istanbul ignore next */ []));
2313
+ /**
2314
+ * The {@link Widget} definition used to render the given {@link widgetConfig}.
2315
+ * It must match the config's `widgetId`.
2316
+ */
2317
+ widget = input.required(/* @ts-ignore */
2318
+ ...(ngDevMode ? [{ debugName: "widget" }] : /* istanbul ignore next */ []));
2319
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: SiWidgetRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2320
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.5", type: SiWidgetRendererComponent, isStandalone: true, selector: "si-widget-renderer", inputs: { widgetConfig: { classPropertyName: "widgetConfig", publicName: "widgetConfig", isSignal: true, isRequired: true, transformFunction: null }, widget: { classPropertyName: "widget", publicName: "widget", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<si-widget-host\n class=\"h-100\"\n [widgetConfig]=\"widgetConfig()\"\n [componentFactory]=\"widget().componentFactory\"\n/>\n", styles: ["si-widget-host ::ng-deep .grid-stack-item-content{flex:1 1 auto;min-inline-size:0;min-block-size:0;padding:0!important}\n"], dependencies: [{ kind: "component", type: SiWidgetHostComponent, selector: "si-widget-host", inputs: ["widgetConfig", "grid", "componentFactory", "editable"], outputs: ["remove", "edit", "gridEvent"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2321
+ }
2322
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: SiWidgetRendererComponent, decorators: [{
2323
+ type: Component,
2324
+ args: [{ selector: 'si-widget-renderer', imports: [SiWidgetHostComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<si-widget-host\n class=\"h-100\"\n [widgetConfig]=\"widgetConfig()\"\n [componentFactory]=\"widget().componentFactory\"\n/>\n", styles: ["si-widget-host ::ng-deep .grid-stack-item-content{flex:1 1 auto;min-inline-size:0;min-block-size:0;padding:0!important}\n"] }]
2325
+ }], propDecorators: { widgetConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "widgetConfig", required: true }] }], widget: [{ type: i0.Input, args: [{ isSignal: true, alias: "widget", required: true }] }] } });
2326
+
2327
+ /**
2328
+ * Formats an error message for failed module loading.
2329
+ */
2330
+ const formatLoadingError = (factory, rejection) => rejection
2331
+ ? `Loading widget module ${factory.exposedModule} failed with ${String(rejection)}`
2332
+ : `Loading widget module ${factory.exposedModule} failed`;
2333
+ /**
2334
+ * Handles the result of a federated module loading promise.
2335
+ * This is the main utility function that encapsulates the common loading pattern
2336
+ * used across module-federation, native-federation, and mf-bridge loaders.
2337
+ *
2338
+ * @param options - Configuration options for loading and creating the component
2339
+ * @returns Observable that emits the ComponentRef once the component is created
2340
+ * @internal
2341
+ */
2342
+ const handleFederatedModuleLoad = ({ loadPromise, factory, componentName, host, injector, environmentInjector }) => {
2343
+ const result = new Subject();
2344
+ loadPromise.then(module => {
2345
+ if (module) {
2346
+ const componentType = module[factory[componentName]];
2347
+ const widgetInstanceRef = host.createComponent(componentType, {
2348
+ injector,
2349
+ environmentInjector
2350
+ });
2351
+ result.next(widgetInstanceRef);
2352
+ }
2353
+ result.complete();
2354
+ }, rejection => result.error(formatLoadingError(factory, rejection)));
2355
+ return result;
2356
+ };
2357
+
2358
+ /**
2359
+ * Copyright (c) Siemens 2016 - 2026
2120
2360
  * SPDX-License-Identifier: MIT
2121
2361
  */
2122
2362
  class SiDashboardsNgModule {
2123
- /**
2124
- * @deprecated The module configuration `forRoot` is not needed any more. You can use the injection tokens
2125
- * `SI_DASHBOARD_CONFIGURATION` and `SI_WIDGET_STORE` direct in your app configuration. We migrated to standalone
2126
- * components already and will delete this module definition later.
2127
- */
2128
- static forRoot({ config, dashboardApi }) {
2129
- return {
2130
- ngModule: SiDashboardsNgModule,
2131
- providers: [
2132
- {
2133
- provide: SI_DASHBOARD_CONFIGURATION,
2134
- useValue: config
2135
- },
2136
- dashboardApi ?? {
2137
- provide: SiWidgetStorage,
2138
- useClass: SiDefaultWidgetStorage
2139
- },
2140
- dashboardApi ? { provide: SI_WIDGET_STORE, useExisting: SiWidgetStorage } : []
2141
- ]
2142
- };
2143
- }
2144
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiDashboardsNgModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
2145
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.0.6", ngImport: i0, type: SiDashboardsNgModule, imports: [SiFlexibleDashboardComponent,
2363
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: SiDashboardsNgModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
2364
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.5", ngImport: i0, type: SiDashboardsNgModule, imports: [SiFlexibleDashboardComponent,
2146
2365
  SiGridComponent,
2147
2366
  SiWidgetCatalogComponent,
2148
2367
  SiWidgetInstanceEditorDialogComponent], exports: [SiFlexibleDashboardComponent,
2149
2368
  SiGridComponent,
2150
2369
  SiWidgetCatalogComponent,
2151
2370
  SiWidgetInstanceEditorDialogComponent] });
2152
- static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiDashboardsNgModule, imports: [SiFlexibleDashboardComponent,
2153
- SiWidgetCatalogComponent,
2154
- SiWidgetInstanceEditorDialogComponent] });
2371
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: SiDashboardsNgModule, imports: [SiFlexibleDashboardComponent,
2372
+ SiGridComponent,
2373
+ SiWidgetCatalogComponent] });
2155
2374
  }
2156
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiDashboardsNgModule, decorators: [{
2375
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: SiDashboardsNgModule, decorators: [{
2157
2376
  type: NgModule,
2158
2377
  args: [{
2159
2378
  imports: [
@@ -2170,9 +2389,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImpor
2170
2389
  ]
2171
2390
  }]
2172
2391
  }] });
2392
+ /**
2393
+ * @deprecated Use {@link SiDashboardsNgModule} instead. The `Simpl` prefix is deprecated and will be removed in v51.
2394
+ */
2395
+ // eslint-disable-next-line @typescript-eslint/naming-convention
2396
+ const SimplDashboardsNgModule = SiDashboardsNgModule;
2173
2397
 
2174
2398
  /**
2175
- * Copyright (c) Siemens 2016 - 2025
2399
+ * Copyright (c) Siemens 2016 - 2026
2176
2400
  * SPDX-License-Identifier: MIT
2177
2401
  */
2178
2402
 
@@ -2180,5 +2404,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImpor
2180
2404
  * Generated bundle index. Do not edit.
2181
2405
  */
2182
2406
 
2183
- export { CONFIG_TOKEN, DEFAULT_GRIDSTACK_OPTIONS, DEFAULT_WIDGET_STORAGE_TOKEN, NEW_WIDGET_PREFIX, SI_DASHBOARD_CONFIGURATION, SI_WIDGET_STORE, STORAGE_KEY, SiDashboardsNgModule, SiDefaultWidgetStorage, SiFlexibleDashboardComponent, SiGridComponent, SiWidgetCatalogComponent, SiWidgetInstanceEditorDialogComponent, SiWidgetStorage, SiDashboardsNgModule as SimplDashboardsNgModule, createWidgetConfig, setupWidgetEditor, setupWidgetInstance, widgetFactoryRegistry };
2407
+ export { DEFAULT_GRIDSTACK_OPTIONS, DEFAULT_WIDGET_STORAGE_TOKEN, SI_DASHBOARD_CONFIGURATION, SI_DASHBOARD_TOOLBAR_ITEMS, SI_WIDGET_ID_PROVIDER, SI_WIDGET_STORE, STORAGE_KEY, SiDashboardsNgModule, SiDefaultWidgetStorage, SiFlexibleDashboardComponent, SiGridComponent, SiWidgetCatalogComponent, SiWidgetDefaultIdProvider, SiWidgetIdProvider, SiWidgetInstanceEditorDialogComponent, SiWidgetRendererComponent, SiWidgetStorage, SimplDashboardsNgModule, createWidgetConfig, handleFederatedModuleLoad, provideDashboardToolbarItems, setupWidgetEditor, setupWidgetInstance, widgetFactoryRegistry };
2184
2408
  //# sourceMappingURL=spike-rabbit-dashboards-ng.mjs.map