@praxisui/expansion 8.0.0-beta.2 → 8.0.0-beta.20

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
@@ -130,9 +130,37 @@ const expansionConfig: ExpansionMetadata = {
130
130
  };
131
131
  ```
132
132
 
133
+ ## Nested Ports e Conexoes
134
+
135
+ Para conectar widgets dentro de paineis a estado ou widgets externos, use `composition.links` com endpoint `component-port + nestedPath`.
136
+
137
+ Exemplo de output de chart dentro do painel `analytics`:
138
+
139
+ ```json
140
+ {
141
+ "kind": "component-port",
142
+ "ref": {
143
+ "widget": "expansion-widget",
144
+ "nestedPath": [
145
+ { "kind": "panel", "id": "analytics", "index": 0 },
146
+ { "kind": "widget", "key": "payroll-chart", "componentType": "praxis-chart" }
147
+ ],
148
+ "port": "pointClick",
149
+ "direction": "output"
150
+ }
151
+ }
152
+ ```
153
+
154
+ Regras:
155
+
156
+ - `ref.widget` e a instancia top-level de `praxis-expansion`;
157
+ - o segmento terminal deve ser `kind: "widget"` com `key` estavel;
158
+ - `widgetEvent` permanece como bridge avancada/legado, nao como contrato principal;
159
+ - inputs nested devem atualizar o config declarativo do widget filho, inclusive quando o painel ainda nao montou por lazy loading.
160
+
133
161
  ## Action Row por painel
134
162
 
135
- Adicione botões de ação no rodapé do painel com `actionButtons`. O evento é reemitido via `widgetEvent` com `output: 'action'`.
163
+ Adicione botões de ação no rodapé do painel com `actionButtons`. O evento é reemitido via `widgetEvent` com `output: 'action'`; trate esse fluxo como bridge avancada/legado para acoes de painel, nao como modelo principal de nested component ports.
136
164
 
137
165
  ```ts
