@praxisui/settings-panel 9.0.4-rc.3 → 9.0.4-rc.31

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.
package/README.md CHANGED
@@ -87,14 +87,22 @@ Peer dependencies (Angular v21):
87
87
  - O shell runtime dedicado fica disponivel por `SurfaceDrawerComponent`.
88
88
  - O bridge canonico de runtime e `providePraxisSurfaceDrawerBridge()`.
89
89
  - Esse drawer runtime nao deve mostrar `Apply`, `Save`, `Reset` ou `Cancel` por heranca.
90
+ - `title`, `titleIcon` e `subtitle` formam o cabecalho governado do drawer; o shell resolve espacamento, quebra responsiva e relaciona o subtitulo por `aria-describedby`, sem exigir CSS local do host.
90
91
  - O runtime drawer aceita `width`, `minWidth`, `maxWidth`, `resizable` e `persistSizeKey`; o handle visual so aparece quando `resizable=true`.
91
92
  - Quando `persistSizeKey` estiver presente, a largura volta no proximo open do mesmo painel.
93
+ - Uma sessao runtime pode manter uma unica surface primaria e navegar entre etapas com `surfaceRuntime.push(frame)`, `replace(frame)` e `back()`.
94
+ - O shell preserva a instancia Angular, o scroll e o alvo de foco dos frames anteriores. O botao Voltar so aparece quando existe historico e devolve o foco para a etapa restaurada.
95
+ - `surfaceRuntime.navigation$` publica `sessionId`, `activeFrameId`, profundidade e `canGoBack`; esse estado e efemero e nunca deve ser persistido em metadata, `x-ui` ou configuracao de componente.
96
+ - Formularios podem registrar `surfaceRuntime.setCanLeave(guard)` para impedir `back`, `replace`, Escape, backdrop ou fechamento enquanto o lifecycle da etapa exigir confirmacao. O guard e runtime-only e nao substitui regras de negocio nem autorizacao.
97
+ - Componentes criados dentro de uma frame podem injetar `SURFACE_DRAWER_REF` e `SURFACE_DRAWER_CONTENT_DATA`. Esses tokens representam exclusivamente a frame ativa e seus inputs efemeros; nao devem ser usados como uma segunda fonte de metadata.
98
+ - O CRUD oficial reutiliza automaticamente a sessao ativa: formularios abertos em modo drawer viram uma nova frame, publicam `save/delete/close` no mesmo ref e retornam ao contexto anterior sem empilhar overlays.
92
99
 
93
100
  ## Fronteira canonica
94
101
 
95
102
  - `SettingsPanelService` e `SETTINGS_PANEL_BRIDGE` pertencem a authoring/configuracao.
96
103
  - `SURFACE_DRAWER_BRIDGE` pertence ao runtime de `surface.open`.
97
104
  - Ambos compartilham a mesma infraestrutura base de side panel, mas com shells diferentes.
105
+ - Tipos de componente e callbacks de navegacao pertencem exclusivamente a sessao runtime; JSON governado continua descrevendo a intencao de abrir a surface, nao uma pilha imperativa serializada.
98
106
 
99
107
  ## Customizacao visual
100
108
 
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-07-31T14:02:14.681Z",
3
+ "generatedAt": "2026-08-05T03:38:59.493Z",
4
4
  "packageName": "@praxisui/settings-panel",
5
- "packageVersion": "9.0.4-rc.3",
5
+ "packageVersion": "9.0.4-rc.31",
6
6
  "sourceRegistry": "praxis-component-registry-ingestion",
7
7
  "sourceRegistryVersion": "1.0.0",
8
8
  "componentCount": 1,
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, DestroyRef, ViewContainerRef, HostListener, ViewChild, ViewEncapsulation, ChangeDetectionStrategy, Component, InjectionToken, Injector, Injectable, ChangeDetectorRef, ENVIRONMENT_INITIALIZER } from '@angular/core';
2
+ import { inject, DestroyRef, ViewContainerRef, HostListener, ViewChild, ViewEncapsulation, ChangeDetectionStrategy, Component, InjectionToken, Injector, Injectable, ElementRef, ChangeDetectorRef, ENVIRONMENT_INITIALIZER } from '@angular/core';
3
3
  import * as i2 from '@angular/common';
4
4
  import { CommonModule, NgStyle, NgClass } from '@angular/common';
5
5
  import { CdkTrapFocus } from '@angular/cdk/a11y';
@@ -17,7 +17,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
17
17
  import { filter, switchMap, debounceTime, distinctUntilChanged } from 'rxjs/operators';
18
18
  import { isObservable, firstValueFrom, of, from, Subject, ReplaySubject, take, BehaviorSubject } from 'rxjs';
19
19
  import * as i1$2 from '@praxisui/core';
20
- import { providePraxisI18n, PraxisI18nService, PraxisIconButtonComponent, PraxisIconDirective, SETTINGS_PANEL_DATA as SETTINGS_PANEL_DATA$1, PraxisLayerScaleStyleService, SETTINGS_PANEL_BRIDGE, SURFACE_DRAWER_BRIDGE, GlobalConfigService, FieldControlType, IconPickerService, LoggerService, ComponentMetadataRegistry } from '@praxisui/core';
20
+ import { providePraxisI18n, PraxisI18nService, PraxisIconButtonComponent, PraxisIconDirective, SETTINGS_PANEL_DATA as SETTINGS_PANEL_DATA$1, PraxisLayerScaleStyleService, SETTINGS_PANEL_BRIDGE, SURFACE_DRAWER_BRIDGE, SURFACE_DRAWER_CONTENT_DATA, SURFACE_DRAWER_REF, GlobalConfigService, FieldControlType, IconPickerService, LoggerService, ComponentMetadataRegistry } from '@praxisui/core';
21
21
  import { ConfirmDialogComponent } from '@praxisui/dynamic-fields';
22
22
  import * as i1$1 from '@angular/cdk/overlay';
23
23
  import { ComponentPortal } from '@angular/cdk/portal';
@@ -438,6 +438,7 @@ const PRAXIS_SETTINGS_PANEL_EN_US = {
438
438
  'Collapse panel': 'Collapse panel',
439
439
  'Expand panel': 'Expand panel',
440
440
  Close: 'Close',
441
+ Back: 'Back',
441
442
  Apply: 'Apply',
442
443
  'Save & Close': 'Save & Close',
443
444
  };
@@ -460,6 +461,7 @@ const PRAXIS_SETTINGS_PANEL_PT_BR = {
460
461
  'Collapse panel': 'Reduzir painel',
461
462
  'Expand panel': 'Expandir painel',
462
463
  Close: 'Fechar',
464
+ Back: 'Voltar',
463
465
  Apply: 'Aplicar',
464
466
  'Save & Close': 'Salvar & Fechar',
465
467
  };
