@scion/workbench 21.0.0-beta.4 → 21.0.0-beta.6

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.
@@ -6395,7 +6395,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImpor
6395
6395
  */
6396
6396
  function trackFocus(target, workbenchElement) {
6397
6397
  const focusMonitor = inject(ɵWorkbenchFocusMonitor);
6398
- const subscription = toObservable(focusMonitor.activeElement)
6398
+ toObservable(focusMonitor.activeElement)
6399
6399
  .pipe(startWith(focusMonitor.activeElement()), // Immediately subscribe to `focusin` events, required when the DOM element is focused right after invocation.
6400
6400
  switchMap(activeWorkbenchElement => activeWorkbenchElement === workbenchElement ? EMPTY : merge(fromEvent(target, 'focusin', { once: true }), fromEvent(target, 'sci-microfrontend-focusin', { once: true }))), finalize(() => requestAnimationFrame(() => focusMonitor.unsetActiveElement(workbenchElement))), // Asynchronously unset the active workbench element to prevent a `null` focus during destruction until the next element gains focus.
6401
6401
  takeUntilDestroyed())
@@ -6404,7 +6404,6 @@ function trackFocus(target, workbenchElement) {
6404
6404
  });
6405
6405
  return {
6406
6406
  unsetActiveElement: () => focusMonitor.unsetActiveElement(workbenchElement),
6407
- destroy: () => subscription.unsubscribe(),
6408
6407
  };
6409
6408
  }
6410
6409
  class ɵWorkbenchFocusMonitor {
@@ -6426,7 +6425,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImpor
6426
6425
  }] });
6427
6426
 
6428
6427
  /*
6429
- * Copyright (c) 2018-2023 Swiss Federal Railways
6428
+ * Copyright (c) 2018-2026 Swiss Federal Railways
6430
6429
  *
6431
6430
  * This program and the accompanying materials are made
6432
6431
  * available under the terms of the Eclipse Public License 2.0
@@ -6854,7 +6853,7 @@ class WbComponentPortal {
6854
6853
  this.detach();
6855
6854
  }
6856
6855
  this._viewContainerRef.set(viewContainerRef);
6857
- this._componentRef.update(componentRef => componentRef ?? createPortalComponent(this._componentType, { providers: this._options?.providers, injector: viewContainerRef.injector }));
6856
+ this._componentRef.update(componentRef => componentRef ?? createPortalComponent(this._componentType, { providers: this._options?.providers, injector: this._options?.injector ?? viewContainerRef.injector }));
6858
6857
  this._logger.debug(() => 'Attaching portal', LoggerNames.LIFECYCLE, this._options?.debugName ?? this._componentType.name);
6859
6858
  this._componentRef().changeDetectorRef.reattach();
6860
6859
  this._viewContainerRef().insert(this._componentRef().hostView);
@@ -8330,7 +8329,7 @@ class MainAreaPartComponent {
8330
8329
  });
8331
8330
  }
8332
8331
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: MainAreaPartComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8333
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.1", type: MainAreaPartComponent, isStandalone: true, selector: "wb-part[data-partid=\"part.main-area\"]", host: { properties: { "attr.data-grid": "dasherize(part.gridName())", "attr.data-active": "part.active() ? '' : null" } }, ngImport: i0, template: "@if (mainAreaGrid().root.visible) {\n <wb-grid [grid]=\"mainAreaGrid()\"\n [gridDropZone]=\"{\n dropRegionSize: 100,\n dropPlaceholderSize: 100,\n dropZoneAttributes: {\n 'data-grid': 'main-area',\n 'data-partid': part.id,\n }\n }\"\n [attr.data-grid]=\"'main-area'\"\n class=\"e2e-content\"/>\n} @else {\n <div wbViewDropZone\n [wbViewDropZoneRegions]=\"canDrop() && {center: true, north: false, south: false, west: false, east: false}\"\n [wbViewDropZoneAttributes]=\"{\n 'data-desktop': '',\n 'data-partid': part.id,\n }\"\n (wbViewDropZoneDrop)=\"onDesktopViewDrop($event)\"\n class=\"desktop e2e-part-content e2e-content\">\n @if (part.navigation()) {\n <ng-container *wbPortalOutlet=\"part.slot.portal; destroyOnDetach: false\"/>\n } @else {\n <ng-container *wbPortalOutlet=\"desktop.slot.portal; destroyOnDetach: false\"/>\n }\n </div>\n}\n", styles: [":host{display:flex;flex-direction:column}:host>wb-grid{flex:auto}:host>div.desktop{flex:auto;display:grid;position:relative}\n"], dependencies: [{ kind: "component", type: GridComponent, selector: "wb-grid", inputs: ["grid", "gridDropZone"] }, { kind: "directive", type: ViewDropZoneDirective, selector: "[wbViewDropZone]", inputs: ["wbViewDropZoneRegions", "wbViewDropZoneAttributes", "wbViewDropZoneRegionSize", "wbViewDropZonePlaceholderSize"], outputs: ["wbViewDropZoneDrop"] }, { kind: "directive", type: WorkbenchPortalOutletDirective, selector: "ng-template[wbPortalOutlet]", inputs: ["wbPortalOutlet", "wbPortalOutletDestroyOnDetach"] }] });
8332
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.1", type: MainAreaPartComponent, isStandalone: true, selector: "wb-part[data-partid=\"part.main-area\"]", host: { properties: { "attr.data-grid": "dasherize(part.gridName())", "attr.data-active": "part.active() ? '' : null" } }, ngImport: i0, template: "@if (mainAreaGrid().root.visible) {\n <wb-grid [grid]=\"mainAreaGrid()\"\n [gridDropZone]=\"{\n dropRegionSize: 100,\n dropPlaceholderSize: 100,\n dropZoneAttributes: {\n 'data-grid': 'main-area',\n 'data-partid': part.id,\n }\n }\"\n [attr.data-grid]=\"'main-area'\"\n class=\"e2e-slot\"/>\n} @else {\n <div wbViewDropZone\n [wbViewDropZoneRegions]=\"canDrop() && {center: true, north: false, south: false, west: false, east: false}\"\n [wbViewDropZoneAttributes]=\"{\n 'data-desktop': '',\n 'data-partid': part.id,\n }\"\n (wbViewDropZoneDrop)=\"onDesktopViewDrop($event)\"\n class=\"desktop e2e-part-slot e2e-slot\">\n @if (part.navigation()) {\n <ng-container *wbPortalOutlet=\"part.slot.portal; destroyOnDetach: false\"/>\n } @else {\n <ng-container *wbPortalOutlet=\"desktop.slot.portal; destroyOnDetach: false\"/>\n }\n </div>\n}\n", styles: [":host{display:flex;flex-direction:column}:host>wb-grid{flex:auto}:host>div.desktop{flex:auto;display:grid;position:relative}\n"], dependencies: [{ kind: "component", type: GridComponent, selector: "wb-grid", inputs: ["grid", "gridDropZone"] }, { kind: "directive", type: ViewDropZoneDirective, selector: "[wbViewDropZone]", inputs: ["wbViewDropZoneRegions", "wbViewDropZoneAttributes", "wbViewDropZoneRegionSize", "wbViewDropZonePlaceholderSize"], outputs: ["wbViewDropZoneDrop"] }, { kind: "directive", type: WorkbenchPortalOutletDirective, selector: "ng-template[wbPortalOutlet]", inputs: ["wbPortalOutlet", "wbPortalOutletDestroyOnDetach"] }] });
8334
8333
  }
8335
8334
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: MainAreaPartComponent, decorators: [{
8336
8335
  type: Component,
@@ -8341,7 +8340,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImpor
8341
8340
  ], host: {
8342
8341
  '[attr.data-grid]': 'dasherize(part.gridName())',
8343
8342
  '[attr.data-active]': `part.active() ? '' : null`,
8344
- }, template: "@if (mainAreaGrid().root.visible) {\n <wb-grid [grid]=\"mainAreaGrid()\"\n [gridDropZone]=\"{\n dropRegionSize: 100,\n dropPlaceholderSize: 100,\n dropZoneAttributes: {\n 'data-grid': 'main-area',\n 'data-partid': part.id,\n }\n }\"\n [attr.data-grid]=\"'main-area'\"\n class=\"e2e-content\"/>\n} @else {\n <div wbViewDropZone\n [wbViewDropZoneRegions]=\"canDrop() && {center: true, north: false, south: false, west: false, east: false}\"\n [wbViewDropZoneAttributes]=\"{\n 'data-desktop': '',\n 'data-partid': part.id,\n }\"\n (wbViewDropZoneDrop)=\"onDesktopViewDrop($event)\"\n class=\"desktop e2e-part-content e2e-content\">\n @if (part.navigation()) {\n <ng-container *wbPortalOutlet=\"part.slot.portal; destroyOnDetach: false\"/>\n } @else {\n <ng-container *wbPortalOutlet=\"desktop.slot.portal; destroyOnDetach: false\"/>\n }\n </div>\n}\n", styles: [":host{display:flex;flex-direction:column}:host>wb-grid{flex:auto}:host>div.desktop{flex:auto;display:grid;position:relative}\n"] }]
8343
+ }, template: "@if (mainAreaGrid().root.visible) {\n <wb-grid [grid]=\"mainAreaGrid()\"\n [gridDropZone]=\"{\n dropRegionSize: 100,\n dropPlaceholderSize: 100,\n dropZoneAttributes: {\n 'data-grid': 'main-area',\n 'data-partid': part.id,\n }\n }\"\n [attr.data-grid]=\"'main-area'\"\n class=\"e2e-slot\"/>\n} @else {\n <div wbViewDropZone\n [wbViewDropZoneRegions]=\"canDrop() && {center: true, north: false, south: false, west: false, east: false}\"\n [wbViewDropZoneAttributes]=\"{\n 'data-desktop': '',\n 'data-partid': part.id,\n }\"\n (wbViewDropZoneDrop)=\"onDesktopViewDrop($event)\"\n class=\"desktop e2e-part-slot e2e-slot\">\n @if (part.navigation()) {\n <ng-container *wbPortalOutlet=\"part.slot.portal; destroyOnDetach: false\"/>\n } @else {\n <ng-container *wbPortalOutlet=\"desktop.slot.portal; destroyOnDetach: false\"/>\n }\n </div>\n}\n", styles: [":host{display:flex;flex-direction:column}:host>wb-grid{flex:auto}:host>div.desktop{flex:auto;display:grid;position:relative}\n"] }]
8345
8344
  }] });
8346
8345
 
8347
8346
  /*
@@ -9723,7 +9722,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImpor
9723
9722
  }] });
9724
9723
 
9725
9724
  /*
9726
- * Copyright (c) 2018-2023 Swiss Federal Railways
9725
+ * Copyright (c) 2018-2026 Swiss Federal Railways
9727
9726
  *
9728
9727
  * This program and the accompanying materials are made
9729
9728
  * available under the terms of the Eclipse Public License 2.0
@@ -9745,7 +9744,8 @@ class WorkbenchDialogComponent {
9745
9744
  headerHeight = signal(undefined, { ...(ngDevMode ? { debugName: "headerHeight" } : {}) });
9746
9745
  transformTranslateX = signal(0, { ...(ngDevMode ? { debugName: "transformTranslateX" } : {}) });
9747
9746
  transformTranslateY = signal(0, { ...(ngDevMode ? { debugName: "transformTranslateY" } : {}) });
9748
- dialogContent = viewChild.required(SciViewportComponent, { read: (ElementRef) });
9747
+ slotAnchorName = this.dialog.id.replace('.', '_'); // Anchor must not contain a dot.
9748
+ dialogSlotBounds = viewChild.required('slot_bounds', { read: (ElementRef) });
9749
9749
  constructor() {
9750
9750
  this.setDialogOffset();
9751
9751
  this.trackFocus();
@@ -9847,7 +9847,7 @@ class WorkbenchDialogComponent {
9847
9847
  this.focus();
9848
9848
  }
9849
9849
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9850
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.1", type: WorkbenchDialogComponent, isStandalone: true, selector: "wb-dialog", host: { listeners: { "keydown.escape": "onEscape($event)" }, properties: { "attr.data-dialogid": "dialog.id", "class.justified": "!dialog.padding()", "style.--\u0275dialog-transform-translate-x": "transformTranslateX()", "style.--\u0275dialog-transform-translate-y": "transformTranslateY()", "style.--\u0275dialog-min-height": "dialog.size.minHeight() ?? headerHeight()", "style.--\u0275dialog-height": "dialog.size.height()", "style.--\u0275dialog-max-height": "dialog.size.maxHeight()", "style.--\u0275dialog-min-width": "dialog.size.minWidth() ?? '100px'", "style.--\u0275dialog-width": "dialog.size.width()", "style.--\u0275dialog-max-width": "dialog.size.maxWidth()", "class": "dialog.cssClass()" } }, viewQueries: [{ propertyName: "_cdkTrapFocus", first: true, predicate: CdkTrapFocus, descendants: true, isSignal: true }, { propertyName: "_dialogElement", first: true, predicate: ["dialog_element"], descendants: true, isSignal: true }, { propertyName: "dialogContent", first: true, predicate: SciViewportComponent, descendants: true, read: ElementRef, isSignal: true }], ngImport: i0, template: "<div class=\"dialog e2e-dialog\"\n [class.blinking]=\"dialog.blinking$ | async\"\n [tabindex]=\"-1\"\n wbMovable [wbHandle]=\"header\" (wbMovableMove)=\"onMove($event)\"\n (mousedown)=\"onDialogMouseDown()\"\n wbResizable [wbResizableEnabled]=\"dialog.resizable()\" (wbResizableResize)=\"onResize($event)\"\n @enter\n [@.disabled]=\"!dialog.animate\"\n wbGlassPane\n #dialog_element>\n <div class=\"dialog-box e2e-dialog-box\" cdkTrapFocus>\n <header #header\n class=\"e2e-dialog-header\"\n [class.divider]=\"dialog.header?.divider() ?? true\"\n sciDimension (sciDimensionChange)=\"onHeaderDimensionChange($event)\">\n <ng-container *ngTemplateOutlet=\"dialog.header?.template ?? default_dialog_header\"/>\n </header>\n\n <sci-viewport class=\"content e2e-dialog-content\">\n <ng-container *ngComponentOutlet=\"dialog.component; inputs: dialog.inputs\"/>\n </sci-viewport>\n\n @if (dialog.footer || dialog.actions.length) {\n <footer class=\"e2e-dialog-footer\" [class.divider]=\"dialog.footer?.divider() ?? true\">\n <ng-container *ngTemplateOutlet=\"dialog.footer?.template ?? default_dialog_footer\"/>\n </footer>\n }\n </div>\n</div>\n\n<ng-template #default_dialog_header>\n <wb-dialog-header/>\n</ng-template>\n\n<ng-template #default_dialog_footer>\n <wb-dialog-footer/>\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{--\\275 dialog-transform-translate-x: 0;--\\275 dialog-transform-translate-y: 0;--\\275 dialog-min-height: initial;--\\275 dialog-height: initial;--\\275 dialog-max-height: initial;--\\275 dialog-min-width: initial;--\\275 dialog-width: initial;--\\275 dialog-max-width: initial;--\\275 dialog-padding: var(--sci-workbench-dialog-padding)}:host.justified{--\\275 dialog-padding: 0}:host{display:flex;flex-direction:column;align-items:center;position:relative}:host>div.dialog{display:flex;flex-direction:column;position:absolute;top:3%;color:var(--sci-color-text);transform:translate(calc(1px * var(--\\275 dialog-transform-translate-x))) translateY(calc(1px * var(--\\275 dialog-transform-translate-y)));min-height:var(--\\275 dialog-min-height);height:var(--\\275 dialog-height);max-height:var(--\\275 dialog-max-height);min-width:var(--\\275 dialog-min-width);width:var(--\\275 dialog-width);max-width:var(--\\275 dialog-max-width);outline:none;pointer-events:auto}:host>div.dialog>div.dialog-box{flex:auto;display:flex;flex-direction:column;gap:calc(1.25 * var(--\\275 dialog-padding));border:1px solid var(--sci-color-border);border-radius:var(--sci-corner);background-color:var(--sci-color-background-elevation);box-shadow:var(--sci-elevation) var(--sci-static-color-black);overflow:hidden}:host>div.dialog>div.dialog-box>header{flex:none}:host>div.dialog>div.dialog-box>header.divider{border-bottom:1px solid var(--sci-color-border)}:host>div.dialog>div.dialog-box>sci-viewport{flex:auto}:host>div.dialog>div.dialog-box>sci-viewport::part(content){padding-inline:var(--\\275 dialog-padding)}:host>div.dialog>div.dialog-box>footer{flex:none}:host>div.dialog>div.dialog-box>footer.divider{border-top:1px solid var(--sci-color-border)}:host>div.dialog.blinking{animation-duration:50ms;animation-iteration-count:infinite;animation-name:blink-animation}@keyframes blink-animation{0%{transform:translate(calc(1px * var(--\\275 dialog-transform-translate-x) - 2px)) translateY(calc(1px * var(--\\275 dialog-transform-translate-y) - 1px))}to{transform:translate(calc(1px * var(--\\275 dialog-transform-translate-x) + 2px)) translateY(calc(1px * var(--\\275 dialog-transform-translate-y) + 1px))}}\n"], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "directive", type: MovableDirective, selector: "[wbMovable]", inputs: ["wbHandle"], outputs: ["wbMovableMove"] }, { kind: "directive", type: ResizableDirective, selector: "[wbResizable]", inputs: ["wbResizableEnabled"], outputs: ["wbResizableResize"] }, { kind: "component", type: SciViewportComponent, selector: "sci-viewport", inputs: ["scrollbarStyle"], outputs: ["scroll"] }, { kind: "directive", type: SciDimensionDirective, selector: "[sciDimension]", inputs: ["emitOutsideAngular"], outputs: ["sciDimensionChange"] }, { kind: "component", type: DialogHeaderComponent, selector: "wb-dialog-header" }, { kind: "component", type: DialogFooterComponent, selector: "wb-dialog-footer" }, { kind: "directive", type: GlassPaneDirective, selector: "[wbGlassPane]" }, { kind: "pipe", type: AsyncPipe, name: "async" }], viewProviders: [
9850
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.1", type: WorkbenchDialogComponent, isStandalone: true, selector: "wb-dialog", host: { listeners: { "keydown.escape": "onEscape($event)" }, properties: { "attr.data-dialogid": "dialog.id", "class.justified": "!dialog.padding()", "style.--\u0275dialog-transform-translate-x": "transformTranslateX()", "style.--\u0275dialog-transform-translate-y": "transformTranslateY()", "style.--\u0275dialog-min-height": "dialog.size.minHeight() ?? headerHeight()", "style.--\u0275dialog-height": "dialog.size.height()", "style.--\u0275dialog-max-height": "dialog.size.maxHeight()", "style.--\u0275dialog-min-width": "dialog.size.minWidth() ?? '100px'", "style.--\u0275dialog-width": "dialog.size.width()", "style.--\u0275dialog-max-width": "dialog.size.maxWidth()", "style.--\u0275slot-anchor": "`--${slotAnchorName}`", "class": "dialog.cssClass()" } }, viewQueries: [{ propertyName: "_cdkTrapFocus", first: true, predicate: CdkTrapFocus, descendants: true, isSignal: true }, { propertyName: "_dialogElement", first: true, predicate: ["dialog_element"], descendants: true, isSignal: true }, { propertyName: "dialogSlotBounds", first: true, predicate: ["slot_bounds"], descendants: true, read: ElementRef, isSignal: true }], ngImport: i0, template: "<div class=\"dialog e2e-dialog\"\n [class.blinking]=\"dialog.blinking$ | async\"\n [tabindex]=\"-1\"\n wbMovable [wbHandle]=\"header\" (wbMovableMove)=\"onMove($event)\"\n (mousedown)=\"onDialogMouseDown()\"\n wbResizable [wbResizableEnabled]=\"dialog.resizable()\" (wbResizableResize)=\"onResize($event)\"\n @enter\n [@.disabled]=\"!dialog.animate\"\n wbGlassPane\n #dialog_element>\n <div class=\"dialog-box e2e-dialog-box\" cdkTrapFocus>\n <header #header\n class=\"e2e-dialog-header\"\n [class.divider]=\"dialog.header?.divider() ?? true\"\n sciDimension (sciDimensionChange)=\"onHeaderDimensionChange($event)\">\n <ng-container *ngTemplateOutlet=\"dialog.header?.template ?? default_dialog_header\"/>\n </header>\n\n <sci-viewport class=\"e2e-dialog-slot\">\n <ng-container *ngComponentOutlet=\"dialog.component; inputs: dialog.inputs\"/>\n </sci-viewport>\n\n <!-- Extra DIV to capture bounds available to slotted content, excluding viewport content padding. May differ from the actual content size if content overflows or does not fill the slot. -->\n <div class=\"slot-bounds e2e-dialog-slot-bounds\" #slot_bounds></div>\n\n @if (dialog.footer || dialog.actions.length) {\n <footer class=\"e2e-dialog-footer\" [class.divider]=\"dialog.footer?.divider() ?? true\">\n <ng-container *ngTemplateOutlet=\"dialog.footer?.template ?? default_dialog_footer\"/>\n </footer>\n }\n </div>\n</div>\n\n<ng-template #default_dialog_header>\n <wb-dialog-header/>\n</ng-template>\n\n<ng-template #default_dialog_footer>\n <wb-dialog-footer/>\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{--\\275 dialog-transform-translate-x: 0;--\\275 dialog-transform-translate-y: 0;--\\275 dialog-min-height: initial;--\\275 dialog-height: initial;--\\275 dialog-max-height: initial;--\\275 dialog-min-width: initial;--\\275 dialog-width: initial;--\\275 dialog-max-width: initial;--\\275 dialog-padding: var(--sci-workbench-dialog-padding)}:host.justified{--\\275 dialog-padding: 0}:host{display:flex;flex-direction:column;align-items:center;position:relative}:host>div.dialog{display:flex;flex-direction:column;position:absolute;top:3%;color:var(--sci-color-text);transform:translate(calc(1px * var(--\\275 dialog-transform-translate-x))) translateY(calc(1px * var(--\\275 dialog-transform-translate-y)));min-height:var(--\\275 dialog-min-height);height:var(--\\275 dialog-height);max-height:var(--\\275 dialog-max-height);min-width:var(--\\275 dialog-min-width);width:var(--\\275 dialog-width);max-width:var(--\\275 dialog-max-width);outline:none;pointer-events:auto}:host>div.dialog>div.dialog-box{flex:auto;display:flex;flex-direction:column;gap:calc(1.25 * var(--\\275 dialog-padding));border:1px solid var(--sci-color-border);border-radius:var(--sci-corner);background-color:var(--sci-color-background-elevation);box-shadow:var(--sci-elevation) var(--sci-static-color-black);overflow:hidden}:host>div.dialog>div.dialog-box>header{flex:none}:host>div.dialog>div.dialog-box>header.divider{border-bottom:1px solid var(--sci-color-border)}:host>div.dialog>div.dialog-box>sci-viewport{flex:auto;anchor-name:var(--\\275slot-anchor)}:host>div.dialog>div.dialog-box>sci-viewport::part(content){padding-inline:var(--\\275 dialog-padding)}:host>div.dialog>div.dialog-box>div.slot-bounds{position:absolute;position-anchor:var(--\\275slot-anchor);inset:anchor(top) anchor(right) anchor(bottom) anchor(left);margin-inline:var(--\\275 dialog-padding);visibility:hidden}:host>div.dialog>div.dialog-box>footer{flex:none}:host>div.dialog>div.dialog-box>footer.divider{border-top:1px solid var(--sci-color-border)}:host>div.dialog.blinking{animation-duration:50ms;animation-iteration-count:infinite;animation-name:blink-animation}@keyframes blink-animation{0%{transform:translate(calc(1px * var(--\\275 dialog-transform-translate-x) - 2px)) translateY(calc(1px * var(--\\275 dialog-transform-translate-y) - 1px))}to{transform:translate(calc(1px * var(--\\275 dialog-transform-translate-x) + 2px)) translateY(calc(1px * var(--\\275 dialog-transform-translate-y) + 1px))}}\n"], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "directive", type: MovableDirective, selector: "[wbMovable]", inputs: ["wbHandle"], outputs: ["wbMovableMove"] }, { kind: "directive", type: ResizableDirective, selector: "[wbResizable]", inputs: ["wbResizableEnabled"], outputs: ["wbResizableResize"] }, { kind: "component", type: SciViewportComponent, selector: "sci-viewport", inputs: ["scrollbarStyle"], outputs: ["scroll"] }, { kind: "directive", type: SciDimensionDirective, selector: "[sciDimension]", inputs: ["emitOutsideAngular"], outputs: ["sciDimensionChange"] }, { kind: "component", type: DialogHeaderComponent, selector: "wb-dialog-header" }, { kind: "component", type: DialogFooterComponent, selector: "wb-dialog-footer" }, { kind: "directive", type: GlassPaneDirective, selector: "[wbGlassPane]" }, { kind: "pipe", type: AsyncPipe, name: "async" }], viewProviders: [
9851
9851
  configureDialogGlassPane(),
9852
9852
  ], animations: [
9853
9853
  trigger('enter', provideEnterAnimation()),
@@ -9882,9 +9882,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImpor
9882
9882
  '[style.--ɵdialog-min-width]': 'dialog.size.minWidth() ?? \'100px\'',
9883
9883
  '[style.--ɵdialog-width]': 'dialog.size.width()',
9884
9884
  '[style.--ɵdialog-max-width]': 'dialog.size.maxWidth()',
9885
+ '[style.--ɵslot-anchor]': '`--${slotAnchorName}`',
9885
9886
  '[class]': 'dialog.cssClass()',
9886
- }, template: "<div class=\"dialog e2e-dialog\"\n [class.blinking]=\"dialog.blinking$ | async\"\n [tabindex]=\"-1\"\n wbMovable [wbHandle]=\"header\" (wbMovableMove)=\"onMove($event)\"\n (mousedown)=\"onDialogMouseDown()\"\n wbResizable [wbResizableEnabled]=\"dialog.resizable()\" (wbResizableResize)=\"onResize($event)\"\n @enter\n [@.disabled]=\"!dialog.animate\"\n wbGlassPane\n #dialog_element>\n <div class=\"dialog-box e2e-dialog-box\" cdkTrapFocus>\n <header #header\n class=\"e2e-dialog-header\"\n [class.divider]=\"dialog.header?.divider() ?? true\"\n sciDimension (sciDimensionChange)=\"onHeaderDimensionChange($event)\">\n <ng-container *ngTemplateOutlet=\"dialog.header?.template ?? default_dialog_header\"/>\n </header>\n\n <sci-viewport class=\"content e2e-dialog-content\">\n <ng-container *ngComponentOutlet=\"dialog.component; inputs: dialog.inputs\"/>\n </sci-viewport>\n\n @if (dialog.footer || dialog.actions.length) {\n <footer class=\"e2e-dialog-footer\" [class.divider]=\"dialog.footer?.divider() ?? true\">\n <ng-container *ngTemplateOutlet=\"dialog.footer?.template ?? default_dialog_footer\"/>\n </footer>\n }\n </div>\n</div>\n\n<ng-template #default_dialog_header>\n <wb-dialog-header/>\n</ng-template>\n\n<ng-template #default_dialog_footer>\n <wb-dialog-footer/>\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{--\\275 dialog-transform-translate-x: 0;--\\275 dialog-transform-translate-y: 0;--\\275 dialog-min-height: initial;--\\275 dialog-height: initial;--\\275 dialog-max-height: initial;--\\275 dialog-min-width: initial;--\\275 dialog-width: initial;--\\275 dialog-max-width: initial;--\\275 dialog-padding: var(--sci-workbench-dialog-padding)}:host.justified{--\\275 dialog-padding: 0}:host{display:flex;flex-direction:column;align-items:center;position:relative}:host>div.dialog{display:flex;flex-direction:column;position:absolute;top:3%;color:var(--sci-color-text);transform:translate(calc(1px * var(--\\275 dialog-transform-translate-x))) translateY(calc(1px * var(--\\275 dialog-transform-translate-y)));min-height:var(--\\275 dialog-min-height);height:var(--\\275 dialog-height);max-height:var(--\\275 dialog-max-height);min-width:var(--\\275 dialog-min-width);width:var(--\\275 dialog-width);max-width:var(--\\275 dialog-max-width);outline:none;pointer-events:auto}:host>div.dialog>div.dialog-box{flex:auto;display:flex;flex-direction:column;gap:calc(1.25 * var(--\\275 dialog-padding));border:1px solid var(--sci-color-border);border-radius:var(--sci-corner);background-color:var(--sci-color-background-elevation);box-shadow:var(--sci-elevation) var(--sci-static-color-black);overflow:hidden}:host>div.dialog>div.dialog-box>header{flex:none}:host>div.dialog>div.dialog-box>header.divider{border-bottom:1px solid var(--sci-color-border)}:host>div.dialog>div.dialog-box>sci-viewport{flex:auto}:host>div.dialog>div.dialog-box>sci-viewport::part(content){padding-inline:var(--\\275 dialog-padding)}:host>div.dialog>div.dialog-box>footer{flex:none}:host>div.dialog>div.dialog-box>footer.divider{border-top:1px solid var(--sci-color-border)}:host>div.dialog.blinking{animation-duration:50ms;animation-iteration-count:infinite;animation-name:blink-animation}@keyframes blink-animation{0%{transform:translate(calc(1px * var(--\\275 dialog-transform-translate-x) - 2px)) translateY(calc(1px * var(--\\275 dialog-transform-translate-y) - 1px))}to{transform:translate(calc(1px * var(--\\275 dialog-transform-translate-x) + 2px)) translateY(calc(1px * var(--\\275 dialog-transform-translate-y) + 1px))}}\n"] }]
9887
- }], ctorParameters: () => [], propDecorators: { _cdkTrapFocus: [{ type: i0.ViewChild, args: [i0.forwardRef(() => CdkTrapFocus), { isSignal: true }] }], _dialogElement: [{ type: i0.ViewChild, args: ['dialog_element', { isSignal: true }] }], dialogContent: [{ type: i0.ViewChild, args: [i0.forwardRef(() => SciViewportComponent), { ...{ read: (ElementRef) }, isSignal: true }] }], onEscape: [{
9887
+ }, template: "<div class=\"dialog e2e-dialog\"\n [class.blinking]=\"dialog.blinking$ | async\"\n [tabindex]=\"-1\"\n wbMovable [wbHandle]=\"header\" (wbMovableMove)=\"onMove($event)\"\n (mousedown)=\"onDialogMouseDown()\"\n wbResizable [wbResizableEnabled]=\"dialog.resizable()\" (wbResizableResize)=\"onResize($event)\"\n @enter\n [@.disabled]=\"!dialog.animate\"\n wbGlassPane\n #dialog_element>\n <div class=\"dialog-box e2e-dialog-box\" cdkTrapFocus>\n <header #header\n class=\"e2e-dialog-header\"\n [class.divider]=\"dialog.header?.divider() ?? true\"\n sciDimension (sciDimensionChange)=\"onHeaderDimensionChange($event)\">\n <ng-container *ngTemplateOutlet=\"dialog.header?.template ?? default_dialog_header\"/>\n </header>\n\n <sci-viewport class=\"e2e-dialog-slot\">\n <ng-container *ngComponentOutlet=\"dialog.component; inputs: dialog.inputs\"/>\n </sci-viewport>\n\n <!-- Extra DIV to capture bounds available to slotted content, excluding viewport content padding. May differ from the actual content size if content overflows or does not fill the slot. -->\n <div class=\"slot-bounds e2e-dialog-slot-bounds\" #slot_bounds></div>\n\n @if (dialog.footer || dialog.actions.length) {\n <footer class=\"e2e-dialog-footer\" [class.divider]=\"dialog.footer?.divider() ?? true\">\n <ng-container *ngTemplateOutlet=\"dialog.footer?.template ?? default_dialog_footer\"/>\n </footer>\n }\n </div>\n</div>\n\n<ng-template #default_dialog_header>\n <wb-dialog-header/>\n</ng-template>\n\n<ng-template #default_dialog_footer>\n <wb-dialog-footer/>\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{--\\275 dialog-transform-translate-x: 0;--\\275 dialog-transform-translate-y: 0;--\\275 dialog-min-height: initial;--\\275 dialog-height: initial;--\\275 dialog-max-height: initial;--\\275 dialog-min-width: initial;--\\275 dialog-width: initial;--\\275 dialog-max-width: initial;--\\275 dialog-padding: var(--sci-workbench-dialog-padding)}:host.justified{--\\275 dialog-padding: 0}:host{display:flex;flex-direction:column;align-items:center;position:relative}:host>div.dialog{display:flex;flex-direction:column;position:absolute;top:3%;color:var(--sci-color-text);transform:translate(calc(1px * var(--\\275 dialog-transform-translate-x))) translateY(calc(1px * var(--\\275 dialog-transform-translate-y)));min-height:var(--\\275 dialog-min-height);height:var(--\\275 dialog-height);max-height:var(--\\275 dialog-max-height);min-width:var(--\\275 dialog-min-width);width:var(--\\275 dialog-width);max-width:var(--\\275 dialog-max-width);outline:none;pointer-events:auto}:host>div.dialog>div.dialog-box{flex:auto;display:flex;flex-direction:column;gap:calc(1.25 * var(--\\275 dialog-padding));border:1px solid var(--sci-color-border);border-radius:var(--sci-corner);background-color:var(--sci-color-background-elevation);box-shadow:var(--sci-elevation) var(--sci-static-color-black);overflow:hidden}:host>div.dialog>div.dialog-box>header{flex:none}:host>div.dialog>div.dialog-box>header.divider{border-bottom:1px solid var(--sci-color-border)}:host>div.dialog>div.dialog-box>sci-viewport{flex:auto;anchor-name:var(--\\275slot-anchor)}:host>div.dialog>div.dialog-box>sci-viewport::part(content){padding-inline:var(--\\275 dialog-padding)}:host>div.dialog>div.dialog-box>div.slot-bounds{position:absolute;position-anchor:var(--\\275slot-anchor);inset:anchor(top) anchor(right) anchor(bottom) anchor(left);margin-inline:var(--\\275 dialog-padding);visibility:hidden}:host>div.dialog>div.dialog-box>footer{flex:none}:host>div.dialog>div.dialog-box>footer.divider{border-top:1px solid var(--sci-color-border)}:host>div.dialog.blinking{animation-duration:50ms;animation-iteration-count:infinite;animation-name:blink-animation}@keyframes blink-animation{0%{transform:translate(calc(1px * var(--\\275 dialog-transform-translate-x) - 2px)) translateY(calc(1px * var(--\\275 dialog-transform-translate-y) - 1px))}to{transform:translate(calc(1px * var(--\\275 dialog-transform-translate-x) + 2px)) translateY(calc(1px * var(--\\275 dialog-transform-translate-y) + 1px))}}\n"] }]
9888
+ }], ctorParameters: () => [], propDecorators: { _cdkTrapFocus: [{ type: i0.ViewChild, args: [i0.forwardRef(() => CdkTrapFocus), { isSignal: true }] }], _dialogElement: [{ type: i0.ViewChild, args: ['dialog_element', { isSignal: true }] }], dialogSlotBounds: [{ type: i0.ViewChild, args: ['slot_bounds', { ...{ read: (ElementRef) }, isSignal: true }] }], onEscape: [{
9888
9889
  type: HostListener,
9889
9890
  args: ['keydown.escape', ['$event']]
9890
9891
  }] } });
@@ -9916,7 +9917,7 @@ function configureDialogGlassPane() {
9916
9917
  }
9917
9918
 
9918
9919
  /*
9919
- * Copyright (c) 2018-2023 Swiss Federal Railways
9920
+ * Copyright (c) 2018-2026 Swiss Federal Railways
9920
9921
  *
9921
9922
  * This program and the accompanying materials are made
9922
9923
  * available under the terms of the Eclipse Public License 2.0
@@ -9925,96 +9926,17 @@ function configureDialogGlassPane() {
9925
9926
  * SPDX-License-Identifier: EPL-2.0
9926
9927
  */