138
166
  const expansionConfig: ExpansionMetadata = {
@@ -205,6 +233,33 @@ O componente expõe métodos para controlar a expansão por índice (numérico)
205
233
 
206
234
  Cada evento inclui `{ panelId?: string; panelIndex: number }` (ou `expanded: boolean` no caso de `expandedChange`).
207
235
 
236
+ ## Agentic Authoring
237
+
238
+ O contrato executavel de authoring fica em `PRAXIS_EXPANSION_AUTHORING_MANIFEST`.
239
+ Ele modela edicoes em `ExpansionMetadata` por operacoes atomicas sobre paineis,
240
+ cabecalhos, conteudo lazy, estado expandido/desabilitado e comportamento do
241
+ accordion.
242
+
243
+ Cobertura principal:
244
+
245
+ - `panel.add`, `panel.remove`, `panel.order.set`
246
+ - `panel.title.set`, `panel.description.set`, `panel.icon.set`
247
+ - `panel.disabled.set`
248
+ - `behavior.multiExpand.set`
249
+ - `behavior.defaultExpanded.set`
250
+ - `panel.content.set`
251
+
252
+ As operacoes preservam `panels[].id` como identidade canonica. Remover um painel
253
+ com `content`, `widgets` ou `actionButtons` exige confirmacao explicita. Quando
254
+ `accordion.multi` estiver desabilitado, somente um painel pode ficar expandido por
255
+ padrao; o authoring deve colapsar os demais ou falhar validacao.
256
+
257
+ Cada operacao declara `target.resolver`, `preconditions`, `validators`,
258
+ `affectedPaths`, `effects` e `submissionImpact` tipado. Edicoes de conteudo de
259
+ painel podem afetar dados schema-backed porque `panels[].content` hospeda
260
+ `FieldMetadata[]`; widgets filhos continuam governados pelos contratos dos
261
+ respectivos componentes.
262
+
208
263
  ## Tokens de Estilo por instância
209
264
 
210
265
  Os tokens opcionais em `appearance.tokens` permitem customizações rápidas por instância; por exemplo:
@@ -62,6 +62,7 @@ const CAPS = [
62
62
  { path: 'panels[].id', category: 'panels', valueKind: 'string', description: 'Panel id.' },
63
63
  { path: 'panels[].title', category: 'panels', valueKind: 'string', description: 'Panel title.' },
64
64
  { path: 'panels[].description', category: 'panels', valueKind: 'string', description: 'Panel description.' },
65
+ { path: 'panels[].icon', category: 'panels', valueKind: 'string', description: 'Panel header icon rendered through PraxisIconDirective.' },
65
66
  { path: 'panels[].disabled', category: 'panels', valueKind: 'boolean', description: 'Disable panel.' },
66
67
  { path: 'panels[].expanded', category: 'panels', valueKind: 'boolean', description: 'Expanded state.' },
67
68
  { path: 'panels[].hideToggle', category: 'panels', valueKind: 'boolean', description: 'Hide toggle icon for panel.' },
@@ -603,8 +604,9 @@ class PraxisExpansion {
603
604
  >
604
605
  <mat-expansion-panel-header
605
606
  [collapsedHeight]="p.collapsedHeight || (defaultOptions?.collapsedHeight || injectedDefaults?.collapsedHeight) || ''"
606
- [expandedHeight]="p.expandedHeight || (defaultOptions?.expandedHeight || injectedDefaults?.expandedHeight) || ''"
607
- >
607
+ [expandedHeight]="p.expandedHeight || (defaultOptions?.expandedHeight || injectedDefaults?.expandedHeight) || ''"
608
+ >
609
+ <mat-icon *ngIf="p.icon" [praxisIcon]="p.icon"></mat-icon>
608
610
  <mat-panel-title>{{ p.title }}</mat-panel-title>
609
611
  <mat-panel-description *ngIf="p.description">{{ p.description }}</mat-panel-description>
610
612
  </mat-expansion-panel-header>
@@ -660,8 +662,9 @@ class PraxisExpansion {
660
662
  >
661
663
  <mat-expansion-panel-header
662
664
  [collapsedHeight]="p.collapsedHeight || (defaultOptions?.collapsedHeight || injectedDefaults?.collapsedHeight) || ''"
663
- [expandedHeight]="p.expandedHeight || (defaultOptions?.expandedHeight || injectedDefaults?.expandedHeight) || ''"
664
- >
665
+ [expandedHeight]="p.expandedHeight || (defaultOptions?.expandedHeight || injectedDefaults?.expandedHeight) || ''"
666
+ >
667
+ <mat-icon *ngIf="p.icon" [praxisIcon]="p.icon"></mat-icon>
665
668
  <mat-panel-title>{{ p.title }}</mat-panel-title>
666
669
  <mat-panel-description *ngIf="p.description">{{ p.description }}</mat-panel-description>
667
670
  </mat-expansion-panel-header>
@@ -775,8 +778,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
775
778
  >
776
779
  <mat-expansion-panel-header
777
780
  [collapsedHeight]="p.collapsedHeight || (defaultOptions?.collapsedHeight || injectedDefaults?.collapsedHeight) || ''"
778
- [expandedHeight]="p.expandedHeight || (defaultOptions?.expandedHeight || injectedDefaults?.expandedHeight) || ''"
779
- >
781
+ [expandedHeight]="p.expandedHeight || (defaultOptions?.expandedHeight || injectedDefaults?.expandedHeight) || ''"
782
+ >
783
+ <mat-icon *ngIf="p.icon" [praxisIcon]="p.icon"></mat-icon>
780
784
  <mat-panel-title>{{ p.title }}</mat-panel-title>
781
785
  <mat-panel-description *ngIf="p.description">{{ p.description }}</mat-panel-description>
782
786
  </mat-expansion-panel-header>
@@ -832,8 +836,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
832
836
  >
833
837
  <mat-expansion-panel-header
834
838
  [collapsedHeight]="p.collapsedHeight || (defaultOptions?.collapsedHeight || injectedDefaults?.collapsedHeight) || ''"
835
- [expandedHeight]="p.expandedHeight || (defaultOptions?.expandedHeight || injectedDefaults?.expandedHeight) || ''"
836
- >
839
+ [expandedHeight]="p.expandedHeight || (defaultOptions?.expandedHeight || injectedDefaults?.expandedHeight) || ''"
840
+ >
841
+ <mat-icon *ngIf="p.icon" [praxisIcon]="p.icon"></mat-icon>
837
842
  <mat-panel-title>{{ p.title }}</mat-panel-title>
838
843
  <mat-panel-description *ngIf="p.description">{{ p.description }}</mat-panel-description>
839
844
  </mat-expansion-panel-header>
@@ -1321,6 +1326,9 @@ class PraxisExpansionConfigEditor {
1321
1326
  <mat-form-field appearance="outline"><mat-label>Descrição</mat-label>
1322
1327
  <input matInput [value]="p.description || ''" (input)="setPanel(i, 'description', $any($event.target).value)" />
1323
1328
  </mat-form-field>
1329
+ <mat-form-field appearance="outline"><mat-label>Ícone</mat-label>
1330
+ <input matInput [value]="p.icon || ''" (input)="setPanel(i, 'icon', $any($event.target).value)" placeholder="info" />
1331
+ </mat-form-field>
1324
1332
  <mat-form-field appearance="outline"><mat-label>Altura recolhida</mat-label>
1325
1333
  <input matInput placeholder="48px" [value]="p.collapsedHeight || ''" (input)="setPanel(i, 'collapsedHeight', $any($event.target).value)" />
1326
1334
  </mat-form-field>
@@ -1571,6 +1579,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
1571
1579
  <mat-form-field appearance="outline"><mat-label>Descrição</mat-label>
1572
1580
  <input matInput [value]="p.description || ''" (input)="setPanel(i, 'description', $any($event.target).value)" />
1573
1581
  </mat-form-field>
1582
+ <mat-form-field appearance="outline"><mat-label>Ícone</mat-label>
1583
+ <input matInput [value]="p.icon || ''" (input)="setPanel(i, 'icon', $any($event.target).value)" placeholder="info" />
1584
+ </mat-form-field>
1574
1585
  <mat-form-field appearance="outline"><mat-label>Altura recolhida</mat-label>
1575
1586
  <input matInput placeholder="48px" [value]="p.collapsedHeight || ''" (input)="setPanel(i, 'collapsedHeight', $any($event.target).value)" />
1576
1587
  </mat-form-field>
@@ -1595,6 +1606,35 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
1595
1606
  type: Input
1596
1607
  }] } });