@@ -1408,6 +1410,7 @@ class BaseSidePanelOverlayRef {
1408
1410
  overlayRef;
1409
1411
  titleUpdater;
1410
1412
  sizePersistor;
1413
+ terminalOutcomeEmitted = false;
1411
1414
  closed$ = this.closedSubject.asObservable();
1412
1415
  result$ = this.resultSubject.asObservable();
1413
1416
  sizeChanged$ = this.sizeChangedSubject.asObservable();
@@ -1423,6 +1426,14 @@ class BaseSidePanelOverlayRef {
1423
1426
  emitResult(result) {
1424
1427
  this.resultSubject.next(result);
1425
1428
  }
1429
+ complete(outcome) {
1430
+ if (this.terminalOutcomeEmitted || !this.overlayRef?.hasAttached()) {
1431
+ return;
1432
+ }
1433
+ this.terminalOutcomeEmitted = true;
1434
+ this.resultSubject.next(outcome);
1435
+ this.close(outcome);
1436
+ }
1426
1437
  updateTitle(title) {
1427
1438
  this.titleUpdater?.(title);
1428
1439
  }
@@ -1802,10 +1813,15 @@ class SurfaceDrawerComponent {
1802
1813
  useHostWidth = false;
1803
1814
  theme = null;
1804
1815
  onClose;
1816
+ onBack;
1805
1817
  static nextId = 0;
1806
1818
  titleId = `praxis-surface-drawer-title-${SurfaceDrawerComponent.nextId++}`;
1819
+ subtitleId = `${this.titleId}-description`;
1807
1820
  contentRef;
1808
1821
  contentHost;
1822
+ body;
1823
+ backButton;
1824
+ frames = [];
1809
1825
  constructor(cdr, i18n) {
1810
1826
  this.cdr = cdr;
1811
1827
  this.i18n = i18n;
@@ -1817,7 +1833,25 @@ class SurfaceDrawerComponent {
1817
1833
  return (this.theme?.cssVars ?? {});
1818
1834
  }
1819
1835
  get closeLabel() {
1820
- return this.i18n.t('Close', undefined, 'Close', PRAXIS_SETTINGS_PANEL_I18N_NAMESPACE);
1836
+ const fallback = this.i18n.getLocale().toLowerCase().startsWith('en')
1837
+ ? 'Close'
1838
+ : 'Fechar';
1839
+ return this.i18n.t('Close', undefined, fallback, PRAXIS_SETTINGS_PANEL_I18N_NAMESPACE);
1840
+ }
1841
+ get backLabel() {
1842
+ const fallback = this.i18n.getLocale().toLowerCase().startsWith('en')
1843
+ ? 'Back'
1844
+ : 'Voltar';
1845
+ return this.i18n.t('Back', undefined, fallback, PRAXIS_SETTINGS_PANEL_I18N_NAMESPACE);
1846
+ }
1847
+ get canGoBack() {
1848
+ return this.frames.length > 1;
1849
+ }
1850
+ get navigationDepth() {
1851
+ return this.frames.length;
1852
+ }
1853
+ get activeFrameId() {
1854
+ return this.frames[this.frames.length - 1]?.definition.id ?? '';
1821
1855
  }
1822
1856
  get hasHeader() {
1823
1857
  return !!(this.title || this.titleIcon || this.subtitle);
@@ -1832,11 +1866,156 @@ class SurfaceDrawerComponent {
1832
1866
  this.title = title;
1833
1867
  this.cdr.markForCheck();
1834
1868
  }
1869
+ ngOnDestroy() {
1870
+ for (const frame of this.frames) {
1871
+ if (!frame.componentRef.hostView.destroyed) {
1872
+ frame.componentRef.destroy();
1873
+ }
1874
+ }
1875
+ this.frames = [];
1876
+ }
1835
1877
  attachContent(component, injector, inputs) {
1836
- this.contentRef = this.contentHost.createComponent(component, { injector });
1837
- this.applyInputs(this.contentRef, inputs);
1878
+ return this.initializeFrame({
1879
+ id: 'root',
1880
+ title: this.title,
1881
+ titleIcon: this.titleIcon,
1882
+ subtitle: this.subtitle,
1883
+ content: { component, inputs },
1884
+ }, injector, inputs);
1885
+ }
1886
+ initializeFrame(definition, injector, inputs) {
1887
+ this.destroyAllFrames();
1888
+ const frame = this.createFrame(definition, injector, inputs);
1889
+ this.frames = [frame];
1890
+ this.applyFrameHeader(frame);
1891
+ return frame.componentRef;
1892
+ }
1893
+ pushFrame(definition, injector, inputs) {
1894
+ this.captureActiveFrameState();
1895
+ this.detachActiveView();
1896
+ const frame = this.createFrame(definition, injector, inputs);
1897
+ this.frames.push(frame);
1898
+ this.applyFrameHeader(frame);
1899
+ this.focusNavigationControl();
1900
+ return frame.componentRef;
1901
+ }
1902
+ async replaceFrame(definition, injector, inputs) {
1903
+ if (!(await this.canLeaveActiveFrame())) {
1904
+ return null;
1905
+ }
1906
+ this.removeActiveFrame();
1907
+ const frame = this.createFrame(definition, injector, inputs);
1908
+ this.frames.push(frame);
1909
+ this.applyFrameHeader(frame);
1910
+ this.focusNavigationControl();
1911
+ return frame.componentRef;
1912
+ }
1913
+ async backFrame() {
1914
+ if (!this.canGoBack || !(await this.canLeaveActiveFrame())) {
1915
+ return false;
1916
+ }
1917
+ this.removeActiveFrame();
1918
+ const previous = this.frames[this.frames.length - 1];
1919
+ if (!previous) {
1920
+ return false;
1921
+ }
1922
+ this.contentHost.insert(previous.componentRef.hostView);
1923
+ this.contentRef = previous.componentRef;
1924
+ this.applyFrameHeader(previous);
1925
+ queueMicrotask(() => {
1926
+ if (this.body?.nativeElement) {
1927
+ this.body.nativeElement.scrollTop = previous.scrollTop;
1928
+ }
1929
+ const target = previous.restoreFocusTo;
1930
+ if (target?.isConnected) {
1931
+ target.focus();
1932
+ }
1933
+ else {
1934
+ this.backButton?.nativeElement.focus();
1935
+ }
1936
+ });
1937
+ return true;
1938
+ }
1939
+ setActiveCanLeave(guard) {
1940
+ const active = this.frames[this.frames.length - 1];
1941
+ if (active) {
1942
+ active.canLeave = guard;
1943
+ }
1944
+ }
1945
+ async canCloseSession() {
1946
+ for (let index = this.frames.length - 1; index >= 0; index -= 1) {
1947
+ const guard = this.frames[index].canLeave;
1948
+ if (guard && !(await Promise.resolve(guard()))) {
1949
+ return false;
1950
+ }
1951
+ }
1952
+ return true;
1953
+ }
1954
+ createFrame(definition, injector, inputs) {
1955
+ const componentRef = this.contentHost.createComponent(definition.content.component, { injector });
1956
+ this.applyInputs(componentRef, inputs);
1957
+ this.contentRef = componentRef;
1838
1958
  this.cdr.markForCheck();
1839
- return this.contentRef;
1959
+ return {
1960
+ definition,
1961
+ componentRef,
1962
+ scrollTop: 0,
1963
+ restoreFocusTo: null,
1964
+ canLeave: definition.canLeave ?? null,
1965
+ };
1966
+ }
1967
+ captureActiveFrameState() {
1968
+ const active = this.frames[this.frames.length - 1];
1969
+ if (!active)
1970
+ return;
1971
+ active.scrollTop = this.body?.nativeElement.scrollTop ?? 0;
1972
+ const focused = typeof document !== 'undefined'
1973
+ ? document.activeElement
1974
+ : null;
1975
+ active.restoreFocusTo = focused instanceof HTMLElement ? focused : null;
1976
+ }
1977
+ detachActiveView() {
1978
+ const active = this.frames[this.frames.length - 1];
1979
+ if (!active)
1980
+ return;
1981
+ const index = this.contentHost.indexOf(active.componentRef.hostView);
1982
+ if (index >= 0) {
1983
+ this.contentHost.detach(index);
1984
+ }
1985
+ }
1986
+ removeActiveFrame() {
1987
+ const active = this.frames.pop();
1988
+ if (!active)
1989
+ return;
1990
+ const index = this.contentHost.indexOf(active.componentRef.hostView);
1991
+ if (index >= 0) {
1992
+ this.contentHost.remove(index);
1993
+ }
1994
+ else if (!active.componentRef.hostView.destroyed) {
1995
+ active.componentRef.destroy();
1996
+ }
1997
+ }
1998
+ destroyAllFrames() {
1999
+ this.contentHost.clear();
2000
+ for (const frame of this.frames) {
2001
+ if (!frame.componentRef.hostView.destroyed) {
2002
+ frame.componentRef.destroy();
2003
+ }
2004
+ }
2005
+ this.frames = [];
2006
+ }
2007
+ async canLeaveActiveFrame() {
2008
+ const guard = this.frames[this.frames.length - 1]?.canLeave;
2009
+ return guard ? Promise.resolve(guard()) : true;
2010
+ }
2011
+ applyFrameHeader(frame) {
2012
+ this.title = frame.definition.title;
2013
+ this.titleIcon = frame.definition.titleIcon;
2014
+ this.subtitle = frame.definition.subtitle;
2015
+ this.cdr.markForCheck();
2016
+ }
2017
+ focusNavigationControl() {
2018
+ queueMicrotask(() => this.backButton?.nativeElement.focus());
1840
2019
  }
1841
2020
  applyInputs(componentRef, inputs) {
1842
2021
  if (!inputs) {
@@ -1869,7 +2048,7 @@ class SurfaceDrawerComponent {
1869
2048
  return new Set(Object.keys(declaredInputs));
1870
2049
  }
1871
2050
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SurfaceDrawerComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i1$2.PraxisI18nService }], target: i0.ɵɵFactoryTarget.Component });
1872
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: SurfaceDrawerComponent, isStandalone: true, selector: "praxis-surface-drawer", viewQueries: [{ propertyName: "contentHost", first: true, predicate: ["contentHost"], descendants: true, read: ViewContainerRef, static: true }], ngImport: i0, template: "<div\n class=\"pfx-surface-drawer\"\n [class.has-header]=\"hasHeader\"\n [class.width-narrow]=\"widthClass === 'width-narrow'\"\n [class.width-default]=\"widthClass === 'width-default'\"\n [class.width-wide]=\"widthClass === 'width-wide'\"\n [class.width-full]=\"widthClass === 'width-full'\"\n [class.use-host-width]=\"useHostWidth\"\n [ngClass]=\"hostClasses\"\n [ngStyle]=\"hostCssVars\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-labelledby]=\"hasHeader ? titleId : null\"\n cdkTrapFocus\n cdkTrapFocusAutoCapture\n>\n @if (hasHeader) {\n <header class=\"pfx-surface-drawer__header\">\n <div class=\"pfx-surface-drawer__title-block\">\n @if (titleIcon) {\n <mat-icon class=\"pfx-surface-drawer__title-icon\" [praxisIcon]=\"titleIcon\"></mat-icon>\n }\n <div class=\"pfx-surface-drawer__copy\">\n @if (title) {\n <h2 class=\"pfx-surface-drawer__title\" [id]=\"titleId\">{{ title }}</h2>\n }\n @if (subtitle) {\n <p class=\"pfx-surface-drawer__subtitle\">{{ subtitle }}</p>\n }\n </div>\n </div>\n <button\n praxisIconButton=\"close\"\n size=\"compact\"\n type=\"button\"\n class=\"pfx-surface-drawer__close\"\n [attr.aria-label]=\"closeLabel\"\n [matTooltip]=\"closeLabel\"\n (click)=\"onClose?.()\"\n ></button>\n </header>\n }\n\n <section class=\"pfx-surface-drawer__body\">\n <ng-template #contentHost></ng-template>\n </section>\n</div>\n", styles: [":host{display:block;height:100%;width:100%;min-width:0;flex:1 1 auto}.pfx-surface-drawer{display:grid;grid-template-rows:auto 1fr;height:100%;min-width:0;max-width:100%;overflow:hidden;background:var(--md-sys-color-surface-container, #fff);color:var(--md-sys-color-on-surface, #111);border-left:1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, .12));border-radius:var(--pfx-side-panel-border-radius, 0);box-shadow:var(--pfx-side-panel-elevation, var(--md-sys-elevation-level2, 0 8px 24px rgba(0, 0, 0, .16)));width:var(--pfx-side-panel-width, 720px);transition:width var(--pfx-side-panel-motion-duration-enter, .22s) var(--pfx-side-panel-motion-easing-enter, cubic-bezier(.2, 0, 0, 1)),box-shadow var(--pfx-side-panel-motion-duration-enter, .22s) var(--pfx-side-panel-motion-easing-enter, cubic-bezier(.2, 0, 0, 1))}.pfx-surface-drawer.use-host-width{width:100%;max-width:100%}.pfx-surface-drawer.width-narrow{width:var(--pfx-side-panel-width, 420px)}.pfx-surface-drawer.width-default{width:var(--pfx-side-panel-width, 720px)}.pfx-surface-drawer.width-wide{width:var(--pfx-side-panel-width, 960px)}.pfx-surface-drawer.width-full{width:min(100vw,var(--pfx-side-panel-max-width, 100vw))}.pfx-surface-drawer__header{display:flex;align-items:center;gap:var(--pfx-side-panel-header-gap, 8px);min-height:var(--pfx-side-panel-header-height, 64px);padding:0 var(--pfx-side-panel-header-padding-x, 16px);border-bottom:1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, .12));background:var(--md-sys-color-surface-container-high, #f7f7f7)}.pfx-surface-drawer__title-block{display:flex;align-items:flex-start;gap:10px;min-width:0;flex:1 1 auto}.pfx-surface-drawer__title-icon{color:var(--md-sys-color-primary, currentColor)}.pfx-surface-drawer__copy{min-width:0}.pfx-surface-drawer__title,.pfx-surface-drawer__subtitle{margin:0}.pfx-surface-drawer__title{font-size:1rem;font-weight:600}.pfx-surface-drawer__subtitle{margin-top:4px;color:var(--md-sys-color-on-surface-variant, rgba(0, 0, 0, .64));font-size:.875rem}.pfx-surface-drawer__body{min-height:0;overflow:auto;padding:var(--pfx-side-panel-body-padding, 16px);background:var(--md-sys-color-surface, #fff)}@media(prefers-reduced-motion:reduce){.pfx-surface-drawer{transition:none}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4$1.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2051
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: SurfaceDrawerComponent, isStandalone: true, selector: "praxis-surface-drawer", viewQueries: [{ propertyName: "contentHost", first: true, predicate: ["contentHost"], descendants: true, read: ViewContainerRef, static: true }, { propertyName: "body", first: true, predicate: ["body"], descendants: true, read: ElementRef }, { propertyName: "backButton", first: true, predicate: ["backButton"], descendants: true, read: ElementRef }], ngImport: i0, template: "<div\n class=\"pfx-surface-drawer\"\n [class.has-header]=\"hasHeader\"\n [class.width-narrow]=\"widthClass === 'width-narrow'\"\n [class.width-default]=\"widthClass === 'width-default'\"\n [class.width-wide]=\"widthClass === 'width-wide'\"\n [class.width-full]=\"widthClass === 'width-full'\"\n [class.use-host-width]=\"useHostWidth\"\n [ngClass]=\"hostClasses\"\n [ngStyle]=\"hostCssVars\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-labelledby]=\"hasHeader ? titleId : null\"\n [attr.aria-describedby]=\"subtitle ? subtitleId : null\"\n cdkTrapFocus\n cdkTrapFocusAutoCapture\n>\n @if (hasHeader) {\n <header class=\"pfx-surface-drawer__header\">\n @if (canGoBack) {\n <button\n #backButton\n praxisIconButton=\"arrow_back\"\n size=\"compact\"\n type=\"button\"\n class=\"pfx-surface-drawer__back\"\n [attr.aria-label]=\"backLabel\"\n [matTooltip]=\"backLabel\"\n (click)=\"onBack?.()\"\n ></button>\n }\n <div class=\"pfx-surface-drawer__title-block\">\n @if (titleIcon) {\n <mat-icon class=\"pfx-surface-drawer__title-icon\" [praxisIcon]=\"titleIcon\"></mat-icon>\n }\n <div class=\"pfx-surface-drawer__copy\">\n @if (title) {\n <h2 class=\"pfx-surface-drawer__title\" [id]=\"titleId\">{{ title }}</h2>\n }\n @if (subtitle) {\n <p class=\"pfx-surface-drawer__subtitle\" [id]=\"subtitleId\">{{ subtitle }}</p>\n }\n </div>\n </div>\n <button\n praxisIconButton=\"close\"\n size=\"compact\"\n type=\"button\"\n class=\"pfx-surface-drawer__close\"\n [attr.aria-label]=\"closeLabel\"\n [matTooltip]=\"closeLabel\"\n (click)=\"onClose?.()\"\n ></button>\n </header>\n }\n\n <section #body class=\"pfx-surface-drawer__body\">\n <ng-template #contentHost></ng-template>\n </section>\n</div>\n", styles: [":host{display:block;height:100%;width:100%;min-width:0;flex:1 1 auto}.pfx-surface-drawer{display:grid;grid-template-rows:auto 1fr;height:100%;min-width:0;max-width:100%;overflow:hidden;background:var(--md-sys-color-surface-container, #fff);color:var(--md-sys-color-on-surface, #111);border-left:1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, .12));border-radius:var(--pfx-side-panel-border-radius, 0);box-shadow:var(--pfx-side-panel-elevation, var(--md-sys-elevation-level2, 0 8px 24px rgba(0, 0, 0, .16)));width:var(--pfx-side-panel-width, 720px);transition:width var(--pfx-side-panel-motion-duration-enter, .22s) var(--pfx-side-panel-motion-easing-enter, cubic-bezier(.2, 0, 0, 1)),box-shadow var(--pfx-side-panel-motion-duration-enter, .22s) var(--pfx-side-panel-motion-easing-enter, cubic-bezier(.2, 0, 0, 1))}.pfx-surface-drawer.use-host-width{width:100%;max-width:100%}.pfx-surface-drawer.width-narrow{width:var(--pfx-side-panel-width, 420px)}.pfx-surface-drawer.width-default{width:var(--pfx-side-panel-width, 720px)}.pfx-surface-drawer.width-wide{width:var(--pfx-side-panel-width, 960px)}.pfx-surface-drawer.width-full{width:min(100vw,var(--pfx-side-panel-max-width, 100vw))}.pfx-surface-drawer__header{display:flex;align-items:flex-start;gap:var(--pfx-side-panel-header-gap, 12px);min-height:var(--pfx-side-panel-header-height, 64px);padding:var(--pfx-side-panel-header-padding-y, 16px) var(--pfx-side-panel-header-padding-x, 20px);box-sizing:border-box;border-bottom:1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, .12));background:var(--md-sys-color-surface-container-high, #f7f7f7)}.pfx-surface-drawer__title-block{display:flex;align-items:flex-start;gap:12px;min-width:0;flex:1 1 auto}.pfx-surface-drawer__title-icon{flex:0 0 auto;display:grid;place-items:center;width:36px;height:36px;margin-top:1px;border-radius:10px;background:var(--md-sys-color-primary-container, rgba(47, 78, 178, .12));color:var(--md-sys-color-primary, currentColor)}.pfx-surface-drawer__copy{min-width:0}.pfx-surface-drawer__title,.pfx-surface-drawer__subtitle{margin:0}.pfx-surface-drawer__title{font-size:1.125rem;font-weight:650;line-height:1.35}.pfx-surface-drawer__subtitle{margin-top:6px;color:var(--md-sys-color-on-surface-variant, rgba(0, 0, 0, .64));font-size:.875rem;line-height:1.45;text-wrap:pretty}.pfx-surface-drawer__close{flex:0 0 auto}.pfx-surface-drawer__back{flex:0 0 auto;margin-top:1px}.pfx-surface-drawer__body{min-height:0;overflow:auto;padding:var(--pfx-side-panel-body-padding, 16px);background:var(--md-sys-color-surface, #fff)}@media(prefers-reduced-motion:reduce){.pfx-surface-drawer{transition:none}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4$1.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1873
2052
  }
1874
2053
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SurfaceDrawerComponent, decorators: [{
1875
2054
  type: Component,
@@ -1882,10 +2061,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
1882
2061
  CdkTrapFocus,
1883
2062
  PraxisIconButtonComponent,
1884
2063
  PraxisIconDirective,
1885
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n class=\"pfx-surface-drawer\"\n [class.has-header]=\"hasHeader\"\n [class.width-narrow]=\"widthClass === 'width-narrow'\"\n [class.width-default]=\"widthClass === 'width-default'\"\n [class.width-wide]=\"widthClass === 'width-wide'\"\n [class.width-full]=\"widthClass === 'width-full'\"\n [class.use-host-width]=\"useHostWidth\"\n [ngClass]=\"hostClasses\"\n [ngStyle]=\"hostCssVars\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-labelledby]=\"hasHeader ? titleId : null\"\n cdkTrapFocus\n cdkTrapFocusAutoCapture\n>\n @if (hasHeader) {\n <header class=\"pfx-surface-drawer__header\">\n <div class=\"pfx-surface-drawer__title-block\">\n @if (titleIcon) {\n <mat-icon class=\"pfx-surface-drawer__title-icon\" [praxisIcon]=\"titleIcon\"></mat-icon>\n }\n <div class=\"pfx-surface-drawer__copy\">\n @if (title) {\n <h2 class=\"pfx-surface-drawer__title\" [id]=\"titleId\">{{ title }}</h2>\n }\n @if (subtitle) {\n <p class=\"pfx-surface-drawer__subtitle\">{{ subtitle }}</p>\n }\n </div>\n </div>\n <button\n praxisIconButton=\"close\"\n size=\"compact\"\n type=\"button\"\n class=\"pfx-surface-drawer__close\"\n [attr.aria-label]=\"closeLabel\"\n [matTooltip]=\"closeLabel\"\n (click)=\"onClose?.()\"\n ></button>\n </header>\n }\n\n <section class=\"pfx-surface-drawer__body\">\n <ng-template #contentHost></ng-template>\n </section>\n</div>\n", styles: [":host{display:block;height:100%;width:100%;min-width:0;flex:1 1 auto}.pfx-surface-drawer{display:grid;grid-template-rows:auto 1fr;height:100%;min-width:0;max-width:100%;overflow:hidden;background:var(--md-sys-color-surface-container, #fff);color:var(--md-sys-color-on-surface, #111);border-left:1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, .12));border-radius:var(--pfx-side-panel-border-radius, 0);box-shadow:var(--pfx-side-panel-elevation, var(--md-sys-elevation-level2, 0 8px 24px rgba(0, 0, 0, .16)));width:var(--pfx-side-panel-width, 720px);transition:width var(--pfx-side-panel-motion-duration-enter, .22s) var(--pfx-side-panel-motion-easing-enter, cubic-bezier(.2, 0, 0, 1)),box-shadow var(--pfx-side-panel-motion-duration-enter, .22s) var(--pfx-side-panel-motion-easing-enter, cubic-bezier(.2, 0, 0, 1))}.pfx-surface-drawer.use-host-width{width:100%;max-width:100%}.pfx-surface-drawer.width-narrow{width:var(--pfx-side-panel-width, 420px)}.pfx-surface-drawer.width-default{width:var(--pfx-side-panel-width, 720px)}.pfx-surface-drawer.width-wide{width:var(--pfx-side-panel-width, 960px)}.pfx-surface-drawer.width-full{width:min(100vw,var(--pfx-side-panel-max-width, 100vw))}.pfx-surface-drawer__header{display:flex;align-items:center;gap:var(--pfx-side-panel-header-gap, 8px);min-height:var(--pfx-side-panel-header-height, 64px);padding:0 var(--pfx-side-panel-header-padding-x, 16px);border-bottom:1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, .12));background:var(--md-sys-color-surface-container-high, #f7f7f7)}.pfx-surface-drawer__title-block{display:flex;align-items:flex-start;gap:10px;min-width:0;flex:1 1 auto}.pfx-surface-drawer__title-icon{color:var(--md-sys-color-primary, currentColor)}.pfx-surface-drawer__copy{min-width:0}.pfx-surface-drawer__title,.pfx-surface-drawer__subtitle{margin:0}.pfx-surface-drawer__title{font-size:1rem;font-weight:600}.pfx-surface-drawer__subtitle{margin-top:4px;color:var(--md-sys-color-on-surface-variant, rgba(0, 0, 0, .64));font-size:.875rem}.pfx-surface-drawer__body{min-height:0;overflow:auto;padding:var(--pfx-side-panel-body-padding, 16px);background:var(--md-sys-color-surface, #fff)}@media(prefers-reduced-motion:reduce){.pfx-surface-drawer{transition:none}}\n"] }]
2064
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n class=\"pfx-surface-drawer\"\n [class.has-header]=\"hasHeader\"\n [class.width-narrow]=\"widthClass === 'width-narrow'\"\n [class.width-default]=\"widthClass === 'width-default'\"\n [class.width-wide]=\"widthClass === 'width-wide'\"\n [class.width-full]=\"widthClass === 'width-full'\"\n [class.use-host-width]=\"useHostWidth\"\n [ngClass]=\"hostClasses\"\n [ngStyle]=\"hostCssVars\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-labelledby]=\"hasHeader ? titleId : null\"\n [attr.aria-describedby]=\"subtitle ? subtitleId : null\"\n cdkTrapFocus\n cdkTrapFocusAutoCapture\n>\n @if (hasHeader) {\n <header class=\"pfx-surface-drawer__header\">\n @if (canGoBack) {\n <button\n #backButton\n praxisIconButton=\"arrow_back\"\n size=\"compact\"\n type=\"button\"\n class=\"pfx-surface-drawer__back\"\n [attr.aria-label]=\"backLabel\"\n [matTooltip]=\"backLabel\"\n (click)=\"onBack?.()\"\n ></button>\n }\n <div class=\"pfx-surface-drawer__title-block\">\n @if (titleIcon) {\n <mat-icon class=\"pfx-surface-drawer__title-icon\" [praxisIcon]=\"titleIcon\"></mat-icon>\n }\n <div class=\"pfx-surface-drawer__copy\">\n @if (title) {\n <h2 class=\"pfx-surface-drawer__title\" [id]=\"titleId\">{{ title }}</h2>\n }\n @if (subtitle) {\n <p class=\"pfx-surface-drawer__subtitle\" [id]=\"subtitleId\">{{ subtitle }}</p>\n }\n </div>\n </div>\n <button\n praxisIconButton=\"close\"\n size=\"compact\"\n type=\"button\"\n class=\"pfx-surface-drawer__close\"\n [attr.aria-label]=\"closeLabel\"\n [matTooltip]=\"closeLabel\"\n (click)=\"onClose?.()\"\n ></button>\n </header>\n }\n\n <section #body class=\"pfx-surface-drawer__body\">\n <ng-template #contentHost></ng-template>\n </section>\n</div>\n", styles: [":host{display:block;height:100%;width:100%;min-width:0;flex:1 1 auto}.pfx-surface-drawer{display:grid;grid-template-rows:auto 1fr;height:100%;min-width:0;max-width:100%;overflow:hidden;background:var(--md-sys-color-surface-container, #fff);color:var(--md-sys-color-on-surface, #111);border-left:1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, .12));border-radius:var(--pfx-side-panel-border-radius, 0);box-shadow:var(--pfx-side-panel-elevation, var(--md-sys-elevation-level2, 0 8px 24px rgba(0, 0, 0, .16)));width:var(--pfx-side-panel-width, 720px);transition:width var(--pfx-side-panel-motion-duration-enter, .22s) var(--pfx-side-panel-motion-easing-enter, cubic-bezier(.2, 0, 0, 1)),box-shadow var(--pfx-side-panel-motion-duration-enter, .22s) var(--pfx-side-panel-motion-easing-enter, cubic-bezier(.2, 0, 0, 1))}.pfx-surface-drawer.use-host-width{width:100%;max-width:100%}.pfx-surface-drawer.width-narrow{width:var(--pfx-side-panel-width, 420px)}.pfx-surface-drawer.width-default{width:var(--pfx-side-panel-width, 720px)}.pfx-surface-drawer.width-wide{width:var(--pfx-side-panel-width, 960px)}.pfx-surface-drawer.width-full{width:min(100vw,var(--pfx-side-panel-max-width, 100vw))}.pfx-surface-drawer__header{display:flex;align-items:flex-start;gap:var(--pfx-side-panel-header-gap, 12px);min-height:var(--pfx-side-panel-header-height, 64px);padding:var(--pfx-side-panel-header-padding-y, 16px) var(--pfx-side-panel-header-padding-x, 20px);box-sizing:border-box;border-bottom:1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, .12));background:var(--md-sys-color-surface-container-high, #f7f7f7)}.pfx-surface-drawer__title-block{display:flex;align-items:flex-start;gap:12px;min-width:0;flex:1 1 auto}.pfx-surface-drawer__title-icon{flex:0 0 auto;display:grid;place-items:center;width:36px;height:36px;margin-top:1px;border-radius:10px;background:var(--md-sys-color-primary-container, rgba(47, 78, 178, .12));color:var(--md-sys-color-primary, currentColor)}.pfx-surface-drawer__copy{min-width:0}.pfx-surface-drawer__title,.pfx-surface-drawer__subtitle{margin:0}.pfx-surface-drawer__title{font-size:1.125rem;font-weight:650;line-height:1.35}.pfx-surface-drawer__subtitle{margin-top:6px;color:var(--md-sys-color-on-surface-variant, rgba(0, 0, 0, .64));font-size:.875rem;line-height:1.45;text-wrap:pretty}.pfx-surface-drawer__close{flex:0 0 auto}.pfx-surface-drawer__back{flex:0 0 auto;margin-top:1px}.pfx-surface-drawer__body{min-height:0;overflow:auto;padding:var(--pfx-side-panel-body-padding, 16px);background:var(--md-sys-color-surface, #fff)}@media(prefers-reduced-motion:reduce){.pfx-surface-drawer{transition:none}}\n"] }]
1886
2065
  }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i1$2.PraxisI18nService }], propDecorators: { contentHost: [{
1887
2066
  type: ViewChild,
1888
2067
  args: ['contentHost', { read: ViewContainerRef, static: true }]
2068
+ }], body: [{
2069
+ type: ViewChild,
2070
+ args: ['body', { read: ElementRef }]
2071
+ }], backButton: [{
2072
+ type: ViewChild,
2073
+ args: ['backButton', { read: ElementRef }]
1889
2074
  }] } });