9927
9928
  /**
9928
- * Enables the display of a component in a dialog.
9929
- *
9930
- * A dialog is a visual element for focused interaction with the user, such as prompting the user for input or confirming actions.
9931
- * The user can move and resize a dialog.
9932
- *
9933
- * Displayed on top of other content, a modal dialog blocks interaction with other parts of the application.
9934
- *
9935
- * ## Modality
9936
- * A dialog can be context-modal or application-modal. Context-modal blocks a specific part of the application, as specified by the context;
9937
- * application-modal blocks the workbench or browser viewport, based on {@link WorkbenchConfig.dialog.modalityScope}.
9938
- *
9939
- * ## Context
9940
- * A dialog can be bound to a context (e.g., part or view), defaulting to the calling context.
9941
- * The dialog is displayed only if the context is visible and closes when the context is disposed.
9942
- *
9943
- * ## Positioning
9944
- * A dialog is opened in the center of its context, if any, unless opened from the peripheral area.
9945
- *
9946
- * ## Stacking
9947
- * Dialogs are stacked per modality, with only the topmost dialog in each stack being interactive.
9948
- *
9949
- * ## Dialog Component
9950
- * The dialog component can inject the {@link WorkbenchDialog} handle to interact with the dialog, such as setting the title or closing the dialog.
9951
- * Inputs passed to the dialog are available as input properties in the dialog component.
9952
- *
9953
- * ## Dialog Header
9954
- * By default, the dialog displays the title and a close button in the header. Alternatively, the dialog supports the use of a custom header.
9955
- * To provide a custom header, add an Angular template to the HTML of the dialog component and decorate it with the `wbDialogHeader` directive.
9956
- *
9957
- * ```html
9958
- * <ng-template wbDialogHeader>
9959
- * <app-dialog-header/>
9960
- * </ng-template>
9961
- * ```
9962
- *
9963
- * ## Dialog Footer
9964
- * A dialog has a default footer that displays actions defined in the HTML of the dialog component. An action is an Angular template decorated with
9965
- * the `wbDialogAction` directive. Multiple actions are supported, rendered in modeling order, and can be left- or right-aligned.
9966
- *
9967
- * ```html
9968
- * <!-- Checkbox -->
9969
- * <ng-template wbDialogAction align="start">
9970
- * <label>
9971
- * <input type="checkbox"/>
9972
- * Do not ask me again
9973
- * </label>
9974
- * </ng-template>
9975
- *
9976
- * <!-- OK Button -->
9977
- * <ng-template wbDialogAction align="end">
9978
- * <button (click)="...">OK</button>
9979
- * </ng-template>
9980
- *
9981
- * <!-- Cancel Button -->
9982
- * <ng-template wbDialogAction align="end">
9983
- * <button (click)="...">Cancel</button>
9984
- * </ng-template>
9985
- * ```
9986
- *
9987
- * Alternatively, the dialog supports the use of a custom footer. To provide a custom footer, add an Angular template to the HTML of the dialog component and
9988
- * decorate it with the `wbDialogFooter` directive.
9989
- *
9990
- * ```html
9991
- * <ng-template wbDialogFooter>
9992
- * <app-dialog-footer/>
9993
- * </ng-template>
9994
- * ```
9995
- *
9996
- * ## Styling
9997
- * The following CSS variables can be set to customize the default look of a dialog.
9998
- *
9999
- * - `--sci-workbench-dialog-padding`
10000
- * - `--sci-workbench-dialog-header-height`
10001
- * - `--sci-workbench-dialog-header-background-color`
10002
- * - `--sci-workbench-dialog-title-font-family`
10003
- * - `--sci-workbench-dialog-title-font-weight`
10004
- * - `--sci-workbench-dialog-title-font-size`
10005
- * - `--sci-workbench-dialog-title-align`
9929
+ * Provides {@link WorkbenchDialogService} for dependency injection.
10006
9930
  */
10007
- class WorkbenchDialogService {
10008
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchDialogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
10009
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchDialogService, providedIn: 'root', useExisting: ɵWorkbenchDialogService });
9931
+ function provideWorkbenchDialogService() {
9932
+ return [
9933
+ ɵWorkbenchDialogService,
9934
+ { provide: WorkbenchDialogService, useExisting: ɵWorkbenchDialogService },
9935
+ ];
10010
9936
  }
10011
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchDialogService, decorators: [{
10012
- type: Injectable,
10013
- args: [{ providedIn: 'root', useExisting: ɵWorkbenchDialogService }]
10014
- }] });
10015
9937
 
10016
9938
  /*
10017
- * Copyright (c) 2018-2023 Swiss Federal Railways
9939
+ * Copyright (c) 2018-2022 Swiss Federal Railways
10018
9940
  *
10019
9941
  * This program and the accompanying materials are made
10020
9942
  * available under the terms of the Eclipse Public License 2.0
@@ -10022,95 +9944,87 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImpor
10022
9944
  *
10023
9945
  * SPDX-License-Identifier: EPL-2.0
10024
9946
  */
10025
- class MessageBoxFooterComponent {
10026
- actions = input.required({ ...(ngDevMode ? { debugName: "actions" } : {}) });
10027
- severity = input.required({ ...(ngDevMode ? { debugName: "severity" } : {}) });
10028
- action = output();
10029
- preferredSizeChange = output();
10030
- _actionButtons = viewChildren('action_button', { ...(ngDevMode ? { debugName: "_actionButtons" } : {}) });
9947
+ /**
9948
+ * Renders the content of a workbench popup.
9949
+ */
9950
+ class WorkbenchPopupComponent {
9951
+ _host = inject(ElementRef).nativeElement;
9952
+ _cdkTrapFocus = viewChild.required('focus_trap', { read: CdkTrapFocus });
9953
+ popup = inject(ɵWorkbenchPopup);
10031
9954
  constructor() {
10032
- void this.emitPreferredSize();
10033
- }
10034
- insertionSortOrderFn = () => 0;
10035
- onAction(key) {
10036
- this.action.emit(key);
10037
- }
10038
- onArrowKey(index, direction) {
10039
- const actionButtonCount = this._actionButtons().length;
10040
- const newIndex = (direction === 'left' ? index - 1 : index + 1);
10041
- this._actionButtons()[(newIndex + actionButtonCount) % actionButtonCount].nativeElement.focus();
9955
+ trackFocus(this._host, this.popup);
9956
+ this.focusInitialElement();
10042
9957
  }
10043
- async emitPreferredSize() {
10044
- const host = inject(ElementRef).nativeElement;
10045
- host.classList.add('calculating-min-width');
10046
- try {
10047
- // Wait for the CSS class to take effect, then wait an animation frame to avoid the error: "ResizeObserver loop completed with undelivered notifications".
10048
- await firstValueFrom(fromResize$(host).pipe(observeOn(animationFrameScheduler)));
10049
- this.preferredSizeChange.emit(host.offsetWidth);
10050
- }
10051
- finally {
10052
- host.classList.remove('calculating-min-width');
10053
- }
9958
+ focusInitialElement() {
9959
+ const effectRef = effect(() => {
9960
+ // [Angular 14] The initial focus must not be requested via `cdkTrapFocusAutoCapture` as this would restore
9961
+ // focus to the previously focused element when the `FocusTrap` is destroyed. This behavior is unwanted if the
9962
+ // popup is closed by losing focus. Otherwise, the newly focused element that caused the loss of focus and thus
9963
+ // the closing of the popup would immediately become unfocused again. This behavior could only be observed when
9964
+ // the popup loses focus by clicking on an element in a microfrontend.
9965
+ void this._cdkTrapFocus().focusTrap.focusInitialElementWhenReady();
9966
+ effectRef.destroy();
9967
+ }, { ...(ngDevMode ? { debugName: "effectRef" } : {}) });
10054
9968
  }
10055
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: MessageBoxFooterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10056
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.1", type: MessageBoxFooterComponent, isStandalone: true, selector: "wb-message-box-footer", inputs: { actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: true, transformFunction: null }, severity: { classPropertyName: "severity", publicName: "severity", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { action: "action", preferredSizeChange: "preferredSizeChange" }, host: { properties: { "attr.data-severity": "severity()" } }, viewQueries: [{ propertyName: "_actionButtons", predicate: ["action_button"], descendants: true, isSignal: true }], ngImport: i0, template: "@for (action of actions() | keyvalue:insertionSortOrderFn; track action.key) {\n <button #action_button\n (click)=\"onAction(action.key)\"\n (keydown.arrowLeft)=\"onArrowKey($index, 'left')\"\n (keydown.arrowRight)=\"onArrowKey($index, 'right')\"\n [attr.data-action]=\"action.key\"\n class=\"action e2e-action\">\n {{(action.value | wbText)()}}\n </button>\n\n @if (!$last) {\n <span class=\"divider\"></span>\n }\n}\n", styles: ["@charset \"UTF-8\";:host{--\\275message-box-severity-color: initial;display:flex;height:3em;background-color:var(--sci-color-background-secondary);color:var(--sci-color-text)}:host[data-severity=info]{--\\275message-box-severity-color: var(--sci-color-accent)}:host[data-severity=warn]{--\\275message-box-severity-color: var(--sci-color-notice)}:host[data-severity=error]{--\\275message-box-severity-color: var(--sci-color-negative)}:host.calculating-min-width{position:absolute}:host>button.action:is(button,#sci-reset){all:unset;flex:1;margin:2px;border:1px solid transparent;border-radius:var(--sci-corner-small);transition:border-color ease-in-out .15s;cursor:var(--sci-workbench-messagebox-action-cursor);-webkit-user-select:none;user-select:none;text-align:center;min-width:7.5em;padding-inline:.5em;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}:host>button.action:is(button,#sci-reset):focus,:host>button.action:is(button,#sci-reset):active{outline:none;color:var(--\\275message-box-severity-color);border-color:var(--\\275message-box-severity-color)}:host>button.action:is(button,#sci-reset):hover{background-color:var(--sci-workbench-messagebox-action-background-color-hover)}:host>span.divider{width:1px;background-color:var(--sci-color-border)}\n"], dependencies: [{ kind: "pipe", type: KeyValuePipe, name: "keyvalue" }, { kind: "pipe", type: TextPipe, name: "wbText" }] });
9969
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchPopupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9970
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.0.1", type: WorkbenchPopupComponent, isStandalone: true, selector: "wb-popup", host: { properties: { "attr.data-popupid": "popup.id", "style.width": "popup.size.width()", "style.min-width": "popup.size.minWidth()", "style.max-width": "popup.size.maxWidth()", "style.height": "popup.size.height()", "style.min-height": "popup.size.minHeight()", "style.max-height": "popup.size.maxHeight()", "class": "popup.cssClass()" } }, providers: [
9971
+ configurePopupGlassPane(),
9972
+ ], viewQueries: [{ propertyName: "_cdkTrapFocus", first: true, predicate: ["focus_trap"], descendants: true, read: CdkTrapFocus, isSignal: true }], hostDirectives: [{ directive: GlassPaneDirective }], ngImport: i0, template: "<sci-viewport cdkTrapFocus class=\"e2e-popup-viewport\" #focus_trap>\n <ng-container *ngComponentOutlet=\"popup.component; inputs: popup.inputs\"/>\n</sci-viewport>\n", styles: [":host{display:grid;grid-template-columns:100%;grid-template-rows:100%;outline:none;border-radius:var(--sci-corner);overflow:hidden}\n"], dependencies: [{ kind: "directive", type: CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: SciViewportComponent, selector: "sci-viewport", inputs: ["scrollbarStyle"], outputs: ["scroll"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }] });
10057
9973
  }
10058
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: MessageBoxFooterComponent, decorators: [{
9974
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchPopupComponent, decorators: [{
10059
9975
  type: Component,
10060
- args: [{ selector: 'wb-message-box-footer', imports: [
10061
- KeyValuePipe,
10062
- TextPipe,
9976
+ args: [{ selector: 'wb-popup', imports: [
9977
+ CdkTrapFocus,
9978
+ SciViewportComponent,
9979
+ NgComponentOutlet,
9980
+ ], hostDirectives: [
9981
+ GlassPaneDirective,
9982
+ ], providers: [
9983
+ configurePopupGlassPane(),
10063
9984
  ], host: {
10064
- '[attr.data-severity]': 'severity()',
10065
- }, template: "@for (action of actions() | keyvalue:insertionSortOrderFn; track action.key) {\n <button #action_button\n (click)=\"onAction(action.key)\"\n (keydown.arrowLeft)=\"onArrowKey($index, 'left')\"\n (keydown.arrowRight)=\"onArrowKey($index, 'right')\"\n [attr.data-action]=\"action.key\"\n class=\"action e2e-action\">\n {{(action.value | wbText)()}}\n </button>\n\n @if (!$last) {\n <span class=\"divider\"></span>\n }\n}\n", styles: ["@charset \"UTF-8\";:host{--\\275message-box-severity-color: initial;display:flex;height:3em;background-color:var(--sci-color-background-secondary);color:var(--sci-color-text)}:host[data-severity=info]{--\\275message-box-severity-color: var(--sci-color-accent)}:host[data-severity=warn]{--\\275message-box-severity-color: var(--sci-color-notice)}:host[data-severity=error]{--\\275message-box-severity-color: var(--sci-color-negative)}:host.calculating-min-width{position:absolute}:host>button.action:is(button,#sci-reset){all:unset;flex:1;margin:2px;border:1px solid transparent;border-radius:var(--sci-corner-small);transition:border-color ease-in-out .15s;cursor:var(--sci-workbench-messagebox-action-cursor);-webkit-user-select:none;user-select:none;text-align:center;min-width:7.5em;padding-inline:.5em;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}:host>button.action:is(button,#sci-reset):focus,:host>button.action:is(button,#sci-reset):active{outline:none;color:var(--\\275message-box-severity-color);border-color:var(--\\275message-box-severity-color)}:host>button.action:is(button,#sci-reset):hover{background-color:var(--sci-workbench-messagebox-action-background-color-hover)}:host>span.divider{width:1px;background-color:var(--sci-color-border)}\n"] }]
10066
- }], ctorParameters: () => [], propDecorators: { actions: [{ type: i0.Input, args: [{ isSignal: true, alias: "actions", required: true }] }], severity: [{ type: i0.Input, args: [{ isSignal: true, alias: "severity", required: true }] }], action: [{ type: i0.Output, args: ["action"] }], preferredSizeChange: [{ type: i0.Output, args: ["preferredSizeChange"] }], _actionButtons: [{ type: i0.ViewChildren, args: ['action_button', { isSignal: true }] }] } });
9985
+ '[attr.data-popupid]': 'popup.id',
9986
+ '[style.width]': 'popup.size.width()',
9987
+ '[style.min-width]': 'popup.size.minWidth()',
9988
+ '[style.max-width]': 'popup.size.maxWidth()',
9989
+ '[style.height]': 'popup.size.height()',
9990
+ '[style.min-height]': 'popup.size.minHeight()',
9991
+ '[style.max-height]': 'popup.size.maxHeight()',
9992
+ '[class]': 'popup.cssClass()',
9993
+ }, template: "<sci-viewport cdkTrapFocus class=\"e2e-popup-viewport\" #focus_trap>\n <ng-container *ngComponentOutlet=\"popup.component; inputs: popup.inputs\"/>\n</sci-viewport>\n", styles: [":host{display:grid;grid-template-columns:100%;grid-template-rows:100%;outline:none;border-radius:var(--sci-corner);overflow:hidden}\n"] }]
9994
+ }], ctorParameters: () => [], propDecorators: { _cdkTrapFocus: [{ type: i0.ViewChild, args: ['focus_trap', { ...{ read: CdkTrapFocus }, isSignal: true }] }] } });
9995
+ /**
9996
+ * Blocks this popup when dialog(s) overlay it.
9997
+ */
9998
+ function configurePopupGlassPane() {
9999
+ return [
10000
+ {
10001
+ provide: GLASS_PANE_BLOCKABLE,
10002
+ useFactory: () => inject(ɵWorkbenchPopup),
10003
+ },
10004
+ {
10005
+ provide: GLASS_PANE_OPTIONS,
10006
+ useFactory: () => ({ attributes: { 'data-popupid': inject(ɵWorkbenchPopup).id } }),
10007
+ },
10008
+ ];
10009
+ }
10067
10010
 
10068
- /*
10069
- * Copyright (c) 2018-2023 Swiss Federal Railways
10011
+ /**
10012
+ * A popup is a visual workbench element for displaying content above other content. The popup is positioned relative
10013
+ * to an anchor based on its preferred alignment. The anchor can be an element or a coordinate.
10070
10014
  *
10071
- * This program and the accompanying materials are made
10072
- * available under the terms of the Eclipse Public License 2.0
10073
- * which is available at https://www.eclipse.org/legal/epl-2.0/
10015
+ * The popup component can inject this handle to interact with the popup.
10074
10016
  *
10075
- * SPDX-License-Identifier: EPL-2.0
10076
- */
10077
- /**
10078
- * Use this directive to replace the default dialog footer that renders actions contributed via the {@link WorkbenchDialogActionDirective} directive.
10017
+ * Popup inputs are available as input properties in the popup component.
10079
10018
  *
10080
- * The host element of this modeling directive must be a <ng-template>. The footer shares the lifecycle of the host element.
10019
+ * @see WorkbenchPopupService
10081
10020
  *
10082
- * **Example:**
10083
- * ```html
10084
- * <ng-template wbDialogFooter>
10085
- * <app-dialog-footer/>
10086
- * </ng-template>
10087
- * ```
10021
+ * @deprecated since version 21.0.0-beta.1. Replaced by `WorkbenchPopup`. Marked for removal in version 22.
10088
10022
  */
10089
- class WorkbenchDialogFooterDirective {
10090
- /**
10091
- * Specifies if to display a visual separator between the dialog content and this footer.
10092
- * Defaults to `true`.
10093
- */
10094
- divider = input(undefined, { ...(ngDevMode ? { debugName: "divider" } : {}), transform: booleanAttribute });
10095
- template = inject(TemplateRef);
10096
- _footer;
10097
- constructor() {
10098
- const dialog = inject(ɵWorkbenchDialog);
10099
- // Defer registering footer to avoid `ExpressionChangedAfterItHasBeenCheckedError`.
10100
- asapScheduler.schedule(() => this._footer = dialog.registerFooter(this));
10101
- // Defer disposing footer to avoid `ExpressionChangedAfterItHasBeenCheckedError`.
10102
- inject(DestroyRef).onDestroy(() => asapScheduler.schedule(() => this._footer?.dispose()));
10103
- }
10104
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchDialogFooterDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
10105
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.1", type: WorkbenchDialogFooterDirective, isStandalone: true, selector: "ng-template[wbDialogFooter]", inputs: { divider: { classPropertyName: "divider", publicName: "divider", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
10023
+ class Popup extends WorkbenchPopup {
10106
10024
  }
10107
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchDialogFooterDirective, decorators: [{
10108
- type: Directive,
10109
- args: [{ selector: 'ng-template[wbDialogFooter]' }]
10110
- }], ctorParameters: () => [], propDecorators: { divider: [{ type: i0.Input, args: [{ isSignal: true, alias: "divider", required: false }] }] } });
10111
10025
 
10112
10026
  /*
10113
- * Copyright (c) 2018-2023 Swiss Federal Railways
10027
+ * Copyright (c) 2018-2025 Swiss Federal Railways
10114
10028
  *
10115
10029
  * This program and the accompanying materials are made
10116
10030
  * available under the terms of the Eclipse Public License 2.0
@@ -10119,90 +10033,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImpor
10119
10033
  * SPDX-License-Identifier: EPL-2.0
10120
10034
  */
10121
10035
  /**
10122
- * Use this directive to replace the default dialog header that displays the title and a close button.
10123
- *
10124
- * The host element of this modeling directive must be a <ng-template>. The header shares the lifecycle of the host element.
10125
- *
10126
- * **Example:**
10127
- * ```html
10128
- * <ng-template wbDialogHeader>
10129
- * <app-dialog-header/>
10130
- * </ng-template>
10131
- * ```
10132
- */
10133
- class WorkbenchDialogHeaderDirective {
10134
- /**
10135
- * Specifies if to display a visual separator between this header and the dialog content.
10136
- * Defaults to `true`.
10137
- */
10138
- divider = input(undefined, { ...(ngDevMode ? { debugName: "divider" } : {}), transform: booleanAttribute });
10139
- template = inject(TemplateRef);
10140
- _header;
10141
- constructor() {
10142
- const dialog = inject(ɵWorkbenchDialog);
10143
- // Defer registering header to avoid `ExpressionChangedAfterItHasBeenCheckedError`.
10144
- asapScheduler.schedule(() => this._header = dialog.registerHeader(this));
10145
- // Defer disposing header to avoid `ExpressionChangedAfterItHasBeenCheckedError`.
10146
- inject(DestroyRef).onDestroy(() => asapScheduler.schedule(() => this._header?.dispose()));
10147
- }
10148
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchDialogHeaderDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
10149
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.1", type: WorkbenchDialogHeaderDirective, isStandalone: true, selector: "ng-template[wbDialogHeader]", inputs: { divider: { classPropertyName: "divider", publicName: "divider", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
10150
- }
10151
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchDialogHeaderDirective, decorators: [{
10152
- type: Directive,
10153
- args: [{ selector: 'ng-template[wbDialogHeader]' }]
10154
- }], ctorParameters: () => [], propDecorators: { divider: [{ type: i0.Input, args: [{ isSignal: true, alias: "divider", required: false }] }] } });
10155
-
10156
- /*
10157
- * Copyright (c) 2018-2023 Swiss Federal Railways
10158
- *
10159
- * This program and the accompanying materials are made
10160
- * available under the terms of the Eclipse Public License 2.0
10161
- * which is available at https://www.eclipse.org/legal/epl-2.0/
10162
- *
10163
- * SPDX-License-Identifier: EPL-2.0
10164
- */
10165
- class MessageBoxHeaderComponent {
10166
- title = input(undefined, { ...(ngDevMode ? { debugName: "title" } : {}) });
10167
- severity = input.required({ ...(ngDevMode ? { debugName: "severity" } : {}) });
10168
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: MessageBoxHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10169
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.1", type: MessageBoxHeaderComponent, isStandalone: true, selector: "wb-message-box-header", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, severity: { classPropertyName: "severity", publicName: "severity", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "attr.data-severity": "severity()" } }, ngImport: i0, template: "@if (title()) {\n <span class=\"title e2e-title\">{{(title() | wbText)()}}</span>\n}\n", styles: ["@charset \"UTF-8\";:host{--\\275message-box-severity-color: initial;display:grid;border-top:var(--sci-workbench-messagebox-severity-indicator-size) solid var(--\\275message-box-severity-color);padding-inline:var(--sci-workbench-messagebox-padding);padding-top:var(--sci-workbench-messagebox-padding);-webkit-user-select:none;user-select:none}:host[data-severity=info]{--\\275message-box-severity-color: var(--sci-color-accent)}:host[data-severity=warn]{--\\275message-box-severity-color: var(--sci-color-notice)}:host[data-severity=error]{--\\275message-box-severity-color: var(--sci-color-negative)}:host>span.title{word-break:break-word;white-space:pre-line;font-family:var(--sci-workbench-messagebox-title-font-family),sans-serif;font-size:var(--sci-workbench-messagebox-title-font-size);font-weight:var(--sci-workbench-messagebox-title-font-weight);text-align:var(--sci-workbench-messagebox-title-align)}\n"], dependencies: [{ kind: "pipe", type: TextPipe, name: "wbText" }] });
10170
- }
10171
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: MessageBoxHeaderComponent, decorators: [{
10172
- type: Component,
10173
- args: [{ selector: 'wb-message-box-header', imports: [
10174
- TextPipe,
10175
- ], host: {
10176
- '[attr.data-severity]': 'severity()',
10177
- }, template: "@if (title()) {\n <span class=\"title e2e-title\">{{(title() | wbText)()}}</span>\n}\n", styles: ["@charset \"UTF-8\";:host{--\\275message-box-severity-color: initial;display:grid;border-top:var(--sci-workbench-messagebox-severity-indicator-size) solid var(--\\275message-box-severity-color);padding-inline:var(--sci-workbench-messagebox-padding);padding-top:var(--sci-workbench-messagebox-padding);-webkit-user-select:none;user-select:none}:host[data-severity=info]{--\\275message-box-severity-color: var(--sci-color-accent)}:host[data-severity=warn]{--\\275message-box-severity-color: var(--sci-color-notice)}:host[data-severity=error]{--\\275message-box-severity-color: var(--sci-color-negative)}:host>span.title{word-break:break-word;white-space:pre-line;font-family:var(--sci-workbench-messagebox-title-font-family),sans-serif;font-size:var(--sci-workbench-messagebox-title-font-size);font-weight:var(--sci-workbench-messagebox-title-font-weight);text-align:var(--sci-workbench-messagebox-title-align)}\n"] }]
10178
- }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], severity: [{ type: i0.Input, args: [{ isSignal: true, alias: "severity", required: true }] }] } });
10179
-
10180
- /*
10181
- * Copyright (c) 2018-2024 Swiss Federal Railways
10182
- *
10183
- * This program and the accompanying materials are made
10184
- * available under the terms of the Eclipse Public License 2.0
10185
- * which is available at https://www.eclipse.org/legal/epl-2.0/
10186
- *
10187
- * SPDX-License-Identifier: EPL-2.0
10036
+ * DI token to register providers available for DI if in the context of a workbench popup.
10188
10037
  */