1597
1608
 
1609
+ const PRAXIS_EXPANSION_PORTS = [
1610
+ {
1611
+ id: 'config',
1612
+ label: 'Configuracao',
1613
+ direction: 'input',
1614
+ semanticKind: 'config-fragment',
1615
+ schema: {
1616
+ id: 'ExpansionMetadata',
1617
+ kind: 'ts-type',
1618
+ ref: 'ExpansionMetadata',
1619
+ },
1620
+ description: 'Fragmento canonico de configuracao dos paineis e dos widgets internos.',
1621
+ exposure: { public: true, group: 'config' },
1622
+ },
1623
+ {
1624
+ id: 'widgetEvent',
1625
+ label: 'Evento interno de widget',
1626
+ direction: 'output',
1627
+ semanticKind: 'event',
1628
+ schema: {
1629
+ id: 'WidgetEventEnvelope',
1630
+ kind: 'ts-type',
1631
+ ref: 'WidgetEventEnvelope',
1632
+ },
1633
+ cardinality: 'stream',
1634
+ description: 'Bridge composta para transporte de eventos internos. As portas canonicas dos filhos sao resolvidas por component-port + nestedPath.',
1635
+ exposure: { public: true, advanced: true, group: 'composite' },
1636
+ },
1637
+ ];
1598
1638
  const PRAXIS_EXPANSION_COMPONENT_METADATA = {
1599
1639
  id: 'praxis-expansion',
1600
1640
  selector: 'praxis-expansion',
@@ -1676,6 +1716,7 @@ const PRAXIS_EXPANSION_COMPONENT_METADATA = {
1676
1716
  ],
1677
1717
  tags: ['widget', 'expansion', 'accordion', 'configurable'],
1678
1718
  lib: '@praxisui/expansion',
1719
+ ports: PRAXIS_EXPANSION_PORTS,
1679
1720
  };