1890
2075
 
1891
2076
  function buildSurfaceRuntime(ref) {
@@ -1894,8 +2079,15 @@ function buildSurfaceRuntime(ref) {
1894
2079
  result$: ref.result$,
1895
2080
  close: (result) => ref.close?.(result),
1896
2081
  emitResult: (result) => ref.emitResult?.(result),
2082
+ complete: (outcome) => ref.complete?.(outcome),
1897
2083
  updateTitle: (title) => ref.updateTitle?.(title),
1898
2084
  updateSize: (preset) => ref.updateSize?.(preset),
2085
+ navigation$: ref.navigation$,
2086
+ push: (frame) => ref.push?.(frame),
2087
+ replace: (frame) => ref.replace?.(frame),
2088
+ back: () => ref.back?.(),
2089
+ canGoBack: () => ref.canGoBack?.() ?? false,
2090
+ setCanLeave: (guard) => ref.setCanLeave?.(guard),
1899
2091
  };
1900
2092
  }
1901
2093
  function enrichSurfaceContentInputs(inputs, ref) {
@@ -1941,7 +2133,159 @@ function providePraxisSurfaceDrawerBridge() {
1941
2133
  useFactory: (service, injector, layerScale) => ({
1942
2134
  open: (opts) => {
1943
2135
  layerScale.ensureInstalled();
1944
- return service.openHost({
2136
+ const sessionId = `${opts.id}:${Date.now().toString(36)}`;
2137
+ const navigationState = new BehaviorSubject({
2138
+ sessionId,
2139
+ activeFrameId: opts.id,
2140
+ depth: 1,
2141
+ canGoBack: false,
2142
+ });
2143
+ let panel = null;
2144
+ let activeRef = null;
2145
+ const frameRefs = [];
2146
+ const createContent = (frame, mode, runtimeRef) => {
2147
+ if (!panel)
2148
+ return null;
2149
+ const contentInputs = enrichSurfaceContentInputs(frame.content.inputs, runtimeRef);
2150
+ const contentInjector = Injector.create({
2151
+ providers: [
2152
+ { provide: BASE_SIDE_PANEL_DATA, useValue: contentInputs },
2153
+ { provide: BASE_SIDE_PANEL_REF, useValue: runtimeRef },
2154
+ { provide: SURFACE_DRAWER_CONTENT_DATA, useValue: contentInputs },
2155
+ { provide: SURFACE_DRAWER_REF, useValue: runtimeRef },
2156
+ ],
2157
+ parent: injector,
2158
+ });
2159
+ if (mode === 'initial') {
2160
+ return panel.initializeFrame(frame, contentInjector, contentInputs);
2161
+ }
2162
+ if (mode === 'push') {
2163
+ return panel.pushFrame(frame, contentInjector, contentInputs);
2164
+ }
2165
+ return panel.replaceFrame(frame, contentInjector, contentInputs);
2166
+ };
2167
+ const publishNavigationState = () => {
2168
+ if (!panel)
2169
+ return;
2170
+ navigationState.next({
2171
+ sessionId,
2172
+ activeFrameId: panel.activeFrameId,
2173
+ depth: panel.navigationDepth,
2174
+ canGoBack: panel.canGoBack,
2175
+ });
2176
+ };
2177
+ const closeFrameRef = (frameRef, result) => {
2178
+ const runtime = frameRef;
2179
+ runtime.__closeFrame?.(result);
2180
+ };
2181
+ const navigateBack = async (result) => {
2182
+ const leaving = frameRefs[frameRefs.length - 1];
2183
+ const moved = await panel?.backFrame() ?? false;
2184
+ if (!moved)
2185
+ return false;
2186
+ if (leaving) {
2187
+ frameRefs.pop();
2188
+ closeFrameRef(leaving, result ?? { type: 'back' });
2189
+ }
2190
+ publishNavigationState();
2191
+ return true;
2192
+ };
2193
+ const createFrameRef = (frameId) => {
2194
+ const closedSubject = new Subject();
2195
+ const resultSubject = new Subject();
2196
+ let closed = false;
2197
+ let cleanup = null;
2198
+ const frameRef = {
2199
+ frameId,
2200
+ closed$: closedSubject.asObservable(),
2201
+ result$: resultSubject.asObservable(),
2202
+ navigation$: navigationState.asObservable(),
2203
+ close: (result) => { void navigateBack(result); },
2204
+ emitResult: (result) => resultSubject.next(result),
2205
+ complete: (outcome) => {
2206
+ resultSubject.next(outcome);
2207
+ void navigateBack(outcome);
2208
+ },
2209
+ updateTitle: (title) => activeRef?.updateTitle?.(title),
2210
+ updateSize: (preset) => activeRef?.updateSize?.(preset),
2211
+ push: (frame) => activeRef?.push?.(frame) ?? Promise.resolve(null),
2212
+ replace: (frame) => activeRef?.replace?.(frame) ?? Promise.resolve(null),
2213
+ back: () => activeRef?.back?.() ?? Promise.resolve(false),
2214
+ canGoBack: () => activeRef?.canGoBack?.() ?? false,
2215
+ setCanLeave: (guard) => activeRef?.setCanLeave?.(guard),
2216
+ __closeFrame: (result) => {
2217
+ if (closed)
2218
+ return;
2219
+ closed = true;
2220
+ cleanup?.();
2221
+ cleanup = null;
2222
+ closedSubject.next(result);
2223
+ closedSubject.complete();
2224
+ resultSubject.complete();
2225
+ },
2226
+ __setCleanup: (nextCleanup) => {
2227
+ cleanup?.();
2228
+ cleanup = nextCleanup;
2229
+ },
2230
+ };
2231
+ return frameRef;
2232
+ };
2233
+ const configureRef = (ref) => {
2234
+ activeRef = ref;
2235
+ if (ref.navigation$) {
2236
+ return ref;
2237
+ }
2238
+ ref.navigation$ = navigationState.asObservable();
2239
+ ref.canGoBack = () => panel?.canGoBack ?? false;
2240
+ ref.setCanLeave = (guard) => panel?.setActiveCanLeave(guard);
2241
+ ref.push = async (frame) => {
2242
+ const frameRef = createFrameRef(frame.id);
2243
+ const contentRef = await createContent(frame, 'push', frameRef);
2244
+ if (!contentRef)
2245
+ return null;
2246
+ frameRefs.push(frameRef);
2247
+ frameRef.__setCleanup?.(bindSurfaceContentSelectionResults(contentRef.instance, frameRef));
2248
+ publishNavigationState();
2249
+ return frameRef;
2250
+ };
2251
+ ref.replace = async (frame) => {
2252
+ if ((panel?.navigationDepth ?? 1) === 1) {
2253
+ const rootRef = frameRefs[0]
2254
+ ?? ref;
2255
+ const contentRef = await createContent(frame, 'replace', rootRef);
2256
+ if (!contentRef)
2257
+ return null;
2258
+ Object.defineProperty(rootRef, 'frameId', {
2259
+ configurable: true,
2260
+ value: frame.id,
2261
+ });
2262
+ bindSurfaceContentSelectionResults(contentRef.instance, rootRef);
2263
+ publishNavigationState();
2264
+ return rootRef;
2265
+ }
2266
+ const frameRef = createFrameRef(frame.id);
2267
+ const contentRef = await createContent(frame, 'replace', frameRef);
2268
+ if (!contentRef)
2269
+ return null;
2270
+ const replaced = frameRefs.pop();
2271
+ if (replaced)
2272
+ closeFrameRef(replaced, { type: 'replace' });
2273
+ frameRefs.push(frameRef);
2274
+ frameRef.__setCleanup?.(bindSurfaceContentSelectionResults(contentRef.instance, frameRef));
2275
+ publishNavigationState();
2276
+ return frameRef;
2277
+ };
2278
+ ref.back = () => navigateBack();
2279
+ if (frameRefs.length === 0) {
2280
+ Object.defineProperty(ref, 'frameId', {
2281
+ configurable: true,
2282
+ value: opts.id,
2283
+ });
2284
+ frameRefs.push(ref);
2285
+ }
2286
+ return ref;
2287
+ };
2288
+ const result = service.openHost({
1945
2289
  component: SurfaceDrawerComponent,
1946
2290
  config: {
1947
2291
  id: opts.id,
@@ -1969,26 +2313,32 @@ function providePraxisSurfaceDrawerBridge() {
1969
2313
  minHeight: opts.minHeight,
1970
2314
  maxHeight: opts.maxHeight,
1971
2315
  },
1972
- createRef: (overlayRef) => new BaseSidePanelOverlayRef(overlayRef),
2316
+ createRef: (overlayRef) => {
2317
+ const ref = new BaseSidePanelOverlayRef(overlayRef);
2318
+ return configureRef(ref);
2319
+ },
1973
2320
  attach: (panelRef, ref) => {
1974
- const contentInputs = enrichSurfaceContentInputs(opts.content.inputs, ref);
1975
- panelRef.instance.title = opts.title;
1976
- panelRef.instance.titleIcon = opts.titleIcon;
1977
- panelRef.instance.subtitle = opts.subtitle;
2321
+ panel = panelRef.instance;
2322
+ configureRef(ref);
1978
2323
  panelRef.instance.widthPreset = opts.widthPreset ?? 'default';
1979
2324
  panelRef.instance.useHostWidth = !!(opts.width ||
1980
2325
  opts.minWidth ||
1981
2326
  opts.maxWidth);
1982
- panelRef.instance.onClose = () => ref.close({ type: 'close' });
2327
+ panelRef.instance.onClose = () => {
2328
+ void panelRef.instance.canCloseSession().then((allowed) => {
2329
+ if (allowed)
2330
+ ref.close({ type: 'close' });
2331
+ });
2332
+ };
2333
+ panelRef.instance.onBack = () => { void activeRef?.back?.(); };
1983
2334
  ref.bindTitleUpdater((title) => panelRef.instance.setTitle(title));
1984
- const contentInjector = Injector.create({
1985
- providers: [
1986
- { provide: BASE_SIDE_PANEL_DATA, useValue: contentInputs },
1987
- { provide: BASE_SIDE_PANEL_REF, useValue: ref },
1988
- ],
1989
- parent: injector,
1990
- });
1991
- const contentRef = panelRef.instance.attachContent(opts.content.component, contentInjector, contentInputs);
2335
+ const contentRef = createContent({
2336
+ id: opts.id,
2337
+ title: opts.title,
2338
+ titleIcon: opts.titleIcon,
2339
+ subtitle: opts.subtitle,
2340
+ content: opts.content,
2341
+ }, 'initial', frameRefs[0] ?? ref);
1992
2342
  const unbindSelectionResults = contentRef?.instance
1993
2343
  ? bindSurfaceContentSelectionResults(contentRef.instance, ref)
1994
2344
  : null;
@@ -1997,10 +2347,35 @@ function providePraxisSurfaceDrawerBridge() {
1997
2347
  }
1998
2348
  panelRef.changeDetectorRef.detectChanges();
1999
2349
  },
2000
- createCloser: (ref) => (reason = 'close') => ref.close({ type: reason }),
2001
- onBackdrop: (ref) => ref.close({ type: 'backdrop' }),
2002
- onEsc: (ref) => ref.close({ type: 'esc' }),
2350
+ createCloser: (ref) => (reason = 'close') => {
2351
+ void panel?.canCloseSession().then((allowed) => {
2352
+ if (allowed)
2353
+ ref.close({ type: reason });
2354
+ });
2355
+ },
2356
+ onBackdrop: (ref) => {
2357
+ void panel?.canCloseSession().then((allowed) => {
2358
+ if (allowed)
2359
+ ref.close({ type: 'backdrop' });
2360
+ });
2361
+ },
2362
+ onEsc: (ref) => {
2363
+ void panel?.canCloseSession().then((allowed) => {
2364
+ if (allowed)
2365
+ ref.close({ type: 'esc' });
2366
+ });
2367
+ },
2368
+ });
2369
+ result.closed$.subscribe(() => {
2370
+ while (frameRefs.length > 1) {
2371
+ closeFrameRef(frameRefs.pop(), { type: 'close' });
2372
+ }
2373
+ frameRefs.length = 0;
2374
+ navigationState.complete();
2375
+ panel = null;
2376
+ activeRef = null;
2003
2377
  });
2378
+ return result;
2004
2379
  },
2005
2380
  }),
2006
2381
  deps: [BaseSidePanelService, Injector, PraxisLayerScaleStyleService],
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@praxisui/settings-panel",
3
- "version": "9.0.4-rc.3",
3
+ "version": "9.0.4-rc.31",
4
4
  "description": "Settings panel for Praxis UI libraries: open editors for configuration at runtime and persist settings.",
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^21.0.0",
7
7
  "@angular/core": "^21.0.0",
8
8
  "@angular/cdk": "^21.0.0",
9
9
  "@angular/material": "^21.0.0",
10
- "@praxisui/ai": "^9.0.4-rc.3",
11
- "@praxisui/core": "^9.0.4-rc.3",
12
- "@praxisui/dynamic-fields": "^9.0.4-rc.3",
10
+ "@praxisui/ai": "^9.0.4-rc.31",
11
+ "@praxisui/core": "^9.0.4-rc.31",
12
+ "@praxisui/dynamic-fields": "^9.0.4-rc.31",
13
13
  "rxjs": "~7.8.0"
14
14
  },
15
15
  "dependencies": {
@@ -1,9 +1,9 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Type, Injector, ComponentRef, Provider, ChangeDetectorRef, ElementRef, InjectionToken, OnDestroy, OnInit, AfterViewInit, ViewContainerRef } from '@angular/core';
2
+ import { Type, Injector, ComponentRef, Provider, OnDestroy, ChangeDetectorRef, ElementRef, InjectionToken, OnInit, AfterViewInit, ViewContainerRef } from '@angular/core';
3
3
  import * as rxjs from 'rxjs';
4
4
  import { Observable, BehaviorSubject } from 'rxjs';
5
5
  import { OverlayRef, Overlay, OverlayContainer } from '@angular/cdk/overlay';
6
- import { PraxisI18nService, GlobalConfig, FormConfig, FormValueChangeEvent, AiCapability, AiCapabilityCategory, AiCapabilityCatalog, AiValueKind, ComponentAuthoringManifest, ComponentDocMeta } from '@praxisui/core';
6
+ import { SurfaceOutcome, PraxisI18nService, SurfaceDrawerNavigationFrame, SurfaceDrawerNavigationGuard, GlobalConfig, FormConfig, FormValueChangeEvent, AiCapability, AiCapabilityCategory, AiCapabilityCatalog, AiValueKind, ComponentAuthoringManifest, ComponentDocMeta } from '@praxisui/core';
7
7
  import { MatDialog } from '@angular/material/dialog';
8
8
  import { MatSnackBar } from '@angular/material/snack-bar';
9
9
  import { AiProviderCatalogItem } from '@praxisui/ai';
@@ -224,6 +224,7 @@ interface BaseSidePanelRef<T = unknown> {
224
224
  }>;
225
225
  close(result?: BaseSidePanelResult<T>): void;
226
226
  emitResult?(result: BaseSidePanelResult<T>): void;
227
+ complete?(outcome: SurfaceOutcome<T>): void;
227
228
  updateTitle?(title: string): void;
228
229
  updateSize?(size: SidePanelWidthPreset | string | {
229
230
  width?: string;
@@ -260,6 +261,7 @@ declare class BaseSidePanelOverlayRef<T = unknown> implements BaseSidePanelRef<T
260
261
  private overlayRef;
261
262
  private titleUpdater?;
262
263
  private sizePersistor?;
264
+ private terminalOutcomeEmitted;
263
265
  readonly closed$: rxjs.Observable<BaseSidePanelResult<T> | undefined>;
264
266
  readonly result$: rxjs.Observable<BaseSidePanelResult<T>>;
265
267
  readonly sizeChanged$: rxjs.Observable<{
@@ -269,6 +271,7 @@ declare class BaseSidePanelOverlayRef<T = unknown> implements BaseSidePanelRef<T
269
271
  bindTitleUpdater(fn: (title: string) => void): void;
270
272
  bindSizePersistor(fn: (width: string) => void): void;
271
273
  emitResult(result: BaseSidePanelResult<T>): void;
274
+ complete(outcome: SurfaceOutcome<T>): void;
272
275
  updateTitle(title: string): void;
273
276
  updateSize(size: string | {
274
277
  width?: string;
@@ -350,7 +353,7 @@ declare function providePraxisSettingsPanelBridge(): Provider[];
350
353
 
351
354
  declare function providePraxisSurfaceDrawerBridge(): Provider[];
352
355
 
353
- declare class SurfaceDrawerComponent {
356
+ declare class SurfaceDrawerComponent implements OnDestroy {
354
357
  private readonly cdr;
355
358
  private readonly i18n;
356
359
  title: string;
@@ -360,18 +363,42 @@ declare class SurfaceDrawerComponent {
360
363
  useHostWidth: boolean;
361
364
  theme: SidePanelThemeContract | null;
362
365
  onClose?: () => void;
366
+ onBack?: () => void;
363
367
  private static nextId;
364
368
  titleId: string;
369
+ subtitleId: string;
365
370
  contentRef?: ComponentRef<any>;
366
371
  private contentHost;
372
+ private body?;
373
+ private backButton?;
374
+ private frames;
367
375
  constructor(cdr: ChangeDetectorRef, i18n: PraxisI18nService);
368
376
  get hostClasses(): string[];
369
377
  get hostCssVars(): Record<string, string>;
370
378
  get closeLabel(): string;
379
+ get backLabel(): string;
380
+ get canGoBack(): boolean;
381
+ get navigationDepth(): number;
382
+ get activeFrameId(): string;
371
383
  get hasHeader(): boolean;
372
384
  get widthClass(): string | null;
373
385
  setTitle(title: string): void;
386
+ ngOnDestroy(): void;
374
387
  attachContent(component: Type<any>, injector: Injector, inputs?: Record<string, unknown>): ComponentRef<any>;
388
+ initializeFrame(definition: SurfaceDrawerNavigationFrame, injector: Injector, inputs?: Record<string, unknown>): ComponentRef<any>;
389
+ pushFrame(definition: SurfaceDrawerNavigationFrame, injector: Injector, inputs?: Record<string, unknown>): ComponentRef<any>;
390
+ replaceFrame(definition: SurfaceDrawerNavigationFrame, injector: Injector, inputs?: Record<string, unknown>): Promise<ComponentRef<any> | null>;
391
+ backFrame(): Promise<boolean>;
392
+ setActiveCanLeave(guard: SurfaceDrawerNavigationGuard | null): void;
393
+ canCloseSession(): Promise<boolean>;
394
+ private createFrame;
395
+ private captureActiveFrameState;
396
+ private detachActiveView;
397
+ private removeActiveFrame;
398
+ private destroyAllFrames;
399
+ private canLeaveActiveFrame;
400
+ private applyFrameHeader;
401
+ private focusNavigationControl;
375
402
  private applyInputs;
376
403
  private resolveSupportedInputs;
377
404
  static ɵfac: i0.ɵɵFactoryDeclaration<SurfaceDrawerComponent, never>;