10038
+ const WORKBENCH_POPUP_CONTEXT = new InjectionToken('WORKBENCH_POPUP_CONTEXT');
10189
10039
  /**
10190
- * Tests if the object is of the specified type.
10040
+ * Provides providers available for DI if in the context of a workbench popup.
10191
10041
  */
10192
- class TypeofPipe {
10193
- transform(object, type) {
10194
- return typeof object === type;
10195
- }
10196
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: TypeofPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
10197
- static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.1", ngImport: i0, type: TypeofPipe, isStandalone: true, name: "wbTypeof" });
10042
+ function provideWorkbenchPopupContext() {
10043
+ return {
10044
+ provide: WORKBENCH_POPUP_CONTEXT,
10045
+ useFactory: () => [
10046
+ provideWorkbenchDialogService(),
10047
+ provideWorkbenchMessageBoxService(),
10048
+ provideWorkbenchPopupService(),
10049
+ ],
10050
+ multi: true,
10051
+ };
10198
10052
  }
10199
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: TypeofPipe, decorators: [{
10200
- type: Pipe,
10201
- args: [{ name: 'wbTypeof' }]
10202
- }] });
10203
10053
 
10204
10054
  /*
10205
- * Copyright (c) 2018-2023 Swiss Federal Railways
10055
+ * Copyright (c) 2018-2025 Swiss Federal Railways
10206
10056
  *
10207
10057
  * This program and the accompanying materials are made
10208
10058
  * available under the terms of the Eclipse Public License 2.0
@@ -10210,453 +10060,166 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImpor
10210
10060
  *
10211
10061
  * SPDX-License-Identifier: EPL-2.0
10212
10062
  */
10213
- /**
10214
- * Renders the workbench message box.
10215
- *
10216
- * This component is designed to be opened in a workbench dialog.
10217
- */
10218
- class WorkbenchMessageBoxComponent {
10219
- message = input.required({ ...(ngDevMode ? { debugName: "message" } : {}), transform: nullIfEmptyMessage });
10220
- options = input(undefined, { ...(ngDevMode ? { debugName: "options" } : {}) });
10221
- _dialog = inject(ɵWorkbenchDialog);
10222
- empty = signal(false, { ...(ngDevMode ? { debugName: "empty" } : {}) });
10223
- constructor() {
10224
- this._dialog.closable = false;
10225
- this._dialog.resizable = false;
10226
- this._dialog.padding = false;
10227
- // Limit the maximum messagebox width if text message to break the message.
10228
- effect(() => {
10229
- if (typeof this.message() === 'string' || this.message() === null) {
10230
- this._dialog.size.maxWidth = 'var(--sci-workbench-messagebox-max-width)';
10063
+ /** @inheritDoc */
10064
+ class ɵWorkbenchPopup {
10065
+ id;
10066
+ component;
10067
+ invocationContext;
10068
+ _options;
10069
+ /** Injector for the popup; destroyed when the popup is closed. */
10070
+ injector = inject(Injector);
10071
+ _overlayRef;
10072
+ _focusMonitor = inject(WorkbenchFocusMonitor);
10073
+ _portal;
10074
+ _componentRef = signal(undefined, { ...(ngDevMode ? { debugName: "_componentRef" } : {}) });
10075
+ _popupOrigin;
10076
+ _cssClass = signal([], { ...(ngDevMode ? { debugName: "_cssClass" } : {}) });
10077
+ size;
10078
+ focused = computed(() => this._focusMonitor.activeElement()?.id === this.id, { ...(ngDevMode ? { debugName: "focused" } : {}) });
10079
+ attached;
10080
+ destroyed = signal(false, { ...(ngDevMode ? { debugName: "destroyed" } : {}) });
10081
+ bounds = boundingClientRect(computed(() => this._componentRef()?.location.nativeElement));
10082
+ blockedBy;
10083
+ // TODO [Angular 22] Remove with Angular 22. Used for backward compatiblity.
10084
+ input;
10085
+ result;
10086
+ constructor(id, component, invocationContext, _options) {
10087
+ this.id = id;
10088
+ this.component = component;
10089
+ this.invocationContext = invocationContext;
10090
+ this._options = _options;
10091
+ this._portal = this.createPortal();
10092
+ this.input = this._portal.injector?.get(LEGACY_POPUP_INPUT, undefined, { optional: true });
10093
+ this._popupOrigin = this.trackPopupOrigin();
10094
+ this._cssClass.set(Arrays.coerce(this._options.cssClass));
10095
+ this.size = new ɵWorkbenchPopupSize(this._options);
10096
+ this.blockedBy = inject(WorkbenchDialogRegistry).top(this.id);
10097
+ this.attached = this.monitorHostElementAttached();
10098
+ const positionStrategy = inject(Overlay).position()
10099
+ .flexibleConnectedTo({ x: 0, y: 0 })
10100
+ .withFlexibleDimensions(false)
10101
+ .withLockedPosition(false) // If locked, the popup won't attempt to reposition itself if not enough space available.
10102
+ .withPositions((() => {
10103
+ switch (this._options.align ?? 'north') {
10104
+ case 'north':
10105
+ return [NORTH, SOUTH, WEST, EAST];
10106
+ case 'south':
10107
+ return [SOUTH, NORTH, WEST, EAST];
10108
+ case 'west':
10109
+ return [WEST, EAST, NORTH, SOUTH];
10110
+ case 'east':
10111
+ return [EAST, WEST, NORTH, SOUTH];
10112
+ default:
10113
+ throw Error('[PopupPositionError] Illegal position; must be north, south, west or east');
10231
10114
  }
10115
+ })());
10116
+ this._overlayRef = this.createOverlay(positionStrategy);
10117
+ const componentRef = this._overlayRef.attach(this._portal);
10118
+ const popupElement = componentRef.location.nativeElement;
10119
+ this._componentRef.set(componentRef);
10120
+ // Make the popup focusable to request focus.
10121
+ popupElement.setAttribute('tabindex', '-1');
10122
+ this.stickToAnchor(positionStrategy);
10123
+ this.repositionOnResize();
10124
+ this.bindToHostElement(popupElement);
10125
+ this.closeOnHostDestroy();
10126
+ this.closeOnFocusLoss(popupElement);
10127
+ this.closeOnEscape(popupElement);
10128
+ inject(DestroyRef).onDestroy(() => this.destroyed.set(true));
10129
+ }
10130
+ /**
10131
+ * Waits for the popup to close, resolving to its result or rejecting if closed with an error.
10132
+ */
10133
+ waitForClose() {
10134
+ return new Promise((resolve, reject) => {
10135
+ this.injector.get(DestroyRef).onDestroy(() => {
10136
+ this.result instanceof Error ? reject(this.result) : resolve(this.result);
10137
+ });
10232
10138
  });
10233
10139
  }
10234
- onAction(action) {
10235
- this._dialog.close(action);
10140
+ /**
10141
+ * Creates a portal to render {@link WorkbenchPopupComponent} in the popup's injection context.
10142
+ */
10143
+ createPortal() {
10144
+ const injector = Injector.create({
10145
+ parent: this._options.injector ?? inject(Injector),
10146
+ providers: [
10147
+ { provide: ɵWorkbenchPopup, useValue: this },
10148
+ { provide: WorkbenchPopup, useExisting: ɵWorkbenchPopup },
10149
+ { provide: Popup, useExisting: ɵWorkbenchPopup },
10150
+ { provide: WORKBENCH_ELEMENT, useExisting: ɵWorkbenchPopup },
10151
+ inject(WORKBENCH_POPUP_CONTEXT, { optional: true }) ?? [],
10152
+ this._options.providers ?? [],
10153
+ ],
10154
+ });
10155
+ inject(DestroyRef).onDestroy(() => injector.destroy());
10156
+ return new ComponentPortal(WorkbenchPopupComponent, null, injector);
10236
10157
  }
10237
- onEscape() {
10238
- if ('cancel' in (this.options()?.actions ?? {})) {
10239
- this._dialog.close('cancel');
10158
+ /**
10159
+ * Creates a dedicated overlay per popup to place it on top of previously created overlays, such as dialogs, popups, dropdowns, etc.
10160
+ */
10161
+ createOverlay(positionStrategy) {
10162
+ return inject(Overlay).create({
10163
+ panelClass: 'wb-popup',
10164
+ hasBackdrop: false,
10165
+ positionStrategy: positionStrategy,
10166
+ scrollStrategy: inject(Overlay).scrollStrategies.noop(),
10167
+ disposeOnNavigation: true,
10168
+ });
10169
+ }
10170
+ /**
10171
+ * Moves the popup with the anchor.
10172
+ */
10173
+ stickToAnchor(positionStrategy) {
10174
+ effect(() => {
10175
+ const origin = this._popupOrigin();
10176
+ untracked(() => {
10177
+ if (origin) {
10178
+ positionStrategy.setOrigin(origin);
10179
+ this._overlayRef.updatePosition();
10180
+ }
10181
+ });
10182
+ });
10183
+ }
10184
+ /**
10185
+ * Monitors attachment of the host element.
10186
+ */
10187
+ monitorHostElementAttached() {
10188
+ if (this.invocationContext) {
10189
+ return this.invocationContext.attached;
10240
10190
  }
10191
+ const workbenchComponentRef = inject(WORKBENCH_COMPONENT_REF);
10192
+ return computed(() => !!workbenchComponentRef());
10241
10193
  }
10242
- onFooterPreferredSizeChange(preferredSize) {
10243
- this._dialog.size.minWidth = `${preferredSize}px`;
10194
+ /**
10195
+ * Repositions the popup position when resized.
10196
+ */
10197
+ repositionOnResize() {
10198
+ const zone = inject(NgZone);
10199
+ fromResize$(this._overlayRef.overlayElement)
10200
+ .pipe(observeIn(fn => zone.run(fn)), takeUntilDestroyed())
10201
+ .subscribe(() => {
10202
+ this._overlayRef.updatePosition();
10203
+ });
10244
10204
  }
10245
- onContentDimensionChange(dimension) {
10246
- this.empty.set(!dimension.offsetHeight);
10247
- }
10248
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchMessageBoxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10249
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.1", type: WorkbenchMessageBoxComponent, isStandalone: true, selector: "wb-message-box", inputs: { message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: true, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "keydown.escape": "onEscape()" }, properties: { "attr.tabindex": "-1", "class.empty": "empty()", "class.content-selectable": "options()?.contentSelectable", "class.has-title": "!!this.options()?.title" } }, ngImport: i0, template: "@let options = this.options() ?? {};\n<ng-template wbDialogHeader [divider]=\"false\">\n <wb-message-box-header [title]=\"options.title\" [severity]=\"options.severity ?? 'info'\"/>\n</ng-template>\n\n@let message = this.message();\n<div class=\"message e2e-message\" [class.text]=\"message | wbTypeof:'string'\" sciDimension (sciDimensionChange)=\"onContentDimensionChange($event)\">\n @if (message | wbTypeof:'string') {\n {{($any(message) | wbText)()}}\n } @else {\n <ng-container *ngComponentOutlet=\"message; inputs: options.inputs\"/>\n }\n</div>\n\n<ng-template wbDialogFooter>\n <wb-message-box-footer [actions]=\"options.actions ?? {ok: '%workbench.ok.action'}\"\n [severity]=\"options.severity ?? 'info'\"\n (action)=\"onAction($event)\"\n (keydown.escape)=\"onEscape()\"\n (preferredSizeChange)=\"onFooterPreferredSizeChange($event)\"/>\n</ng-template>\n", styles: [":host{display:grid;outline:none;padding-inline:var(--sci-workbench-messagebox-padding);padding-bottom:var(--sci-workbench-messagebox-padding)}:host.has-title:not(.empty){padding-top:var(--sci-workbench-messagebox-padding)}:host:not(.content-selectable){-webkit-user-select:none;user-select:none}:host>div.message.text{word-break:break-word;white-space:pre-line;text-align:var(--sci-workbench-messagebox-text-align)}\n"], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: SciDimensionDirective, selector: "[sciDimension]", inputs: ["emitOutsideAngular"], outputs: ["sciDimensionChange"] }, { kind: "directive", type: WorkbenchDialogHeaderDirective, selector: "ng-template[wbDialogHeader]", inputs: ["divider"] }, { kind: "directive", type: WorkbenchDialogFooterDirective, selector: "ng-template[wbDialogFooter]", inputs: ["divider"] }, { kind: "component", type: MessageBoxHeaderComponent, selector: "wb-message-box-header", inputs: ["title", "severity"] }, { kind: "component", type: MessageBoxFooterComponent, selector: "wb-message-box-footer", inputs: ["actions", "severity"], outputs: ["action", "preferredSizeChange"] }, { kind: "pipe", type: TypeofPipe, name: "wbTypeof" }, { kind: "pipe", type: TextPipe, name: "wbText" }] });
10250
- }
10251
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchMessageBoxComponent, decorators: [{
10252
- type: Component,
10253
- args: [{ selector: 'wb-message-box', imports: [
10254
- NgComponentOutlet,
10255
- SciDimensionDirective,
10256
- WorkbenchDialogHeaderDirective,
10257
- WorkbenchDialogFooterDirective,
10258
- MessageBoxHeaderComponent,
10259
- MessageBoxFooterComponent,
10260
- TypeofPipe,
10261
- TextPipe,
10262
- ], host: {
10263
- // Ensure host element to be focusable in order to close the message box on Escape keystroke.
10264
- '[attr.tabindex]': '-1',
10265
- '[class.empty]': 'empty()',
10266
- '[class.content-selectable]': 'options()?.contentSelectable',
10267
- '[class.has-title]': '!!this.options()?.title',
10268
- '(keydown.escape)': 'onEscape()',
10269
- }, template: "@let options = this.options() ?? {};\n<ng-template wbDialogHeader [divider]=\"false\">\n <wb-message-box-header [title]=\"options.title\" [severity]=\"options.severity ?? 'info'\"/>\n</ng-template>\n\n@let message = this.message();\n<div class=\"message e2e-message\" [class.text]=\"message | wbTypeof:'string'\" sciDimension (sciDimensionChange)=\"onContentDimensionChange($event)\">\n @if (message | wbTypeof:'string') {\n {{($any(message) | wbText)()}}\n } @else {\n <ng-container *ngComponentOutlet=\"message; inputs: options.inputs\"/>\n }\n</div>\n\n<ng-template wbDialogFooter>\n <wb-message-box-footer [actions]=\"options.actions ?? {ok: '%workbench.ok.action'}\"\n [severity]=\"options.severity ?? 'info'\"\n (action)=\"onAction($event)\"\n (keydown.escape)=\"onEscape()\"\n (preferredSizeChange)=\"onFooterPreferredSizeChange($event)\"/>\n</ng-template>\n", styles: [":host{display:grid;outline:none;padding-inline:var(--sci-workbench-messagebox-padding);padding-bottom:var(--sci-workbench-messagebox-padding)}:host.has-title:not(.empty){padding-top:var(--sci-workbench-messagebox-padding)}:host:not(.content-selectable){-webkit-user-select:none;user-select:none}:host>div.message.text{word-break:break-word;white-space:pre-line;text-align:var(--sci-workbench-messagebox-text-align)}\n"] }]
10270
- }], ctorParameters: () => [], propDecorators: { message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: true }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }] } });
10271
- function nullIfEmptyMessage(message) {
10272
- return message !== '' ? message : null;
10273
- }
10274
-
10275
- /**
10276
- * Provides a standardized dialog for presenting a message to the user, such as an info, warning or alert,
10277
- * or for prompting the user for confirmation. The message can be plain text or a component, allowing for
10278
- * structured content or input prompts.
10279
- *
10280
- * Displayed on top of other content, a modal message box blocks interaction with other parts of the application.
10281
- *
10282
- * ## Modality
10283
- * A message box can be context-modal or application-modal. Context-modal blocks a specific part of the application, as specified by the context;
10284
- * application-modal blocks the workbench or browser viewport, based on {@link WorkbenchConfig.dialog.modalityScope}.
10285
- *
10286
- * ## Context
10287
- * A message box can be bound to a context (e.g., part or view), defaulting to the calling context.
10288
- * The message box is displayed only if the context is visible and closes when the context is disposed.
10289
- *
10290
- * ## Positioning
10291
- * A message box is opened in the center of its context, if any, unless opened from the peripheral area.
10292
- *
10293
- * ## Stacking
10294
- * Message boxes are stacked per modality, with only the topmost message box in each stack being interactive.
10295
- *
10296
- * ## Styling
10297
- * The following CSS variables can be set to customize the default look of a message box.
10298
- *
10299
- * - `--sci-workbench-messagebox-max-width`
10300
- * - `--sci-workbench-messagebox-severity-indicator-size`
10301
- * - `--sci-workbench-messagebox-padding`
10302
- * - `--sci-workbench-messagebox-text-align`
10303
- * - `--sci-workbench-messagebox-title-align`
10304
- * - `--sci-workbench-messagebox-title-font-family`
10305
- * - `--sci-workbench-messagebox-title-font-weight`
10306
- * - `--sci-workbench-messagebox-title-font-size`
10307
- */
10308
- class WorkbenchMessageBoxService {
10309
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchMessageBoxService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
10310
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchMessageBoxService, providedIn: 'root', useExisting: ɵWorkbenchMessageBoxService });
10311
- }
10312
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchMessageBoxService, decorators: [{
10313
- type: Injectable,
10314
- args: [{ providedIn: 'root', useExisting: ɵWorkbenchMessageBoxService }]
10315
- }] });
10316
-
10317
- /*
10318
- * Copyright (c) 2018-2023 Swiss Federal Railways
10319
- *
10320
- * This program and the accompanying materials are made
10321
- * available under the terms of the Eclipse Public License 2.0
10322
- * which is available at https://www.eclipse.org/legal/epl-2.0/
10323
- *
10324
- * SPDX-License-Identifier: EPL-2.0
10325
- */
10326
- /** @inheritDoc */
10327
- class ɵWorkbenchMessageBoxService {
10328
- _workbenchDialogService = inject(WorkbenchDialogService);
10329
- _zone = inject(NgZone);
10330
- constructor() {
10331
- this.installServiceLifecycleLogger();
10332
- }
10333
- /**
10334
- * @inheritDoc
10335
- */
10336
- async open(message, options) {
10337
- assertNotInReactiveContext(this.open, 'Call WorkbenchMessageBoxService.open() in a non-reactive (non-tracking) context, such as within the untracked() function.');
10338
- // Ensure to run in Angular zone to display the message box even if called from outside the Angular zone, e.g. from an error handler.
10339
- if (!NgZone.isInAngularZone()) {
10340
- return this._zone.run(() => this.open(message, options));
10341
- }
10342
- return (await this._workbenchDialogService.open(WorkbenchMessageBoxComponent, {
10343
- inputs: { message, options },
10344
- modality: options?.modality,
10345
- injector: options?.injector,
10346
- providers: options?.providers,
10347
- cssClass: options?.cssClass,
10348
- context: options?.context,
10349
- animate: true,
10350
- }));
10351
- }
10352
- installServiceLifecycleLogger() {
10353
- const logger = inject(Logger);
10354
- const workbenchElement = inject(WORKBENCH_ELEMENT, { optional: true });
10355
- logger.debug(() => `Constructing WorkbenchMessageBoxService [context=${workbenchElement?.id}]`, LoggerNames.LIFECYCLE);
10356
- inject(DestroyRef).onDestroy(() => logger.debug(() => `Destroying WorkbenchMessageBoxService [context=${workbenchElement?.id}]'`, LoggerNames.LIFECYCLE));
10357
- }
10358
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: ɵWorkbenchMessageBoxService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
10359
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: ɵWorkbenchMessageBoxService, providedIn: 'root' });
10360
- }
10361
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: ɵWorkbenchMessageBoxService, decorators: [{
10362
- type: Injectable,
10363
- args: [{ providedIn: 'root' }]
10364
- }], ctorParameters: () => [] });
10365
- /**
10366
- * Provides {@link WorkbenchDialogService} for dependency injection.
10367
- */
10368
- function provideWorkbenchMessageBoxService() {
10369
- return [
10370
- ɵWorkbenchMessageBoxService,
10371
- { provide: WorkbenchMessageBoxService, useExisting: ɵWorkbenchMessageBoxService },
10372
- ];
10373
- }
10374
-
10375
- /*
10376
- * Copyright (c) 2018-2022 Swiss Federal Railways
10377
- *
10378
- * This program and the accompanying materials are made
10379
- * available under the terms of the Eclipse Public License 2.0
10380
- * which is available at https://www.eclipse.org/legal/epl-2.0/
10381
- *
10382
- * SPDX-License-Identifier: EPL-2.0
10383
- */
10384
- /**
10385
- * Renders the content of a workbench popup.
10386
- */
10387
- class WorkbenchPopupComponent {
10388
- _host = inject(ElementRef).nativeElement;
10389
- _cdkTrapFocus = viewChild.required('focus_trap', { read: CdkTrapFocus });
10390
- popup = inject(ɵWorkbenchPopup);
10391
- constructor() {
10392
- trackFocus(this._host, this.popup);
10393
- this.focusInitialElement();
10394
- }
10395
- focusInitialElement() {
10396
- const effectRef = effect(() => {
10397
- // [Angular 14] The initial focus must not be requested via `cdkTrapFocusAutoCapture` as this would restore
10398
- // focus to the previously focused element when the `FocusTrap` is destroyed. This behavior is unwanted if the
10399
- // popup is closed by losing focus. Otherwise, the newly focused element that caused the loss of focus and thus
10400
- // the closing of the popup would immediately become unfocused again. This behavior could only be observed when
10401
- // the popup loses focus by clicking on an element in a microfrontend.
10402
- void this._cdkTrapFocus().focusTrap.focusInitialElementWhenReady();
10403
- effectRef.destroy();
10404
- }, { ...(ngDevMode ? { debugName: "effectRef" } : {}) });
10405
- }
10406
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchPopupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10407
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.0.1", type: WorkbenchPopupComponent, isStandalone: true, selector: "wb-popup", host: { properties: { "attr.data-popupid": "popup.id", "style.width": "popup.size.width()", "style.min-width": "popup.size.minWidth()", "style.max-width": "popup.size.maxWidth()", "style.height": "popup.size.height()", "style.min-height": "popup.size.minHeight()", "style.max-height": "popup.size.maxHeight()", "class": "popup.cssClass()" } }, providers: [
10408
- configurePopupGlassPane(),
10409
- ], viewQueries: [{ propertyName: "_cdkTrapFocus", first: true, predicate: ["focus_trap"], descendants: true, read: CdkTrapFocus, isSignal: true }], hostDirectives: [{ directive: GlassPaneDirective }], ngImport: i0, template: "<sci-viewport cdkTrapFocus class=\"e2e-popup-viewport\" #focus_trap>\n <ng-container *ngComponentOutlet=\"popup.component; inputs: popup.inputs\"/>\n</sci-viewport>\n", styles: [":host{display:grid;grid-template-columns:100%;grid-template-rows:100%;outline:none;border-radius:var(--sci-corner);overflow:hidden}\n"], dependencies: [{ kind: "directive", type: CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: SciViewportComponent, selector: "sci-viewport", inputs: ["scrollbarStyle"], outputs: ["scroll"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }] });
10410
- }
10411
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchPopupComponent, decorators: [{
10412
- type: Component,
10413
- args: [{ selector: 'wb-popup', imports: [
10414
- CdkTrapFocus,
10415
- SciViewportComponent,
10416
- NgComponentOutlet,
10417
- ], hostDirectives: [
10418
- GlassPaneDirective,
10419
- ], providers: [
10420
- configurePopupGlassPane(),
10421
- ], host: {
10422
- '[attr.data-popupid]': 'popup.id',
10423
- '[style.width]': 'popup.size.width()',
10424
- '[style.min-width]': 'popup.size.minWidth()',
10425
- '[style.max-width]': 'popup.size.maxWidth()',
10426
- '[style.height]': 'popup.size.height()',
10427
- '[style.min-height]': 'popup.size.minHeight()',
10428
- '[style.max-height]': 'popup.size.maxHeight()',
10429
- '[class]': 'popup.cssClass()',
10430
- }, template: "<sci-viewport cdkTrapFocus class=\"e2e-popup-viewport\" #focus_trap>\n <ng-container *ngComponentOutlet=\"popup.component; inputs: popup.inputs\"/>\n</sci-viewport>\n", styles: [":host{display:grid;grid-template-columns:100%;grid-template-rows:100%;outline:none;border-radius:var(--sci-corner);overflow:hidden}\n"] }]
10431
- }], ctorParameters: () => [], propDecorators: { _cdkTrapFocus: [{ type: i0.ViewChild, args: ['focus_trap', { ...{ read: CdkTrapFocus }, isSignal: true }] }] } });
10432
- /**
10433
- * Blocks this popup when dialog(s) overlay it.
10434
- */
10435
- function configurePopupGlassPane() {
10436
- return [
10437
- {
10438
- provide: GLASS_PANE_BLOCKABLE,
10439
- useFactory: () => inject(ɵWorkbenchPopup),
10440
- },
10441
- {
10442
- provide: GLASS_PANE_OPTIONS,
10443
- useFactory: () => ({ attributes: { 'data-popupid': inject(ɵWorkbenchPopup).id } }),
10444
- },
10445
- ];
10446
- }
10447
-
10448
- /**
10449
- * A popup is a visual workbench element for displaying content above other content. The popup is positioned relative
10450
- * to an anchor based on its preferred alignment. The anchor can be an element or a coordinate.
10451
- *
10452
- * The popup component can inject this handle to interact with the popup.
10453
- *
10454
- * Popup inputs are available as input properties in the popup component.
10455
- *
10456
- * @see WorkbenchPopupService
10457
- *
10458
- * @deprecated since version 21.0.0-beta.1. Replaced by `WorkbenchPopup`. Marked for removal in version 22.
10459
- */
10460
- class Popup extends WorkbenchPopup {
10461
- }
10462
-
10463
- /*
10464
- * Copyright (c) 2018-2025 Swiss Federal Railways
10465
- *
10466
- * This program and the accompanying materials are made
10467
- * available under the terms of the Eclipse Public License 2.0
10468
- * which is available at https://www.eclipse.org/legal/epl-2.0/
10469
- *
10470
- * SPDX-License-Identifier: EPL-2.0
10471
- */
10472
- /**
10473
- * DI token to register providers available for DI if in the context of a workbench popup.
10474
- */
10475
- const WORKBENCH_POPUP_CONTEXT = new InjectionToken('WORKBENCH_POPUP_CONTEXT');
10476
- /**
10477
- * Provides providers available for DI if in the context of a workbench popup.
10478
- */
10479
- function provideWorkbenchPopupContext() {
10480
- return {
10481
- provide: WORKBENCH_POPUP_CONTEXT,
10482
- useFactory: () => [
10483
- provideWorkbenchDialogService(),
10484
- provideWorkbenchMessageBoxService(),
10485
- provideWorkbenchPopupService(),
10486
- ],
10487
- multi: true,
10488
- };
10489
- }
10490
-
10491
- /*
10492
- * Copyright (c) 2018-2025 Swiss Federal Railways
10493
- *
10494
- * This program and the accompanying materials are made
10495
- * available under the terms of the Eclipse Public License 2.0
10496
- * which is available at https://www.eclipse.org/legal/epl-2.0/
10497
- *
10498
- * SPDX-License-Identifier: EPL-2.0
10499
- */
10500
- /** @inheritDoc */
10501
- class ɵWorkbenchPopup {
10502
- id;
10503
- component;
10504
- invocationContext;
10505
- _options;
10506
- /** Injector for the popup; destroyed when the popup is closed. */
10507
- injector = inject(Injector);
10508
- _overlayRef;
10509
- _focusMonitor = inject(WorkbenchFocusMonitor);
10510
- _portal;
10511
- _componentRef = signal(undefined, { ...(ngDevMode ? { debugName: "_componentRef" } : {}) });
10512
- _popupOrigin;
10513
- _cssClass = signal([], { ...(ngDevMode ? { debugName: "_cssClass" } : {}) });
10514
- size;
10515
- focused = computed(() => this._focusMonitor.activeElement()?.id === this.id, { ...(ngDevMode ? { debugName: "focused" } : {}) });
10516
- attached;
10517
- destroyed = signal(false, { ...(ngDevMode ? { debugName: "destroyed" } : {}) });
10518
- bounds = boundingClientRect(computed(() => this._componentRef()?.location.nativeElement));
10519
- blockedBy;
10520
- // TODO [Angular 22] Remove with Angular 22. Used for backward compatiblity.
10521
- input;
10522
- result;
10523
- constructor(id, component, invocationContext, _options) {
10524
- this.id = id;
10525
- this.component = component;
10526
- this.invocationContext = invocationContext;
10527
- this._options = _options;
10528
- this._portal = this.createPortal();
10529
- this.input = this._portal.injector?.get(LEGACY_POPUP_INPUT, undefined, { optional: true });
10530
- this._popupOrigin = this.trackPopupOrigin();
10531
- this._cssClass.set(Arrays.coerce(this._options.cssClass));
10532
- this.size = new ɵWorkbenchPopupSize(this._options);
10533
- this.blockedBy = inject(WorkbenchDialogRegistry).top(this.id);
10534
- this.attached = this.monitorHostElementAttached();
10535
- const positionStrategy = inject(Overlay).position()
10536
- .flexibleConnectedTo({ x: 0, y: 0 })
10537
- .withFlexibleDimensions(false)
10538
- .withLockedPosition(false) // If locked, the popup won't attempt to reposition itself if not enough space available.
10539
- .withPositions((() => {
10540
- switch (this._options.align ?? 'north') {
10541
- case 'north':
10542
- return [NORTH, SOUTH, WEST, EAST];
10543
- case 'south':
10544
- return [SOUTH, NORTH, WEST, EAST];
10545
- case 'west':
10546
- return [WEST, EAST, NORTH, SOUTH];
10547
- case 'east':
10548
- return [EAST, WEST, NORTH, SOUTH];
10549
- default:
10550
- throw Error('[PopupPositionError] Illegal position; must be north, south, west or east');
10551
- }
10552
- })());
10553
- this._overlayRef = this.createOverlay(positionStrategy);
10554
- const componentRef = this._overlayRef.attach(this._portal);
10555
- const popupElement = componentRef.location.nativeElement;
10556
- this._componentRef.set(componentRef);
10557
- // Make the popup focusable to request focus.
10558
- popupElement.setAttribute('tabindex', '-1');
10559
- this.stickToAnchor(positionStrategy);
10560
- this.repositionOnResize();
10561
- this.bindToHostElement(popupElement);
10562
- this.closeOnHostDestroy();
10563
- this.closeOnFocusLoss(popupElement);
10564
- this.closeOnEscape(popupElement);
10565
- inject(DestroyRef).onDestroy(() => this.destroyed.set(true));
10566
- }
10567
- /**
10568
- * Waits for the popup to close, resolving to its result or rejecting if closed with an error.
10569
- */
10570
- waitForClose() {
10571
- return new Promise((resolve, reject) => {
10572
- this.injector.get(DestroyRef).onDestroy(() => {
10573
- this.result instanceof Error ? reject(this.result) : resolve(this.result);
10574
- });
10575
- });
10576
- }
10577
- /**
10578
- * Creates a portal to render {@link WorkbenchPopupComponent} in the popup's injection context.
10579
- */
10580
- createPortal() {
10581
- const injector = Injector.create({
10582
- parent: this._options.injector ?? inject(Injector),
10583
- providers: [
10584
- { provide: ɵWorkbenchPopup, useValue: this },
10585
- { provide: WorkbenchPopup, useExisting: ɵWorkbenchPopup },
10586
- { provide: Popup, useExisting: ɵWorkbenchPopup },
10587
- { provide: WORKBENCH_ELEMENT, useExisting: ɵWorkbenchPopup },
10588
- inject(WORKBENCH_POPUP_CONTEXT, { optional: true }) ?? [],
10589
- this._options.providers ?? [],
10590
- ],
10591
- });
10592
- inject(DestroyRef).onDestroy(() => injector.destroy());
10593
- return new ComponentPortal(WorkbenchPopupComponent, null, injector);
10594
- }
10595
- /**
10596
- * Creates a dedicated overlay per popup to place it on top of previously created overlays, such as dialogs, popups, dropdowns, etc.
10597
- */
10598
- createOverlay(positionStrategy) {
10599
- return inject(Overlay).create({
10600
- panelClass: 'wb-popup',
10601
- hasBackdrop: false,
10602
- positionStrategy: positionStrategy,
10603
- scrollStrategy: inject(Overlay).scrollStrategies.noop(),
10604
- disposeOnNavigation: true,
10605
- });
10606
- }
10607
- /**
10608
- * Moves the popup with the anchor.
10609
- */
10610
- stickToAnchor(positionStrategy) {
10611
- effect(() => {
10612
- const origin = this._popupOrigin();
10613
- untracked(() => {
10614
- if (origin) {
10615
- positionStrategy.setOrigin(origin);
10616
- this._overlayRef.updatePosition();
10617
- }
10618
- });
10619
- });
10620
- }
10621
- /**
10622
- * Monitors attachment of the host element.
10623
- */
10624
- monitorHostElementAttached() {
10625
- if (this.invocationContext) {
10626
- return this.invocationContext.attached;
10627
- }
10628
- const workbenchComponentRef = inject(WORKBENCH_COMPONENT_REF);
10629
- return computed(() => !!workbenchComponentRef());
10630
- }
10631
- /**
10632
- * Repositions the popup position when resized.
10633
- */
10634
- repositionOnResize() {
10635
- const zone = inject(NgZone);
10636
- fromResize$(this._overlayRef.overlayElement)
10637
- .pipe(observeIn(fn => zone.run(fn)), takeUntilDestroyed())
10638
- .subscribe(() => {
10639
- this._overlayRef.updatePosition();
10640
- });
10641
- }
10642
- /**
10643
- * Closes the popup on focus loss, if configured.
10644
- */
10645
- closeOnFocusLoss(popupElement) {
10646
- const focusMonitor = inject(FocusMonitor);
10647
- if (this._options.closeStrategy?.onFocusLost ?? true) {
10648
- effect(onCleanup => {
10649
- if (!this.attached() || !this._popupOrigin()) {
10650
- return;
10651
- }
10652
- untracked(() => {
10653
- const subscription = focusMonitor.monitor(popupElement, true)
10654
- .pipe(filter((focusOrigin) => !focusOrigin))
10655
- .subscribe(() => this.close(this.result));
10656
- onCleanup(() => subscription.unsubscribe());
10657
- });
10658
- });
10659
- }
10205
+ /**
10206
+ * Closes the popup on focus loss, if configured.
10207
+ */
10208
+ closeOnFocusLoss(popupElement) {
10209
+ const focusMonitor = inject(FocusMonitor);
10210
+ if (this._options.closeStrategy?.onFocusLost ?? true) {
10211
+ effect(onCleanup => {
10212
+ if (!this.attached() || !this._popupOrigin()) {
10213
+ return;
10214
+ }
10215
+ untracked(() => {
10216
+ const subscription = focusMonitor.monitor(popupElement, true)
10217
+ .pipe(filter((focusOrigin) => !focusOrigin))
10218
+ .subscribe(() => this.close(this.result));
10219
+ onCleanup(() => subscription.unsubscribe());
10220
+ });
10221
+ });
10222
+ }
10660
10223
  }
10661
10224
  /**
10662
10225
  * Closes the popup on escape keystroke.
@@ -10923,6 +10486,38 @@ const LEGACY_POPUP_INPUT = new InjectionToken('LEGACY_POPUP_INPUT');
10923
10486
  *
10924
10487
  * SPDX-License-Identifier: EPL-2.0
10925
10488
  */
10489
+ /**
10490
+ * Registry for {@link WorkbenchNotification} elements.
10491
+ */
10492
+ class WorkbenchNotificationRegistry extends WorkbenchElementRegistry {
10493
+ /**
10494
+ * Gets the most recently opened notification.
10495
+ */
10496
+ top;
10497
+ constructor() {
10498
+ super({
10499
+ nullElementErrorFn: notificationId => Error(`[NullNotificationError] Notification '${notificationId}' not found.`),
10500
+ onUnregister: notification => notification.destroy(),
10501
+ });
10502
+ this.top = computed(() => this.elements().at(-1), { ...(ngDevMode ? { debugName: "top" } : {}) });
10503
+ }
10504
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchNotificationRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
10505
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchNotificationRegistry, providedIn: 'root' });
10506
+ }
10507
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchNotificationRegistry, decorators: [{
10508
+ type: Injectable,
10509
+ args: [{ providedIn: 'root' }]
10510
+ }], ctorParameters: () => [] });
10511
+
10512
+ /*
10513
+ * Copyright (c) 2018-2026 Swiss Federal Railways
10514
+ *
10515
+ * This program and the accompanying materials are made
10516
+ * available under the terms of the Eclipse Public License 2.0
10517
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
10518
+ *
10519
+ * SPDX-License-Identifier: EPL-2.0
10520
+ */
10926
10521
  /**
10927
10522
  * Creates the invocation context for given element.
10928
10523
  *
@@ -10978,7 +10573,76 @@ function createInvocationContext(elementId, options) {
10978
10573
  peripheral: signal(false),
10979
10574
  };
10980
10575
  }
10981
- return null;
10576
+ else if (isNotificationId(contextualElementId)) {
10577
+ const notification = injector.get(WorkbenchNotificationRegistry).get(contextualElementId);
10578
+ return {
10579
+ elementId: notification.id,
10580
+ attached: computed(() => !notification.destroyed()),
10581
+ bounds: notification.bounds,
10582
+ destroyed: notification.destroyed,
10583
+ peripheral: signal(true),
10584
+ };
10585
+ }
10586
+ return null;
10587
+ }
10588
+
10589
+ /*
10590
+ * Copyright (c) 2018-2022 Swiss Federal Railways
10591
+ *
10592
+ * This program and the accompanying materials are made
10593
+ * available under the terms of the Eclipse Public License 2.0
10594
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
10595
+ *
10596
+ * SPDX-License-Identifier: EPL-2.0
10597
+ */
10598
+ /** @inheritDoc */
10599
+ class ɵWorkbenchPopupService {
10600
+ _injector = inject(Injector);
10601
+ _rootInjector = inject(ApplicationRef).injector;
10602
+ _popupRegistry = inject(WorkbenchPopupRegistry);
10603
+ _zone = inject(NgZone);
10604
+ /** @inheritDoc */
10605
+ async open(component, options) {
10606
+ assertNotInReactiveContext(this.open, 'Call WorkbenchPopupService.open() in a non-reactive (non-tracking) context, such as within the untracked() function.');
10607
+ // Ensure to run in Angular zone to display the popup even when called from outside the Angular zone.
10608
+ if (!NgZone.isInAngularZone()) {
10609
+ return this._zone.run(() => this.open(component, options));
10610
+ }
10611
+ const popup = this.createPopup(component, options);
10612
+ this._popupRegistry.register(popup.id, popup);
10613
+ try {
10614
+ return await popup.waitForClose();
10615
+ }
10616
+ finally {
10617
+ this._popupRegistry.unregister(popup.id);
10618
+ }
10619
+ }
10620
+ /**
10621
+ * Creates the popup handle.
10622
+ */
10623
+ createPopup(component, options) {
10624
+ // Construct the handle in an injection context that shares the popup's lifecycle, allowing for automatic cleanup of effects and RxJS interop functions.
10625
+ const popupId = options.id ?? computePopupId();
10626
+ const popupInjector = Injector.create({
10627
+ parent: this._rootInjector, // use root injector to be independent of service construction context
10628
+ providers: [],
10629
+ name: `Workbench Popup ${popupId}`,
10630
+ });
10631
+ const invocationContext = createPopupInvocationContext(options, this._injector);
10632
+ return runInInjectionContext(popupInjector, () => new ɵWorkbenchPopup(popupId, component, invocationContext, options));
10633
+ }
10634
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: ɵWorkbenchPopupService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
10635
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: ɵWorkbenchPopupService, providedIn: 'root' });
10636
+ }
10637
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: ɵWorkbenchPopupService, decorators: [{
10638
+ type: Injectable,
10639
+ args: [{ providedIn: 'root' }]
10640
+ }] });
10641
+ /**
10642
+ * Computes the popup's invocation context based on passsed options and injection context.
10643
+ */
10644
+ function createPopupInvocationContext(options, injector) {
10645
+ return createInvocationContext(options.context && (typeof options.context === 'object' ? options.context.viewId : options.context), { injector });
10982
10646
  }