1680
1721
  function providePraxisExpansionMetadata() {
1681
1722
  return {
@@ -1691,6 +1732,315 @@ function providePraxisExpansionDefaults(opts) {
1691
1732
  return { provide: MAT_EXPANSION_PANEL_DEFAULT_OPTIONS, useValue: opts };
1692
1733
  }
1693
1734
 
1735
+ const panelItemSchema = {
1736
+ type: 'object',
1737
+ required: ['id', 'title'],
1738
+ properties: {
1739
+ id: { type: 'string' },
1740
+ title: { type: 'string' },
1741
+ description: { type: 'string' },
1742
+ icon: { type: 'string' },
1743
+ disabled: { type: 'boolean' },
1744
+ expanded: { type: 'boolean' },
1745
+ hideToggle: { type: 'boolean' },
1746
+ collapsedHeight: { type: 'string' },
1747
+ expandedHeight: { type: 'string' },
1748
+ content: { type: 'array', items: { type: 'object' } },
1749
+ widgets: { type: 'array', items: { type: 'object' } },
1750
+ actionButtons: { type: 'array', items: { type: 'object' } },
1751
+ },
1752
+ };
1753
+ const panelPatchSchema = {
1754
+ type: 'object',
1755
+ minProperties: 1,
1756
+ properties: {
1757
+ id: { type: 'string' },
1758
+ title: { type: 'string' },
1759
+ description: { type: 'string' },
1760
+ icon: { type: 'string' },
1761
+ disabled: { type: 'boolean' },
1762
+ expanded: { type: 'boolean' },
1763
+ hideToggle: { type: 'boolean' },
1764
+ collapsedHeight: { type: 'string' },
1765
+ expandedHeight: { type: 'string' },
1766
+ content: { type: 'array', items: { type: 'object' } },
1767
+ widgets: { type: 'array', items: { type: 'object' } },
1768
+ actionButtons: { type: 'array', items: { type: 'object' } },
1769
+ },
1770
+ };
1771
+ const PRAXIS_EXPANSION_AUTHORING_MANIFEST = {
1772
+ schemaVersion: '1.0.0',
1773
+ componentId: 'praxis-expansion',
1774
+ ownerPackage: '@praxisui/expansion',
1775
+ configSchemaId: 'ExpansionMetadata',
1776
+ manifestVersion: '1.0.0',
1777
+ runtimeInputs: [
1778
+ { name: 'config', type: 'ExpansionMetadata', description: 'Canonical accordion and panel configuration.' },
1779
+ { name: 'expansionId', type: 'string', description: 'Stable id used to derive expansion config persistence scope.' },
1780
+ { name: 'componentInstanceId', type: 'string', description: 'Optional instance discriminator for persistence scope.' },
1781
+ { name: 'context', type: 'Record<string, any>', description: 'Context passed to nested widgets.' },
1782
+ { name: 'strictValidation', type: 'boolean', description: 'Controls nested widget validation strictness.' },
1783
+ { name: 'defaultOptions', type: 'MatExpansionPanelDefaultOptions', description: 'Instance-level Material expansion defaults.' },
1784
+ { name: 'enableCustomization', type: 'boolean', description: 'Enables Settings Panel authoring surfaces.' },
1785
+ ],
1786
+ editableTargets: [
1787
+ { kind: 'panel', resolver: 'panel-by-id-or-title', description: 'A panel in config.panels[].' },
1788
+ { kind: 'panelHeader', resolver: 'panel-by-id-or-title', description: 'Header title, description, icon and heights for a panel.' },
1789
+ { kind: 'panelContent', resolver: 'panel-content-by-id', description: 'Lazy panel content hosted through fields, widgets or action buttons.' },
1790
+ { kind: 'expandedState', resolver: 'panel-by-id-or-title', description: 'Panel expanded state and default expanded selection.' },
1791
+ { kind: 'disabledState', resolver: 'panel-by-id-or-title', description: 'Panel disabled state.' },
1792
+ { kind: 'layout', resolver: 'expansion-layout-config', description: 'Accordion display mode, toggle position, density and visual layout.' },
1793
+ { kind: 'behavior', resolver: 'expansion-behavior-config', description: 'Accordion multi-expand and hide-toggle behavior.' },
1794
+ ],
1795
+ operations: [
1796
+ {
1797
+ operationId: 'panel.add',
1798
+ title: 'Add panel',
1799
+ scope: 'global',
1800
+ targetKind: 'panel',
1801
+ target: { kind: 'panel', resolver: 'panels-array', ambiguityPolicy: 'fail', required: false },
1802
+ inputSchema: panelItemSchema,
1803
+ effects: [{ kind: 'append-unique', path: 'panels[]', key: 'id' }],
1804
+ destructive: false,
1805
+ requiresConfirmation: false,
1806
+ validators: ['panel-id-unique', 'panel-order-deterministic', 'panel-content-valid'],
1807
+ affectedPaths: ['panels[]'],
1808
+ submissionImpact: 'config-only',
1809
+ preconditions: ['config-initialized'],
1810
+ },
1811
+ {
1812
+ operationId: 'panel.remove',
1813
+ title: 'Remove panel',
1814
+ scope: 'layout',
1815
+ targetKind: 'panel',
1816
+ target: { kind: 'panel', resolver: 'panel-by-id-or-title', ambiguityPolicy: 'fail', required: true },
1817
+ inputSchema: {
1818
+ type: 'object',
1819
+ properties: {
1820
+ replacementExpandedPanelId: { type: 'string' },
1821
+ },
1822
+ },
1823
+ effects: [{
1824
+ kind: 'compile-domain-patch',
1825
+ handler: 'expansion-panel-remove',
1826
+ handlerContract: {
1827
+ reads: ['accordion.multi', 'panels[]', 'panels[].id', 'panels[].expanded'],
1828
+ writes: ['panels[]', 'panels[].expanded'],
1829
+ identityKeys: ['panels[].id'],
1830
+ inputSchema: {
1831
+ type: 'object',
1832
+ properties: {
1833
+ replacementExpandedPanelId: { type: 'string' },
1834
+ },
1835
+ },
1836
+ failureModes: ['panel-not-found', 'replacement-panel-not-found', 'content-removal-not-confirmed', 'single-expand-conflict'],
1837
+ description: 'Removes the target panel by stable id and applies replacement expanded state when the removed panel was expanded.',
1838
+ },
1839
+ }],
1840
+ destructive: true,
1841
+ requiresConfirmation: true,
1842
+ validators: ['panel-exists', 'default-expanded-removal-safe', 'panel-content-removal-confirmed'],
1843
+ affectedPaths: ['panels[]', 'panels[].expanded'],
1844
+ submissionImpact: 'config-only',
1845
+ preconditions: ['config-initialized', 'target-panel-exists', 'confirmation-collected'],
1846
+ },
1847
+ {
1848
+ operationId: 'panel.title.set',
1849
+ title: 'Set panel title',
1850
+ scope: 'layout',
1851
+ targetKind: 'panelHeader',
1852
+ target: { kind: 'panelHeader', resolver: 'panel-by-id-or-title', ambiguityPolicy: 'fail', required: true },
1853
+ inputSchema: { type: 'object', required: ['title'], properties: { title: { type: 'string' } } },
1854
+ effects: [{ kind: 'merge-by-key', path: 'panels[]', key: 'id' }],
1855
+ destructive: false,
1856
+ requiresConfirmation: false,
1857
+ validators: ['panel-exists', 'panel-title-valid'],
1858
+ affectedPaths: ['panels[].title'],
1859
+ submissionImpact: 'config-only',
1860
+ preconditions: ['config-initialized', 'target-panel-exists'],
1861
+ },
1862
+ {
1863
+ operationId: 'panel.description.set',
1864
+ title: 'Set panel description',
1865
+ scope: 'layout',
1866
+ targetKind: 'panelHeader',
1867
+ target: { kind: 'panelHeader', resolver: 'panel-by-id-or-title', ambiguityPolicy: 'fail', required: true },
1868
+ inputSchema: { type: 'object', required: ['description'], properties: { description: { type: 'string' } } },
1869
+ effects: [{ kind: 'merge-by-key', path: 'panels[]', key: 'id' }],
1870
+ destructive: false,
1871
+ requiresConfirmation: false,
1872
+ validators: ['panel-exists', 'panel-description-valid'],
1873
+ affectedPaths: ['panels[].description'],
1874
+ submissionImpact: 'config-only',
1875
+ preconditions: ['config-initialized', 'target-panel-exists'],
1876
+ },
1877
+ {
1878
+ operationId: 'panel.icon.set',
1879
+ title: 'Set panel icon',
1880
+ scope: 'layout',
1881
+ targetKind: 'panelHeader',
1882
+ target: { kind: 'panelHeader', resolver: 'panel-by-id-or-title', ambiguityPolicy: 'fail', required: true },
1883
+ inputSchema: { type: 'object', required: ['icon'], properties: { icon: { type: 'string' } } },
1884
+ effects: [{ kind: 'merge-by-key', path: 'panels[]', key: 'id' }],
1885
+ destructive: false,
1886
+ requiresConfirmation: false,
1887
+ validators: ['panel-exists', 'panel-icon-valid', 'editor-runtime-round-trip'],
1888
+ affectedPaths: ['panels[].icon'],
1889
+ submissionImpact: 'config-only',
1890
+ preconditions: ['config-initialized', 'target-panel-exists'],
1891
+ },
1892
+ {
1893
+ operationId: 'panel.order.set',
1894
+ title: 'Reorder panels',
1895
+ scope: 'layout',
1896
+ targetKind: 'panel',
1897
+ target: { kind: 'panel', resolver: 'panel-by-id-or-title', ambiguityPolicy: 'fail', required: true },
1898
+ inputSchema: { type: 'object', required: ['beforePanelId'], properties: { beforePanelId: { type: 'string' } } },
1899
+ effects: [{ kind: 'reorder-by-key', path: 'panels[]', key: 'id' }],
1900
+ destructive: false,
1901
+ requiresConfirmation: false,
1902
+ validators: ['panel-exists', 'panel-order-deterministic'],
1903
+ affectedPaths: ['panels[]'],
1904
+ submissionImpact: 'config-only',
1905
+ preconditions: ['config-initialized', 'target-panel-exists'],
1906
+ },
1907
+ {
1908
+ operationId: 'panel.disabled.set',
1909
+ title: 'Set disabled state',
1910
+ scope: 'interaction',
1911
+ targetKind: 'disabledState',
1912
+ target: { kind: 'disabledState', resolver: 'panel-by-id-or-title', ambiguityPolicy: 'fail', required: true },
1913
+ inputSchema: { type: 'object', required: ['disabled'], properties: { disabled: { type: 'boolean' } } },
1914
+ effects: [{ kind: 'merge-by-key', path: 'panels[]', key: 'id' }],
1915
+ destructive: false,
1916
+ requiresConfirmation: false,
1917
+ validators: ['panel-exists', 'disabled-expanded-compatible'],
1918
+ affectedPaths: ['panels[].disabled', 'panels[].expanded'],
1919
+ submissionImpact: 'config-only',
1920
+ preconditions: ['config-initialized', 'target-panel-exists'],
1921
+ },
1922
+ {
1923
+ operationId: 'behavior.multiExpand.set',
1924
+ title: 'Set multi-expand behavior',
1925
+ scope: 'interaction',
1926
+ targetKind: 'behavior',
1927
+ target: { kind: 'behavior', resolver: 'expansion-behavior-config', ambiguityPolicy: 'fail', required: true },
1928
+ inputSchema: { type: 'object', required: ['multi'], properties: { multi: { type: 'boolean' } } },
1929
+ effects: [{
1930
+ kind: 'compile-domain-patch',
1931
+ handler: 'expansion-multi-expand-set',
1932
+ handlerContract: {
1933
+ reads: ['accordion.multi', 'panels[]', 'panels[].id', 'panels[].expanded'],
1934
+ writes: ['accordion.multi', 'panels[].expanded'],
1935
+ identityKeys: ['panels[].id'],
1936
+ inputSchema: { type: 'object', required: ['multi'], properties: { multi: { type: 'boolean' } } },
1937
+ failureModes: ['multiple-expanded-panels-conflict', 'panel-id-missing'],
1938
+ description: 'Sets accordion.multi and collapses competing expanded panels when switching to single-expand behavior.',
1939
+ },
1940
+ }],
1941
+ destructive: false,
1942
+ requiresConfirmation: false,
1943
+ validators: ['multi-expand-default-compatible', 'accordion-values-valid', 'editor-runtime-round-trip'],
1944
+ affectedPaths: ['accordion.multi', 'panels[].expanded'],
1945
+ submissionImpact: 'config-only',
1946
+ preconditions: ['config-initialized'],
1947
+ },
1948
+ {
1949
+ operationId: 'behavior.defaultExpanded.set',
1950
+ title: 'Set default expanded panel',
1951
+ scope: 'interaction',
1952
+ targetKind: 'expandedState',
1953
+ target: { kind: 'expandedState', resolver: 'panel-by-id-or-title', ambiguityPolicy: 'fail', required: true },
1954
+ inputSchema: {
1955
+ type: 'object',
1956
+ required: ['expanded'],
1957
+ properties: {
1958
+ expanded: { type: 'boolean' },
1959
+ collapseOthers: { type: 'boolean' },
1960
+ },
1961
+ },
1962
+ effects: [{
1963
+ kind: 'compile-domain-patch',
1964
+ handler: 'expansion-default-expanded-upsert',
1965
+ handlerContract: {
1966
+ reads: ['accordion.multi', 'panels[]', 'panels[].id', 'panels[].expanded', 'panels[].disabled'],
1967
+ writes: ['panels[].expanded'],
1968
+ identityKeys: ['panels[].id'],
1969
+ inputSchema: {
1970
+ type: 'object',
1971
+ required: ['panelId', 'expanded'],
1972
+ properties: {
1973
+ panelId: { type: 'string' },
1974
+ expanded: { type: 'boolean' },
1975
+ collapseOthers: { type: 'boolean' },
1976
+ },
1977
+ },
1978
+ failureModes: ['panel-not-found', 'panel-disabled', 'single-expand-conflict'],
1979
+ description: 'Sets a panel expanded state and collapses competing panels when accordion.multi is false.',
1980
+ },
1981
+ }],
1982
+ validators: ['panel-exists', 'default-expanded-panel-exists', 'multi-expand-default-compatible', 'disabled-expanded-compatible'],
1983
+ destructive: false,
1984
+ requiresConfirmation: false,
1985
+ affectedPaths: ['panels[].expanded', 'accordion.multi'],
1986
+ submissionImpact: 'config-only',
1987
+ preconditions: ['config-initialized', 'target-panel-exists'],
1988
+ },
1989
+ {
1990
+ operationId: 'panel.content.set',
1991
+ title: 'Set panel content',
1992
+ scope: 'layout',
1993
+ targetKind: 'panelContent',
1994
+ target: { kind: 'panelContent', resolver: 'panel-content-by-id', ambiguityPolicy: 'fail', required: true },
1995
+ inputSchema: panelPatchSchema,
1996
+ effects: [{ kind: 'merge-by-key', path: 'panels[]', key: 'id' }],
1997
+ destructive: false,
1998
+ requiresConfirmation: false,
1999
+ validators: ['panel-exists', 'panel-content-valid', 'lazy-content-compatible', 'nested-widget-contract-delegated'],
2000
+ affectedPaths: ['panels[].content', 'panels[].widgets', 'panels[].actionButtons'],
2001
+ submissionImpact: 'affects-schema-backed-data',
2002
+ preconditions: ['config-initialized', 'target-panel-exists'],
2003
+ },
2004
+ ],
2005
+ validators: [
2006
+ { validatorId: 'panel-id-unique', level: 'error', code: 'PEXP001', description: 'Panel ids must be unique and stable within config.panels[].' },
2007
+ { validatorId: 'panel-exists', level: 'error', code: 'PEXP002', description: 'Target panel must exist before applying the operation.' },
2008
+ { validatorId: 'panel-order-deterministic', level: 'error', code: 'PEXP003', description: 'Panel ordering must use stable ids, not transient array index as identity.' },
2009
+ { validatorId: 'panel-title-valid', level: 'error', code: 'PEXP004', description: 'Panel title must be a non-empty text value after localization/domain projection.' },
2010
+ { validatorId: 'panel-description-valid', level: 'warning', code: 'PEXP005', description: 'Panel description should remain plain header-support text and not replace panel content.' },
2011
+ { validatorId: 'panel-icon-valid', level: 'warning', code: 'PEXP006', description: 'Panel icon metadata must remain compatible with PraxisIconDirective and editor round-trip.' },
2012
+ { validatorId: 'panel-content-valid', level: 'error', code: 'PEXP007', description: 'Panel content must remain valid FieldMetadata[], WidgetDefinition[] or action button metadata.' },
2013
+ { validatorId: 'panel-content-removal-confirmed', level: 'error', code: 'PEXP008', description: 'Removing a panel with fields, widgets or action buttons is destructive and requires confirmation.' },
2014
+ { validatorId: 'default-expanded-panel-exists', level: 'error', code: 'PEXP009', description: 'Default expanded state must reference an existing panel id.' },
2015
+ { validatorId: 'default-expanded-removal-safe', level: 'error', code: 'PEXP010', description: 'Removing an expanded panel requires deterministic replacement state or explicit confirmation.' },
2016
+ { validatorId: 'multi-expand-default-compatible', level: 'error', code: 'PEXP011', description: 'When accordion.multi is false, at most one panel may be marked expanded by default.' },
2017
+ { validatorId: 'disabled-expanded-compatible', level: 'warning', code: 'PEXP012', description: 'A disabled panel should not be the only expanded/default focus target without explicit intent.' },
2018
+ { validatorId: 'accordion-values-valid', level: 'error', code: 'PEXP013', description: 'Accordion behavior values must match ExpansionMetadata and Angular Material expansion bindings.' },
2019
+ { validatorId: 'lazy-content-compatible', level: 'info', code: 'PEXP015', description: 'Panel content remains lazy through matExpansionPanelContent and should not require eager child runtime state.' },
2020
+ { validatorId: 'nested-widget-contract-delegated', level: 'info', code: 'PEXP016', description: 'Nested widget content remains governed by child component contracts and component-port nestedPath semantics.' },
2021
+ { validatorId: 'editor-runtime-round-trip', level: 'error', code: 'PEXP017', description: 'Settings Panel editor, runtime persistence and registry projection must preserve panel ids, order, icons and expanded state.' },
2022
+ ],
2023
+ roundTripRequirements: [
2024
+ 'Operations must preserve stable panel ids; array index may be used only as a resolver fallback, never as canonical identity.',
2025
+ 'Settings Panel editor, runtime persistence and registry projection must round-trip ExpansionMetadata without losing panel ids, order, icons or expanded state.',
2026
+ 'When accordion.multi is false, authoring must collapse competing panels or fail validation instead of producing multiple default-expanded panels.',
2027
+ 'Lazy panel content remains represented by panels[].content, panels[].widgets and panels[].actionButtons; authoring cannot require eager child component instances.',
2028
+ 'Nested widgets remain delegated through WidgetDefinition and component-port nestedPath semantics instead of being redefined by the expansion contract.',
2029
+ ],
2030
+ examples: [
2031
+ { id: 'add-summary-panel', request: 'Add a summary panel before the audit panel.', operationId: 'panel.add', params: { id: 'summary', title: 'Summary' }, isPositive: true },
2032
+ { id: 'rename-details-panel', request: 'Rename details to Account details.', operationId: 'panel.title.set', target: 'details', params: { title: 'Account details' }, isPositive: true },
2033
+ { id: 'describe-audit-panel', request: 'Set audit panel description to Recent changes.', operationId: 'panel.description.set', target: 'audit', params: { description: 'Recent changes' }, isPositive: true },
2034
+ { id: 'set-panel-icon', request: 'Use the info icon on the summary panel.', operationId: 'panel.icon.set', target: 'summary', params: { icon: 'info' }, isPositive: true },
2035
+ { id: 'set-default-expanded-panel', request: 'Open the summary panel by default.', operationId: 'behavior.defaultExpanded.set', target: 'summary', params: { expanded: true, collapseOthers: true }, isPositive: true },
2036
+ { id: 'enable-multi-expand', request: 'Allow multiple panels to stay open.', operationId: 'behavior.multiExpand.set', params: { multi: true }, isPositive: true },
2037
+ { id: 'disable-archive-panel', request: 'Disable the archive panel.', operationId: 'panel.disabled.set', target: 'archive', params: { disabled: true }, isPositive: true },
2038
+ { id: 'reject-missing-default-expanded', request: 'Open the missing panel by default.', operationId: 'behavior.defaultExpanded.set', target: 'missing', params: { expanded: true }, isPositive: false },
2039
+ { id: 'reject-duplicate-panel-id', request: 'Add another panel with id summary.', operationId: 'panel.add', params: { id: 'summary', title: 'Duplicate summary' }, isPositive: false },
2040
+ { id: 'confirm-remove-content-panel', request: 'Remove the details panel that contains widgets.', operationId: 'panel.remove', target: 'details', params: { replacementExpandedPanelId: 'summary' }, isPositive: true },
2041
+ ],
2042
+ };
2043
+
1694
2044
  /*
1695
2045
  * Public API Surface of praxis-expansion
1696
2046
  */
@@ -1699,4 +2049,4 @@ function providePraxisExpansionDefaults(opts) {
1699
2049
  * Generated bundle index. Do not edit.
1700
2050
  */
1701
2051
 
1702
- export { EXPANSION_AI_CAPABILITIES, PRAXIS_EXPANSION_COMPONENT_METADATA, PraxisExpansion, PraxisExpansionConfigEditor, providePraxisExpansionDefaults, providePraxisExpansionMetadata };
2052
+ export { EXPANSION_AI_CAPABILITIES, PRAXIS_EXPANSION_AUTHORING_MANIFEST, PRAXIS_EXPANSION_COMPONENT_METADATA, PraxisExpansion, PraxisExpansionConfigEditor, providePraxisExpansionDefaults, providePraxisExpansionMetadata };
package/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import * as i0 from '@angular/core';
2
2
  import { OnChanges, OnInit, EventEmitter, QueryList, SimpleChanges, Provider } from '@angular/core';
3
3
  import { FormGroup } from '@angular/forms';
4
4
  import { MatAccordionDisplayMode, MatAccordionTogglePosition, MatExpansionPanelDefaultOptions, MatAccordion, MatExpansionPanel } from '@angular/material/expansion';
5
- import { AiCapability, FieldMetadata, WidgetDefinition, WidgetEventEnvelope, ComponentDocMeta, AiCapabilityCategory, AiValueKind, AiCapabilityCatalog } from '@praxisui/core';
5
+ import { AiCapability, FieldMetadata, WidgetDefinition, WidgetEventEnvelope, ComponentDocMeta, AiCapabilityCategory, AiValueKind, AiCapabilityCatalog, ComponentAuthoringManifest } from '@praxisui/core';
6
6
  import { BehaviorSubject } from 'rxjs';
7
7
  import { BaseAiAdapter, PatchResult } from '@praxisui/ai';
8
8
  import { SettingsValueProvider } from '@praxisui/settings-panel';
@@ -89,6 +89,7 @@ interface PanelMetadata {
89
89
  id?: string;
90
90
  title: string;
91
91
  description?: string;
92
+ icon?: string;
92
93
  disabled?: boolean;
93
94
  expanded?: boolean;
94
95
  hideToggle?: boolean;
@@ -242,5 +243,7 @@ interface CapabilityCatalog extends AiCapabilityCatalog {
242
243
  }
243
244
  declare const EXPANSION_AI_CAPABILITIES: CapabilityCatalog;
244
245
 
245
- export { EXPANSION_AI_CAPABILITIES, PRAXIS_EXPANSION_COMPONENT_METADATA, PraxisExpansion, PraxisExpansionConfigEditor, providePraxisExpansionDefaults, providePraxisExpansionMetadata };
246
+ declare const PRAXIS_EXPANSION_AUTHORING_MANIFEST: ComponentAuthoringManifest;
247
+
248
+ export { EXPANSION_AI_CAPABILITIES, PRAXIS_EXPANSION_AUTHORING_MANIFEST, PRAXIS_EXPANSION_COMPONENT_METADATA, PraxisExpansion, PraxisExpansionConfigEditor, providePraxisExpansionDefaults, providePraxisExpansionMetadata };
246
249
  export type { Capability, CapabilityCatalog, CapabilityCategory, ExpansionAppearanceContainer, ExpansionAppearanceHeader, ExpansionAppearanceStates, ExpansionMetadata, PanelMetadata, ValueKind };
package/package.json CHANGED
@@ -1,15 +1,19 @@
1
1
  {
2
2
  "name": "@praxisui/expansion",
3
- "version": "8.0.0-beta.2",
3
+ "version": "8.0.0-beta.20",
4
4
  "description": "Expansion panel (accordion) components for Praxis UI with metadata configuration and editor integration.",
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^20.0.0",
7
7
  "@angular/core": "^20.0.0",
8
8
  "@angular/material": "^20.0.0",
9
9
  "@angular/cdk": "^20.0.0",
10
- "@praxisui/core": "^8.0.0-beta.2",
11
- "@praxisui/dynamic-fields": "^8.0.0-beta.2",
12
- "@praxisui/settings-panel": "^8.0.0-beta.2"
10
+ "@praxisui/core": "^8.0.0-beta.20",
11
+ "@praxisui/dynamic-fields": "^8.0.0-beta.20",
12
+ "@praxisui/settings-panel": "^8.0.0-beta.20",
13
+ "@angular/forms": "^20.0.0",
14
+ "@angular/router": "^20.0.0",
15
+ "@praxisui/ai": "^8.0.0-beta.20",
16
+ "rxjs": "~7.8.0"
13
17
  },
14
18
  "dependencies": {
15
19
  "tslib": "^2.3.0"