@praxisui/expansion 9.0.66 → 9.0.68-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -0
- package/ai/component-registry.json +19 -6
- package/fesm2022/praxisui-expansion.mjs +38 -20
- package/package.json +5 -5
- package/types/praxisui-expansion.d.ts +7 -1
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-
|
|
3
|
+
"generatedAt": "2026-09-09T10:03:24.330Z",
|
|
4
4
|
"packageName": "@praxisui/expansion",
|
|
5
|
-
"packageVersion": "9.0.
|
|
5
|
+
"packageVersion": "9.0.68-rc.0",
|
|
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": "
|
|
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": "
|
|
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(
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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.
|
|
3
|
+
"version": "9.0.68-rc.0",
|
|
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.
|
|
11
|
-
"@praxisui/dynamic-fields": "^9.0.
|
|
12
|
-
"@praxisui/settings-panel": "^9.0.
|
|
10
|
+
"@praxisui/core": "^9.0.68-rc.0",
|
|
11
|
+
"@praxisui/dynamic-fields": "^9.0.68-rc.0",
|
|
12
|
+
"@praxisui/settings-panel": "^9.0.68-rc.0",
|
|
13
13
|
"@angular/forms": "^21.0.0",
|
|
14
14
|
"@angular/router": "^21.0.0",
|
|
15
|
-
"@praxisui/ai": "^9.0.
|
|
15
|
+
"@praxisui/ai": "^9.0.68-rc.0",
|
|
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;
|