10983
10647
 
10984
10648
  /*
@@ -11077,7 +10741,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImpor
11077
10741
  }] });
11078
10742
 
11079
10743
  /*
11080
- * Copyright (c) 2018-2022 Swiss Federal Railways
10744
+ * Copyright (c) 2018-2026 Swiss Federal Railways
11081
10745
  *
11082
10746
  * This program and the accompanying materials are made
11083
10747
  * available under the terms of the Eclipse Public License 2.0
@@ -11085,55 +10749,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImpor
11085
10749
  *
11086
10750
  * SPDX-License-Identifier: EPL-2.0
11087
10751
  */
11088
- /** @inheritDoc */
11089
- class ɵWorkbenchPopupService {
11090
- _injector = inject(Injector);
11091
- _rootInjector = inject(ApplicationRef).injector;
11092
- _popupRegistry = inject(WorkbenchPopupRegistry);
11093
- _zone = inject(NgZone);
11094
- /** @inheritDoc */
11095
- async open(component, options) {
11096
- assertNotInReactiveContext(this.open, 'Call WorkbenchPopupService.open() in a non-reactive (non-tracking) context, such as within the untracked() function.');
11097
- // Ensure to run in Angular zone to display the popup even when called from outside the Angular zone.
11098
- if (!NgZone.isInAngularZone()) {
11099
- return this._zone.run(() => this.open(component, options));
11100
- }
11101
- const popup = this.createPopup(component, options);
11102
- this._popupRegistry.register(popup.id, popup);
11103
- try {
11104
- return await popup.waitForClose();
11105
- }
11106
- finally {
11107
- this._popupRegistry.unregister(popup.id);
11108
- }
11109
- }
11110
- /**
11111
- * Creates the popup handle.
11112
- */
11113
- createPopup(component, options) {
11114
- // Construct the handle in an injection context that shares the popup's lifecycle, allowing for automatic cleanup of effects and RxJS interop functions.
11115
- const popupId = options.id ?? computePopupId();
11116
- const popupInjector = Injector.create({
11117
- parent: this._rootInjector, // use root injector to be independent of service construction context
11118
- providers: [],
11119
- name: `Workbench Popup ${popupId}`,
11120
- });
11121
- const invocationContext = createPopupInvocationContext(options, this._injector);
11122
- return runInInjectionContext(popupInjector, () => new ɵWorkbenchPopup(popupId, component, invocationContext, options));
11123
- }
11124
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: ɵWorkbenchPopupService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
11125
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: ɵWorkbenchPopupService, providedIn: 'root' });
11126
- }
11127
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: ɵWorkbenchPopupService, decorators: [{
11128
- type: Injectable,
11129
- args: [{ providedIn: 'root' }]
11130
- }] });
11131
- /**
11132
- * Computes the popup's invocation context based on passsed options and injection context.
11133
- */
11134
- function createPopupInvocationContext(options, injector) {
11135
- return createInvocationContext(options.context && (typeof options.context === 'object' ? options.context.viewId : options.context), { injector });
11136
- }
11137
10752
  /**
11138
10753
  * Provides {@link WorkbenchPopupService} for dependency injection.
11139
10754
  */
@@ -11175,7 +10790,7 @@ function provideWorkbenchDialogContext() {
11175
10790
  }
11176
10791
 
