@praxisui/expansion 9.0.67 → 9.0.68-rc.1

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
@@ -10,6 +10,18 @@ The package owns the expansion shell and metadata shape. The host owns domain da
10
10
  - Avoid when: for a one-off static Material expansion panel that does not need Praxis metadata or authoring.
11
11
  - Pair with: @praxisui/core, @praxisui/settings-panel, and @praxisui/dynamic-fields for editable panel content.
12
12
 
13
+ ### Nested page headers and scrolling
14
+
15
+ The panel clips content to its rounded boundary without creating a separate
16
+ scroll container. Nested Dynamic Page Widget Shells can follow the enclosing
17
+ page's scrollport when their existing `stickyHeader` option is enabled. Material
18
+ continues to own expansion/collapse, lazy content and collapsed-content isolation.
19
+
20
+ `panels[].widgets` loads components directly; it does not create child shells.
21
+ For a nested page, register `providePraxisDynamicPageMetadata()` from
22
+ `@praxisui/core`, load `praxis-dynamic-page`, and declare shells on that page's
23
+ `WidgetPageDefinition`, not on the container's `WidgetDefinition`.
24
+
13
25
  ## Install
14
26
 
15
27
  ```bash
@@ -84,6 +96,7 @@ export class ExpansionHostComponent {
84
96
  - `config: ExpansionMetadata | null`: canonical accordion and panel configuration.
85
97
  - `expansionId: string`: required stable id used for local configuration persistence.
86
98
  - `componentInstanceId?: string`: disambiguates repeated instances on the same route.
99
+ - `configPersistenceStrategy: 'storage-first' | 'input-first'`: defaults to `storage-first`; with `input-first` and a supplied `config`, the parent composition owns configuration and Expansion does not read or write component storage.
87
100
  - `context: Record<string, unknown>`: context passed to dynamic child widgets.
88
101
  - `strictValidation: boolean`: keeps runtime validation strict by default.
89
102
  - `enableCustomization: boolean`: opens runtime authoring affordances.
@@ -127,6 +140,22 @@ Panel `content` and `widgets` are rendered lazily with `matExpansionPanelContent
127
140
 
128
141
  When `expansionId` is provided, configuration is stored under a key derived from route, component type, `expansionId` and optional `componentInstanceId`. `enableCustomization` opens Settings Panel editing and the governed semantic assistant flow.
129
142
 
143
+ For input-owned nested compositions, set `configPersistenceStrategy: 'input-first'`
144
+ on the Expansion instance and supply `config`. This lifecycle policy is independent
145
+ of customization: `enableCustomization:false` hides authoring controls, but does
146
+ not disable storage in the default `storage-first` mode. A child Dynamic Page's
147
+ `autoPersist:false` does not govern its parent container.
148
+
149
+ Input-first prevents stored configuration from replacing incoming child context,
150
+ including a storage request already in flight when ownership changes. Adapter and
151
+ component-editor application also respect this policy. The widget config editor
152
+ preserves the host's input envelope on apply/save; the composition owner remains
153
+ responsible for persisting the authored document, never the runtime employee or
154
+ record context. This is a host lifecycle input, not a panel-design setting.
155
+ Without a supplied config, input-first falls back to the standalone storage path.
156
+ Existing storage-first hosts keep their default behavior; no stored data is deleted
157
+ or migrated automatically.
158
+
130
159
  `PRAXIS_EXPANSION_AUTHORING_MANIFEST` describes supported AI/tooling operations for panel add/remove/order, titles, descriptions, icons, disabled state, default expanded state, multi-expand behavior and panel content. Free JSON patches are not the public authoring contract.
131
160
 
132
161
  ## Table Detail Resource Resolver
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-09-07T00:54:55.301Z",
3
+ "generatedAt": "2026-09-09T11:40:45.048Z",
4
4
  "packageName": "@praxisui/expansion",
5
- "packageVersion": "9.0.67",
5
+ "packageVersion": "9.0.68-rc.1",
6
6
  "sourceRegistry": "praxis-component-registry-ingestion",
7
7
  "sourceRegistryVersion": "1.0.0",
8
8
  "componentCount": 1,
@@ -32,6 +32,14 @@
32
32
  "description": "Identificador opcional para múltiplas instâncias na mesma rota",
33
33
  "label": "ID da instância"
34
34
  },
35
+ {
36
+ "name": "configPersistenceStrategy",
37
+ "type": "\"storage-first\" | \"input-first\"",
38
+ "required": false,
39
+ "description": "Em input-first com config fornecido, a composição governa a instância sem carregar ou gravar configuração própria.",
40
+ "label": "Estratégia de persistência",
41
+ "default": "storage-first"
42
+ },
35
43
  {
36
44
  "name": "enableCustomization",
37
45
  "type": "boolean",
@@ -794,6 +802,11 @@
794
802
  "type": "string",
795
803
  "description": "Optional instance discriminator for persistence scope."
796
804
  },