11177
10792
  /*
11178
- * Copyright (c) 2018-2023 Swiss Federal Railways
10793
+ * Copyright (c) 2018-2026 Swiss Federal Railways
11179
10794
  *
11180
10795
  * This program and the accompanying materials are made
11181
10796
  * available under the terms of the Eclipse Public License 2.0
@@ -11210,7 +10825,7 @@ class ɵWorkbenchDialog {
11210
10825
  focused = computed(() => this._focusMonitor.activeElement()?.id === this.id, { ...(ngDevMode ? { debugName: "focused" } : {}) });
11211
10826
  attached;
11212
10827
  destroyed = signal(false, { ...(ngDevMode ? { debugName: "destroyed" } : {}) });
11213
- bounds = boundingClientRect(computed(() => this._componentRef()?.instance.dialogContent()));
10828
+ bounds = boundingClientRect(computed(() => this._componentRef()?.instance.dialogSlotBounds()));
11214
10829
  modal;
11215
10830
  blinking$ = new BehaviorSubject(false);
11216
10831
  header;
@@ -11420,155 +11035,605 @@ class ɵWorkbenchDialog {
11420
11035
  if (this.invocationContext) {
11421
11036
  return this.invocationContext.attached;
11422
11037
  }
11423
- if (this._workbenchConfig.dialog?.modalityScope === 'viewport') {
11424
- return computed(() => true);
11038
+ if (this._workbenchConfig.dialog?.modalityScope === 'viewport') {
11039
+ return computed(() => true);
11040
+ }
11041
+ const workbenchComponentRef = inject(WORKBENCH_COMPONENT_REF);
11042
+ return computed(() => !!workbenchComponentRef());
11043
+ }
11044
+ /**
11045
+ * Binds this dialog to its workbench host element, displaying it only when the host element is attached.
11046
+ *
11047
+ * Dialogs opened in non-peripheral area are displayed in the center of the host.
11048
+ */
11049
+ bindToHostElement() {
11050
+ if (!this.invocationContext && this._workbenchConfig.dialog?.modalityScope === 'viewport') {
11051
+ setStyle(this._overlayRef.hostElement, { inset: '0' });
11052
+ }
11053
+ else {
11054
+ const viewDragService = inject(ViewDragService);
11055
+ const workbenchComponentBounds = inject(WORKBENCH_COMPONENT_BOUNDS);
11056
+ const document = inject(DOCUMENT);
11057
+ effect(() => {
11058
+ const visible = this.attached() && !viewDragService.dragging();
11059
+ // Maintain position and size when hidden to prevent flickering when visible again and to support for virtual scrolling in dialog content.
11060
+ if (!visible) {
11061
+ setStyle(this._overlayRef.overlayElement, { visibility: 'hidden' }); // Hide via `visibility` instead of `display` property to retain the size.
11062
+ return;
11063
+ }
11064
+ // IMPORTANT: Track host bounds only if visible to prevent flickering.
11065
+ // Align dialog relative to contextual element if opened in non-peripheral area.
11066
+ const hostBounds = this.invocationContext?.peripheral() === false ? this.invocationContext.bounds() : workbenchComponentBounds();
11067
+ if (!hostBounds) {
11068
+ setStyle(this._overlayRef.overlayElement, { visibility: 'hidden' }); // Hide via `visibility` instead of `display` property to retain the size.
11069
+ return;
11070
+ }
11071
+ setStyle(this._overlayRef.overlayElement, { visibility: null });
11072
+ // Center the dialog horizontally within the host bounds.
11073
+ // Shift the overlay instead of fitting it to the host bounds, so the dialog can grow beyond host bounds
11074
+ // if not specifying dialog size via a dialog handle.
11075
+ const { left, top, width } = hostBounds;
11076
+ const viewportCenter = document.documentElement.offsetWidth / 2;
11077
+ const dialogCenter = left + width / 2;
11078
+ const xDelta = -1 * (viewportCenter - dialogCenter);
11079
+ setStyle(this._overlayRef.hostElement, {
11080
+ transform: `translateX(${Math.round(xDelta)}px) translateY(${Math.round(top)}px)`, // round offset to avoid blurry dialog
11081
+ });
11082
+ });
11083
+ }
11084
+ }
11085
+ /**
11086
+ * Computes if this dialog is blocked by another dialog.
11087
+ */
11088
+ computeBlocked() {
11089
+ const dialogRegistry = inject(WorkbenchDialogRegistry);
11090
+ const topInThisContext = dialogRegistry.top(this.id);
11091
+ const topInInvocationContext = dialogRegistry.top(this.invocationContext?.elementId);
11092
+ return computed(() => {
11093
+ // Get the top dialog in the context spawned by this dialog.
11094
+ if (topInThisContext()) {
11095
+ return topInThisContext();
11096
+ }
11097
+ // Get the top dialog in the context this dialog was opened in.
11098
+ if (topInInvocationContext() !== this) {
11099
+ return topInInvocationContext();
11100
+ }
11101
+ return null;
11102
+ });
11103
+ }
11104
+ blinkOnRequest() {
11105
+ this._blink$
11106
+ .pipe(switchMap(() => of(true).pipe(concatWith(of(false).pipe(delay(300))))), distinctUntilChanged(), takeUntilDestroyed())
11107
+ .subscribe(this.blinking$);
11108
+ }
11109
+ /**
11110
+ * Closes the dialog when the context element is destroyed.
11111
+ */
11112
+ closeOnHostDestroy() {
11113
+ if (this.invocationContext) {
11114
+ effect(() => {
11115
+ if (this.invocationContext.destroyed()) {
11116
+ untracked(() => this.close());
11117
+ }
11118
+ });
11119
+ }
11120
+ }
11121
+ /**
11122
+ * Destroys this dialog and associated resources.
11123
+ */
11124
+ destroy() {
11125
+ if (!this.destroyed()) {
11126
+ this.injector.destroy();
11127
+ this._overlayRef.dispose();
11128
+ }
11129
+ }
11130
+ }
11131
+ /** @inheritDoc */
11132
+ class ɵWorkbenchDialogSize {
11133
+ _height = signal(undefined, { ...(ngDevMode ? { debugName: "_height" } : {}) });
11134
+ _width = signal(undefined, { ...(ngDevMode ? { debugName: "_width" } : {}) });
11135
+ _minHeight = signal(undefined, { ...(ngDevMode ? { debugName: "_minHeight" } : {}) });
11136
+ _maxHeight = signal(undefined, { ...(ngDevMode ? { debugName: "_maxHeight" } : {}) });
11137
+ _minWidth = signal(undefined, { ...(ngDevMode ? { debugName: "_minWidth" } : {}) });
11138
+ _maxWidth = signal(undefined, { ...(ngDevMode ? { debugName: "_maxWidth" } : {}) });
11139
+ /** @inheritDoc */
11140
+ get height() {
11141
+ return this._height;
11142
+ }
11143
+ /** @inheritDoc */
11144
+ set height(height) {
11145
+ untracked(() => this._height.set(height));
11146
+ }
11147
+ /** @inheritDoc */
11148
+ get width() {
11149
+ return this._width;
11150
+ }
11151
+ /** @inheritDoc */
11152
+ set width(width) {
11153
+ untracked(() => this._width.set(width));
11154
+ }
11155
+ /** @inheritDoc */
11156
+ get minHeight() {
11157
+ return this._minHeight;
11158
+ }
11159
+ /** @inheritDoc */
11160
+ set minHeight(minHeight) {
11161
+ untracked(() => this._minHeight.set(minHeight));
11162
+ }
11163
+ /** @inheritDoc */
11164
+ get maxHeight() {
11165
+ return this._maxHeight;
11166
+ }
11167
+ /** @inheritDoc */
11168
+ set maxHeight(maxHeight) {
11169
+ untracked(() => this._maxHeight.set(maxHeight));
11170
+ }
11171
+ /** @inheritDoc */
11172
+ get minWidth() {
11173
+ return this._minWidth;
11174
+ }
11175
+ /** @inheritDoc */
11176
+ set minWidth(minWidth) {
11177
+ untracked(() => this._minWidth.set(minWidth));
11178
+ }
11179
+ /** @inheritDoc */
11180
+ get maxWidth() {
11181
+ return this._maxWidth;
11182
+ }
11183
+ /** @inheritDoc */
11184
+ set maxWidth(maxWidth) {
11185
+ untracked(() => this._maxWidth.set(maxWidth));
11186
+ }
11187
+ }
11188
+
11189
+ /*
11190
+ * Copyright (c) 2018-2023 Swiss Federal Railways
11191
+ *
11192
+ * This program and the accompanying materials are made
11193
+ * available under the terms of the Eclipse Public License 2.0
11194
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
11195
+ *
11196
+ * SPDX-License-Identifier: EPL-2.0
11197
+ */
11198
+ /** @inheritDoc */
11199
+ class ɵWorkbenchDialogService {
11200
+ _injector = inject(Injector);
11201
+ _rootInjector = inject(ApplicationRef).injector;
11202
+ _dialogRegistry = inject(WorkbenchDialogRegistry);
11203
+ _document = inject(DOCUMENT);
11204
+ _zone = inject(NgZone);
11205
+ constructor() {
11206
+ this.installServiceLifecycleLogger();
11207
+ }
11208
+ /** @inheritDoc */
11209
+ async open(component, options) {
11210
+ assertNotInReactiveContext(this.open, 'Call WorkbenchDialogService.open() in a non-reactive (non-tracking) context, such as within the untracked() function.');
11211
+ // Ensure to run in Angular zone to display the dialog even when called from outside the Angular zone.
11212
+ if (!NgZone.isInAngularZone()) {
11213
+ return this._zone.run(() => this.open(component, options));
11214
+ }
11215
+ // Delay the opening of a context-modal dialog until all application-modal dialogs are closed.
11216
+ // Otherwise, the context-modal dialog would overlap already opened application-modal dialogs.
11217
+ const invocationContext = createDialogInvocationContext(options ?? {}, this._injector);
11218
+ if (invocationContext) {
11219
+ await this.waitUntilApplicationModalDialogsClosed();
11220
+ }
11221
+ // Create the dialog.
11222
+ const dialog = this.createDialog(component, invocationContext, options ?? {});
11223
+ this._dialogRegistry.register(dialog.id, dialog);
11224
+ // Capture focused element to restore focus when closing the dialog.
11225
+ const previouslyFocusedElement = this._document.activeElement instanceof HTMLElement ? this._document.activeElement : undefined;
11226
+ try {
11227
+ return await dialog.waitForClose();
11228
+ }
11229
+ finally {
11230
+ this._dialogRegistry.unregister(dialog.id);
11231
+ // Restore focus to previously focused element when closing the last dialog in the current context.
11232
+ if (previouslyFocusedElement && !this._dialogRegistry.top(invocationContext?.elementId)()) {
11233
+ previouslyFocusedElement.focus();
11234
+ }
11425
11235
  }
11426
- const workbenchComponentRef = inject(WORKBENCH_COMPONENT_REF);
11427
- return computed(() => !!workbenchComponentRef());
11428
11236
  }
11429
11237
  /**
11430
- * Binds this dialog to its workbench host element, displaying it only when the host element is attached.
11431
- *
11432
- * Dialogs opened in non-peripheral area are displayed in the center of the host.
11238
+ * Creates the dialog handle.
11433
11239
  */
11434
- bindToHostElement() {
11435
- if (!this.invocationContext && this._workbenchConfig.dialog?.modalityScope === 'viewport') {
11436
- setStyle(this._overlayRef.hostElement, { inset: '0' });
11437
- }
11438
- else {
11439
- const viewDragService = inject(ViewDragService);
11440
- const workbenchComponentBounds = inject(WORKBENCH_COMPONENT_BOUNDS);
11441
- const document = inject(DOCUMENT);
11442
- effect(() => {
11443
- const visible = this.attached() && !viewDragService.dragging();
11444
- // Maintain position and size when hidden to prevent flickering when visible again and to support for virtual scrolling in dialog content.
11445
- if (!visible) {
11446
- setStyle(this._overlayRef.overlayElement, { visibility: 'hidden' }); // Hide via `visibility` instead of `display` property to retain the size.
11447
- return;
11448
- }
11449
- // IMPORTANT: Track host bounds only if visible to prevent flickering.
11450
- // Align dialog relative to contextual element if opened in non-peripheral area.
11451
- const hostBounds = this.invocationContext?.peripheral() === false ? this.invocationContext.bounds() : workbenchComponentBounds();
11452
- if (!hostBounds) {
11453
- setStyle(this._overlayRef.overlayElement, { visibility: 'hidden' }); // Hide via `visibility` instead of `display` property to retain the size.
11454
- return;
11455
- }
11456
- setStyle(this._overlayRef.overlayElement, { visibility: null });
11457
- // Center the dialog horizontally within the host bounds.
11458
- // Shift the overlay instead of fitting it to the host bounds, so the dialog can grow beyond host bounds
11459
- // if not specifying dialog size via a dialog handle.
11460
- const { left, top, width } = hostBounds;
11461
- const viewportCenter = document.documentElement.offsetWidth / 2;
11462
- const dialogCenter = left + width / 2;
11463
- const xDelta = -1 * (viewportCenter - dialogCenter);
11464
- setStyle(this._overlayRef.hostElement, {
11465
- transform: `translateX(${Math.round(xDelta)}px) translateY(${Math.round(top)}px)`, // round offset to avoid blurry dialog
11466
- });
11467
- });
11468
- }
11240
+ createDialog(component, invocationContext, options) {
11241
+ // Construct the handle in an injection context that shares the dialog's lifecycle, allowing for automatic cleanup of effects and RxJS interop functions.
11242
+ const dialogId = computeDialogId();
11243
+ const dialogInjector = Injector.create({
11244
+ parent: this._rootInjector, // use root injector to be independent of service construction context
11245
+ providers: [],
11246
+ name: `Workbench Dialog ${dialogId}`,
11247
+ });
11248
+ return runInInjectionContext(dialogInjector, () => new ɵWorkbenchDialog(dialogId, component, invocationContext, options));
11469
11249
  }
11470
11250
  /**
11471
- * Computes if this dialog is blocked by another dialog.
11251
+ * Returns a Promise that resolves when all application modal-dialogs are closed. If none are opened, the Promise resolves immediately.
11472
11252
  */
11473
- computeBlocked() {
11474
- const dialogRegistry = inject(WorkbenchDialogRegistry);
11475
- const topInThisContext = dialogRegistry.top(this.id);
11476
- const topInInvocationContext = dialogRegistry.top(this.invocationContext?.elementId);
11477
- return computed(() => {
11478
- // Get the top dialog in the context spawned by this dialog.
11479
- if (topInThisContext()) {
11480
- return topInThisContext();
11481
- }
11482
- // Get the top dialog in the context this dialog was opened in.
11483
- if (topInInvocationContext() !== this) {
11484
- return topInInvocationContext();
11485
- }
11486
- return null;
11487
- });
11253
+ async waitUntilApplicationModalDialogsClosed() {
11254
+ // Use root injector to be independent of service construction context.
11255
+ const injector = Injector.create({ parent: this._rootInjector, providers: [] });
11256
+ await firstValueFrom(toObservable(this._dialogRegistry.top(), { injector }).pipe(filter(top => !top)));
11257
+ injector.destroy();
11488
11258
  }
11489
- blinkOnRequest() {
11490
- this._blink$
11491
- .pipe(switchMap(() => of(true).pipe(concatWith(of(false).pipe(delay(300))))), distinctUntilChanged(), takeUntilDestroyed())
11492
- .subscribe(this.blinking$);
11259
+ installServiceLifecycleLogger() {
11260
+ const logger = inject(Logger);
11261
+ const workbenchElement = inject(WORKBENCH_ELEMENT, { optional: true });
11262
+ logger.debug(() => `Constructing WorkbenchDialogService [context=${workbenchElement?.id}]`, LoggerNames.LIFECYCLE);
11263
+ inject(DestroyRef).onDestroy(() => logger.debug(() => `Destroying WorkbenchDialogService [context=${workbenchElement?.id}]'`, LoggerNames.LIFECYCLE));
11264
+ }
11265
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: ɵWorkbenchDialogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
11266
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: ɵWorkbenchDialogService, providedIn: 'root' });
11267
+ }
11268
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: ɵWorkbenchDialogService, decorators: [{
11269
+ type: Injectable,
11270
+ args: [{ providedIn: 'root' }]
11271
+ }], ctorParameters: () => [] });
11272
+ /**
11273
+ * Computes the dialog's invocation context based on passsed options and injection context.
11274
+ */
11275
+ function createDialogInvocationContext(options, injector) {
11276
+ if (options.modality === 'application') {
11277
+ return null;
11278
+ }
11279
+ return createInvocationContext(options.context && (typeof options.context === 'object' ? options.context.viewId : options.context), { injector });
11280
+ }
11281
+
11282
+ /*
11283
+ * Copyright (c) 2018-2023 Swiss Federal Railways
11284
+ *
11285
+ * This program and the accompanying materials are made
11286
+ * available under the terms of the Eclipse Public License 2.0
11287
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
11288
+ *
11289
+ * SPDX-License-Identifier: EPL-2.0
11290
+ */
11291
+ /**
11292
+ * Enables the display of a component in a dialog.
11293
+ *
11294
+ * A dialog is a visual element for focused interaction with the user, such as prompting the user for input or confirming actions.
11295
+ * The user can move and resize a dialog.
11296
+ *
11297
+ * Displayed on top of other content, a modal dialog blocks interaction with other parts of the application.
11298
+ *
11299
+ * ## Modality
11300
+ * A dialog can be context-modal or application-modal. Context-modal blocks a specific part of the application, as specified by the context;
11301
+ * application-modal blocks the workbench or browser viewport, based on {@link WorkbenchConfig.dialog.modalityScope}.
11302
+ *
11303
+ * ## Context
11304
+ * A dialog can be bound to a context (e.g., part or view), defaulting to the calling context.
11305
+ * The dialog is displayed only if the context is visible and closes when the context is disposed.
11306
+ *
11307
+ * ## Positioning
11308
+ * A dialog is opened in the center of its context, if any, unless opened from the peripheral area.
11309
+ *
11310
+ * ## Stacking
11311
+ * Dialogs are stacked per modality, with only the topmost dialog in each stack being interactive.
11312
+ *
11313
+ * ## Dialog Component
11314
+ * The dialog component can inject the {@link WorkbenchDialog} handle to interact with the dialog, such as setting the title or closing the dialog.
11315
+ * Inputs passed to the dialog are available as input properties in the dialog component.
11316
+ *
11317
+ * ## Dialog Header
11318
+ * By default, the dialog displays the title and a close button in the header. Alternatively, the dialog supports the use of a custom header.
11319
+ * To provide a custom header, add an Angular template to the HTML of the dialog component and decorate it with the `wbDialogHeader` directive.
11320
+ *
11321
+ * ```html
11322
+ * <ng-template wbDialogHeader>
11323
+ * <app-dialog-header/>
11324
+ * </ng-template>
11325
+ * ```
11326
+ *
11327
+ * ## Dialog Footer
11328
+ * A dialog has a default footer that displays actions defined in the HTML of the dialog component. An action is an Angular template decorated with
11329
+ * the `wbDialogAction` directive. Multiple actions are supported, rendered in modeling order, and can be left- or right-aligned.
11330
+ *
11331
+ * ```html
11332
+ * <!-- Checkbox -->
11333
+ * <ng-template wbDialogAction align="start">
11334
+ * <label>
11335
+ * <input type="checkbox"/>
11336
+ * Do not ask me again
11337
+ * </label>
11338
+ * </ng-template>
11339
+ *
11340
+ * <!-- OK Button -->
11341
+ * <ng-template wbDialogAction align="end">
11342
+ * <button (click)="...">OK</button>
11343
+ * </ng-template>
11344
+ *
11345
+ * <!-- Cancel Button -->
11346
+ * <ng-template wbDialogAction align="end">
11347
+ * <button (click)="...">Cancel</button>
11348
+ * </ng-template>
11349
+ * ```
11350
+ *
11351
+ * Alternatively, the dialog supports the use of a custom footer. To provide a custom footer, add an Angular template to the HTML of the dialog component and
11352
+ * decorate it with the `wbDialogFooter` directive.
11353
+ *
11354
+ * ```html
11355
+ * <ng-template wbDialogFooter>
11356
+ * <app-dialog-footer/>
11357
+ * </ng-template>
11358
+ * ```
11359
+ *
11360
+ * ## Styling
11361
+ * The following CSS variables can be set to customize the default look of a dialog.
11362
+ *
11363
+ * - `--sci-workbench-dialog-padding`
11364
+ * - `--sci-workbench-dialog-header-height`
11365
+ * - `--sci-workbench-dialog-header-background-color`
11366
+ * - `--sci-workbench-dialog-title-font-family`
11367
+ * - `--sci-workbench-dialog-title-font-weight`
11368
+ * - `--sci-workbench-dialog-title-font-size`
11369
+ * - `--sci-workbench-dialog-title-align`
11370
+ */
11371
+ class WorkbenchDialogService {
11372
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchDialogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
11373
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchDialogService, providedIn: 'root', useExisting: ɵWorkbenchDialogService });
11374
+ }
11375
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchDialogService, decorators: [{
11376
+ type: Injectable,
11377
+ args: [{ providedIn: 'root', useExisting: ɵWorkbenchDialogService }]
11378
+ }] });
11379
+
11380
+ /*
11381
+ * Copyright (c) 2018-2023 Swiss Federal Railways
11382
+ *
11383
+ * This program and the accompanying materials are made
11384
+ * available under the terms of the Eclipse Public License 2.0
11385
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
11386
+ *
11387
+ * SPDX-License-Identifier: EPL-2.0
11388
+ */
11389
+ class MessageBoxFooterComponent {
11390
+ actions = input.required({ ...(ngDevMode ? { debugName: "actions" } : {}) });
11391
+ severity = input.required({ ...(ngDevMode ? { debugName: "severity" } : {}) });
11392
+ action = output();
11393
+ preferredSizeChange = output();
11394
+ _actionButtons = viewChildren('action_button', { ...(ngDevMode ? { debugName: "_actionButtons" } : {}) });
11395
+ constructor() {
11396
+ void this.emitPreferredSize();
11397
+ }
11398
+ insertionSortOrderFn = () => 0;
11399
+ onAction(key) {
11400
+ this.action.emit(key);
11401
+ }
11402
+ onArrowKey(index, direction) {
11403
+ const actionButtonCount = this._actionButtons().length;
11404
+ const newIndex = (direction === 'left' ? index - 1 : index + 1);
11405
+ this._actionButtons()[(newIndex + actionButtonCount) % actionButtonCount].nativeElement.focus();
11406
+ }
11407
+ async emitPreferredSize() {
11408
+ const host = inject(ElementRef).nativeElement;
11409
+ host.classList.add('calculating-min-width');
11410
+ try {
11411
+ // Wait for the CSS class to take effect, then wait an animation frame to avoid the error: "ResizeObserver loop completed with undelivered notifications".
11412
+ await firstValueFrom(fromResize$(host).pipe(observeOn(animationFrameScheduler)));
11413
+ this.preferredSizeChange.emit(host.offsetWidth);
11414
+ }
11415
+ finally {
11416
+ host.classList.remove('calculating-min-width');
11417
+ }
11493
11418
  }
11419
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: MessageBoxFooterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11420
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.1", type: MessageBoxFooterComponent, isStandalone: true, selector: "wb-message-box-footer", inputs: { actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: true, transformFunction: null }, severity: { classPropertyName: "severity", publicName: "severity", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { action: "action", preferredSizeChange: "preferredSizeChange" }, host: { properties: { "attr.data-severity": "severity()" } }, viewQueries: [{ propertyName: "_actionButtons", predicate: ["action_button"], descendants: true, isSignal: true }], ngImport: i0, template: "@for (action of actions() | keyvalue:insertionSortOrderFn; track action.key) {\n <button #action_button\n (click)=\"onAction(action.key)\"\n (keydown.arrowLeft)=\"onArrowKey($index, 'left')\"\n (keydown.arrowRight)=\"onArrowKey($index, 'right')\"\n [attr.data-action]=\"action.key\"\n class=\"action e2e-action\">\n {{(action.value | wbText)()}}\n </button>\n\n @if (!$last) {\n <span class=\"divider\"></span>\n }\n}\n", styles: ["@charset \"UTF-8\";:host{--\\275message-box-severity-color: initial;display:flex;height:3em;background-color:var(--sci-color-background-secondary);color:var(--sci-color-text)}:host[data-severity=info]{--\\275message-box-severity-color: var(--sci-color-accent)}:host[data-severity=warn]{--\\275message-box-severity-color: var(--sci-color-notice)}:host[data-severity=error]{--\\275message-box-severity-color: var(--sci-color-negative)}:host.calculating-min-width{position:absolute}:host>button.action:is(button,#sci-reset){all:unset;flex:1;margin:2px;border:1px solid transparent;border-radius:var(--sci-corner-small);transition:border-color ease-in-out .15s;cursor:var(--sci-workbench-messagebox-action-cursor);-webkit-user-select:none;user-select:none;text-align:center;min-width:7.5em;padding-inline:.5em;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}:host>button.action:is(button,#sci-reset):focus,:host>button.action:is(button,#sci-reset):active{outline:none;color:var(--\\275message-box-severity-color);border-color:var(--\\275message-box-severity-color)}:host>button.action:is(button,#sci-reset):hover{background-color:var(--sci-workbench-messagebox-action-background-color-hover)}:host>span.divider{width:1px;background-color:var(--sci-color-border)}\n"], dependencies: [{ kind: "pipe", type: KeyValuePipe, name: "keyvalue" }, { kind: "pipe", type: TextPipe, name: "wbText" }] });
11421
+ }
11422
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: MessageBoxFooterComponent, decorators: [{
11423
+ type: Component,
11424
+ args: [{ selector: 'wb-message-box-footer', imports: [
11425
+ KeyValuePipe,
11426
+ TextPipe,
11427
+ ], host: {
11428
+ '[attr.data-severity]': 'severity()',
11429
+ }, template: "@for (action of actions() | keyvalue:insertionSortOrderFn; track action.key) {\n <button #action_button\n (click)=\"onAction(action.key)\"\n (keydown.arrowLeft)=\"onArrowKey($index, 'left')\"\n (keydown.arrowRight)=\"onArrowKey($index, 'right')\"\n [attr.data-action]=\"action.key\"\n class=\"action e2e-action\">\n {{(action.value | wbText)()}}\n </button>\n\n @if (!$last) {\n <span class=\"divider\"></span>\n }\n}\n", styles: ["@charset \"UTF-8\";:host{--\\275message-box-severity-color: initial;display:flex;height:3em;background-color:var(--sci-color-background-secondary);color:var(--sci-color-text)}:host[data-severity=info]{--\\275message-box-severity-color: var(--sci-color-accent)}:host[data-severity=warn]{--\\275message-box-severity-color: var(--sci-color-notice)}:host[data-severity=error]{--\\275message-box-severity-color: var(--sci-color-negative)}:host.calculating-min-width{position:absolute}:host>button.action:is(button,#sci-reset){all:unset;flex:1;margin:2px;border:1px solid transparent;border-radius:var(--sci-corner-small);transition:border-color ease-in-out .15s;cursor:var(--sci-workbench-messagebox-action-cursor);-webkit-user-select:none;user-select:none;text-align:center;min-width:7.5em;padding-inline:.5em;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}:host>button.action:is(button,#sci-reset):focus,:host>button.action:is(button,#sci-reset):active{outline:none;color:var(--\\275message-box-severity-color);border-color:var(--\\275message-box-severity-color)}:host>button.action:is(button,#sci-reset):hover{background-color:var(--sci-workbench-messagebox-action-background-color-hover)}:host>span.divider{width:1px;background-color:var(--sci-color-border)}\n"] }]
11430
+ }], ctorParameters: () => [], propDecorators: { actions: [{ type: i0.Input, args: [{ isSignal: true, alias: "actions", required: true }] }], severity: [{ type: i0.Input, args: [{ isSignal: true, alias: "severity", required: true }] }], action: [{ type: i0.Output, args: ["action"] }], preferredSizeChange: [{ type: i0.Output, args: ["preferredSizeChange"] }], _actionButtons: [{ type: i0.ViewChildren, args: ['action_button', { isSignal: true }] }] } });
11431
+
11432
+ /*
11433
+ * Copyright (c) 2018-2023 Swiss Federal Railways
11434
+ *
11435
+ * This program and the accompanying materials are made
11436
+ * available under the terms of the Eclipse Public License 2.0
11437
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
11438
+ *
11439
+ * SPDX-License-Identifier: EPL-2.0
11440
+ */
11441
+ /**
11442
+ * Use this directive to replace the default dialog footer that renders actions contributed via the {@link WorkbenchDialogActionDirective} directive.
11443
+ *
11444
+ * The host element of this modeling directive must be a <ng-template>. The footer shares the lifecycle of the host element.
11445
+ *
11446
+ * **Example:**
11447
+ * ```html
11448
+ * <ng-template wbDialogFooter>
11449
+ * <app-dialog-footer/>
11450
+ * </ng-template>
11451
+ * ```
11452
+ */
11453
+ class WorkbenchDialogFooterDirective {
11494
11454
  /**
11495
- * Closes the dialog when the context element is destroyed.
11455
+ * Specifies if to display a visual separator between the dialog content and this footer.
11456
+ * Defaults to `true`.
11496
11457
  */
11497
- closeOnHostDestroy() {
11498
- if (this.invocationContext) {
11499
- effect(() => {
11500
- if (this.invocationContext.destroyed()) {
11501
- untracked(() => this.close());
11502
- }
11503
- });
11504
- }
11458
+ divider = input(undefined, { ...(ngDevMode ? { debugName: "divider" } : {}), transform: booleanAttribute });
11459
+ template = inject(TemplateRef);
11460
+ _footer;
11461
+ constructor() {
11462
+ const dialog = inject(ɵWorkbenchDialog);
11463
+ // Defer registering footer to avoid `ExpressionChangedAfterItHasBeenCheckedError`.
11464
+ asapScheduler.schedule(() => this._footer = dialog.registerFooter(this));
11465
+ // Defer disposing footer to avoid `ExpressionChangedAfterItHasBeenCheckedError`.
11466
+ inject(DestroyRef).onDestroy(() => asapScheduler.schedule(() => this._footer?.dispose()));
11505
11467
  }
11468
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchDialogFooterDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
11469
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.1", type: WorkbenchDialogFooterDirective, isStandalone: true, selector: "ng-template[wbDialogFooter]", inputs: { divider: { classPropertyName: "divider", publicName: "divider", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
11470
+ }
11471
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchDialogFooterDirective, decorators: [{
11472
+ type: Directive,
11473
+ args: [{ selector: 'ng-template[wbDialogFooter]' }]
11474
+ }], ctorParameters: () => [], propDecorators: { divider: [{ type: i0.Input, args: [{ isSignal: true, alias: "divider", required: false }] }] } });
11475
+
11476
+ /*
11477
+ * Copyright (c) 2018-2023 Swiss Federal Railways
11478
+ *
11479
+ * This program and the accompanying materials are made
11480
+ * available under the terms of the Eclipse Public License 2.0
11481
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
11482
+ *
11483
+ * SPDX-License-Identifier: EPL-2.0
11484
+ */
11485
+ /**
11486
+ * Use this directive to replace the default dialog header that displays the title and a close button.
11487
+ *
11488
+ * The host element of this modeling directive must be a <ng-template>. The header shares the lifecycle of the host element.
11489
+ *
11490
+ * **Example:**
11491
+ * ```html
11492
+ * <ng-template wbDialogHeader>
11493
+ * <app-dialog-header/>
11494
+ * </ng-template>
11495
+ * ```
11496
+ */
11497
+ class WorkbenchDialogHeaderDirective {
11506
11498
  /**
11507
- * Destroys this dialog and associated resources.
11499
+ * Specifies if to display a visual separator between this header and the dialog content.
11500
+ * Defaults to `true`.
11508
11501
  */
11509
- destroy() {
11510
- if (!this.destroyed()) {
11511
- this.injector.destroy();
11512
- this._overlayRef.dispose();
11513
- }
11502
+ divider = input(undefined, { ...(ngDevMode ? { debugName: "divider" } : {}), transform: booleanAttribute });
11503
+ template = inject(TemplateRef);
11504
+ _header;
11505
+ constructor() {
11506
+ const dialog = inject(ɵWorkbenchDialog);
11507
+ // Defer registering header to avoid `ExpressionChangedAfterItHasBeenCheckedError`.
11508
+ asapScheduler.schedule(() => this._header = dialog.registerHeader(this));
11509
+ // Defer disposing header to avoid `ExpressionChangedAfterItHasBeenCheckedError`.
11510
+ inject(DestroyRef).onDestroy(() => asapScheduler.schedule(() => this._header?.dispose()));
11514
11511
  }
11512
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchDialogHeaderDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
11513
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.1", type: WorkbenchDialogHeaderDirective, isStandalone: true, selector: "ng-template[wbDialogHeader]", inputs: { divider: { classPropertyName: "divider", publicName: "divider", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
11515
11514
  }
11516
- /** @inheritDoc */
11517
- class ɵWorkbenchDialogSize {
11518
- _height = signal(undefined, { ...(ngDevMode ? { debugName: "_height" } : {}) });
11519
- _width = signal(undefined, { ...(ngDevMode ? { debugName: "_width" } : {}) });
11520
- _minHeight = signal(undefined, { ...(ngDevMode ? { debugName: "_minHeight" } : {}) });
11521
- _maxHeight = signal(undefined, { ...(ngDevMode ? { debugName: "_maxHeight" } : {}) });
11522
- _minWidth = signal(undefined, { ...(ngDevMode ? { debugName: "_minWidth" } : {}) });
11523
- _maxWidth = signal(undefined, { ...(ngDevMode ? { debugName: "_maxWidth" } : {}) });
11524
- /** @inheritDoc */
11525
- get height() {
11526
- return this._height;
11527
- }
11528
- /** @inheritDoc */
11529
- set height(height) {
11530
- untracked(() => this._height.set(height));
11531
- }
11532
- /** @inheritDoc */
11533
- get width() {
11534
- return this._width;
11535
- }
11536
- /** @inheritDoc */
11537
- set width(width) {
11538
- untracked(() => this._width.set(width));
11539
- }
11540
- /** @inheritDoc */
11541
- get minHeight() {
11542
- return this._minHeight;
11543
- }
11544
- /** @inheritDoc */
11545
- set minHeight(minHeight) {
11546
- untracked(() => this._minHeight.set(minHeight));
11547
- }
11548
- /** @inheritDoc */
11549
- get maxHeight() {
11550
- return this._maxHeight;
11515
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchDialogHeaderDirective, decorators: [{
11516
+ type: Directive,
11517
+ args: [{ selector: 'ng-template[wbDialogHeader]' }]
11518
+ }], ctorParameters: () => [], propDecorators: { divider: [{ type: i0.Input, args: [{ isSignal: true, alias: "divider", required: false }] }] } });
11519
+
11520
+ /*
11521
+ * Copyright (c) 2018-2023 Swiss Federal Railways
11522
+ *
11523
+ * This program and the accompanying materials are made
11524
+ * available under the terms of the Eclipse Public License 2.0
11525
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
11526
+ *
11527
+ * SPDX-License-Identifier: EPL-2.0
11528
+ */
11529
+ class MessageBoxHeaderComponent {
11530
+ title = input(undefined, { ...(ngDevMode ? { debugName: "title" } : {}) });
11531
+ severity = input.required({ ...(ngDevMode ? { debugName: "severity" } : {}) });
11532
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: MessageBoxHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11533
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.1", type: MessageBoxHeaderComponent, isStandalone: true, selector: "wb-message-box-header", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, severity: { classPropertyName: "severity", publicName: "severity", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "attr.data-severity": "severity()" } }, ngImport: i0, template: "@if (title()) {\n <span class=\"title e2e-title\">{{(title() | wbText)()}}</span>\n}\n", styles: ["@charset \"UTF-8\";:host{--\\275message-box-severity-color: initial;display:grid;border-top:var(--sci-workbench-messagebox-severity-indicator-size) solid var(--\\275message-box-severity-color);padding-inline:var(--sci-workbench-messagebox-padding);padding-top:var(--sci-workbench-messagebox-padding);-webkit-user-select:none;user-select:none}:host[data-severity=info]{--\\275message-box-severity-color: var(--sci-color-accent)}:host[data-severity=warn]{--\\275message-box-severity-color: var(--sci-color-notice)}:host[data-severity=error]{--\\275message-box-severity-color: var(--sci-color-negative)}:host>span.title{word-break:break-word;white-space:pre-line;font-family:var(--sci-workbench-messagebox-title-font-family),sans-serif;font-size:var(--sci-workbench-messagebox-title-font-size);font-weight:var(--sci-workbench-messagebox-title-font-weight);text-align:var(--sci-workbench-messagebox-title-align)}\n"], dependencies: [{ kind: "pipe", type: TextPipe, name: "wbText" }] });
11534
+ }
11535
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: MessageBoxHeaderComponent, decorators: [{
11536
+ type: Component,
11537
+ args: [{ selector: 'wb-message-box-header', imports: [
11538
+ TextPipe,
11539
+ ], host: {
11540
+ '[attr.data-severity]': 'severity()',
11541
+ }, template: "@if (title()) {\n <span class=\"title e2e-title\">{{(title() | wbText)()}}</span>\n}\n", styles: ["@charset \"UTF-8\";:host{--\\275message-box-severity-color: initial;display:grid;border-top:var(--sci-workbench-messagebox-severity-indicator-size) solid var(--\\275message-box-severity-color);padding-inline:var(--sci-workbench-messagebox-padding);padding-top:var(--sci-workbench-messagebox-padding);-webkit-user-select:none;user-select:none}:host[data-severity=info]{--\\275message-box-severity-color: var(--sci-color-accent)}:host[data-severity=warn]{--\\275message-box-severity-color: var(--sci-color-notice)}:host[data-severity=error]{--\\275message-box-severity-color: var(--sci-color-negative)}:host>span.title{word-break:break-word;white-space:pre-line;font-family:var(--sci-workbench-messagebox-title-font-family),sans-serif;font-size:var(--sci-workbench-messagebox-title-font-size);font-weight:var(--sci-workbench-messagebox-title-font-weight);text-align:var(--sci-workbench-messagebox-title-align)}\n"] }]
11542
+ }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], severity: [{ type: i0.Input, args: [{ isSignal: true, alias: "severity", required: true }] }] } });
11543
+
11544
+ /*
11545
+ * Copyright (c) 2018-2024 Swiss Federal Railways
11546
+ *
11547
+ * This program and the accompanying materials are made
11548
+ * available under the terms of the Eclipse Public License 2.0
11549
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
11550
+ *
11551
+ * SPDX-License-Identifier: EPL-2.0
11552
+ */
11553
+ /**
11554
+ * Tests if the object is of the specified type.
11555
+ */
11556
+ class TypeofPipe {
11557
+ transform(object, type) {
11558
+ return typeof object === type;
11551
11559
  }
11552
- /** @inheritDoc */
11553
- set maxHeight(maxHeight) {
11554
- untracked(() => this._maxHeight.set(maxHeight));
11560
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: TypeofPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
11561
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.1", ngImport: i0, type: TypeofPipe, isStandalone: true, name: "wbTypeof" });
11562
+ }
11563
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: TypeofPipe, decorators: [{
11564
+ type: Pipe,
11565
+ args: [{ name: 'wbTypeof' }]
11566
+ }] });
11567
+
11568
+ /*
11569
+ * Copyright (c) 2018-2023 Swiss Federal Railways
11570
+ *
11571
+ * This program and the accompanying materials are made
11572
+ * available under the terms of the Eclipse Public License 2.0
11573
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
11574
+ *
11575
+ * SPDX-License-Identifier: EPL-2.0
11576
+ */
11577
+ /**
11578
+ * Renders the workbench message box.
11579
+ *
11580
+ * This component is designed to be opened in a workbench dialog.
11581
+ */
11582
+ class WorkbenchMessageBoxComponent {
11583
+ message = input.required({ ...(ngDevMode ? { debugName: "message" } : {}), transform: nullIfEmptyMessage });
11584
+ options = input(undefined, { ...(ngDevMode ? { debugName: "options" } : {}) });
11585
+ _dialog = inject(ɵWorkbenchDialog);
11586
+ empty = signal(false, { ...(ngDevMode ? { debugName: "empty" } : {}) });
11587
+ constructor() {
11588
+ this._dialog.closable = false;
11589
+ this._dialog.resizable = false;
11590
+ this._dialog.padding = false;
11591
+ // Limit the maximum messagebox width if text message to break the message.
11592
+ effect(() => {
11593
+ if (typeof this.message() === 'string' || this.message() === null) {
11594
+ this._dialog.size.maxWidth = 'var(--sci-workbench-messagebox-max-width)';
11595
+ }
11596
+ });
11555
11597
  }
11556
- /** @inheritDoc */
11557
- get minWidth() {
11558
- return this._minWidth;
11598
+ onAction(action) {
11599
+ this._dialog.close(action);
11559
11600
  }
11560
- /** @inheritDoc */
11561
- set minWidth(minWidth) {
11562
- untracked(() => this._minWidth.set(minWidth));
11601
+ onEscape() {
11602
+ if ('cancel' in (this.options()?.actions ?? {})) {
11603
+ this._dialog.close('cancel');
11604
+ }
11563
11605
  }
11564
- /** @inheritDoc */
11565
- get maxWidth() {
11566
- return this._maxWidth;
11606
+ onFooterPreferredSizeChange(preferredSize) {
11607
+ this._dialog.size.minWidth = `${preferredSize}px`;
11567
11608
  }
11568
- /** @inheritDoc */
11569
- set maxWidth(maxWidth) {
11570
- untracked(() => this._maxWidth.set(maxWidth));
11609
+ onContentDimensionChange(dimension) {
11610
+ this.empty.set(!dimension.offsetHeight);
11571
11611
  }
11612
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchMessageBoxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11613
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.1", type: WorkbenchMessageBoxComponent, isStandalone: true, selector: "wb-message-box", inputs: { message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: true, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "keydown.escape": "onEscape()" }, properties: { "attr.tabindex": "-1", "class.empty": "empty()", "class.content-selectable": "options()?.contentSelectable", "class.has-title": "!!this.options()?.title" } }, ngImport: i0, template: "@let options = this.options() ?? {};\n<ng-template wbDialogHeader [divider]=\"false\">\n <wb-message-box-header [title]=\"options.title\" [severity]=\"options.severity ?? 'info'\"/>\n</ng-template>\n\n@let message = this.message();\n<div class=\"slot e2e-slot\" [class.text]=\"message | wbTypeof:'string'\" sciDimension (sciDimensionChange)=\"onContentDimensionChange($event)\">\n @if (message | wbTypeof:'string') {\n {{($any(message) | wbText)()}}\n } @else {\n <ng-container *ngComponentOutlet=\"message; inputs: options.inputs\"/>\n }\n</div>\n\n<ng-template wbDialogFooter>\n <wb-message-box-footer [actions]=\"options.actions ?? {ok: '%workbench.ok.action'}\"\n [severity]=\"options.severity ?? 'info'\"\n (action)=\"onAction($event)\"\n (keydown.escape)=\"onEscape()\"\n (preferredSizeChange)=\"onFooterPreferredSizeChange($event)\"/>\n</ng-template>\n", styles: [":host{display:grid;outline:none;padding-inline:var(--sci-workbench-messagebox-padding);padding-bottom:var(--sci-workbench-messagebox-padding)}:host.has-title:not(.empty){padding-top:var(--sci-workbench-messagebox-padding)}:host:not(.content-selectable){-webkit-user-select:none;user-select:none}:host>div.slot.text{word-break:break-word;white-space:pre-line;text-align:var(--sci-workbench-messagebox-text-align)}\n"], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: SciDimensionDirective, selector: "[sciDimension]", inputs: ["emitOutsideAngular"], outputs: ["sciDimensionChange"] }, { kind: "directive", type: WorkbenchDialogHeaderDirective, selector: "ng-template[wbDialogHeader]", inputs: ["divider"] }, { kind: "directive", type: WorkbenchDialogFooterDirective, selector: "ng-template[wbDialogFooter]", inputs: ["divider"] }, { kind: "component", type: MessageBoxHeaderComponent, selector: "wb-message-box-header", inputs: ["title", "severity"] }, { kind: "component", type: MessageBoxFooterComponent, selector: "wb-message-box-footer", inputs: ["actions", "severity"], outputs: ["action", "preferredSizeChange"] }, { kind: "pipe", type: TypeofPipe, name: "wbTypeof" }, { kind: "pipe", type: TextPipe, name: "wbText" }] });
11614
+ }
11615
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchMessageBoxComponent, decorators: [{
11616
+ type: Component,
11617
+ args: [{ selector: 'wb-message-box', imports: [
11618
+ NgComponentOutlet,
11619
+ SciDimensionDirective,
11620
+ WorkbenchDialogHeaderDirective,
11621
+ WorkbenchDialogFooterDirective,
11622
+ MessageBoxHeaderComponent,
11623
+ MessageBoxFooterComponent,
11624
+ TypeofPipe,
11625
+ TextPipe,
11626
+ ], host: {
11627
+ // Ensure host element to be focusable in order to close the message box on Escape keystroke.
11628
+ '[attr.tabindex]': '-1',
11629
+ '[class.empty]': 'empty()',
11630
+ '[class.content-selectable]': 'options()?.contentSelectable',
11631
+ '[class.has-title]': '!!this.options()?.title',
11632
+ '(keydown.escape)': 'onEscape()',
11633
+ }, template: "@let options = this.options() ?? {};\n<ng-template wbDialogHeader [divider]=\"false\">\n <wb-message-box-header [title]=\"options.title\" [severity]=\"options.severity ?? 'info'\"/>\n</ng-template>\n\n@let message = this.message();\n<div class=\"slot e2e-slot\" [class.text]=\"message | wbTypeof:'string'\" sciDimension (sciDimensionChange)=\"onContentDimensionChange($event)\">\n @if (message | wbTypeof:'string') {\n {{($any(message) | wbText)()}}\n } @else {\n <ng-container *ngComponentOutlet=\"message; inputs: options.inputs\"/>\n }\n</div>\n\n<ng-template wbDialogFooter>\n <wb-message-box-footer [actions]=\"options.actions ?? {ok: '%workbench.ok.action'}\"\n [severity]=\"options.severity ?? 'info'\"\n (action)=\"onAction($event)\"\n (keydown.escape)=\"onEscape()\"\n (preferredSizeChange)=\"onFooterPreferredSizeChange($event)\"/>\n</ng-template>\n", styles: [":host{display:grid;outline:none;padding-inline:var(--sci-workbench-messagebox-padding);padding-bottom:var(--sci-workbench-messagebox-padding)}:host.has-title:not(.empty){padding-top:var(--sci-workbench-messagebox-padding)}:host:not(.content-selectable){-webkit-user-select:none;user-select:none}:host>div.slot.text{word-break:break-word;white-space:pre-line;text-align:var(--sci-workbench-messagebox-text-align)}\n"] }]
11634
+ }], ctorParameters: () => [], propDecorators: { message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: true }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }] } });
11635
+ function nullIfEmptyMessage(message) {
11636
+ return message !== '' ? message : null;
11572
11637
  }
11573
11638
 
11574
11639
  /*
@@ -11581,95 +11646,103 @@ class ɵWorkbenchDialogSize {
11581
11646
  * SPDX-License-Identifier: EPL-2.0
11582
11647
  */
11583
11648
  /** @inheritDoc */
11584
- class ɵWorkbenchDialogService {
11585
- _injector = inject(Injector);
11586
- _rootInjector = inject(ApplicationRef).injector;
11587
- _dialogRegistry = inject(WorkbenchDialogRegistry);
11588
- _document = inject(DOCUMENT);
11649
+ class ɵWorkbenchMessageBoxService {
11650
+ _workbenchDialogService = inject(WorkbenchDialogService);
11589
11651
  _zone = inject(NgZone);
11590
11652
  constructor() {
11591
11653
  this.installServiceLifecycleLogger();
11592
11654
  }
11593
- /** @inheritDoc */
11594
- async open(component, options) {
11595
- assertNotInReactiveContext(this.open, 'Call WorkbenchDialogService.open() in a non-reactive (non-tracking) context, such as within the untracked() function.');
11596
- // Ensure to run in Angular zone to display the dialog even when called from outside the Angular zone.
11597
- if (!NgZone.isInAngularZone()) {
11598
- return this._zone.run(() => this.open(component, options));
11599
- }
11600
- // Delay the opening of a context-modal dialog until all application-modal dialogs are closed.
11601
- // Otherwise, the context-modal dialog would overlap already opened application-modal dialogs.
11602
- const invocationContext = createDialogInvocationContext(options ?? {}, this._injector);
11603
- if (invocationContext) {
11604
- await this.waitUntilApplicationModalDialogsClosed();
11605
- }
11606
- // Create the dialog.
11607
- const dialog = this.createDialog(component, invocationContext, options ?? {});
11608
- this._dialogRegistry.register(dialog.id, dialog);
11609
- // Capture focused element to restore focus when closing the dialog.
11610
- const previouslyFocusedElement = this._document.activeElement instanceof HTMLElement ? this._document.activeElement : undefined;
11611
- try {
11612
- return await dialog.waitForClose();
11613
- }
11614
- finally {
11615
- this._dialogRegistry.unregister(dialog.id);
11616
- // Restore focus to previously focused element when closing the last dialog in the current context.
11617
- if (previouslyFocusedElement && !this._dialogRegistry.top(invocationContext?.elementId)()) {
11618
- previouslyFocusedElement.focus();
11619
- }
11620
- }
11621
- }
11622
- /**
11623
- * Creates the dialog handle.
11624
- */
11625
- createDialog(component, invocationContext, options) {
11626
- // Construct the handle in an injection context that shares the dialog's lifecycle, allowing for automatic cleanup of effects and RxJS interop functions.
11627
- const dialogId = computeDialogId();
11628
- const dialogInjector = Injector.create({
11629
- parent: this._rootInjector, // use root injector to be independent of service construction context
11630
- providers: [],
11631
- name: `Workbench Dialog ${dialogId}`,
11632
- });
11633
- return runInInjectionContext(dialogInjector, () => new ɵWorkbenchDialog(dialogId, component, invocationContext, options));
11634
- }
11635
11655
  /**
11636
- * Returns a Promise that resolves when all application modal-dialogs are closed. If none are opened, the Promise resolves immediately.
11656
+ * @inheritDoc
11637
11657
  */
11638
- async waitUntilApplicationModalDialogsClosed() {
11639
- // Use root injector to be independent of service construction context.
11640
- const injector = Injector.create({ parent: this._rootInjector, providers: [] });
11641
- await firstValueFrom(toObservable(this._dialogRegistry.top(), { injector }).pipe(filter(top => !top)));
11642
- injector.destroy();
11658
+ async open(message, options) {
11659
+ assertNotInReactiveContext(this.open, 'Call WorkbenchMessageBoxService.open() in a non-reactive (non-tracking) context, such as within the untracked() function.');
11660
+ // Ensure to run in Angular zone to display the message box even if called from outside the Angular zone, e.g. from an error handler.
11661
+ if (!NgZone.isInAngularZone()) {
11662
+ return this._zone.run(() => this.open(message, options));
11663
+ }
11664
+ return (await this._workbenchDialogService.open(WorkbenchMessageBoxComponent, {
11665
+ inputs: { message, options },
11666
+ modality: options?.modality,
11667
+ injector: options?.injector,
11668
+ providers: options?.providers,
11669
+ cssClass: options?.cssClass,
11670
+ context: options?.context,
11671
+ animate: true,
11672
+ }));
11643
11673
  }
11644
11674
  installServiceLifecycleLogger() {
11645
11675
  const logger = inject(Logger);
11646
11676
  const workbenchElement = inject(WORKBENCH_ELEMENT, { optional: true });
11647
- logger.debug(() => `Constructing WorkbenchDialogService [context=${workbenchElement?.id}]`, LoggerNames.LIFECYCLE);
11648
- inject(DestroyRef).onDestroy(() => logger.debug(() => `Destroying WorkbenchDialogService [context=${workbenchElement?.id}]'`, LoggerNames.LIFECYCLE));
11677
+ logger.debug(() => `Constructing WorkbenchMessageBoxService [context=${workbenchElement?.id}]`, LoggerNames.LIFECYCLE);
11678
+ inject(DestroyRef).onDestroy(() => logger.debug(() => `Destroying WorkbenchMessageBoxService [context=${workbenchElement?.id}]'`, LoggerNames.LIFECYCLE));
11649
11679
  }
11650
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: ɵWorkbenchDialogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
11651
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: ɵWorkbenchDialogService, providedIn: 'root' });
11680
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: ɵWorkbenchMessageBoxService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
11681
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: ɵWorkbenchMessageBoxService, providedIn: 'root' });
11652
11682
  }
11653
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: ɵWorkbenchDialogService, decorators: [{
11683
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: ɵWorkbenchMessageBoxService, decorators: [{
11654
11684
  type: Injectable,
11655
11685
  args: [{ providedIn: 'root' }]
11656
11686
  }], ctorParameters: () => [] });
11687
+
11657
11688
  /**
11658
- * Computes the dialog's invocation context based on passsed options and injection context.
11689
+ * Provides a standardized dialog for presenting a message to the user, such as an info, warning or alert,
11690
+ * or for prompting the user for confirmation. The message can be plain text or a component, allowing for
11691
+ * structured content or input prompts.
11692
+ *
11693
+ * Displayed on top of other content, a modal message box blocks interaction with other parts of the application.
11694
+ *
11695
+ * ## Modality
11696
+ * A message box can be context-modal or application-modal. Context-modal blocks a specific part of the application, as specified by the context;
11697
+ * application-modal blocks the workbench or browser viewport, based on {@link WorkbenchConfig.dialog.modalityScope}.
11698
+ *
11699
+ * ## Context
11700
+ * A message box can be bound to a context (e.g., part or view), defaulting to the calling context.
11701
+ * The message box is displayed only if the context is visible and closes when the context is disposed.
11702
+ *
11703
+ * ## Positioning
11704
+ * A message box is opened in the center of its context, if any, unless opened from the peripheral area.
11705
+ *
11706
+ * ## Stacking
11707
+ * Message boxes are stacked per modality, with only the topmost message box in each stack being interactive.
11708
+ *
11709
+ * ## Styling
11710
+ * The following CSS variables can be set to customize the default look of a message box.
11711
+ *
11712
+ * - `--sci-workbench-messagebox-max-width`
11713
+ * - `--sci-workbench-messagebox-severity-indicator-size`
11714
+ * - `--sci-workbench-messagebox-padding`
11715
+ * - `--sci-workbench-messagebox-text-align`
11716
+ * - `--sci-workbench-messagebox-title-align`
11717
+ * - `--sci-workbench-messagebox-title-font-family`
11718
+ * - `--sci-workbench-messagebox-title-font-weight`
11719
+ * - `--sci-workbench-messagebox-title-font-size`
11659
11720
  */
11660
- function createDialogInvocationContext(options, injector) {
11661
- if (options.modality === 'application') {
11662
- return null;
11663
- }
11664
- return createInvocationContext(options.context && (typeof options.context === 'object' ? options.context.viewId : options.context), { injector });
11721
+ class WorkbenchMessageBoxService {
11722
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchMessageBoxService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
11723
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchMessageBoxService, providedIn: 'root', useExisting: ɵWorkbenchMessageBoxService });
11665
11724
  }
11725
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchMessageBoxService, decorators: [{
11726
+ type: Injectable,
11727
+ args: [{ providedIn: 'root', useExisting: ɵWorkbenchMessageBoxService }]
11728
+ }] });
11729
+
11730
+ /*
11731
+ * Copyright (c) 2018-2026 Swiss Federal Railways
11732
+ *
11733
+ * This program and the accompanying materials are made
11734
+ * available under the terms of the Eclipse Public License 2.0
11735
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
11736
+ *
11737
+ * SPDX-License-Identifier: EPL-2.0
11738
+ */
11666
11739
  /**
11667
- * Provides {@link WorkbenchDialogService} for dependency injection.
11740
+ * Provides {@link WorkbenchMessageBoxService} for dependency injection.
11668
11741
  */
11669
- function provideWorkbenchDialogService() {
11742
+ function provideWorkbenchMessageBoxService() {
11670
11743
  return [
11671
- ɵWorkbenchDialogService,
11672
- { provide: WorkbenchDialogService, useExisting: ɵWorkbenchDialogService },
11744
+ ɵWorkbenchMessageBoxService,
11745
+ { provide: WorkbenchMessageBoxService, useExisting: ɵWorkbenchMessageBoxService },
11673
11746
  ];
11674
11747
  }
11675
11748
 
@@ -12651,6 +12724,11 @@ class ViewTabComponent {
12651
12724
  event.stopPropagation();
12652
12725
  event.preventDefault();
12653
12726
  }
12727
+ onMouseDown(event) {
12728
+ if (event.button === 1) { // primary aux button
12729
+ event.preventDefault(); // prevent middle-click scrolling; necessary for aux click to work
12730
+ }
12731
+ }
12654
12732
  onDragStart(event) {
12655
12733
  if (!event.dataTransfer) {
12656
12734
  return;
@@ -12715,7 +12793,7 @@ class ViewTabComponent {
12715
12793
  });
12716
12794
  }
12717
12795
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: ViewTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
12718
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.1", type: ViewTabComponent, isStandalone: true, selector: "wb-view-tab", inputs: { view: { classPropertyName: "view", publicName: "viewId", isSignal: true, isRequired: true, transformFunction: null } }, host: { listeners: { "click": "onClick()", "auxclick": "onAuxClick($event)", "contextmenu": "onContextmenu($event)", "dragstart": "onDragStart($event)", "dragend": "onDragEnd()" }, properties: { "attr.data-viewid": "view().id", "attr.data-active": "view().active() ? '' : null", "attr.data-dirty": "view().dirty() ? '' : null", "attr.data-focus-within-view": "view().focused() ? '' : null", "attr.draggable": "true", "attr.tabindex": "-1", "class.view-drag": "viewDragService.dragging()", "class": "view().classList.asList()", "style.--sci-workbench-tab-title-offset-right": "viewTitleOffsetRight()" } }, ngImport: i0, template: "<!-- IMPORTANT: THIS HTML FILE IS ALSO USED BY `ViewTabDragImageComponent` -->\n\n@if (view().active()) {\n <div class=\"corner-radius start\">\n <div class=\"circle\"></div>\n </div>\n}\n\n<div class=\"content\">\n <ng-container *cdkPortalOutlet=\"viewTabContentPortal()\"/>\n</div>\n\n@if (view().active()) {\n <div class=\"corner-radius end\">\n <div class=\"circle\"></div>\n </div>\n}\n\n@if (view().closable()) {\n <button (click)=\"onClose($event)\"\n [title]=\"('%workbench.close_tab.tooltip;close_others_modifier=Alt' | wbText)()\"\n [disabled]=\"!view().isClosable()\"\n class=\"close e2e-close\">\n <wb-icon icon=\"workbench.close\"/>\n </button>\n}\n", styles: [":host{display:grid;align-items:center;padding-left:var(--sci-workbench-tab-padding-inline);padding-right:var(--sci-workbench-tab-padding-inline);position:relative;-webkit-user-select:none;user-select:none;cursor:var(--sci-workbench-tab-cursor);box-sizing:border-box;outline:none;border-top:var(--sci-workbench-tab-border-top-width) solid transparent;border-left:var(--sci-workbench-tab-border-width) solid transparent;border-right:var(--sci-workbench-tab-border-width) solid transparent;border-top-left-radius:var(--sci-workbench-tab-border-radius);border-top-right-radius:var(--sci-workbench-tab-border-radius)}:host[data-active]{cursor:default;border-left-color:var(--sci-workbench-tab-border-color);border-right-color:var(--sci-workbench-tab-border-color);border-top-color:var(--sci-workbench-tab-border-color);background-color:var(--sci-workbench-view-background-color)}wb-part[data-peripheral] :host[data-active]{background-color:var(--sci-workbench-view-peripheral-background-color)}:host[data-active]>div.content{color:var(--sci-workbench-tab-text-color-active)}:host[data-active][data-focus-within-view]>div.content{color:var(--sci-workbench-part-active-tab-text-color-active)}:host>div.content{display:inline-grid;font-family:var(--sci-workbench-tab-font-family),sans-serif;font-size:var(--sci-workbench-tab-font-size);font-weight:var(--sci-workbench-tab-font-weight);min-width:var(--sci-workbench-tab-min-width);max-width:var(--sci-workbench-tab-max-width);isolation:isolate;transform:translateY(calc(-1 * var(--sci-workbench-tab-border-top-width)))}:host>button.close:is(button,#sci-reset){all:unset;display:inline-grid;place-content:center;place-items:center;padding:.25em;border-radius:var(--sci-corner);-webkit-user-select:none;user-select:none;overflow:hidden;cursor:var(--sci-workbench-button-cursor)}:host>button.close:is(button,#sci-reset):hover:where(:not(:disabled)){background-color:var(--sci-workbench-button-background-color-hover)}:host>button.close:is(button,#sci-reset):active:where(:not(:disabled)){background-color:var(--sci-workbench-button-background-color-active)}:host>button.close:is(button,#sci-reset):focus:not(:focus-visible){outline:none}:host>button.close:is(button,#sci-reset):focus-visible{outline:var(--sci-workbench-button-outline-width-focus) solid var(--sci-color-accent)}:host>button.close:is(button,#sci-reset):disabled{color:var(--sci-color-gray-500)}:host>button.close:is(button,#sci-reset){position:absolute;right:calc(var(--sci-workbench-tab-padding-inline) - .125em);visibility:hidden;padding:.125em;border-radius:var(--sci-corner-small);transform:translateY(calc(-1 * var(--sci-workbench-tab-border-top-width)))}:host[data-active]>button.close:is(button,#sci-reset),:host:hover:not(.view-drag)>button.close:is(button,#sci-reset){visibility:visible}@container style(--sci-workbench-tab-background-color-hover){:host:hover:not([data-active]):not(.view-drag):before{content:\"\";position:absolute;place-self:start center;box-sizing:border-box;height:calc(100% - var(--sci-workbench-tab-padding-block-hover) - var(--sci-workbench-part-bar-border-bottom-width));width:calc(100% - var(--sci-workbench-tab-padding-inline-hover) + 2 * var(--sci-workbench-tab-border-width));background-color:var(--sci-workbench-tab-background-color-hover);border:var(--sci-workbench-tab-border-width) solid var(--sci-workbench-tab-background-color-hover);border-radius:var(--sci-workbench-tab-border-radius);pointer-events:none}:host:hover:not([data-active]):not(.view-drag)>button.close:is(button,#sci-reset):not(:disabled):hover{background-color:color-mix(in srgb,var(--sci-workbench-tab-background-color-hover) 90%,light-dark(var(--sci-static-color-black),var(--sci-static-color-white)))}}:host>div.corner-radius{height:var(--sci-workbench-tab-border-radius);width:var(--sci-workbench-tab-border-radius);overflow:hidden;position:absolute;bottom:0}:host>div.corner-radius>div.circle{position:absolute;top:calc(-2 * var(--sci-workbench-tab-border-radius));width:calc(2 * var(--sci-workbench-tab-border-radius));height:calc(2 * var(--sci-workbench-tab-border-radius));border:var(--sci-workbench-tab-border-radius) solid var(--sci-workbench-view-background-color);border-radius:50%;box-shadow:inset 0 0 0 var(--sci-workbench-tab-border-width) var(--sci-workbench-tab-border-color);box-sizing:content-box}wb-part[data-peripheral] :host>div.corner-radius>div.circle{border-color:var(--sci-workbench-view-peripheral-background-color)}:host>div.corner-radius.start{left:calc(-1 * var(--sci-workbench-tab-border-radius))}:host>div.corner-radius.start>div.circle{left:calc(-2 * var(--sci-workbench-tab-border-radius))}:host>div.corner-radius.end{right:calc(-1 * var(--sci-workbench-tab-border-radius))}:host>div.corner-radius.end>div.circle{right:calc(-2 * var(--sci-workbench-tab-border-radius))}@container viewtab (height >= 3.5rem){:host>button.close:is(button,#sci-reset){top:5px;right:5px}}\n"], dependencies: [{ kind: "directive", type: CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }, { kind: "component", type: IconComponent, selector: "wb-icon", inputs: ["icon"] }, { kind: "pipe", type: TextPipe, name: "wbText" }] });
12796
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.1", type: ViewTabComponent, isStandalone: true, selector: "wb-view-tab", inputs: { view: { classPropertyName: "view", publicName: "viewId", isSignal: true, isRequired: true, transformFunction: null } }, host: { listeners: { "click": "onClick()", "auxclick": "onAuxClick($event)", "contextmenu": "onContextmenu($event)", "mousedown": "onMouseDown($event)", "dragstart": "onDragStart($event)", "dragend": "onDragEnd()" }, properties: { "attr.data-viewid": "view().id", "attr.data-active": "view().active() ? '' : null", "attr.data-dirty": "view().dirty() ? '' : null", "attr.data-focus-within-view": "view().focused() ? '' : null", "attr.draggable": "true", "attr.tabindex": "-1", "class.view-drag": "viewDragService.dragging()", "class": "view().classList.asList()", "style.--sci-workbench-tab-title-offset-right": "viewTitleOffsetRight()" } }, ngImport: i0, template: "<!-- IMPORTANT: THIS HTML FILE IS ALSO USED BY `ViewTabDragImageComponent` -->\n\n@if (view().active()) {\n <div class=\"corner-radius start\">\n <div class=\"circle\"></div>\n </div>\n}\n\n<div class=\"content\">\n <ng-container *cdkPortalOutlet=\"viewTabContentPortal()\"/>\n</div>\n\n@if (view().active()) {\n <div class=\"corner-radius end\">\n <div class=\"circle\"></div>\n </div>\n}\n\n@if (view().closable()) {\n <button (click)=\"onClose($event)\"\n [title]=\"('%workbench.close_tab.tooltip;close_others_modifier=Alt' | wbText)()\"\n [disabled]=\"!view().isClosable()\"\n class=\"close e2e-close\">\n <wb-icon icon=\"workbench.close\"/>\n </button>\n}\n", styles: [":host{display:grid;align-items:center;padding-left:var(--sci-workbench-tab-padding-inline);padding-right:var(--sci-workbench-tab-padding-inline);position:relative;-webkit-user-select:none;user-select:none;cursor:var(--sci-workbench-tab-cursor);box-sizing:border-box;outline:none;border-top:var(--sci-workbench-tab-border-top-width) solid transparent;border-left:var(--sci-workbench-tab-border-width) solid transparent;border-right:var(--sci-workbench-tab-border-width) solid transparent;border-top-left-radius:var(--sci-workbench-tab-border-radius);border-top-right-radius:var(--sci-workbench-tab-border-radius)}:host[data-active]{cursor:default;border-left-color:var(--sci-workbench-tab-border-color);border-right-color:var(--sci-workbench-tab-border-color);border-top-color:var(--sci-workbench-tab-border-color);background-color:var(--sci-workbench-view-background-color)}wb-part[data-peripheral] :host[data-active]{background-color:var(--sci-workbench-view-peripheral-background-color)}:host[data-active]>div.content{color:var(--sci-workbench-tab-text-color-active)}:host[data-active][data-focus-within-view]>div.content{color:var(--sci-workbench-part-active-tab-text-color-active)}:host>div.content{display:inline-grid;font-family:var(--sci-workbench-tab-font-family),sans-serif;font-size:var(--sci-workbench-tab-font-size);font-weight:var(--sci-workbench-tab-font-weight);min-width:var(--sci-workbench-tab-min-width);max-width:var(--sci-workbench-tab-max-width);isolation:isolate;transform:translateY(calc(-1 * var(--sci-workbench-tab-border-top-width)))}:host>button.close:is(button,#sci-reset){all:unset;display:inline-grid;place-content:center;place-items:center;padding:.25em;border-radius:var(--sci-corner);-webkit-user-select:none;user-select:none;overflow:hidden;cursor:var(--sci-workbench-button-cursor)}:host>button.close:is(button,#sci-reset):hover:where(:not(:disabled)){background-color:var(--sci-workbench-button-background-color-hover)}:host>button.close:is(button,#sci-reset):active:where(:not(:disabled)){background-color:var(--sci-workbench-button-background-color-active)}:host>button.close:is(button,#sci-reset):focus:not(:focus-visible){outline:none}:host>button.close:is(button,#sci-reset):focus-visible{outline:var(--sci-workbench-button-outline-width-focus) solid var(--sci-color-accent)}:host>button.close:is(button,#sci-reset):disabled{color:var(--sci-color-gray-500)}:host>button.close:is(button,#sci-reset){position:absolute;right:calc(var(--sci-workbench-tab-padding-inline) - .125em);visibility:hidden;padding:.125em;border-radius:var(--sci-corner-small);transform:translateY(calc(-1 * var(--sci-workbench-tab-border-top-width)))}:host[data-active]>button.close:is(button,#sci-reset),:host:hover:not(.view-drag)>button.close:is(button,#sci-reset){visibility:visible}@container style(--sci-workbench-tab-background-color-hover){:host:hover:not([data-active]):not(.view-drag):before{content:\"\";position:absolute;place-self:start center;box-sizing:border-box;height:calc(100% - var(--sci-workbench-tab-padding-block-hover) - var(--sci-workbench-part-bar-border-bottom-width));width:calc(100% - var(--sci-workbench-tab-padding-inline-hover) + 2 * var(--sci-workbench-tab-border-width));background-color:var(--sci-workbench-tab-background-color-hover);border:var(--sci-workbench-tab-border-width) solid var(--sci-workbench-tab-background-color-hover);border-radius:var(--sci-workbench-tab-border-radius);pointer-events:none}:host:hover:not([data-active]):not(.view-drag)>button.close:is(button,#sci-reset):not(:disabled):hover{background-color:color-mix(in srgb,var(--sci-workbench-tab-background-color-hover) 90%,light-dark(var(--sci-static-color-black),var(--sci-static-color-white)))}}:host>div.corner-radius{height:var(--sci-workbench-tab-border-radius);width:var(--sci-workbench-tab-border-radius);overflow:hidden;position:absolute;bottom:0}:host>div.corner-radius>div.circle{position:absolute;top:calc(-2 * var(--sci-workbench-tab-border-radius));width:calc(2 * var(--sci-workbench-tab-border-radius));height:calc(2 * var(--sci-workbench-tab-border-radius));border:var(--sci-workbench-tab-border-radius) solid var(--sci-workbench-view-background-color);border-radius:50%;box-shadow:inset 0 0 0 var(--sci-workbench-tab-border-width) var(--sci-workbench-tab-border-color);box-sizing:content-box}wb-part[data-peripheral] :host>div.corner-radius>div.circle{border-color:var(--sci-workbench-view-peripheral-background-color)}:host>div.corner-radius.start{left:calc(-1 * var(--sci-workbench-tab-border-radius))}:host>div.corner-radius.start>div.circle{left:calc(-2 * var(--sci-workbench-tab-border-radius))}:host>div.corner-radius.end{right:calc(-1 * var(--sci-workbench-tab-border-radius))}:host>div.corner-radius.end>div.circle{right:calc(-2 * var(--sci-workbench-tab-border-radius))}@container viewtab (height >= 3.5rem){:host>button.close:is(button,#sci-reset){top:5px;right:5px}}\n"], dependencies: [{ kind: "directive", type: CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }, { kind: "component", type: IconComponent, selector: "wb-icon", inputs: ["icon"] }, { kind: "pipe", type: TextPipe, name: "wbText" }] });
12719
12797
  }