805
+ {
806
+ "name": "configPersistenceStrategy",
807
+ "type": "\"storage-first\" | \"input-first\"",
808
+ "description": "Host-owned lifecycle policy. Input-first with supplied config skips component storage; authoring returns the widget input document to its composition owner."
809
+ },
797
810
  {
798
811
  "name": "context",
799
812
  "type": "Record<string, any>",
@@ -1699,9 +1712,9 @@
1699
1712
  {
1700
1713
  "chunkIndex": 0,
1701
1714
  "chunkKind": "summary",
1702
- "content": "Component ID: praxis-expansion\nSelector: praxis-expansion\nFriendly Name: Praxis Expansion Panel\nDescription: Acordeão/Painéis de expansão configuráveis por metadata, com aparência e tokens M3.\nLib/Package: @praxisui/expansion\nTags: widget, expansion, accordion, configurable\nInputs:\n - config (ExpansionMetadata)\n - expansionId (string)\n - componentInstanceId (string)\n - enableCustomization (boolean)\n - context (Record<string, any>)\n - strictValidation (boolean)\n - defaultOptions (MatExpansionPanelDefaultOptions)\nOutputs:\n - opened ({ panelId?: string; panelIndex: number })\n - closed ({ panelId?: string; panelIndex: number })\n - expandedChange ({ panelId?: string; panelIndex: number; expanded: boolean })\n - afterExpand ({ panelId?: string; panelIndex: number })\n - afterCollapse ({ panelId?: string; panelIndex: number })\n - destroyed ({ panelId?: string; panelIndex: number })\n - widgetEvent (WidgetEventEnvelope)\n",
1715
+ "content": "Component ID: praxis-expansion\nSelector: praxis-expansion\nFriendly Name: Praxis Expansion Panel\nDescription: Acordeão/Painéis de expansão configuráveis por metadata, com aparência e tokens M3.\nLib/Package: @praxisui/expansion\nTags: widget, expansion, accordion, configurable\nInputs:\n - config (ExpansionMetadata)\n - expansionId (string)\n - componentInstanceId (string)\n - configPersistenceStrategy (\"storage-first\" | \"input-first\")\n - enableCustomization (boolean)\n - context (Record<string, any>)\n - strictValidation (boolean)\n - defaultOptions (MatExpansionPanelDefaultOptions)\nOutputs:\n - opened ({ panelId?: string; panelIndex: number })\n - closed ({ panelId?: string; panelIndex: number })\n - expandedChange ({ panelId?: string; panelIndex: number; expanded: boolean })\n - afterExpand ({ panelId?: string; panelIndex: number })\n - afterCollapse ({ panelId?: string; panelIndex: number })\n - destroyed ({ panelId?: string; panelIndex: number })\n - widgetEvent (WidgetEventEnvelope)\n",
1703
1716
  "sourcePointer": "projects/praxis-expansion/src/lib/praxis-expansion.metadata.ts",
1704
- "contentHash": "30ccb793a31c22baf57710e1c70b92fc952e40dcea38869a7f32ce91d1600ece",
1717
+ "contentHash": "11cbeea26795063d61451d06de8a4b040e38611191e5c8e5bad4065ce1e27095",
1705
1718
  "sourceKind": "component_definition",
1706
1719
  "sourceId": "praxis-expansion",
1707
1720
  "corpusVersion": "1.0.0"
@@ -1829,9 +1842,9 @@
1829
1842
  {
1830
1843
  "chunkIndex": 13,
1831
1844
  "chunkKind": "authoring_manifest",
1832
- "content": "{\"schemaVersion\":\"1.0.0\",\"manifestVersion\":\"1.0.0\",\"componentId\":\"praxis-expansion\",\"ownerPackage\":\"@praxisui/expansion\",\"configSchemaId\":\"ExpansionMetadata\",\"chunkSection\":\"runtimeInputs\",\"runtimeInputs\":[{\"name\":\"config\",\"type\":\"ExpansionMetadata\",\"description\":\"Canonical accordion and panel configuration.\"},{\"name\":\"expansionId\",\"type\":\"string\",\"description\":\"Stable id used to derive expansion config persistence scope.\"},{\"name\":\"componentInstanceId\",\"type\":\"string\",\"description\":\"Optional instance discriminator for persistence scope.\"},{\"name\":\"context\",\"type\":\"Record<string, any>\",\"description\":\"Context passed to nested widgets.\"},{\"name\":\"strictValidation\",\"type\":\"boolean\",\"description\":\"Controls nested widget validation strictness.\"},{\"name\":\"defaultOptions\",\"type\":\"MatExpansionPanelDefaultOptions\",\"description\":\"Instance-level Material expansion defaults.\"},{\"name\":\"enableCustomization\",\"type\":\"boolean\",\"description\":\"Enables Settings Panel authoring surfaces.\"}]}",
1845
+ "content": "{\"schemaVersion\":\"1.0.0\",\"manifestVersion\":\"1.0.0\",\"componentId\":\"praxis-expansion\",\"ownerPackage\":\"@praxisui/expansion\",\"configSchemaId\":\"ExpansionMetadata\",\"chunkSection\":\"runtimeInputs\",\"runtimeInputs\":[{\"name\":\"config\",\"type\":\"ExpansionMetadata\",\"description\":\"Canonical accordion and panel configuration.\"},{\"name\":\"expansionId\",\"type\":\"string\",\"description\":\"Stable id used to derive expansion config persistence scope.\"},{\"name\":\"componentInstanceId\",\"type\":\"string\",\"description\":\"Optional instance discriminator for persistence scope.\"},{\"name\":\"configPersistenceStrategy\",\"type\":\"\\\"storage-first\\\" | \\\"input-first\\\"\",\"description\":\"Host-owned lifecycle policy. Input-first with supplied config skips component storage; authoring returns the widget input document to its composition owner.\"},{\"name\":\"context\",\"type\":\"Record<string, any>\",\"description\":\"Context passed to nested widgets.\"},{\"name\":\"strictValidation\",\"type\":\"boolean\",\"description\":\"Controls nested widget validation strictness.\"},{\"name\":\"defaultOptions\",\"type\":\"MatExpansionPanelDefaultOptions\",\"description\":\"Instance-level Material expansion defaults.\"},{\"name\":\"enableCustomization\",\"type\":\"boolean\",\"description\":\"Enables Settings Panel authoring surfaces.\"}]}",
1833
1846
  "sourcePointer": "projects/praxis-expansion/src/lib/ai/praxis-expansion-authoring-manifest.ts",
1834
- "contentHash": "087beeaa793a947e5c4de9b19068ef3bd5ba74a5d8503a08339c73f6e1c81bfd",
1847
+ "contentHash": "14027b9ef389e9737fd06096fc21ef833098f73d63a2e1afdecccc7ae7315b75",
1835
1848
  "sourceKind": "component_definition",
1836
1849
  "sourceId": "praxis-expansion",
1837
1850
  "corpusVersion": "1.0.0"
@@ -1,7 +1,7 @@
1
1
  import * as i1 from '@angular/common';
2
2
  import { CommonModule } from '@angular/common';
3
3
  import * as i0 from '@angular/core';
4
- import { EventEmitter, inject, ChangeDetectorRef, DestroyRef, effect, ViewChildren, ViewChild, Output, Input, ChangeDetectionStrategy, Component, Inject, ENVIRONMENT_INITIALIZER } from '@angular/core';
4
+ import { input, EventEmitter, inject, ChangeDetectorRef, DestroyRef, effect, ViewChildren, ViewChild, Output, Input, ChangeDetectionStrategy, Component, Inject, ENVIRONMENT_INITIALIZER } from '@angular/core';
5
5
  import { ActivatedRoute } from '@angular/router';
6
6
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
7
7
  import * as i2 from '@angular/material/expansion';
@@ -508,6 +508,8 @@ class PraxisExpansion {
508
508
  config;
509
509
  expansionId;
510
510
  componentInstanceId;
511
+ /** Input-owned compositions must not restore or persist materialized child inputs. */
512
+ configPersistenceStrategy = input('storage-first', ...(ngDevMode ? [{ debugName: "configPersistenceStrategy" }] : /* istanbul ignore next */ []));
511
513
  context = {};
512
514
  strictValidation = true;
513
515
  enableCustomization = false;
@@ -543,6 +545,8 @@ class PraxisExpansion {
543
545
  }
544
546
  }, ...(ngDevMode ? [{ debugName: "aiAssistantSessionEffect" }] : /* istanbul ignore next */ []));
545
547
  warnedMissingId = false;
548
+ storageInitialized = false;
549
+ storageLoad;
546
550
  panelForms = new Map();
547
551
  aiAdapter = new ExpansionAiAdapter(this);
548
552
  aiAssistantOpen = false;
@@ -566,24 +570,39 @@ class PraxisExpansion {
566
570
  hasMultiple = () => (this.config?.panels?.length || 0) > 1;
567
571
  single = () => (this.config?.panels && this.config.panels[0]) || null;
568
572
  ngOnInit() {
573
+ this.storageInitialized = true;
574
+ this.loadStoredConfig();
575
+ }
576
+ loadStoredConfig() {
577
+ this.storageLoad?.unsubscribe();
578
+ if (this.usesInputFirstConfig())
579
+ return;
569
580
  const key = this.storageKey();
570
581
  if (!key)
571
582
  return;
572
- this.storage.loadConfig(key).pipe(take(1)).subscribe((stored) => {
573
- if (!stored)
574
- return;
575
- this.config = stored;
576
- this.buildPanelForms();
577
- this.cdr.markForCheck();
583
+ this.storageLoad = this.storage.loadConfig(key).pipe(take(1)).subscribe({
584
+ next: (stored) => {
585
+ if (!stored || this.usesInputFirstConfig() || key !== this.storageKey())
586
+ return;
587
+ this.config = stored;
588
+ this.buildPanelForms();
589
+ this.cdr.markForCheck();
590
+ },
591
+ error: () => { },
578
592
  });
579
593
  }
580
594
  ngOnChanges(changes) {
595
+ if (this.storageInitialized && (changes['expansionId'] || changes['componentInstanceId']
596
+ || changes['configPersistenceStrategy'])) {
597
+ this.loadStoredConfig();
598
+ }
581
599
  if ('config' in changes) {
582
600
  this.buildPanelForms();
583
601
  this.persistConfig(this.config);
584
602
  }
585
603
  }
586
604
  ngOnDestroy() {
605
+ this.storageLoad?.unsubscribe();
587
606
  this.assistantSessions.removeContextSession(this.buildAiAssistantContextSnapshot().identity);
588
607
  this.aiAssistantStateSubscription?.unsubscribe();
589
608
  }
@@ -820,10 +839,7 @@ class PraxisExpansion {
820
839
  applyConfigFromAdapter(next) {
821
840
  this.config = next;
822
841
  this.buildPanelForms();
823
- const key = this.storageKey();
824
- if (key) {
825
- this.storage.saveConfig(key, this.config).pipe(take(1)).subscribe({ error: () => { } });
826
- }
842
+ this.persistConfig(this.config);
827
843
  this.cdr.markForCheck();
828
844
  }
829
845
  openAiAssistant() {
@@ -1147,10 +1163,7 @@ class PraxisExpansion {
1147
1163
  if (nextCfg) {
1148
1164
  this.config = { ...nextCfg };
1149
1165
  this.buildPanelForms();
1150
- const storageKey = this.storageKey();
1151
- if (storageKey) {
1152
- this.storage.saveConfig(storageKey, this.config).pipe(take(1)).subscribe({ error: () => { } });
1153
- }
1166
+ this.persistConfig(this.config);
1154
1167
  this.cdr.markForCheck();
1155
1168
  }
1156
1169
  };
@@ -1162,13 +1175,16 @@ class PraxisExpansion {
1162
1175
  return id ? `expansion:${id}` : null;
1163
1176
  }
1164
1177
  persistConfig(config) {
1165
- if (!config)
1178
+ if (!config || this.usesInputFirstConfig())
1166
1179
  return;
1167
1180
  const key = this.storageKey();
1168
1181
  if (!key)
1169
1182
  return;
1170
1183
  this.storage.saveConfig(key, config).pipe(take(1)).subscribe({ error: () => { } });
1171
1184
  }
1185
+ usesInputFirstConfig() {
1186
+ return this.configPersistenceStrategy() === 'input-first' && !!this.config;
1187
+ }
1172
1188
  emitAction(p, index, action) {
1173
1189
  this.widgetEvent.emit({
1174
1190
  sourceComponentId: 'praxis-expansion',
@@ -1218,7 +1234,7 @@ class PraxisExpansion {
1218
1234
  console.warn('[PraxisExpansion] expansionId is required for config persistence.');
1219
1235
  }
1220
1236
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisExpansion, deps: [], target: i0.ɵɵFactoryTarget.Component });
1221
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisExpansion, isStandalone: true, selector: "praxis-expansion", inputs: { config: "config", expansionId: "expansionId", componentInstanceId: "componentInstanceId", context: "context", strictValidation: "strictValidation", enableCustomization: "enableCustomization", defaultOptions: "defaultOptions" }, outputs: { opened: "opened", closed: "closed", expandedChange: "expandedChange", afterExpand: "afterExpand", afterCollapse: "afterCollapse", destroyed: "destroyed", widgetEvent: "widgetEvent" }, viewQueries: [{ propertyName: "accordionRef", first: true, predicate: ["accordion"], descendants: true }, { propertyName: "panels", predicate: ["panel"], descendants: true, read: MatExpansionPanel }], usesOnChanges: true, ngImport: i0, template: `
1237
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisExpansion, isStandalone: true, selector: "praxis-expansion", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: false, isRequired: false, transformFunction: null }, expansionId: { classPropertyName: "expansionId", publicName: "expansionId", isSignal: false, isRequired: true, transformFunction: null }, componentInstanceId: { classPropertyName: "componentInstanceId", publicName: "componentInstanceId", isSignal: false, isRequired: false, transformFunction: null }, configPersistenceStrategy: { classPropertyName: "configPersistenceStrategy", publicName: "configPersistenceStrategy", isSignal: true, isRequired: false, transformFunction: null }, context: { classPropertyName: "context", publicName: "context", isSignal: false, isRequired: false, transformFunction: null }, strictValidation: { classPropertyName: "strictValidation", publicName: "strictValidation", isSignal: false, isRequired: false, transformFunction: null }, enableCustomization: { classPropertyName: "enableCustomization", publicName: "enableCustomization", isSignal: false, isRequired: false, transformFunction: null }, defaultOptions: { classPropertyName: "defaultOptions", publicName: "defaultOptions", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { opened: "opened", closed: "closed", expandedChange: "expandedChange", afterExpand: "afterExpand", afterCollapse: "afterCollapse", destroyed: "destroyed", widgetEvent: "widgetEvent" }, viewQueries: [{ propertyName: "accordionRef", first: true, predicate: ["accordion"], descendants: true }, { propertyName: "panels", predicate: ["panel"], descendants: true, read: MatExpansionPanel }], usesOnChanges: true, ngImport: i0, template: `
1222
1238
  <div
1223
1239
  class="praxis-expansion-root"
1224
1240
  [class.density-compact]="config?.appearance?.density === 'compact'"
@@ -1437,7 +1453,7 @@ class PraxisExpansion {
1437
1453
  <mat-icon fontIcon="edit"></mat-icon>
1438
1454
  </button>
1439
1455
  }
1440
- `, isInline: true, styles: [":host{display:block;position:relative;color:var(--md-sys-color-on-surface)}.praxis-expansion-root{display:block;--p-exp-surface: var(--md-sys-color-surface);--p-exp-surface-container: var(--md-sys-color-surface-container);--p-exp-border: var(--md-sys-color-outline-variant);--p-exp-text: var(--md-sys-color-on-surface);--p-exp-text-muted: var(--md-sys-color-on-surface-variant);--p-exp-focus: var(--md-sys-color-primary);--p-exp-radius: 12px}.mat-expansion-panel{background:var(--p-exp-surface);border:1px solid var(--p-exp-border);border-radius:var(--p-exp-radius);overflow:hidden}.mat-expansion-panel:not(:last-child){margin-bottom:var(--p-exp-panel-gap, 12px)}.mat-expansion-panel-header{background:var(--p-exp-surface-container);color:var(--p-exp-text)}.mat-expansion-panel-header:focus-visible{outline:2px solid var(--p-exp-focus);outline-offset:-2px}.mat-expansion-panel-header-title{font-weight:600}.mat-expansion-panel-header-description{color:var(--p-exp-text-muted)}.mat-expansion-panel-body{padding:12px 16px 16px}.mat-action-row{border-top:1px solid var(--p-exp-border)}.density-compact .mat-expansion-panel-body{padding:8px 12px 12px}.density-compact .mat-expansion-panel-header{min-height:40px;padding:0 12px}.density-comfortable .mat-expansion-panel-body{padding:12px 16px 16px}.density-comfortable .mat-expansion-panel-header{min-height:48px;padding:0 16px}.density-spacious .mat-expansion-panel-body{padding:16px 20px 20px}.density-spacious .mat-expansion-panel-header{min-height:56px;padding:0 20px}.praxis-expansion-empty{display:flex;gap:8px;align-items:center;padding:8px 12px;color:var(--p-exp-text-muted)}.edit-fab{position:absolute;right:12px;bottom:12px;z-index:2}.expansion-ai-assistant{position:absolute;right:12px;bottom:72px;z-index:3}.expansion-ai-assistant-trigger{box-shadow:var(--md-sys-elevation-level2, 0 4px 12px rgba(0, 0, 0, .18))}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "directive", type: i2.MatAccordion, selector: "mat-accordion", inputs: ["hideToggle", "displayMode", "togglePosition"], exportAs: ["matAccordion"] }, { kind: "component", type: i2.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "directive", type: i2.MatExpansionPanelActionRow, selector: "mat-action-row" }, { kind: "component", type: i2.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i2.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i2.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "directive", type: i2.MatExpansionPanelContent, selector: "ng-template[matExpansionPanelContent]" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i4.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatMiniFabButton, selector: "button[mat-mini-fab], a[mat-mini-fab], button[matMiniFab], a[matMiniFab]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: DynamicFieldLoaderDirective, selector: "[dynamicFieldLoader]", inputs: ["fields", "formGroup", "enableExternalControlBinding", "itemTemplate", "debugTrace", "debugTraceLabel", "readonlyMode", "disabledMode", "presentationMode", "visible", "canvasMode"], outputs: ["componentsCreated", "fieldCreated", "fieldDestroyed", "renderError", "canvasMouseEnter", "canvasMouseLeave", "canvasClick", "canvasFocus"] }, { kind: "directive", type: DynamicWidgetLoaderDirective, selector: "[dynamicWidgetLoader]", inputs: ["dynamicWidgetLoader", "ownerWidgetKey", "context", "strictValidation", "autoWireOutputs"], outputs: ["widgetEvent", "widgetDiagnostic"], exportAs: ["dynamicWidgetLoader"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: PraxisAiAssistantShellComponent, selector: "praxis-ai-assistant-shell", inputs: ["labels", "mode", "state", "contextItems", "attachments", "messages", "quickReplies", "recommendedIntents", "prompt", "statusText", "errorText", "testIdPrefix", "panelTestId", "submitTestId", "applyTestId", "primaryAction", "secondaryActions", "governanceActions", "busy", "canSubmit", "canApply", "submitOnEnter", "showAttachAction", "enablePastedAttachments", "enableFileAttachments", "attachmentAccept", "attachmentMultiple", "voiceInputMode", "voiceLanguage", "draggable", "resizable", "minWidth", "minHeight", "margin", "layout"], outputs: ["promptChange", "submitPrompt", "apply", "retryTurn", "cancelTurn", "shellAction", "close", "attach", "attachmentsPasted", "attachmentsSelected", "removeAttachment", "messageAction", "editMessage", "resendMessage", "quickReply", "recommendedIntent", "layoutChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1456
+ `, isInline: true, styles: [":host{display:block;position:relative;color:var(--md-sys-color-on-surface)}.praxis-expansion-root{display:block;--p-exp-surface: var(--md-sys-color-surface);--p-exp-surface-container: var(--md-sys-color-surface-container);--p-exp-border: var(--md-sys-color-outline-variant);--p-exp-text: var(--md-sys-color-on-surface);--p-exp-text-muted: var(--md-sys-color-on-surface-variant);--p-exp-focus: var(--md-sys-color-primary);--p-exp-radius: 12px}.mat-expansion-panel{background:var(--p-exp-surface);border:1px solid var(--p-exp-border);border-radius:var(--p-exp-radius);overflow:clip}.mat-expansion-panel:not(:last-child){margin-bottom:var(--p-exp-panel-gap, 12px)}.mat-expansion-panel-header{background:var(--p-exp-surface-container);color:var(--p-exp-text)}.mat-expansion-panel-header:focus-visible{outline:2px solid var(--p-exp-focus);outline-offset:-2px}.mat-expansion-panel-header-title{font-weight:600}.mat-expansion-panel-header-description{color:var(--p-exp-text-muted)}.mat-expansion-panel-body{padding:12px 16px 16px}.mat-action-row{border-top:1px solid var(--p-exp-border)}.density-compact .mat-expansion-panel-body{padding:8px 12px 12px}.density-compact .mat-expansion-panel-header{min-height:40px;padding:0 12px}.density-comfortable .mat-expansion-panel-body{padding:12px 16px 16px}.density-comfortable .mat-expansion-panel-header{min-height:48px;padding:0 16px}.density-spacious .mat-expansion-panel-body{padding:16px 20px 20px}.density-spacious .mat-expansion-panel-header{min-height:56px;padding:0 20px}.praxis-expansion-empty{display:flex;gap:8px;align-items:center;padding:8px 12px;color:var(--p-exp-text-muted)}.edit-fab{position:absolute;right:12px;bottom:12px;z-index:2}.expansion-ai-assistant{position:absolute;right:12px;bottom:72px;z-index:3}.expansion-ai-assistant-trigger{box-shadow:var(--md-sys-elevation-level2, 0 4px 12px rgba(0, 0, 0, .18))}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "directive", type: i2.MatAccordion, selector: "mat-accordion", inputs: ["hideToggle", "displayMode", "togglePosition"], exportAs: ["matAccordion"] }, { kind: "component", type: i2.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "directive", type: i2.MatExpansionPanelActionRow, selector: "mat-action-row" }, { kind: "component", type: i2.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i2.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i2.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "directive", type: i2.MatExpansionPanelContent, selector: "ng-template[matExpansionPanelContent]" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i4.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatMiniFabButton, selector: "button[mat-mini-fab], a[mat-mini-fab], button[matMiniFab], a[matMiniFab]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: DynamicFieldLoaderDirective, selector: "[dynamicFieldLoader]", inputs: ["fields", "formGroup", "enableExternalControlBinding", "itemTemplate", "debugTrace", "debugTraceLabel", "readonlyMode", "disabledMode", "presentationMode", "visible", "canvasMode"], outputs: ["componentsCreated", "fieldCreated", "fieldDestroyed", "renderError", "canvasMouseEnter", "canvasMouseLeave", "canvasClick", "canvasFocus"] }, { kind: "directive", type: DynamicWidgetLoaderDirective, selector: "[dynamicWidgetLoader]", inputs: ["dynamicWidgetLoader", "ownerWidgetKey", "context", "strictValidation", "autoWireOutputs"], outputs: ["widgetEvent", "widgetDiagnostic"], exportAs: ["dynamicWidgetLoader"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: PraxisAiAssistantShellComponent, selector: "praxis-ai-assistant-shell", inputs: ["labels", "mode", "state", "contextItems", "attachments", "messages", "quickReplies", "recommendedIntents", "prompt", "statusText", "errorText", "testIdPrefix", "panelTestId", "submitTestId", "applyTestId", "primaryAction", "secondaryActions", "governanceActions", "busy", "canSubmit", "canApply", "submitOnEnter", "showAttachAction", "enablePastedAttachments", "enableFileAttachments", "attachmentAccept", "attachmentMultiple", "voiceInputMode", "voiceLanguage", "draggable", "resizable", "minWidth", "minHeight", "margin", "layout"], outputs: ["promptChange", "submitPrompt", "apply", "retryTurn", "cancelTurn", "shellAction", "close", "attach", "attachmentsPasted", "attachmentsSelected", "removeAttachment", "messageAction", "editMessage", "resendMessage", "quickReply", "recommendedIntent", "layoutChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1441
1457
  }
1442
1458
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisExpansion, decorators: [{
1443
1459
  type: Component,
@@ -1669,7 +1685,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
1669
1685
  <mat-icon fontIcon="edit"></mat-icon>
1670
1686
  </button>
1671
1687
  }
1672
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;position:relative;color:var(--md-sys-color-on-surface)}.praxis-expansion-root{display:block;--p-exp-surface: var(--md-sys-color-surface);--p-exp-surface-container: var(--md-sys-color-surface-container);--p-exp-border: var(--md-sys-color-outline-variant);--p-exp-text: var(--md-sys-color-on-surface);--p-exp-text-muted: var(--md-sys-color-on-surface-variant);--p-exp-focus: var(--md-sys-color-primary);--p-exp-radius: 12px}.mat-expansion-panel{background:var(--p-exp-surface);border:1px solid var(--p-exp-border);border-radius:var(--p-exp-radius);overflow:hidden}.mat-expansion-panel:not(:last-child){margin-bottom:var(--p-exp-panel-gap, 12px)}.mat-expansion-panel-header{background:var(--p-exp-surface-container);color:var(--p-exp-text)}.mat-expansion-panel-header:focus-visible{outline:2px solid var(--p-exp-focus);outline-offset:-2px}.mat-expansion-panel-header-title{font-weight:600}.mat-expansion-panel-header-description{color:var(--p-exp-text-muted)}.mat-expansion-panel-body{padding:12px 16px 16px}.mat-action-row{border-top:1px solid var(--p-exp-border)}.density-compact .mat-expansion-panel-body{padding:8px 12px 12px}.density-compact .mat-expansion-panel-header{min-height:40px;padding:0 12px}.density-comfortable .mat-expansion-panel-body{padding:12px 16px 16px}.density-comfortable .mat-expansion-panel-header{min-height:48px;padding:0 16px}.density-spacious .mat-expansion-panel-body{padding:16px 20px 20px}.density-spacious .mat-expansion-panel-header{min-height:56px;padding:0 20px}.praxis-expansion-empty{display:flex;gap:8px;align-items:center;padding:8px 12px;color:var(--p-exp-text-muted)}.edit-fab{position:absolute;right:12px;bottom:12px;z-index:2}.expansion-ai-assistant{position:absolute;right:12px;bottom:72px;z-index:3}.expansion-ai-assistant-trigger{box-shadow:var(--md-sys-elevation-level2, 0 4px 12px rgba(0, 0, 0, .18))}\n"] }]
1688
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;position:relative;color:var(--md-sys-color-on-surface)}.praxis-expansion-root{display:block;--p-exp-surface: var(--md-sys-color-surface);--p-exp-surface-container: var(--md-sys-color-surface-container);--p-exp-border: var(--md-sys-color-outline-variant);--p-exp-text: var(--md-sys-color-on-surface);--p-exp-text-muted: var(--md-sys-color-on-surface-variant);--p-exp-focus: var(--md-sys-color-primary);--p-exp-radius: 12px}.mat-expansion-panel{background:var(--p-exp-surface);border:1px solid var(--p-exp-border);border-radius:var(--p-exp-radius);overflow:clip}.mat-expansion-panel:not(:last-child){margin-bottom:var(--p-exp-panel-gap, 12px)}.mat-expansion-panel-header{background:var(--p-exp-surface-container);color:var(--p-exp-text)}.mat-expansion-panel-header:focus-visible{outline:2px solid var(--p-exp-focus);outline-offset:-2px}.mat-expansion-panel-header-title{font-weight:600}.mat-expansion-panel-header-description{color:var(--p-exp-text-muted)}.mat-expansion-panel-body{padding:12px 16px 16px}.mat-action-row{border-top:1px solid var(--p-exp-border)}.density-compact .mat-expansion-panel-body{padding:8px 12px 12px}.density-compact .mat-expansion-panel-header{min-height:40px;padding:0 12px}.density-comfortable .mat-expansion-panel-body{padding:12px 16px 16px}.density-comfortable .mat-expansion-panel-header{min-height:48px;padding:0 16px}.density-spacious .mat-expansion-panel-body{padding:16px 20px 20px}.density-spacious .mat-expansion-panel-header{min-height:56px;padding:0 20px}.praxis-expansion-empty{display:flex;gap:8px;align-items:center;padding:8px 12px;color:var(--p-exp-text-muted)}.edit-fab{position:absolute;right:12px;bottom:12px;z-index:2}.expansion-ai-assistant{position:absolute;right:12px;bottom:72px;z-index:3}.expansion-ai-assistant-trigger{box-shadow:var(--md-sys-elevation-level2, 0 4px 12px rgba(0, 0, 0, .18))}\n"] }]
1673
1689
  }], propDecorators: { config: [{
1674
1690
  type: Input
1675
1691
  }], expansionId: [{
@@ -1677,7 +1693,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
1677
1693
  args: [{ required: true }]
1678
1694
  }], componentInstanceId: [{
1679
1695
  type: Input
1680
- }], context: [{
1696
+ }], configPersistenceStrategy: [{ type: i0.Input, args: [{ isSignal: true, alias: "configPersistenceStrategy", required: false }] }], context: [{
1681
1697
  type: Input
1682
1698
  }], strictValidation: [{
1683
1699
  type: Input
@@ -2516,6 +2532,7 @@ const PRAXIS_EXPANSION_COMPONENT_METADATA = {
2516
2532
  { name: 'config', type: 'ExpansionMetadata', label: 'Configuração', description: 'Configuração JSON (acordeão, aparência e painéis)' },
2517
2533
  { name: 'expansionId', type: 'string', label: 'ID', description: 'Identificador para persistência (obrigatório)' },
2518
2534
  { name: 'componentInstanceId', type: 'string', label: 'ID da instância', description: 'Identificador opcional para múltiplas instâncias na mesma rota' },
2535
+ { name: 'configPersistenceStrategy', type: '"storage-first" | "input-first"', default: 'storage-first', label: 'Estratégia de persistência', description: 'Em input-first com config fornecido, a composição governa a instância sem carregar ou gravar configuração própria.' },
2519
2536
  { name: 'enableCustomization', type: 'boolean', default: false, label: 'Modo de customização' },
2520
2537
  { name: 'context', type: 'Record<string, any>', label: 'Contexto' },
2521
2538
  { name: 'strictValidation', type: 'boolean', default: true },
@@ -2648,6 +2665,7 @@ const PRAXIS_EXPANSION_AUTHORING_MANIFEST = {
2648
2665
  { name: 'config', type: 'ExpansionMetadata', description: 'Canonical accordion and panel configuration.' },
2649
2666
  { name: 'expansionId', type: 'string', description: 'Stable id used to derive expansion config persistence scope.' },
2650
2667
  { name: 'componentInstanceId', type: 'string', description: 'Optional instance discriminator for persistence scope.' },
2668
+ { name: 'configPersistenceStrategy', type: '"storage-first" | "input-first"', description: 'Host-owned lifecycle policy. Input-first with supplied config skips component storage; authoring returns the widget input document to its composition owner.' },
2651
2669
  { name: 'context', type: 'Record<string, any>', description: 'Context passed to nested widgets.' },
2652
2670
  { name: 'strictValidation', type: 'boolean', description: 'Controls nested widget validation strictness.' },
2653
2671
  { name: 'defaultOptions', type: 'MatExpansionPanelDefaultOptions', description: 'Instance-level Material expansion defaults.' },
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@praxisui/expansion",
3
- "version": "9.0.67",
3
+ "version": "9.0.68-rc.1",
4
4
  "description": "Expansion panel (accordion) components for Praxis UI with metadata configuration and editor integration.",
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^21.0.0",
7
7
  "@angular/core": "^21.0.0",
8
8
  "@angular/material": "^21.0.0",
9
9
  "@angular/cdk": "^21.0.0",
10
- "@praxisui/core": "^9.0.67",
11
- "@praxisui/dynamic-fields": "^9.0.67",
12
- "@praxisui/settings-panel": "^9.0.67",
10
+ "@praxisui/core": "^9.0.68-rc.1",
11
+ "@praxisui/dynamic-fields": "^9.0.68-rc.1",
12
+ "@praxisui/settings-panel": "^9.0.68-rc.1",
13
13
  "@angular/forms": "^21.0.0",
14
14
  "@angular/router": "^21.0.0",
15
- "@praxisui/ai": "^9.0.67",
15
+ "@praxisui/ai": "^9.0.68-rc.1",
16
16
  "rxjs": "~7.8.0"
17
17
  },
18
18
  "dependencies": {
@@ -112,6 +112,8 @@ declare class PraxisExpansion implements OnChanges, OnDestroy, OnInit {
112
112
  config?: ExpansionMetadata | null;
113
113
  expansionId: string;
114
114
  componentInstanceId?: string;
115
+ /** Input-owned compositions must not restore or persist materialized child inputs. */
116
+ readonly configPersistenceStrategy: i0.InputSignal<"storage-first" | "input-first">;
115
117
  context: Record<string, any>;
116
118
  strictValidation: boolean;
117
119
  enableCustomization: boolean;
@@ -154,6 +156,8 @@ declare class PraxisExpansion implements OnChanges, OnDestroy, OnInit {
154
156
  private readonly aiTurnOrchestrator;
155
157
  private readonly aiAssistantSessionEffect;
156
158
  private warnedMissingId;
159
+ private storageInitialized;
160
+ private storageLoad?;
157
161
  private readonly panelForms;
158
162
  aiAdapter: ExpansionAiAdapter;
159
163
  aiAssistantOpen: boolean;
@@ -169,6 +173,7 @@ declare class PraxisExpansion implements OnChanges, OnDestroy, OnInit {
169
173
  hasMultiple: () => boolean;
170
174
  single: () => PanelMetadata | null;
171
175
  ngOnInit(): void;
176
+ private loadStoredConfig;
172
177
  ngOnChanges(changes: SimpleChanges): void;
173
178
  ngOnDestroy(): void;
174
179
  styleCss(): string | null;
@@ -220,13 +225,14 @@ declare class PraxisExpansion implements OnChanges, OnDestroy, OnInit {
220
225
  openEditor(): void;
221
226
  private storageKey;
222
227
  private persistConfig;
228
+ private usesInputFirstConfig;
223
229
  emitAction(p: PanelMetadata, index: number, action?: string | undefined): void;
224
230
  private panelKey;
225
231
  private buildPanelForms;
226
232
  private componentKeyId;
227
233
  private warnMissingId;
228
234
  static ɵfac: i0.ɵɵFactoryDeclaration<PraxisExpansion, never>;
229
- static ɵcmp: i0.ɵɵComponentDeclaration<PraxisExpansion, "praxis-expansion", never, { "config": { "alias": "config"; "required": false; }; "expansionId": { "alias": "expansionId"; "required": true; }; "componentInstanceId": { "alias": "componentInstanceId"; "required": false; }; "context": { "alias": "context"; "required": false; }; "strictValidation": { "alias": "strictValidation"; "required": false; }; "enableCustomization": { "alias": "enableCustomization"; "required": false; }; "defaultOptions": { "alias": "defaultOptions"; "required": false; }; }, { "opened": "opened"; "closed": "closed"; "expandedChange": "expandedChange"; "afterExpand": "afterExpand"; "afterCollapse": "afterCollapse"; "destroyed": "destroyed"; "widgetEvent": "widgetEvent"; }, never, never, true, never>;
235
+ static ɵcmp: i0.ɵɵComponentDeclaration<PraxisExpansion, "praxis-expansion", never, { "config": { "alias": "config"; "required": false; }; "expansionId": { "alias": "expansionId"; "required": true; }; "componentInstanceId": { "alias": "componentInstanceId"; "required": false; }; "configPersistenceStrategy": { "alias": "configPersistenceStrategy"; "required": false; "isSignal": true; }; "context": { "alias": "context"; "required": false; }; "strictValidation": { "alias": "strictValidation"; "required": false; }; "enableCustomization": { "alias": "enableCustomization"; "required": false; }; "defaultOptions": { "alias": "defaultOptions"; "required": false; }; }, { "opened": "opened"; "closed": "closed"; "expandedChange": "expandedChange"; "afterExpand": "afterExpand"; "afterCollapse": "afterCollapse"; "destroyed": "destroyed"; "widgetEvent": "widgetEvent"; }, never, never, true, never>;
230
236
  }
231
237
  declare class PraxisExpansionConfigEditor implements SettingsValueProvider, OnChanges {
232
238
  config: ExpansionMetadata | null | undefined;