12720
12798
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: ViewTabComponent, decorators: [{
12721
12799
  type: Component,
@@ -12736,6 +12814,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImpor
12736
12814
  '(click)': 'onClick()',
12737
12815
  '(auxclick)': 'onAuxClick($event)',
12738
12816
  '(contextmenu)': 'onContextmenu($event)',
12817
+ '(mousedown)': 'onMouseDown($event)',
12739
12818
  '(dragstart)': 'onDragStart($event)',
12740
12819
  '(dragend)': 'onDragEnd()',
12741
12820
  }, template: "<!-- IMPORTANT: THIS HTML FILE IS ALSO USED BY `ViewTabDragImageComponent` -->\n\n@if (view().active()) {\n <div class=\"corner-radius start\">\n <div class=\"circle\"></div>\n </div>\n}\n\n<div class=\"content\">\n <ng-container *cdkPortalOutlet=\"viewTabContentPortal()\"/>\n</div>\n\n@if (view().active()) {\n <div class=\"corner-radius end\">\n <div class=\"circle\"></div>\n </div>\n}\n\n@if (view().closable()) {\n <button (click)=\"onClose($event)\"\n [title]=\"('%workbench.close_tab.tooltip;close_others_modifier=Alt' | wbText)()\"\n [disabled]=\"!view().isClosable()\"\n class=\"close e2e-close\">\n <wb-icon icon=\"workbench.close\"/>\n </button>\n}\n", styles: [":host{display:grid;align-items:center;padding-left:var(--sci-workbench-tab-padding-inline);padding-right:var(--sci-workbench-tab-padding-inline);position:relative;-webkit-user-select:none;user-select:none;cursor:var(--sci-workbench-tab-cursor);box-sizing:border-box;outline:none;border-top:var(--sci-workbench-tab-border-top-width) solid transparent;border-left:var(--sci-workbench-tab-border-width) solid transparent;border-right:var(--sci-workbench-tab-border-width) solid transparent;border-top-left-radius:var(--sci-workbench-tab-border-radius);border-top-right-radius:var(--sci-workbench-tab-border-radius)}:host[data-active]{cursor:default;border-left-color:var(--sci-workbench-tab-border-color);border-right-color:var(--sci-workbench-tab-border-color);border-top-color:var(--sci-workbench-tab-border-color);background-color:var(--sci-workbench-view-background-color)}wb-part[data-peripheral] :host[data-active]{background-color:var(--sci-workbench-view-peripheral-background-color)}:host[data-active]>div.content{color:var(--sci-workbench-tab-text-color-active)}:host[data-active][data-focus-within-view]>div.content{color:var(--sci-workbench-part-active-tab-text-color-active)}:host>div.content{display:inline-grid;font-family:var(--sci-workbench-tab-font-family),sans-serif;font-size:var(--sci-workbench-tab-font-size);font-weight:var(--sci-workbench-tab-font-weight);min-width:var(--sci-workbench-tab-min-width);max-width:var(--sci-workbench-tab-max-width);isolation:isolate;transform:translateY(calc(-1 * var(--sci-workbench-tab-border-top-width)))}:host>button.close:is(button,#sci-reset){all:unset;display:inline-grid;place-content:center;place-items:center;padding:.25em;border-radius:var(--sci-corner);-webkit-user-select:none;user-select:none;overflow:hidden;cursor:var(--sci-workbench-button-cursor)}:host>button.close:is(button,#sci-reset):hover:where(:not(:disabled)){background-color:var(--sci-workbench-button-background-color-hover)}:host>button.close:is(button,#sci-reset):active:where(:not(:disabled)){background-color:var(--sci-workbench-button-background-color-active)}:host>button.close:is(button,#sci-reset):focus:not(:focus-visible){outline:none}:host>button.close:is(button,#sci-reset):focus-visible{outline:var(--sci-workbench-button-outline-width-focus) solid var(--sci-color-accent)}:host>button.close:is(button,#sci-reset):disabled{color:var(--sci-color-gray-500)}:host>button.close:is(button,#sci-reset){position:absolute;right:calc(var(--sci-workbench-tab-padding-inline) - .125em);visibility:hidden;padding:.125em;border-radius:var(--sci-corner-small);transform:translateY(calc(-1 * var(--sci-workbench-tab-border-top-width)))}:host[data-active]>button.close:is(button,#sci-reset),:host:hover:not(.view-drag)>button.close:is(button,#sci-reset){visibility:visible}@container style(--sci-workbench-tab-background-color-hover){:host:hover:not([data-active]):not(.view-drag):before{content:\"\";position:absolute;place-self:start center;box-sizing:border-box;height:calc(100% - var(--sci-workbench-tab-padding-block-hover) - var(--sci-workbench-part-bar-border-bottom-width));width:calc(100% - var(--sci-workbench-tab-padding-inline-hover) + 2 * var(--sci-workbench-tab-border-width));background-color:var(--sci-workbench-tab-background-color-hover);border:var(--sci-workbench-tab-border-width) solid var(--sci-workbench-tab-background-color-hover);border-radius:var(--sci-workbench-tab-border-radius);pointer-events:none}:host:hover:not([data-active]):not(.view-drag)>button.close:is(button,#sci-reset):not(:disabled):hover{background-color:color-mix(in srgb,var(--sci-workbench-tab-background-color-hover) 90%,light-dark(var(--sci-static-color-black),var(--sci-static-color-white)))}}:host>div.corner-radius{height:var(--sci-workbench-tab-border-radius);width:var(--sci-workbench-tab-border-radius);overflow:hidden;position:absolute;bottom:0}:host>div.corner-radius>div.circle{position:absolute;top:calc(-2 * var(--sci-workbench-tab-border-radius));width:calc(2 * var(--sci-workbench-tab-border-radius));height:calc(2 * var(--sci-workbench-tab-border-radius));border:var(--sci-workbench-tab-border-radius) solid var(--sci-workbench-view-background-color);border-radius:50%;box-shadow:inset 0 0 0 var(--sci-workbench-tab-border-width) var(--sci-workbench-tab-border-color);box-sizing:content-box}wb-part[data-peripheral] :host>div.corner-radius>div.circle{border-color:var(--sci-workbench-view-peripheral-background-color)}:host>div.corner-radius.start{left:calc(-1 * var(--sci-workbench-tab-border-radius))}:host>div.corner-radius.start>div.circle{left:calc(-2 * var(--sci-workbench-tab-border-radius))}:host>div.corner-radius.end{right:calc(-1 * var(--sci-workbench-tab-border-radius))}:host>div.corner-radius.end>div.circle{right:calc(-2 * var(--sci-workbench-tab-border-radius))}@container viewtab (height >= 3.5rem){:host>button.close:is(button,#sci-reset){top:5px;right:5px}}\n"] }]
@@ -13680,7 +13759,7 @@ class PartComponent {
13680
13759
  inject(DestroyRef).onDestroy(() => logger.debug(() => `Destroying PartComponent [partId=${this.part.id}]'`, LoggerNames.LIFECYCLE));
13681
13760
  }
13682
13761
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: PartComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
13683
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.1", type: PartComponent, isStandalone: true, selector: "wb-part", host: { properties: { "attr.data-partid": "part.id", "attr.data-peripheral": "part.peripheral() ? '' : null", "attr.data-grid": "dasherize(part.gridName())", "attr.data-active": "part.active() ? '' : null", "attr.data-referencepart": "part.referencePart() ? '' : null", "attr.tabindex": "-1", "class": "part.classList.asList()" } }, ngImport: i0, template: "@if (part.title() || part.views().length || part.actions().length || part.canMinimize()) {\n <wb-part-bar/>\n}\n\n@if (part.views().length) {\n <!-- Prevent splitting if there is no active view, i.e, when dragging the last view out of the tabbar. -->\n @let canSplit = !!part.activeView();\n <div wbViewDropZone\n [wbViewDropZoneRegionSize]=\".25\"\n [wbViewDropZonePlaceholderSize]=\".5\"\n [wbViewDropZoneAttributes]=\"{'data-partid': part.id}\"\n [wbViewDropZoneRegions]=\"canDrop() && {center: true, north: canSplit, south: canSplit, west: canSplit, east: canSplit}\"\n (wbViewDropZoneDrop)=\"onViewDrop($event)\"\n class=\"content e2e-content e2e-view-content\">\n <ng-container *wbPortalOutlet=\"part.activeView()?.slot!.portal; destroyOnDetach: false\"/>\n </div>\n} @else {\n <div wbViewDropZone\n [wbViewDropZoneRegions]=\"canDrop() && {center: false, north: true, south: true, west: true, east: true}\"\n [wbViewDropZoneAttributes]=\"{'data-partid': part.id}\"\n (wbViewDropZoneDrop)=\"onViewDrop($event)\"\n class=\"content e2e-content e2e-part-content\">\n <ng-container *wbPortalOutlet=\"part.slot.portal; destroyOnDetach: false\"/>\n </div>\n}\n", styles: [":host{display:flex;flex-direction:column;outline:none;background-color:var(--sci-workbench-part-background-color);overflow:hidden}:host[data-peripheral]{background-color:var(--sci-workbench-part-peripheral-background-color)}:host>wb-part-bar{flex:none}:host>div.content{flex:auto;display:grid;position:relative}\n"], dependencies: [{ kind: "component", type: PartBarComponent, selector: "wb-part-bar" }, { kind: "directive", type: ViewDropZoneDirective, selector: "[wbViewDropZone]", inputs: ["wbViewDropZoneRegions", "wbViewDropZoneAttributes", "wbViewDropZoneRegionSize", "wbViewDropZonePlaceholderSize"], outputs: ["wbViewDropZoneDrop"] }, { kind: "directive", type: WorkbenchPortalOutletDirective, selector: "ng-template[wbPortalOutlet]", inputs: ["wbPortalOutlet", "wbPortalOutletDestroyOnDetach"] }] });
13762
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.1", type: PartComponent, isStandalone: true, selector: "wb-part", host: { properties: { "attr.data-partid": "part.id", "attr.data-peripheral": "part.peripheral() ? '' : null", "attr.data-grid": "dasherize(part.gridName())", "attr.data-active": "part.active() ? '' : null", "attr.data-referencepart": "part.referencePart() ? '' : null", "attr.tabindex": "-1", "class": "part.classList.asList()" } }, ngImport: i0, template: "@if (part.title() || part.views().length || part.actions().length || part.canMinimize()) {\n <wb-part-bar/>\n}\n\n@if (part.views().length) {\n <!-- Prevent splitting if there is no active view, i.e, when dragging the last view out of the tabbar. -->\n @let canSplit = !!part.activeView();\n <div wbViewDropZone\n [wbViewDropZoneRegionSize]=\".25\"\n [wbViewDropZonePlaceholderSize]=\".5\"\n [wbViewDropZoneAttributes]=\"{'data-partid': part.id}\"\n [wbViewDropZoneRegions]=\"canDrop() && {center: true, north: canSplit, south: canSplit, west: canSplit, east: canSplit}\"\n (wbViewDropZoneDrop)=\"onViewDrop($event)\"\n class=\"slot e2e-slot e2e-view-slot\">\n <ng-container *wbPortalOutlet=\"part.activeView()?.slot!.portal; destroyOnDetach: false\"/>\n </div>\n} @else {\n <div wbViewDropZone\n [wbViewDropZoneRegions]=\"canDrop() && {center: false, north: true, south: true, west: true, east: true}\"\n [wbViewDropZoneAttributes]=\"{'data-partid': part.id}\"\n (wbViewDropZoneDrop)=\"onViewDrop($event)\"\n class=\"slot e2e-slot e2e-part-slot\">\n <ng-container *wbPortalOutlet=\"part.slot.portal; destroyOnDetach: false\"/>\n </div>\n}\n", styles: [":host{display:flex;flex-direction:column;outline:none;background-color:var(--sci-workbench-part-background-color);overflow:hidden}:host[data-peripheral]{background-color:var(--sci-workbench-part-peripheral-background-color)}:host>wb-part-bar{flex:none}:host>div.slot{flex:auto;display:grid;position:relative}\n"], dependencies: [{ kind: "component", type: PartBarComponent, selector: "wb-part-bar" }, { kind: "directive", type: ViewDropZoneDirective, selector: "[wbViewDropZone]", inputs: ["wbViewDropZoneRegions", "wbViewDropZoneAttributes", "wbViewDropZoneRegionSize", "wbViewDropZonePlaceholderSize"], outputs: ["wbViewDropZoneDrop"] }, { kind: "directive", type: WorkbenchPortalOutletDirective, selector: "ng-template[wbPortalOutlet]", inputs: ["wbPortalOutlet", "wbPortalOutletDestroyOnDetach"] }] });
13684
13763
  }
13685
13764
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: PartComponent, decorators: [{
13686
13765
  type: Component,
@@ -13696,7 +13775,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImpor
13696
13775
  '[attr.data-referencepart]': `part.referencePart() ? '' : null`,
13697
13776
  '[attr.tabindex]': '-1',
13698
13777
  '[class]': 'part.classList.asList()',
13699
- }, template: "@if (part.title() || part.views().length || part.actions().length || part.canMinimize()) {\n <wb-part-bar/>\n}\n\n@if (part.views().length) {\n <!-- Prevent splitting if there is no active view, i.e, when dragging the last view out of the tabbar. -->\n @let canSplit = !!part.activeView();\n <div wbViewDropZone\n [wbViewDropZoneRegionSize]=\".25\"\n [wbViewDropZonePlaceholderSize]=\".5\"\n [wbViewDropZoneAttributes]=\"{'data-partid': part.id}\"\n [wbViewDropZoneRegions]=\"canDrop() && {center: true, north: canSplit, south: canSplit, west: canSplit, east: canSplit}\"\n (wbViewDropZoneDrop)=\"onViewDrop($event)\"\n class=\"content e2e-content e2e-view-content\">\n <ng-container *wbPortalOutlet=\"part.activeView()?.slot!.portal; destroyOnDetach: false\"/>\n </div>\n} @else {\n <div wbViewDropZone\n [wbViewDropZoneRegions]=\"canDrop() && {center: false, north: true, south: true, west: true, east: true}\"\n [wbViewDropZoneAttributes]=\"{'data-partid': part.id}\"\n (wbViewDropZoneDrop)=\"onViewDrop($event)\"\n class=\"content e2e-content e2e-part-content\">\n <ng-container *wbPortalOutlet=\"part.slot.portal; destroyOnDetach: false\"/>\n </div>\n}\n", styles: [":host{display:flex;flex-direction:column;outline:none;background-color:var(--sci-workbench-part-background-color);overflow:hidden}:host[data-peripheral]{background-color:var(--sci-workbench-part-peripheral-background-color)}:host>wb-part-bar{flex:none}:host>div.content{flex:auto;display:grid;position:relative}\n"] }]
13778
+ }, template: "@if (part.title() || part.views().length || part.actions().length || part.canMinimize()) {\n <wb-part-bar/>\n}\n\n@if (part.views().length) {\n <!-- Prevent splitting if there is no active view, i.e, when dragging the last view out of the tabbar. -->\n @let canSplit = !!part.activeView();\n <div wbViewDropZone\n [wbViewDropZoneRegionSize]=\".25\"\n [wbViewDropZonePlaceholderSize]=\".5\"\n [wbViewDropZoneAttributes]=\"{'data-partid': part.id}\"\n [wbViewDropZoneRegions]=\"canDrop() && {center: true, north: canSplit, south: canSplit, west: canSplit, east: canSplit}\"\n (wbViewDropZoneDrop)=\"onViewDrop($event)\"\n class=\"slot e2e-slot e2e-view-slot\">\n <ng-container *wbPortalOutlet=\"part.activeView()?.slot!.portal; destroyOnDetach: false\"/>\n </div>\n} @else {\n <div wbViewDropZone\n [wbViewDropZoneRegions]=\"canDrop() && {center: false, north: true, south: true, west: true, east: true}\"\n [wbViewDropZoneAttributes]=\"{'data-partid': part.id}\"\n (wbViewDropZoneDrop)=\"onViewDrop($event)\"\n class=\"slot e2e-slot e2e-part-slot\">\n <ng-container *wbPortalOutlet=\"part.slot.portal; destroyOnDetach: false\"/>\n </div>\n}\n", styles: [":host{display:flex;flex-direction:column;outline:none;background-color:var(--sci-workbench-part-background-color);overflow:hidden}:host[data-peripheral]{background-color:var(--sci-workbench-part-peripheral-background-color)}:host>wb-part-bar{flex:none}:host>div.slot{flex:auto;display:grid;position:relative}\n"] }]
13700
13779
  }], ctorParameters: () => [] });
13701
13780
 
13702
13781
  /*
@@ -17770,18 +17849,209 @@ class ɵNotification {
17770
17849
  setCssClass(cssClass) {
17771
17850
  this._notification.cssClass = cssClass;
17772
17851
  }
17773
- /** @inheritDoc */
17774
- close() {
17775
- this._notification.close();
17852
+ /** @inheritDoc */
17853
+ close() {
17854
+ this._notification.close();
17855
+ }
17856
+ }
17857
+ /**
17858
+ * TODO [Angular 22] Remove with Angular 22. Used for backward compatiblity.
17859
+ */
17860
+ const LEGACY_NOTIFICATION_INPUT = `${UID.randomUID()}-auto-generated`;
17861
+
17862
+ /*
17863
+ * Copyright (c) 2018-2025 Swiss Federal Railways
17864
+ *
17865
+ * This program and the accompanying materials are made
17866
+ * available under the terms of the Eclipse Public License 2.0
17867
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
17868
+ *
17869
+ * SPDX-License-Identifier: EPL-2.0
17870
+ */
17871
+ /**
17872
+ * Represents a handle that a notification component can inject to interact with the notification, for example,
17873
+ * to read input data or to configure the notification.
17874
+ *
17875
+ * A notification is a closable message that appears in the upper-right corner and disappears automatically after a few seconds.
17876
+ * It informs the user of a system event, e.g., that a task has been completed or an error has occurred.
17877
+ *
17878
+ * @deprecated since version 21.0.0-beta.1. Replaced by `WorkbenchNotification`. Marked for removal in version 22.
17879
+ */
17880
+ class Notification {
17881
+ /**
17882
+ * Input data as passed by the notification opener, or `undefined` if not passed.
17883
+ */
17884
+ input;
17885
+ }
17886
+
17887
+ /*
17888
+ * Copyright (c) 2018-2025 Swiss Federal Railways
17889
+ *
17890
+ * This program and the accompanying materials are made
17891
+ * available under the terms of the Eclipse Public License 2.0
17892
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
17893
+ *
17894
+ * SPDX-License-Identifier: EPL-2.0
17895
+ */
17896
+ /**
17897
+ * TODO [Angular 22] Remove with Angular 22. Used for backward compatiblity.
17898
+ */
17899
+ class RemoveLegacyInputPipe {
17900
+ transform(inputs) {
17901
+ const inputsCopy = { ...inputs ?? {} };
17902
+ delete inputsCopy[LEGACY_NOTIFICATION_INPUT]; // eslint-disable-line @typescript-eslint/no-dynamic-delete
17903
+ return inputsCopy;
17904
+ }
17905
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: RemoveLegacyInputPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
17906
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.1", ngImport: i0, type: RemoveLegacyInputPipe, isStandalone: true, name: "wbRemoveLegacyInput" });
17907
+ }
17908
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: RemoveLegacyInputPipe, decorators: [{
17909
+ type: Pipe,
17910
+ args: [{ name: 'wbRemoveLegacyInput' }]
17911
+ }] });
17912
+
17913
+ /*
17914
+ * Copyright (c) 2018-2026 Swiss Federal Railways
17915
+ *
17916
+ * This program and the accompanying materials are made
17917
+ * available under the terms of the Eclipse Public License 2.0
17918
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
17919
+ *
17920
+ * SPDX-License-Identifier: EPL-2.0
17921
+ */
17922
+ /**
17923
+ * Renders the content of a workbench notification.
17924
+ */
17925
+ class WorkbenchNotificationComponent {
17926
+ notification = inject(ɵWorkbenchNotification);
17927
+ hover = signal(false, { ...(ngDevMode ? { debugName: "hover" } : {}) });
17928
+ slotAnchorName = this.notification.id.replace('.', '_'); // Anchor must not contain a dot.
17929
+ notificationSlotBounds = viewChild('slot_bounds', { ...(ngDevMode ? { debugName: "notificationSlotBounds" } : {}), read: (ElementRef) });
17930
+ constructor() {
17931
+ this.installAutoCloseTimer();
17932
+ this.closeOnEscapeIfOnTop();
17933
+ trackFocus(inject(ElementRef).nativeElement, this.notification);
17934
+ }
17935
+ onClose() {
17936
+ this.notification.close();
17937
+ }
17938
+ onEscape(event) {
17939
+ if (this.notification.focused()) {
17940
+ event.stopPropagation(); // stop propagation to prevent closing the most recently displayed notification
17941
+ this.notification.close();
17942
+ }
17943
+ }
17944
+ onAuxClick(event) {
17945
+ if (event.button === 1) { // primary aux button
17946
+ event.preventDefault(); // prevent user-agent default action
17947
+ this.notification.close();
17948
+ }
17949
+ }
17950
+ onMouseDown(event) {
17951
+ if (event.button === 1) { // primary aux button
17952
+ event.preventDefault(); // prevent middle-click scrolling; necessary for aux click to work
17953
+ }
17954
+ }
17955
+ /**
17956
+ * Closes this notification when pressing escape if it is the most recently displayed notification.
17957
+ */
17958
+ closeOnEscapeIfOnTop() {
17959
+ const zone = inject(NgZone);
17960
+ const document = inject(DOCUMENT);
17961
+ effect(onCleanup => {
17962
+ if (!this.notification.top()) {
17963
+ return;
17964
+ }
17965
+ const subscription = fromEvent(document, 'keydown')
17966
+ .pipe(subscribeIn(fn => zone.runOutsideAngular(fn)), filter((event) => event.key === 'Escape'), observeIn(fn => zone.run(fn)))
17967
+ .subscribe(() => this.notification.close());
17968
+ onCleanup(() => subscription.unsubscribe());
17969
+ });
17970
+ }
17971
+ /**
17972
+ * Installs a timer to close the notification.
17973
+ */
17974
+ installAutoCloseTimer() {
17975
+ effect(onCleanup => {
17976
+ const duration = this.notification.duration();
17977
+ const focus = this.notification.focused();
17978
+ const blockedBy = this.notification.blockedBy();
17979
+ const hover = this.hover();
17980
+ if (hover || focus || blockedBy) {
17981
+ return;
17982
+ }
17983
+ untracked(() => {
17984
+ const subscription = fromDuration$(duration).subscribe(() => this.notification.close());
17985
+ onCleanup(() => subscription.unsubscribe());
17986
+ });
17987
+ });
17988
+ function fromDuration$(duration) {
17989
+ switch (duration) {
17990
+ case 'short':
17991
+ return timer(7000);
17992
+ case 'medium':
17993
+ return timer(15000);
17994
+ case 'long':
17995
+ return timer(30000);
17996
+ default:
17997
+ if (typeof duration === 'number') {
17998
+ return timer(duration);
17999
+ }
18000
+ return NEVER;
18001
+ }
18002
+ }
17776
18003
  }
18004
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchNotificationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
18005
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.1", type: WorkbenchNotificationComponent, isStandalone: true, selector: "wb-notification", host: { listeners: { "mouseenter": "hover.set(true)", "mouseleave": "hover.set(false)", "auxclick": "onAuxClick($event)", "mousedown": "onMouseDown($event)", "keydown.escape": "onEscape($event)" }, properties: { "attr.data-notificationid": "notification.id", "attr.data-severity": "notification.severity()", "style.min-height": "notification.size.minHeight()", "style.height": "notification.size.height()", "style.max-height": "notification.size.maxHeight()", "style.--\u0275slot-anchor": "`--${slotAnchorName}`", "attr.tabindex": "-1", "class": "notification.cssClass()" } }, providers: [
18006
+ configureNotificationGlassPane(),
18007
+ ], viewQueries: [{ propertyName: "notificationSlotBounds", first: true, predicate: ["slot_bounds"], descendants: true, read: ElementRef, isSignal: true }], hostDirectives: [{ directive: GlassPaneDirective }], ngImport: i0, template: "<!-- Title -->\n@if (notification.title(); as title) {\n <header class=\"e2e-title\">{{(title | wbText)()}}</header>\n}\n\n<!-- Message -->\n<div class=\"slot e2e-slot\" [class.text]=\"!!notification.slot.text?.length\">\n @if (notification.slot.text?.length) {\n {{(notification.slot.text | wbText)()}}\n } @else if (notification.slot.component) {\n <sci-viewport class=\"e2e-notification-slot\">\n <ng-container *ngComponentOutlet=\"notification.slot.component; inputs: notification.inputs | wbRemoveLegacyInput;\"/>\n </sci-viewport>\n\n <!-- Extra DIV to capture bounds available to slotted content, excluding viewport content padding. May differ from the actual content size if content overflows or does not fill the slot. -->\n <div class=\"slot-bounds e2e-notification-slot-bounds\" #slot_bounds></div>\n }\n</div>\n\n<button (click)=\"onClose()\"\n [title]=\"('%workbench.close.tooltip' | wbText)()\"\n class=\"close e2e-close\">\n <wb-icon icon=\"workbench.close\"/>\n</button>\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:.75em;background-color:var(--sci-color-background-elevation);color:var(--sci-color-text);font-size:.9em;border:1px solid var(--sci-color-border);border-radius:var(--sci-corner);box-shadow:var(--sci-elevation) var(--sci-static-color-black);padding:1em 0;overflow:hidden;outline:none;position:relative}:host:before{content:\"\";position:absolute;top:0;left:0;bottom:0;width:var(--sci-workbench-notification-severity-indicator-size)}:host[data-severity=info]:before{background-color:var(--sci-color-accent)}:host[data-severity=warn]:before{background-color:var(--sci-color-notice)}:host[data-severity=error]:before{background-color:var(--sci-color-negative)}:host>header{flex:none;font-weight:700;padding:0 var(--sci-workbench-notification-padding);word-break:break-word;white-space:pre-line}:host>div.slot{flex:auto;overflow:hidden;display:grid}:host>div.slot.text{word-break:break-word;white-space:pre-line;padding:0 var(--sci-workbench-notification-padding)}:host>div.slot>sci-viewport{anchor-name:var(--\\275slot-anchor)}:host>div.slot>sci-viewport::part(content){padding-inline:var(--sci-workbench-notification-padding)}:host>div.slot>div.slot-bounds{position:absolute;position-anchor:var(--\\275slot-anchor);inset:anchor(top) anchor(right) anchor(bottom) anchor(left);margin-inline:var(--sci-workbench-notification-padding);visibility:hidden}:host>button.close:is(button,#sci-reset){all:unset;display:inline-grid;place-content:center;place-items:center;padding:.25em;border-radius:var(--sci-corner);-webkit-user-select:none;user-select:none;overflow:hidden;cursor:var(--sci-workbench-button-cursor)}:host>button.close:is(button,#sci-reset):hover:where(:not(:disabled)){background-color:var(--sci-workbench-button-background-color-hover)}:host>button.close:is(button,#sci-reset):active:where(:not(:disabled)){background-color:var(--sci-workbench-button-background-color-active)}:host>button.close:is(button,#sci-reset):focus:not(:focus-visible){outline:none}:host>button.close:is(button,#sci-reset):focus-visible{outline:var(--sci-workbench-button-outline-width-focus) solid var(--sci-color-accent)}:host>button.close:is(button,#sci-reset):disabled{color:var(--sci-color-gray-500)}:host>button.close:is(button,#sci-reset){position:absolute;top:.275em;right:.275em;padding:.125em;border-radius:var(--sci-corner-small);font-size:1rem}\n"], dependencies: [{ kind: "component", type: IconComponent, selector: "wb-icon", inputs: ["icon"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "component", type: SciViewportComponent, selector: "sci-viewport", inputs: ["scrollbarStyle"], outputs: ["scroll"] }, { kind: "pipe", type: TextPipe, name: "wbText" }, { kind: "pipe", type: RemoveLegacyInputPipe, name: "wbRemoveLegacyInput" }] });
17777
18008
  }
18009
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchNotificationComponent, decorators: [{
18010
+ type: Component,
18011
+ args: [{ selector: 'wb-notification', imports: [
18012
+ TextPipe,
18013
+ IconComponent,
18014
+ NgComponentOutlet,
18015
+ RemoveLegacyInputPipe,
18016
+ SciViewportComponent,
18017
+ ], hostDirectives: [
18018
+ GlassPaneDirective,
18019
+ ], providers: [
18020
+ configureNotificationGlassPane(),
18021
+ ], host: {
18022
+ '[attr.data-notificationid]': 'notification.id',
18023
+ '[attr.data-severity]': 'notification.severity()',
18024
+ '[style.min-height]': 'notification.size.minHeight()',
18025
+ '[style.height]': 'notification.size.height()',
18026
+ '[style.max-height]': 'notification.size.maxHeight()',
18027
+ '[style.--ɵslot-anchor]': '`--${slotAnchorName}`',
18028
+ '[attr.tabindex]': '-1',
18029
+ '[class]': 'notification.cssClass()',
18030
+ '(mouseenter)': 'hover.set(true)',
18031
+ '(mouseleave)': 'hover.set(false)',
18032
+ '(auxclick)': 'onAuxClick($event)',
18033
+ '(mousedown)': 'onMouseDown($event)',
18034
+ '(keydown.escape)': 'onEscape($event)',
18035
+ }, template: "<!-- Title -->\n@if (notification.title(); as title) {\n <header class=\"e2e-title\">{{(title | wbText)()}}</header>\n}\n\n<!-- Message -->\n<div class=\"slot e2e-slot\" [class.text]=\"!!notification.slot.text?.length\">\n @if (notification.slot.text?.length) {\n {{(notification.slot.text | wbText)()}}\n } @else if (notification.slot.component) {\n <sci-viewport class=\"e2e-notification-slot\">\n <ng-container *ngComponentOutlet=\"notification.slot.component; inputs: notification.inputs | wbRemoveLegacyInput;\"/>\n </sci-viewport>\n\n <!-- Extra DIV to capture bounds available to slotted content, excluding viewport content padding. May differ from the actual content size if content overflows or does not fill the slot. -->\n <div class=\"slot-bounds e2e-notification-slot-bounds\" #slot_bounds></div>\n }\n</div>\n\n<button (click)=\"onClose()\"\n [title]=\"('%workbench.close.tooltip' | wbText)()\"\n class=\"close e2e-close\">\n <wb-icon icon=\"workbench.close\"/>\n</button>\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:.75em;background-color:var(--sci-color-background-elevation);color:var(--sci-color-text);font-size:.9em;border:1px solid var(--sci-color-border);border-radius:var(--sci-corner);box-shadow:var(--sci-elevation) var(--sci-static-color-black);padding:1em 0;overflow:hidden;outline:none;position:relative}:host:before{content:\"\";position:absolute;top:0;left:0;bottom:0;width:var(--sci-workbench-notification-severity-indicator-size)}:host[data-severity=info]:before{background-color:var(--sci-color-accent)}:host[data-severity=warn]:before{background-color:var(--sci-color-notice)}:host[data-severity=error]:before{background-color:var(--sci-color-negative)}:host>header{flex:none;font-weight:700;padding:0 var(--sci-workbench-notification-padding);word-break:break-word;white-space:pre-line}:host>div.slot{flex:auto;overflow:hidden;display:grid}:host>div.slot.text{word-break:break-word;white-space:pre-line;padding:0 var(--sci-workbench-notification-padding)}:host>div.slot>sci-viewport{anchor-name:var(--\\275slot-anchor)}:host>div.slot>sci-viewport::part(content){padding-inline:var(--sci-workbench-notification-padding)}:host>div.slot>div.slot-bounds{position:absolute;position-anchor:var(--\\275slot-anchor);inset:anchor(top) anchor(right) anchor(bottom) anchor(left);margin-inline:var(--sci-workbench-notification-padding);visibility:hidden}:host>button.close:is(button,#sci-reset){all:unset;display:inline-grid;place-content:center;place-items:center;padding:.25em;border-radius:var(--sci-corner);-webkit-user-select:none;user-select:none;overflow:hidden;cursor:var(--sci-workbench-button-cursor)}:host>button.close:is(button,#sci-reset):hover:where(:not(:disabled)){background-color:var(--sci-workbench-button-background-color-hover)}:host>button.close:is(button,#sci-reset):active:where(:not(:disabled)){background-color:var(--sci-workbench-button-background-color-active)}:host>button.close:is(button,#sci-reset):focus:not(:focus-visible){outline:none}:host>button.close:is(button,#sci-reset):focus-visible{outline:var(--sci-workbench-button-outline-width-focus) solid var(--sci-color-accent)}:host>button.close:is(button,#sci-reset):disabled{color:var(--sci-color-gray-500)}:host>button.close:is(button,#sci-reset){position:absolute;top:.275em;right:.275em;padding:.125em;border-radius:var(--sci-corner-small);font-size:1rem}\n"] }]
18036
+ }], ctorParameters: () => [], propDecorators: { notificationSlotBounds: [{ type: i0.ViewChild, args: ['slot_bounds', { ...{ read: (ElementRef) }, isSignal: true }] }] } });
17778
18037
  /**
17779
- * TODO [Angular 22] Remove with Angular 22. Used for backward compatiblity.
18038
+ * Blocks this notification when dialog(s) overlay it.
17780
18039
  */
17781
- const LEGACY_NOTIFICATION_INPUT = `${UID.randomUID()}-auto-generated`;
18040
+ function configureNotificationGlassPane() {
18041
+ return [
18042
+ {
18043
+ provide: GLASS_PANE_BLOCKABLE,
18044
+ useFactory: () => inject(ɵWorkbenchNotification),
18045
+ },
18046
+ {
18047
+ provide: GLASS_PANE_OPTIONS,
18048
+ useFactory: () => ({ attributes: { 'data-notificationid': inject(ɵWorkbenchNotification).id } }),
18049
+ },
18050
+ ];
18051
+ }
17782
18052
 
17783
18053
  /*
17784
- * Copyright (c) 2018-2025 Swiss Federal Railways
18054
+ * Copyright (c) 2018-2026 Swiss Federal Railways
17785
18055
  *
17786
18056
  * This program and the accompanying materials are made
17787
18057
  * available under the terms of the Eclipse Public License 2.0
@@ -17790,52 +18060,23 @@ const LEGACY_NOTIFICATION_INPUT = `${UID.randomUID()}-auto-generated`;
17790
18060
  * SPDX-License-Identifier: EPL-2.0
17791
18061
  */
17792
18062
  /**
17793
- * Represents a handle that a notification component can inject to interact with the notification, for example,
17794
- * to read input data or to configure the notification.
17795
- *
17796
- * A notification is a closable message that appears in the upper-right corner and disappears automatically after a few seconds.
17797
- * It informs the user of a system event, e.g., that a task has been completed or an error has occurred.
17798
- *
17799
- * @deprecated since version 21.0.0-beta.1. Replaced by `WorkbenchNotification`. Marked for removal in version 22.
17800
- */
17801
- class Notification {
17802
- /**
17803
- * Input data as passed by the notification opener, or `undefined` if not passed.
17804
- */
17805
- input;
17806
- }
17807
-
17808
- /*
17809
- * Copyright (c) 2018-2025 Swiss Federal Railways
17810
- *
17811
- * This program and the accompanying materials are made
17812
- * available under the terms of the Eclipse Public License 2.0
17813
- * which is available at https://www.eclipse.org/legal/epl-2.0/
17814
- *
17815
- * SPDX-License-Identifier: EPL-2.0
18063
+ * DI token to register providers available for DI if in the context of a workbench notification.
17816
18064
  */
18065
+ const WORKBENCH_NOTIFICATION_CONTEXT = new InjectionToken('WORKBENCH_NOTIFICATION_CONTEXT');
17817
18066
  /**
17818
- * Registry for {@link WorkbenchNotification} elements.
18067
+ * Provides providers available for DI if in the context of a workbench notification.
17819
18068
  */
17820
- class WorkbenchNotificationRegistry extends WorkbenchElementRegistry {
17821
- /**
17822
- * Gets the most recently opened notification.
17823
- */
17824
- top;
17825
- constructor() {
17826
- super({
17827
- nullElementErrorFn: notificationId => Error(`[NullNotificationError] Notification '${notificationId}' not found.`),
17828
- onUnregister: notification => notification.destroy(),
17829
- });
17830
- this.top = computed(() => this.elements().at(-1), { ...(ngDevMode ? { debugName: "top" } : {}) });
17831
- }
17832
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchNotificationRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
17833
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchNotificationRegistry, providedIn: 'root' });
18069
+ function provideWorkbenchNotificationContext() {
18070
+ return {
18071
+ provide: WORKBENCH_NOTIFICATION_CONTEXT,
18072
+ useFactory: () => [
18073
+ provideWorkbenchDialogService(),
18074
+ provideWorkbenchMessageBoxService(),
18075
+ provideWorkbenchPopupService(),
18076
+ ],
18077
+ multi: true,
18078
+ };
17834
18079
  }
17835
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchNotificationRegistry, decorators: [{
17836
- type: Injectable,
17837
- args: [{ providedIn: 'root' }]
17838
- }], ctorParameters: () => [] });
17839
18080
 
17840
18081
  /** @inheritDoc */
17841
18082
  class ɵWorkbenchNotification {
@@ -17850,17 +18091,20 @@ class ɵWorkbenchNotification {
17850
18091
  _severity;
17851
18092
  _duration;
17852
18093
  _cssClass;
18094
+ portal;
17853
18095
  size = new ɵWorkbenchNotificationSize();
17854
18096
  focused = computed(() => this._focusMonitor.activeElement()?.id === this.id, { ...(ngDevMode ? { debugName: "focused" } : {}) });
17855
18097
  /** Checks if this notification is the most recently displayed notification. */
17856
18098
  top = computed(() => this._notificationRegistry.top() === this, { ...(ngDevMode ? { debugName: "top" } : {}) });
17857
18099
  destroyed = signal(false, { ...(ngDevMode ? { debugName: "destroyed" } : {}) });
18100
+ bounds;
18101
+ blockedBy;
17858
18102
  group;
17859
18103
  constructor(id, content, _options) {
17860
18104
  this.id = id;
17861
18105
  this._options = _options;
18106
+ this.portal = this.createPortal();
17862
18107
  this.slot = {
17863
- injector: this.createInjector(),
17864
18108
  component: typeof content === 'function' ? content : undefined,
17865
18109
  text: typeof content === 'string' ? content : undefined,
17866
18110
  };
@@ -17869,24 +18113,25 @@ class ɵWorkbenchNotification {
17869
18113
  this._duration = signal(this._options.duration ?? 'medium', { ...(ngDevMode ? { debugName: "_duration" } : {}) });
17870
18114
  this._cssClass = signal(Arrays.coerce(this._options.cssClass), { ...(ngDevMode ? { debugName: "_cssClass" } : {}) });
17871
18115
  this.group = this._options.group;
18116
+ this.blockedBy = inject(WorkbenchDialogRegistry).top(this.id);
18117
+ this.bounds = boundingClientRect(computed(() => this.portal.componentRef()?.instance.notificationSlotBounds()));
17872
18118
  inject(DestroyRef).onDestroy(() => this.destroyed.set(true));
17873
18119
  }
17874
18120
  /**
17875
- * Creates an injector to render content in the notification's injection context.
18121
+ * Creates a portal to render {@link WorkbenchNotificationComponent} in the notification's injection context.
17876
18122
  */
17877
- createInjector() {
17878
- const injector = Injector.create({
17879
- parent: this._options.injector ?? inject(Injector),
18123
+ createPortal() {
18124
+ return new WbComponentPortal(WorkbenchNotificationComponent, {
18125
+ injector: this._options.injector,
17880
18126
  providers: [
17881
18127
  { provide: ɵWorkbenchNotification, useValue: this },
17882
18128
  { provide: WorkbenchNotification, useExisting: ɵWorkbenchNotification },
17883
18129
  { provide: Notification, useClass: ɵNotification },
17884
18130
  { provide: WORKBENCH_ELEMENT, useExisting: ɵWorkbenchNotification },
18131
+ inject(WORKBENCH_NOTIFICATION_CONTEXT, { optional: true }) ?? [],
17885
18132
  ...this._options.providers ?? [],
17886
18133
  ],
17887
18134
  });
17888
- inject(DestroyRef).onDestroy(() => injector.destroy());
17889
- return injector;
17890
18135
  }
17891
18136
  /** @inheritDoc */
17892
18137
  get title() {
@@ -17922,6 +18167,9 @@ class ɵWorkbenchNotification {
17922
18167
  }
17923
18168
  /** @inheritDoc */
17924
18169
  close() {
18170
+ if (this.blockedBy()) {
18171
+ return;
18172
+ }
17925
18173
  this.destroy();
17926
18174
  }
17927
18175
  /**
@@ -18434,6 +18682,7 @@ function provideMicrofrontendNotification() {
18434
18682
  MicrofrontendNotificationCapabilityValidator,
18435
18683
  MicrofrontendTextNotificationCapabilityProvider,
18436
18684
  MicrofrontendNotificationIntentHandler,
18685
+ provideWorkbenchNotificationContext(),
18437
18686
  provideMicrofrontendTextNotificationRoute(),
18438
18687
  provideMicrofrontendPlatformInitializer(onPreStartup, { phase: MicrofrontendPlatformStartupPhase.PreStartup }),
18439
18688
  ]);
@@ -18445,6 +18694,20 @@ function provideMicrofrontendNotification() {
18445
18694
  // Register notification intent handler.
18446
18695
  Beans.register(IntentInterceptor, { useValue: inject(MicrofrontendNotificationIntentHandler), multi: true });
18447
18696
  }
18697
+ /**
18698
+ * Provides beans of @scion/workbench-client available for DI if in the context of a workbench notification.
18699
+ */
18700
+ function provideWorkbenchNotificationContext() {
18701
+ return {
18702
+ provide: WORKBENCH_NOTIFICATION_CONTEXT,
18703
+ useFactory: () => [
18704
+ { provide: WorkbenchDialogService$1, useFactory: () => new _WorkbenchDialogService(inject(WorkbenchNotification).id) },
18705
+ { provide: WorkbenchMessageBoxService$1, useFactory: () => new _WorkbenchMessageBoxService(inject(WorkbenchNotification).id) },
18706
+ { provide: WorkbenchPopupService$1, useFactory: () => new _WorkbenchPopupService(inject(WorkbenchNotification).id) },
18707
+ ],
18708
+ multi: true,
18709
+ };
18710
+ }
18448
18711
  }
18449
18712
 
18450
18713
  /*
@@ -20291,6 +20554,7 @@ function provideWorkbench(config) {
20291
20554
  provideWorkbenchViewContext(),
20292
20555
  provideWorkbenchDialogContext(),
20293
20556
  provideWorkbenchPopupContext(),
20557
+ provideWorkbenchNotificationContext(),
20294
20558
  provideWorkbenchMicrofrontendSupport(config),
20295
20559
  ]);
20296
20560
  }
@@ -20384,32 +20648,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImpor
20384
20648
  args: [{ selector: 'wb-splash', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SciThrobberComponent], template: "<sci-throbber type=\"ellipsis\"/>\n", styles: [":host{display:grid;justify-content:center;margin-top:3em}:host>sci-throbber{--sci-throbber-size: 80px}\n"] }]
20385
20649
  }] });
20386
20650
 
20387
- /*
20388
- * Copyright (c) 2018-2025 Swiss Federal Railways
20389
- *
20390
- * This program and the accompanying materials are made
20391
- * available under the terms of the Eclipse Public License 2.0
20392
- * which is available at https://www.eclipse.org/legal/epl-2.0/
20393
- *
20394
- * SPDX-License-Identifier: EPL-2.0
20395
- */
20396
- /**
20397
- * TODO [Angular 22] Remove with Angular 22. Used for backward compatiblity.
20398
- */
20399
- class RemoveLegacyInputPipe {
20400
- transform(inputs) {
20401
- const inputsCopy = { ...inputs ?? {} };
20402
- delete inputsCopy[LEGACY_NOTIFICATION_INPUT]; // eslint-disable-line @typescript-eslint/no-dynamic-delete
20403
- return inputsCopy;
20404
- }
20405
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: RemoveLegacyInputPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
20406
- static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.1", ngImport: i0, type: RemoveLegacyInputPipe, isStandalone: true, name: "wbRemoveLegacyInput" });
20407
- }
20408
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: RemoveLegacyInputPipe, decorators: [{
20409
- type: Pipe,
20410
- args: [{ name: 'wbRemoveLegacyInput' }]
20411
- }] });
20412
-
20413
20651
  /*
20414
20652
  * Copyright (c) 2018-2026 Swiss Federal Railways
20415
20653
  *
@@ -20419,140 +20657,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImpor
20419
20657
  *
20420
20658
  * SPDX-License-Identifier: EPL-2.0
20421
20659
  */
20422
- /**
20423
- * Renders the content of a workbench notification.
20424
- */
20425
- class WorkbenchNotificationComponent {
20426
- notification = input.required({ ...(ngDevMode ? { debugName: "notification" } : {}) });
20427
- hover = signal(false, { ...(ngDevMode ? { debugName: "hover" } : {}) });
20428
- constructor() {
20429
- this.installAutoCloseTimer();
20430
- this.installFocusTracker();
20431
- this.closeOnEscapeIfOnTop();
20432
- }
20433
- onClose() {
20434
- this.notification().close();
20435
- }
20436
- onEscape(event) {
20437
- if (this.notification().focused()) {
20438
- event.stopPropagation(); // stop propagation to prevent closing the most recently displayed notification
20439
- this.notification().close();
20440
- }
20441
- }
20442
- onAuxClick(event) {
20443
- if (event.button === 1) { // primary aux button
20444
- event.preventDefault(); // prevent user-agent default action
20445
- this.notification().close();
20446
- }
20447
- }
20448
- /**
20449
- * Closes this notification when pressing escape if it is the most recently displayed notification.
20450
- */
20451
- closeOnEscapeIfOnTop() {
20452
- const zone = inject(NgZone);
20453
- const document = inject(DOCUMENT);
20454
- effect(onCleanup => {
20455
- if (!this.notification().top()) {
20456
- return;
20457
- }
20458
- const subscription = fromEvent(document, 'keydown')
20459
- .pipe(subscribeIn(fn => zone.runOutsideAngular(fn)), filter((event) => event.key === 'Escape'), observeIn(fn => zone.run(fn)))
20460
- .subscribe(() => this.notification().close());
20461
- onCleanup(() => subscription.unsubscribe());
20462
- });
20463
- }
20464
- /**
20465
- * Installs a timer to close the notification.
20466
- */
20467
- installAutoCloseTimer() {
20468
- effect(onCleanup => {
20469
- const notification = this.notification();
20470
- const duration = notification.duration();
20471
- const focus = notification.focused();
20472
- const hover = this.hover();
20473
- if (hover || focus) {
20474
- return;
20475
- }
20476
- untracked(() => {
20477
- const subscription = fromDuration$(duration).subscribe(() => this.notification().close());
20478
- onCleanup(() => subscription.unsubscribe());
20479
- });
20480
- });
20481
- function fromDuration$(duration) {
20482
- switch (duration) {
20483
- case 'short':
20484
- return timer(7000);
20485
- case 'medium':
20486
- return timer(15000);
20487
- case 'long':
20488
- return timer(30000);
20489
- default:
20490
- if (typeof duration === 'number') {
20491
- return timer(duration);
20492
- }
20493
- return NEVER;
20494
- }
20495
- }
20496
- }
20497
- installFocusTracker() {
20498
- const host = inject(ElementRef).nativeElement;
20499
- const injector = inject(Injector);
20500
- effect(onCleanup => {
20501
- const notification = this.notification();
20502
- untracked(() => {
20503
- const tracker = runInInjectionContext(injector, () => trackFocus(host, notification));
20504
- onCleanup(() => tracker.destroy());
20505
- });
20506
- });
20507
- }
20508
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchNotificationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
20509
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.1", type: WorkbenchNotificationComponent, isStandalone: true, selector: "wb-notification", inputs: { notification: { classPropertyName: "notification", publicName: "notification", isSignal: true, isRequired: true, transformFunction: null } }, host: { listeners: { "mouseenter": "hover.set(true)", "mouseleave": "hover.set(false)", "auxclick": "onAuxClick($event)", "keydown.escape": "onEscape($event)" }, properties: { "attr.data-notificationid": "notification().id", "attr.data-severity": "notification().severity()", "style.min-height": "notification().size.minHeight()", "style.height": "notification().size.height()", "style.max-height": "notification().size.maxHeight()", "attr.tabindex": "-1", "class": "notification().cssClass()" } }, ngImport: i0, template: "<!-- Title -->\n@if (notification().title(); as title) {\n <header class=\"e2e-title\">{{(title | wbText)()}}</header>\n}\n\n<!-- Message -->\n<div class=\"message e2e-message\" [class.text]=\"!!notification().slot.text?.length\">\n @if (notification().slot.text?.length) {\n {{(notification().slot.text | wbText)()}}\n } @else if (notification().slot.component) {\n <sci-viewport class=\"e2e-message-viewport\">\n <ng-container *ngComponentOutlet=\"notification().slot.component!; inputs: notification().inputs | wbRemoveLegacyInput; injector: notification().slot.injector\"/>\n </sci-viewport>\n }\n</div>\n\n<button (click)=\"onClose()\"\n [title]=\"('%workbench.close.tooltip' | wbText)()\"\n class=\"close e2e-close\">\n <wb-icon icon=\"workbench.close\"/>\n</button>\n", styles: [":host{display:flex;flex-direction:column;gap:.75em;background-color:var(--sci-color-background-elevation);color:var(--sci-color-text);font-size:.9em;border:1px solid var(--sci-color-border);border-radius:var(--sci-corner);box-shadow:var(--sci-elevation) var(--sci-static-color-black);padding:1em 0;overflow:hidden;outline:none;position:relative}:host:before{content:\"\";position:absolute;top:0;left:0;bottom:0;width:var(--sci-workbench-notification-severity-indicator-size)}:host[data-severity=info]:before{background-color:var(--sci-color-accent)}:host[data-severity=warn]:before{background-color:var(--sci-color-notice)}:host[data-severity=error]:before{background-color:var(--sci-color-negative)}:host>header{flex:none;font-weight:700;padding:0 1.5em;word-break:break-word;white-space:pre-line}:host>div.message{flex:auto;overflow:hidden}:host>div.message.text{word-break:break-word;white-space:pre-line;padding:0 1.5em}:host>div.message>sci-viewport{height:100%}:host>div.message>sci-viewport::part(content){padding:0 1.5em}:host>button.close:is(button,#sci-reset){all:unset;display:inline-grid;place-content:center;place-items:center;padding:.25em;border-radius:var(--sci-corner);-webkit-user-select:none;user-select:none;overflow:hidden;cursor:var(--sci-workbench-button-cursor)}:host>button.close:is(button,#sci-reset):hover:where(:not(:disabled)){background-color:var(--sci-workbench-button-background-color-hover)}:host>button.close:is(button,#sci-reset):active:where(:not(:disabled)){background-color:var(--sci-workbench-button-background-color-active)}:host>button.close:is(button,#sci-reset):focus:not(:focus-visible){outline:none}:host>button.close:is(button,#sci-reset):focus-visible{outline:var(--sci-workbench-button-outline-width-focus) solid var(--sci-color-accent)}:host>button.close:is(button,#sci-reset):disabled{color:var(--sci-color-gray-500)}:host>button.close:is(button,#sci-reset){position:absolute;top:.275em;right:.275em;padding:.125em;border-radius:var(--sci-corner-small);font-size:1rem}\n"], dependencies: [{ kind: "component", type: IconComponent, selector: "wb-icon", inputs: ["icon"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "component", type: SciViewportComponent, selector: "sci-viewport", inputs: ["scrollbarStyle"], outputs: ["scroll"] }, { kind: "pipe", type: TextPipe, name: "wbText" }, { kind: "pipe", type: RemoveLegacyInputPipe, name: "wbRemoveLegacyInput" }] });
20510
- }
20511
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: WorkbenchNotificationComponent, decorators: [{
20512
- type: Component,
20513
- args: [{ selector: 'wb-notification', imports: [
20514
- TextPipe,
20515
- IconComponent,
20516
- NgComponentOutlet,
20517
- RemoveLegacyInputPipe,
20518
- SciViewportComponent,
20519
- ], host: {
20520
- '[attr.data-notificationid]': 'notification().id',
20521
- '[attr.data-severity]': 'notification().severity()',
20522
- '[style.min-height]': 'notification().size.minHeight()',
20523
- '[style.height]': 'notification().size.height()',
20524
- '[style.max-height]': 'notification().size.maxHeight()',
20525
- '[attr.tabindex]': '-1',
20526
- '[class]': 'notification().cssClass()',
20527
- '(mouseenter)': 'hover.set(true)',
20528
- '(mouseleave)': 'hover.set(false)',
20529
- '(auxclick)': 'onAuxClick($event)',
20530
- '(keydown.escape)': 'onEscape($event)',
20531
- }, template: "<!-- Title -->\n@if (notification().title(); as title) {\n <header class=\"e2e-title\">{{(title | wbText)()}}</header>\n}\n\n<!-- Message -->\n<div class=\"message e2e-message\" [class.text]=\"!!notification().slot.text?.length\">\n @if (notification().slot.text?.length) {\n {{(notification().slot.text | wbText)()}}\n } @else if (notification().slot.component) {\n <sci-viewport class=\"e2e-message-viewport\">\n <ng-container *ngComponentOutlet=\"notification().slot.component!; inputs: notification().inputs | wbRemoveLegacyInput; injector: notification().slot.injector\"/>\n </sci-viewport>\n }\n</div>\n\n<button (click)=\"onClose()\"\n [title]=\"('%workbench.close.tooltip' | wbText)()\"\n class=\"close e2e-close\">\n <wb-icon icon=\"workbench.close\"/>\n</button>\n", styles: [":host{display:flex;flex-direction:column;gap:.75em;background-color:var(--sci-color-background-elevation);color:var(--sci-color-text);font-size:.9em;border:1px solid var(--sci-color-border);border-radius:var(--sci-corner);box-shadow:var(--sci-elevation) var(--sci-static-color-black);padding:1em 0;overflow:hidden;outline:none;position:relative}:host:before{content:\"\";position:absolute;top:0;left:0;bottom:0;width:var(--sci-workbench-notification-severity-indicator-size)}:host[data-severity=info]:before{background-color:var(--sci-color-accent)}:host[data-severity=warn]:before{background-color:var(--sci-color-notice)}:host[data-severity=error]:before{background-color:var(--sci-color-negative)}:host>header{flex:none;font-weight:700;padding:0 1.5em;word-break:break-word;white-space:pre-line}:host>div.message{flex:auto;overflow:hidden}:host>div.message.text{word-break:break-word;white-space:pre-line;padding:0 1.5em}:host>div.message>sci-viewport{height:100%}:host>div.message>sci-viewport::part(content){padding:0 1.5em}:host>button.close:is(button,#sci-reset){all:unset;display:inline-grid;place-content:center;place-items:center;padding:.25em;border-radius:var(--sci-corner);-webkit-user-select:none;user-select:none;overflow:hidden;cursor:var(--sci-workbench-button-cursor)}:host>button.close:is(button,#sci-reset):hover:where(:not(:disabled)){background-color:var(--sci-workbench-button-background-color-hover)}:host>button.close:is(button,#sci-reset):active:where(:not(:disabled)){background-color:var(--sci-workbench-button-background-color-active)}:host>button.close:is(button,#sci-reset):focus:not(:focus-visible){outline:none}:host>button.close:is(button,#sci-reset):focus-visible{outline:var(--sci-workbench-button-outline-width-focus) solid var(--sci-color-accent)}:host>button.close:is(button,#sci-reset):disabled{color:var(--sci-color-gray-500)}:host>button.close:is(button,#sci-reset){position:absolute;top:.275em;right:.275em;padding:.125em;border-radius:var(--sci-corner-small);font-size:1rem}\n"] }]
20532
- }], ctorParameters: () => [], propDecorators: { notification: [{ type: i0.Input, args: [{ isSignal: true, alias: "notification", required: true }] }] } });
20533
-
20534
- /*
20535
- * Copyright (c) 2018-2022 Swiss Federal Railways
20536
- *
20537
- * This program and the accompanying materials are made
20538
- * available under the terms of the Eclipse Public License 2.0
20539
- * which is available at https://www.eclipse.org/legal/epl-2.0/
20540
- *
20541
- * SPDX-License-Identifier: EPL-2.0
20542
- */
20543
20660
  /**
20544
20661
  * Displays notifications on the right side, stacked vertically.
20545
20662
  */
20546
20663
  class NotificationListComponent {
20547
20664
  notifications = inject(WorkbenchNotificationRegistry).elements;
20548
20665
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: NotificationListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
20549
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.1", type: NotificationListComponent, isStandalone: true, selector: "wb-notification-list", ngImport: i0, template: "@for (notification of notifications(); track notification.group || notification.id) {\n <wb-notification [notification]=\"notification\"/>\n}\n", styles: [":host{display:flex;flex-flow:column wrap-reverse;gap:.5em;max-height:100%;align-items:flex-start;align-content:flex-start;pointer-events:none;margin:.5em}:host>wb-notification{pointer-events:auto;width:var(--sci-workbench-notification-width);max-width:100%;position:relative;animation:slide-in .3s ease-out}@keyframes slide-in{0%{opacity:0;left:100%}to{opacity:1;left:0}}\n"], dependencies: [{ kind: "component", type: WorkbenchNotificationComponent, selector: "wb-notification", inputs: ["notification"] }] });
20666
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.1", type: NotificationListComponent, isStandalone: true, selector: "wb-notification-list", ngImport: i0, template: "@for (notification of notifications(); track notification.group || notification.id) {\n <div class=\"notification\">\n <ng-container *wbPortalOutlet=\"notification.portal; destroyOnDetach: true\"/>\n </div>\n}\n", styles: [":host{display:flex;flex-flow:column wrap-reverse;gap:.5em;max-height:100%;align-items:flex-start;align-content:flex-start;pointer-events:none;margin:.5em}:host>div.notification{pointer-events:auto;width:var(--sci-workbench-notification-width);max-width:100%;position:relative;animation:slide-in .3s ease-out}@keyframes slide-in{0%{opacity:0;left:100%}to{opacity:1;left:0}}\n"], dependencies: [{ kind: "directive", type: WorkbenchPortalOutletDirective, selector: "ng-template[wbPortalOutlet]", inputs: ["wbPortalOutlet", "wbPortalOutletDestroyOnDetach"] }] });
20550
20667
  }
20551
20668
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: NotificationListComponent, decorators: [{
20552
20669
  type: Component,
20553
20670
  args: [{ selector: 'wb-notification-list', imports: [
20554
- WorkbenchNotificationComponent,
20555
- ], template: "@for (notification of notifications(); track notification.group || notification.id) {\n <wb-notification [notification]=\"notification\"/>\n}\n", styles: [":host{display:flex;flex-flow:column wrap-reverse;gap:.5em;max-height:100%;align-items:flex-start;align-content:flex-start;pointer-events:none;margin:.5em}:host>wb-notification{pointer-events:auto;width:var(--sci-workbench-notification-width);max-width:100%;position:relative;animation:slide-in .3s ease-out}@keyframes slide-in{0%{opacity:0;left:100%}to{opacity:1;left:0}}\n"] }]
20671
+ WorkbenchPortalOutletDirective,
20672
+ ], template: "@for (notification of notifications(); track notification.group || notification.id) {\n <div class=\"notification\">\n <ng-container *wbPortalOutlet=\"notification.portal; destroyOnDetach: true\"/>\n </div>\n}\n", styles: [":host{display:flex;flex-flow:column wrap-reverse;gap:.5em;max-height:100%;align-items:flex-start;align-content:flex-start;pointer-events:none;margin:.5em}:host>div.notification{pointer-events:auto;width:var(--sci-workbench-notification-width);max-width:100%;position:relative;animation:slide-in .3s ease-out}@keyframes slide-in{0%{opacity:0;left:100%}to{opacity:1;left:0}}\n"] }]
20556
20673
  }] });
20557
20674
 
20558
20675
  /*
@@ -20963,7 +21080,7 @@ function configureWorkbenchGlassPane() {
20963
21080
  },
20964
21081
  {
20965
21082
  provide: GLASS_PANE_OPTIONS,
20966
- useValue: { cssClass: 'e2e-workbench' },
21083
+ useFactory: () => ({ cssClass: 'e2e-workbench' }),
20967
21084
  },
20968
21085
  ];
20969
21086
  }
@@ -21572,7 +21689,7 @@ function migrateGroupInputReduceFn(config) {
21572
21689
  */
21573
21690
 
21574
21691
  /*
21575
- * Copyright (c) 2018-2022 Swiss Federal Railways
21692
+ * Copyright (c) 2018-2026 Swiss Federal Railways
21576
21693
  *
21577
21694
  * This program and the accompanying materials are made
21578
21695
  * available under the terms of the Eclipse Public License 2.0