@praxisui/rich-content 9.0.0-beta.7 → 9.0.0-beta.9

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
@@ -123,7 +123,9 @@ The validator rejects unsupported document versions, unknown node types, unsafe
123
123
  }
124
124
  ```
125
125
 
126
- The editor supports structured block authoring, nested card/timeline/action collections, preset selection and advanced JSON for less common deep structures. Labels and validation messages use the rich-content i18n dictionary; override them with `providePraxisRichContentI18n()`.
126
+ The editor separates authoring into guided editing, preview and advanced JSON tabs. Guided editing keeps the document structure visible while the selected block exposes content, presentation and advanced data/rule sections. Preview renders the materialized document without mixing authoring fields, and advanced JSON remains available for diagnostics, migration and less common deep structures.
127
+
128
+ The editor supports structured block authoring, nested card/timeline/action collections, preset selection, validation badges and advanced JSON for less common deep structures. Labels and validation messages use the rich-content i18n dictionary; override them with `providePraxisRichContentI18n()`.
127
129
 
128
130
  `PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST` describes governed AI/tooling operations for document replacement, block add/remove/order, text updates, link nodes, media blocks, timeline nodes/items, presets, metadata and sanitization policy. Rich content authoring is structured JSON, not arbitrary HTML or markdown patches.
129
131
 
@@ -13,11 +13,14 @@ const PRAXIS_RICH_CONTENT_EN_US = {
13
13
  'praxis.richContent.editor.subtitle': 'Edit the canonical document consumed by the renderer. The saved payload preserves the remaining widget inputs.',
14
14
  'praxis.richContent.editor.valid': 'Valid document',
15
15
  'praxis.richContent.editor.invalid': 'Invalid JSON',
16
+ 'praxis.richContent.editor.invalidBadge': 'Invalid',
16
17
  'praxis.richContent.editor.layout': 'Layout',
17
18
  'praxis.richContent.editor.layout.block': 'Block',
18
19
  'praxis.richContent.editor.layout.inline': 'Inline',
19
20
  'praxis.richContent.editor.rootClass': 'Root class',
20
21
  'praxis.richContent.editor.rootClassPlaceholder': 'Example: employee-expansion-rich',
22
+ 'praxis.richContent.editor.workbenchTabs': 'Editor sections',
23
+ 'praxis.richContent.editor.guidedEditing': 'Guided editing',
21
24
  'praxis.richContent.editor.document': 'Advanced JSON',
22
25
  'praxis.richContent.editor.documentJson': 'Canonical document JSON',
23
26
  'praxis.richContent.editor.documentHelp': 'Edit the canonical RichContentDocument. The inspector validates the shape and renders a preview before apply or save.',
@@ -28,6 +31,13 @@ const PRAXIS_RICH_CONTENT_EN_US = {
28
31
  'praxis.richContent.editor.structureHelp': 'Select a block to edit its properties.',
29
32
  'praxis.richContent.editor.properties': 'Properties',
30
33
  'praxis.richContent.editor.propertiesHelp': 'Edit the selected block without leaving the document structure.',
34
+ 'praxis.richContent.editor.intentNavigation': 'Property sections',
35
+ 'praxis.richContent.editor.intent.content': 'Content',
36
+ 'praxis.richContent.editor.intent.contentHelp': 'Primary block fields',
37
+ 'praxis.richContent.editor.intent.presentation': 'Presentation',
38
+ 'praxis.richContent.editor.intent.presentationHelp': 'Visual affordances',
39
+ 'praxis.richContent.editor.intent.dataRules': 'Data and rules',
40
+ 'praxis.richContent.editor.intent.dataRulesHelp': 'Metadata and gates',
31
41
  'praxis.richContent.editor.hasIssues': 'Needs attention',
32
42
  'praxis.richContent.editor.block': 'Block',
33
43
  'praxis.richContent.editor.blockType': 'Block type',
@@ -50,6 +60,8 @@ const PRAXIS_RICH_CONTENT_EN_US = {
50
60
  'praxis.richContent.editor.cardContentHelp': 'Card content is preserved in JSON. Use advanced JSON for nested card nodes.',
51
61
  'praxis.richContent.editor.nodeId': 'Stable id',
52
62
  'praxis.richContent.editor.nodeIdPlaceholder': 'Example: hero-title',
63
+ 'praxis.richContent.editor.advancedDataRules': 'Advanced data and rules',
64
+ 'praxis.richContent.editor.advancedDataRulesHelp': 'Metadata, capability gates, visibility and safe style.',
53
65
  'praxis.richContent.editor.visibilityRule': 'Visibility rule',
54
66
  'praxis.richContent.editor.safeStyle': 'Safe style',
55
67
  'praxis.richContent.editor.nodeType.text': 'Text',
@@ -420,6 +432,8 @@ const PRAXIS_RICH_CONTENT_EN_US = {
420
432
  'praxis.richContent.editor.nodeTypes': 'Types',
421
433
  'praxis.richContent.editor.none': 'None',
422
434
  'praxis.richContent.editor.preview': 'Preview',
435
+ 'praxis.richContent.editor.previewHelp': 'Inspect the materialized document without mixing preview with authoring fields.',
436
+ 'praxis.richContent.editor.previewUnavailable': 'Preview is unavailable until the document is valid.',
423
437
  'praxis.richContent.editor.restore': 'Restore',
424
438
  'praxis.richContent.editor.emptyDocument': 'Empty document',
425
439
  'praxis.richContent.editor.validation.documentShape': 'The document must be a valid RichContentDocument.',
@@ -462,11 +476,14 @@ const PRAXIS_RICH_CONTENT_PT_BR = {
462
476
  'praxis.richContent.editor.subtitle': 'Edite o documento canônico consumido pelo renderer. O payload salvo preserva os demais inputs do widget.',
463
477
  'praxis.richContent.editor.valid': 'Documento válido',
464
478
  'praxis.richContent.editor.invalid': 'JSON inválido',
479
+ 'praxis.richContent.editor.invalidBadge': 'Inválido',
465
480
  'praxis.richContent.editor.layout': 'Layout',
466
481
  'praxis.richContent.editor.layout.block': 'Bloco',
467
482
  'praxis.richContent.editor.layout.inline': 'Inline',
468
483
  'praxis.richContent.editor.rootClass': 'Classe raiz',
469
484
  'praxis.richContent.editor.rootClassPlaceholder': 'Exemplo: employee-expansion-rich',
485
+ 'praxis.richContent.editor.workbenchTabs': 'Seções do editor',
486
+ 'praxis.richContent.editor.guidedEditing': 'Edição guiada',
470
487
  'praxis.richContent.editor.document': 'JSON avançado',
471
488
  'praxis.richContent.editor.documentJson': 'JSON do documento canônico',
472
489
  'praxis.richContent.editor.documentHelp': 'Edite o RichContentDocument canônico. O inspetor valida o formato e renderiza uma prévia antes de aplicar ou salvar.',
@@ -477,6 +494,13 @@ const PRAXIS_RICH_CONTENT_PT_BR = {
477
494
  'praxis.richContent.editor.structureHelp': 'Selecione um bloco para editar suas propriedades.',
478
495
  'praxis.richContent.editor.properties': 'Propriedades',
479
496
  'praxis.richContent.editor.propertiesHelp': 'Edite o bloco selecionado sem sair da estrutura do documento.',
497
+ 'praxis.richContent.editor.intentNavigation': 'Seções de propriedades',
498
+ 'praxis.richContent.editor.intent.content': 'Conteúdo',
499
+ 'praxis.richContent.editor.intent.contentHelp': 'Campos principais do bloco',
500
+ 'praxis.richContent.editor.intent.presentation': 'Apresentação',
501
+ 'praxis.richContent.editor.intent.presentationHelp': 'Affordances visuais',
502
+ 'praxis.richContent.editor.intent.dataRules': 'Dados e regras',
503
+ 'praxis.richContent.editor.intent.dataRulesHelp': 'Metadados e gates',
480
504
  'praxis.richContent.editor.hasIssues': 'Requer atenção',
481
505
  'praxis.richContent.editor.block': 'Bloco',
482
506
  'praxis.richContent.editor.blockType': 'Tipo de bloco',
@@ -499,6 +523,8 @@ const PRAXIS_RICH_CONTENT_PT_BR = {
499
523
  'praxis.richContent.editor.cardContentHelp': 'O conteúdo do card é preservado no JSON. Use o JSON avançado para nodes aninhados do card.',
500
524
  'praxis.richContent.editor.nodeId': 'Id estável',
501
525
  'praxis.richContent.editor.nodeIdPlaceholder': 'Exemplo: hero-title',
526
+ 'praxis.richContent.editor.advancedDataRules': 'Dados e regras avançados',
527
+ 'praxis.richContent.editor.advancedDataRulesHelp': 'Metadados, capability gates, visibilidade e estilo seguro.',
502
528
  'praxis.richContent.editor.visibilityRule': 'Regra de visibilidade',
503
529
  'praxis.richContent.editor.safeStyle': 'Estilo seguro',
504
530
  'praxis.richContent.editor.nodeType.text': 'Texto',
@@ -662,6 +688,8 @@ const PRAXIS_RICH_CONTENT_PT_BR = {
662
688
  'praxis.richContent.editor.nodeTypes': 'Tipos',
663
689
  'praxis.richContent.editor.none': 'Nenhum',
664
690
  'praxis.richContent.editor.preview': 'Prévia',
691
+ 'praxis.richContent.editor.previewHelp': 'Inspecione o documento materializado sem misturar prévia com campos de autoria.',
692
+ 'praxis.richContent.editor.previewUnavailable': 'A prévia fica indisponível até que o documento seja válido.',
665
693
  'praxis.richContent.editor.restore': 'Restaurar',
666
694
  'praxis.richContent.editor.emptyDocument': 'Documento vazio',
667
695
  'praxis.richContent.editor.validation.documentShape': 'O documento deve ser um RichContentDocument válido.',
@@ -5872,6 +5900,8 @@ class PraxisRichContentConfigEditor {
5872
5900
  nodeCount = 0;
5873
5901
  nodeTypeSummary = '';
5874
5902
  selectedNodeIndex = 0;
5903
+ activeEditorPanel = 'guided';
5904
+ advancedRulesOpen = false;
5875
5905
  initialInputs = this.normalizeInputs(this.inputs);
5876
5906
  pendingRemoval = null;
5877
5907
  ngOnChanges(changes) {
@@ -5926,6 +5956,19 @@ class PraxisRichContentConfigEditor {
5926
5956
  }
5927
5957
  selectTopLevelNode(index) {
5928
5958
  this.selectedNodeIndex = index;
5959
+ this.advancedRulesOpen = false;
5960
+ }
5961
+ selectEditorPanel(panel) {
5962
+ this.activeEditorPanel = panel;
5963
+ this.cdr.markForCheck();
5964
+ }
5965
+ openAdvancedRules() {
5966
+ this.advancedRulesOpen = true;
5967
+ this.cdr.markForCheck();
5968
+ }
5969
+ onAdvancedRulesToggle(event) {
5970
+ const target = event.target;
5971
+ this.advancedRulesOpen = target?.open === true;
5929
5972
  }
5930
5973
  addTopLevelNode() {
5931
5974
  const document = this.ensureEditableDocument();
@@ -6881,6 +6924,12 @@ class PraxisRichContentConfigEditor {
6881
6924
  .filter((issue) => issue.path === path || issue.path.startsWith(`${path}.`))
6882
6925
  .map((issue) => this.tx(issue.messageKey, issue.fallback));
6883
6926
  }
6927
+ getIssueCount(path) {
6928
+ return this.validationIssues.filter((issue) => issue.path === path || issue.path.startsWith(`${path}.`)).length;
6929
+ }
6930
+ getSelectedNodeIssueCount() {
6931
+ return this.getIssueCount(`$.nodes[${this.selectedNodeIndex}]`);
6932
+ }
6884
6933
  isEditableNodeType(type) {
6885
6934
  return EDITABLE_TOP_LEVEL_NODE_TYPES.includes(type);
6886
6935
  }
@@ -7867,7 +7916,59 @@ class PraxisRichContentConfigEditor {
7867
7916
  </div>
7868
7917
  </ng-template>
7869
7918
 
7870
- <section class="prx-rich-editor__blocks">
7919
+ <nav
7920
+ class="prx-rich-editor__tabs"
7921
+ [attr.aria-label]="tx('editor.workbenchTabs', 'Editor sections')"
7922
+ role="tablist"
7923
+ >
7924
+ <button
7925
+ type="button"
7926
+ role="tab"
7927
+ class="prx-rich-editor__tab"
7928
+ [class.prx-rich-editor__tab--active]="activeEditorPanel === 'guided'"
7929
+ [attr.aria-selected]="activeEditorPanel === 'guided'"
7930
+ (click)="selectEditorPanel('guided')"
7931
+ data-testid="rich-content-guided-tab"
7932
+ >
7933
+ {{ tx('editor.guidedEditing', 'Guided editing') }}
7934
+ </button>
7935
+ <button
7936
+ type="button"
7937
+ role="tab"
7938
+ class="prx-rich-editor__tab"
7939
+ [class.prx-rich-editor__tab--active]="activeEditorPanel === 'preview'"
7940
+ [attr.aria-selected]="activeEditorPanel === 'preview'"
7941
+ (click)="selectEditorPanel('preview')"
7942
+ data-testid="rich-content-preview-tab"
7943
+ >
7944
+ {{ tx('editor.preview', 'Preview') }}
7945
+ </button>
7946
+ <button
7947
+ type="button"
7948
+ role="tab"
7949
+ class="prx-rich-editor__tab"
7950
+ [class.prx-rich-editor__tab--active]="activeEditorPanel === 'json'"
7951
+ [attr.aria-selected]="activeEditorPanel === 'json'"
7952
+ (click)="selectEditorPanel('json')"
7953
+ data-testid="rich-content-json-tab"
7954
+ >
7955
+ {{ tx('editor.document', 'Advanced JSON') }}
7956
+ @if (!valid || validationIssues.length) {
7957
+ <span class="prx-rich-editor__tab-badge">
7958
+ {{
7959
+ validationIssues.length || tx('editor.invalidBadge', 'Invalid')
7960
+ }}
7961
+ </span>
7962
+ }
7963
+ </button>
7964
+ </nav>
7965
+
7966
+ @if (activeEditorPanel === 'guided') {
7967
+ <section
7968
+ class="prx-rich-editor__blocks"
7969
+ role="tabpanel"
7970
+ [attr.aria-label]="tx('editor.guidedEditing', 'Guided editing')"
7971
+ >
7871
7972
  <header class="prx-rich-editor__section-header">
7872
7973
  <div>
7873
7974
  <h3>{{ tx('editor.blocks', 'Blocks') }}</h3>
@@ -7931,8 +8032,11 @@ class PraxisRichContentConfigEditor {
7931
8032
  {{ tx('editor.block', 'Block') }} {{ nodeIndex + 1 }}
7932
8033
  </span>
7933
8034
  <strong>{{ tx('editor.nodeType.' + node.type, node.type) }}</strong>
7934
- @if (getIssueMessages('$.nodes[' + nodeIndex + ']').length) {
7935
- <small>{{ tx('editor.hasIssues', 'Needs attention') }}</small>
8035
+ @if (getIssueCount('$.nodes[' + nodeIndex + ']'); as issueCount) {
8036
+ <small>
8037
+ {{ tx('editor.hasIssues', 'Needs attention') }}
8038
+ <span class="prx-rich-editor__issue-count">{{ issueCount }}</span>
8039
+ </small>
7936
8040
  }
7937
8041
  </button>
7938
8042
  }
@@ -7953,6 +8057,28 @@ class PraxisRichContentConfigEditor {
7953
8057
  </p>
7954
8058
  </div>
7955
8059
  </header>
8060
+ <div
8061
+ class="prx-rich-editor__intent-strip"
8062
+ [attr.aria-label]="tx('editor.intentNavigation', 'Property sections')"
8063
+ >
8064
+ <span>
8065
+ <strong>{{ tx('editor.intent.content', 'Content') }}</strong>
8066
+ <small>{{ tx('editor.intent.contentHelp', 'Primary block fields') }}</small>
8067
+ </span>
8068
+ <span>
8069
+ <strong>{{ tx('editor.intent.presentation', 'Presentation') }}</strong>
8070
+ <small>{{ tx('editor.intent.presentationHelp', 'Visual affordances') }}</small>
8071
+ </span>
8072
+ <button type="button" (click)="openAdvancedRules()">
8073
+ <strong>{{ tx('editor.intent.dataRules', 'Data and rules') }}</strong>
8074
+ <small>{{ tx('editor.intent.dataRulesHelp', 'Metadata and gates') }}</small>
8075
+ @if (getSelectedNodeIssueCount(); as selectedIssueCount) {
8076
+ <span class="prx-rich-editor__issue-count">
8077
+ {{ selectedIssueCount }}
8078
+ </span>
8079
+ }
8080
+ </button>
8081
+ </div>
7956
8082
  <div class="prx-rich-editor__node-list">
7957
8083
  @for (node of parsedDocument?.nodes ?? []; track node.id ?? $index; let nodeIndex = $index) {
7958
8084
  @if (selectedNodeIndex === nodeIndex) {
@@ -10608,83 +10734,101 @@ class PraxisRichContentConfigEditor {
10608
10734
  </p>
10609
10735
  }
10610
10736
  }
10611
- <fieldset class="prx-rich-editor__wide-field prx-rich-editor__field-group">
10612
- <legend>{{ tx('editor.commonMetadata', 'Common metadata') }}</legend>
10613
- <label>
10614
- <span>{{ tx('editor.field.testId', 'Test id') }}</span>
10615
- <input
10616
- [ngModel]="getStringField(node, 'testId')"
10617
- (ngModelChange)="setStringField($index, 'testId', $event)"
10618
- [placeholder]="tx('editor.placeholder.testId', 'rich-hero-title')"
10619
- />
10620
- </label>
10621
- <label>
10622
- <span>{{ tx('editor.field.className', 'CSS class') }}</span>
10623
- <input
10624
- [ngModel]="getStringField(node, 'className')"
10625
- (ngModelChange)="setStringField($index, 'className', $event)"
10626
- [placeholder]="tx('editor.placeholder.className', 'hero-title')"
10627
- />
10628
- </label>
10629
- <label>
10630
- <span>{{ tx('editor.field.requiresCapabilities', 'Capabilities') }}</span>
10631
- <input
10632
- [ngModel]="getCapabilityList(node)"
10633
- (ngModelChange)="setCapabilityList($index, $event)"
10634
- [placeholder]="tx('editor.placeholder.requiresCapabilities', 'form.mode.edit, employee.open')"
10635
- />
10636
- </label>
10637
- <label>
10638
- <span>{{ tx('editor.field.capabilityMode', 'Capability mode') }}</span>
10639
- <select
10640
- [ngModel]="getStringField(node, 'capabilityMode') || 'all'"
10641
- (ngModelChange)="setStringField($index, 'capabilityMode', $event)"
10642
- >
10643
- <option value="all">{{ tx('editor.capabilityMode.all', 'All') }}</option>
10644
- <option value="any">{{ tx('editor.capabilityMode.any', 'Any') }}</option>
10645
- </select>
10646
- </label>
10647
- </fieldset>
10737
+ <details
10738
+ class="prx-rich-editor__advanced-group"
10739
+ [open]="advancedRulesOpen"
10740
+ (toggle)="onAdvancedRulesToggle($event)"
10741
+ >
10742
+ <summary>
10743
+ <span>{{ tx('editor.advancedDataRules', 'Advanced data and rules') }}</span>
10744
+ <small>
10745
+ {{
10746
+ tx(
10747
+ 'editor.advancedDataRulesHelp',
10748
+ 'Metadata, capability gates, visibility and safe style.'
10749
+ )
10750
+ }}
10751
+ </small>
10752
+ </summary>
10648
10753
 
10649
- <fieldset class="prx-rich-editor__wide-field prx-rich-editor__field-group">
10650
- <legend>{{ tx('editor.visibilityRule', 'Visibility rule') }}</legend>
10651
- <label>
10652
- <span>{{ tx('editor.field.visibleWhenPath', 'Context path') }}</span>
10653
- <input
10654
- [ngModel]="getVisibleWhenPath(node)"
10655
- (ngModelChange)="setVisibleWhenPath($index, $event)"
10656
- [placeholder]="tx('editor.placeholder.visibleWhenPath', 'row.active')"
10657
- />
10658
- </label>
10659
- <label>
10660
- <span>{{ tx('editor.field.visibleWhenValue', 'Expected value') }}</span>
10661
- <input
10662
- [ngModel]="getVisibleWhenValue(node)"
10663
- (ngModelChange)="setVisibleWhenValue($index, $event)"
10664
- [placeholder]="tx('editor.placeholder.visibleWhenValue', 'true')"
10665
- />
10666
- </label>
10667
- </fieldset>
10754
+ <fieldset class="prx-rich-editor__field-group">
10755
+ <legend>{{ tx('editor.commonMetadata', 'Common metadata') }}</legend>
10756
+ <label>
10757
+ <span>{{ tx('editor.field.testId', 'Test id') }}</span>
10758
+ <input
10759
+ [ngModel]="getStringField(node, 'testId')"
10760
+ (ngModelChange)="setStringField($index, 'testId', $event)"
10761
+ [placeholder]="tx('editor.placeholder.testId', 'rich-hero-title')"
10762
+ />
10763
+ </label>
10764
+ <label>
10765
+ <span>{{ tx('editor.field.className', 'CSS class') }}</span>
10766
+ <input
10767
+ [ngModel]="getStringField(node, 'className')"
10768
+ (ngModelChange)="setStringField($index, 'className', $event)"
10769
+ [placeholder]="tx('editor.placeholder.className', 'hero-title')"
10770
+ />
10771
+ </label>
10772
+ <label>
10773
+ <span>{{ tx('editor.field.requiresCapabilities', 'Capabilities') }}</span>
10774
+ <input
10775
+ [ngModel]="getCapabilityList(node)"
10776
+ (ngModelChange)="setCapabilityList($index, $event)"
10777
+ [placeholder]="tx('editor.placeholder.requiresCapabilities', 'form.mode.edit, employee.open')"
10778
+ />
10779
+ </label>
10780
+ <label>
10781
+ <span>{{ tx('editor.field.capabilityMode', 'Capability mode') }}</span>
10782
+ <select
10783
+ [ngModel]="getStringField(node, 'capabilityMode') || 'all'"
10784
+ (ngModelChange)="setStringField($index, 'capabilityMode', $event)"
10785
+ >
10786
+ <option value="all">{{ tx('editor.capabilityMode.all', 'All') }}</option>
10787
+ <option value="any">{{ tx('editor.capabilityMode.any', 'Any') }}</option>
10788
+ </select>
10789
+ </label>
10790
+ </fieldset>
10668
10791
 
10669
- <fieldset class="prx-rich-editor__wide-field prx-rich-editor__field-group">
10670
- <legend>{{ tx('editor.safeStyle', 'Safe style') }}</legend>
10671
- <label>
10672
- <span>{{ tx('editor.field.styleName', 'CSS property') }}</span>
10673
- <input
10674
- [ngModel]="getFirstStyleName(node)"
10675
- (ngModelChange)="setFirstStyleName($index, $event)"
10676
- [placeholder]="tx('editor.placeholder.styleName', 'color')"
10677
- />
10678
- </label>
10679
- <label>
10680
- <span>{{ tx('editor.field.styleValue', 'CSS value') }}</span>
10681
- <input
10682
- [ngModel]="getFirstStyleValue(node)"
10683
- (ngModelChange)="setFirstStyleValue($index, $event)"
10684
- [placeholder]="tx('editor.placeholder.styleValue', 'var(--md-sys-color-primary)')"
10685
- />
10686
- </label>
10687
- </fieldset>
10792
+ <fieldset class="prx-rich-editor__field-group">
10793
+ <legend>{{ tx('editor.visibilityRule', 'Visibility rule') }}</legend>
10794
+ <label>
10795
+ <span>{{ tx('editor.field.visibleWhenPath', 'Context path') }}</span>
10796
+ <input
10797
+ [ngModel]="getVisibleWhenPath(node)"
10798
+ (ngModelChange)="setVisibleWhenPath($index, $event)"
10799
+ [placeholder]="tx('editor.placeholder.visibleWhenPath', 'row.active')"
10800
+ />
10801
+ </label>
10802
+ <label>
10803
+ <span>{{ tx('editor.field.visibleWhenValue', 'Expected value') }}</span>
10804
+ <input
10805
+ [ngModel]="getVisibleWhenValue(node)"
10806
+ (ngModelChange)="setVisibleWhenValue($index, $event)"
10807
+ [placeholder]="tx('editor.placeholder.visibleWhenValue', 'true')"
10808
+ />
10809
+ </label>
10810
+ </fieldset>
10811
+
10812
+ <fieldset class="prx-rich-editor__field-group">
10813
+ <legend>{{ tx('editor.safeStyle', 'Safe style') }}</legend>
10814
+ <label>
10815
+ <span>{{ tx('editor.field.styleName', 'CSS property') }}</span>
10816
+ <input
10817
+ [ngModel]="getFirstStyleName(node)"
10818
+ (ngModelChange)="setFirstStyleName($index, $event)"
10819
+ [placeholder]="tx('editor.placeholder.styleName', 'color')"
10820
+ />
10821
+ </label>
10822
+ <label>
10823
+ <span>{{ tx('editor.field.styleValue', 'CSS value') }}</span>
10824
+ <input
10825
+ [ngModel]="getFirstStyleValue(node)"
10826
+ (ngModelChange)="setFirstStyleValue($index, $event)"
10827
+ [placeholder]="tx('editor.placeholder.styleValue', 'var(--md-sys-color-primary)')"
10828
+ />
10829
+ </label>
10830
+ </fieldset>
10831
+ </details>
10688
10832
  </div>
10689
10833
  </article>
10690
10834
  }
@@ -10698,7 +10842,44 @@ class PraxisRichContentConfigEditor {
10698
10842
  </p>
10699
10843
  }
10700
10844
  </section>
10845
+ }
10846
+
10847
+ @if (activeEditorPanel === 'preview') {
10848
+ <section
10849
+ class="prx-rich-editor__preview-panel"
10850
+ role="tabpanel"
10851
+ [attr.aria-label]="tx('editor.preview', 'Preview')"
10852
+ >
10853
+ <header class="prx-rich-editor__section-header">
10854
+ <div>
10855
+ <h3>{{ tx('editor.preview', 'Preview') }}</h3>
10856
+ <p>
10857
+ {{
10858
+ tx(
10859
+ 'editor.previewHelp',
10860
+ 'Inspect the materialized document without mixing preview with authoring fields.'
10861
+ )
10862
+ }}
10863
+ </p>
10864
+ </div>
10865
+ </header>
10866
+ @if (parsedDocument) {
10867
+ <div class="prx-rich-editor__preview prx-rich-editor__preview--large">
10868
+ <praxis-rich-content
10869
+ [document]="parsedDocument"
10870
+ [layout]="layout"
10871
+ [rootClassName]="rootClassName"
10872
+ ></praxis-rich-content>
10873
+ </div>
10874
+ } @else {
10875
+ <p class="prx-rich-editor__empty">
10876
+ {{ tx('editor.previewUnavailable', 'Preview is unavailable until the document is valid.') }}
10877
+ </p>
10878
+ }
10879
+ </section>
10880
+ }
10701
10881
 
10882
+ @if (activeEditorPanel === 'json') {
10702
10883
  <section class="prx-rich-editor__document-panel">
10703
10884
  <header class="prx-rich-editor__section-header">
10704
10885
  <div>
@@ -10749,18 +10930,6 @@ class PraxisRichContentConfigEditor {
10749
10930
  </dl>
10750
10931
  </section>
10751
10932
 
10752
- @if (parsedDocument) {
10753
- <section>
10754
- <h3>{{ tx('editor.preview', 'Preview') }}</h3>
10755
- <div class="prx-rich-editor__preview">
10756
- <praxis-rich-content
10757
- [document]="parsedDocument"
10758
- [layout]="layout"
10759
- [rootClassName]="rootClassName"
10760
- ></praxis-rich-content>
10761
- </div>
10762
- </section>
10763
- }
10764
10933
  </aside>
10765
10934
  </div>
10766
10935
  </section>
@@ -10793,6 +10962,7 @@ class PraxisRichContentConfigEditor {
10793
10962
  }}
10794
10963
  </p>
10795
10964
  }
10965
+ }
10796
10966
 
10797
10967
  <div class="prx-rich-editor__actions">
10798
10968
  <button type="button" (click)="reset()">
@@ -10803,7 +10973,7 @@ class PraxisRichContentConfigEditor {
10803
10973
  </button>
10804
10974
  </div>
10805
10975
  </section>
10806
- `, isInline: true, styles: [":host{display:block;min-width:0}.prx-rich-editor{display:grid;gap:18px;color:var(--md-sys-color-on-surface, #1f2937)}.prx-rich-editor__header{display:flex;gap:16px;align-items:flex-start;justify-content:space-between}.prx-rich-editor__header h2{margin:0;font-size:1.25rem}.prx-rich-editor__header p{margin:6px 0 0;color:var(--md-sys-color-on-surface-variant, #5f6673)}.prx-rich-editor__eyebrow{margin:0 0 4px;text-transform:uppercase;letter-spacing:.08em;font-size:.72rem;color:var(--md-sys-color-primary, #3154e7)}.prx-rich-editor__status{flex:0 0 auto;padding:6px 10px;border-radius:999px;background:color-mix(in srgb,#16a34a 12%,transparent);color:#166534;font-size:.78rem;font-weight:700}.prx-rich-editor__status.invalid{background:color-mix(in srgb,#dc2626 12%,transparent);color:#991b1b}.prx-rich-editor__grid{display:grid;gap:14px;grid-template-columns:minmax(0,180px) minmax(0,1fr)}.prx-rich-editor__blocks{display:grid;gap:14px}.prx-rich-editor__authoring-shell{display:grid;gap:14px;grid-template-columns:minmax(220px,.34fr) minmax(0,1fr);align-items:start}.prx-rich-editor__structure,.prx-rich-editor__properties,.prx-rich-editor__document-panel{min-width:0;display:grid;gap:12px}.prx-rich-editor__structure{position:sticky;top:12px;border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:8px;padding:12px;background:var(--md-sys-color-surface-container-lowest, #fff)}.prx-rich-editor__structure-summary{display:grid;gap:4px}.prx-rich-editor__structure-summary span{color:var(--md-sys-color-on-surface-variant, #5f6673);font-size:.82rem;font-weight:500}.prx-rich-editor__structure-list{display:grid;gap:8px}.prx-rich-editor__structure-item{display:grid;gap:3px;width:100%;padding:10px;text-align:left;color:var(--md-sys-color-on-surface, #1f2937);background:var(--md-sys-color-surface, #fff)}.prx-rich-editor__structure-item--selected{border-color:var(--md-sys-color-primary, #3154e7);background:color-mix(in srgb,var(--md-sys-color-primary, #3154e7) 8%,var(--md-sys-color-surface, #fff))}.prx-rich-editor__structure-item small{color:#991b1b;font-weight:700}.prx-rich-editor__section-header,.prx-rich-editor__node-header{display:flex;gap:16px;align-items:flex-start;justify-content:space-between}.prx-rich-editor__section-header h3,.prx-rich-editor__node-header h4{margin:0}.prx-rich-editor__section-header p,.prx-rich-editor__node-note,.prx-rich-editor__empty{margin:6px 0 0;color:var(--md-sys-color-on-surface-variant, #5f6673);font-size:.88rem}.prx-rich-editor__add-block,.prx-rich-editor__node-actions{display:flex;flex-wrap:wrap;gap:10px;align-items:flex-end}.prx-rich-editor__add-block label{min-width:180px}.prx-rich-editor__node-list{display:grid;gap:12px}.prx-rich-editor__node-card{display:grid;gap:14px;border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:8px;padding:14px;background:var(--md-sys-color-surface-container-lowest, #fff)}.prx-rich-editor__node-eyebrow{margin:0 0 4px;color:var(--md-sys-color-on-surface-variant, #5f6673);font-size:.75rem;font-weight:700;text-transform:uppercase}.prx-rich-editor__node-grid{display:grid;gap:12px;grid-template-columns:repeat(2,minmax(0,1fr))}.prx-rich-editor__wide-field,.prx-rich-editor__node-note{grid-column:1 / -1}.prx-rich-editor__field-group,.prx-rich-editor__nested-editor,.prx-rich-editor__nested-node{grid-column:1 / -1;display:grid;gap:12px}.prx-rich-editor__field-group,.prx-rich-editor__nested-node{margin:0;border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:8px;padding:12px}.prx-rich-editor__field-group{grid-template-columns:repeat(2,minmax(0,1fr))}.prx-rich-editor__field-group legend{padding:0 4px;font-weight:700}.prx-rich-editor__nested-header,.prx-rich-editor__nested-actions{display:flex;flex-wrap:wrap;gap:10px;align-items:center;justify-content:space-between}.prx-rich-editor__quick-actions{display:flex;flex-wrap:wrap;gap:8px}.prx-rich-editor__node-grid textarea{min-height:96px;font-family:inherit}label,.prx-rich-editor__document{display:grid;gap:7px;font-weight:700}.prx-rich-editor__checkbox-field{display:flex;gap:10px;align-items:center;align-self:end}.prx-rich-editor__checkbox-field input[type=checkbox]{width:auto}input,select,textarea{box-sizing:border-box;width:100%;border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:12px;padding:10px 12px;font:inherit;color:inherit;background:var(--md-sys-color-surface, #fff)}textarea{min-height:360px;resize:vertical;font-family:Cascadia Code,Fira Code,Consolas,monospace;font-size:.86rem;line-height:1.45}.prx-rich-editor__workbench{display:grid;gap:14px;grid-template-columns:minmax(0,1.2fr) minmax(260px,.8fr)}.prx-rich-editor__inspector{display:grid;align-content:start;gap:14px;min-width:0;border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:8px;padding:12px;background:var(--md-sys-color-surface-container-lowest, #fff)}.prx-rich-editor__inspector h3{margin:0 0 8px;font-size:.95rem}.prx-rich-editor__inspector dl{display:grid;gap:8px;margin:0}.prx-rich-editor__inspector dl div{display:grid;gap:2px}.prx-rich-editor__inspector dt{color:var(--md-sys-color-on-surface-variant, #5f6673);font-size:.78rem;font-weight:700}.prx-rich-editor__inspector dd{margin:0;overflow-wrap:anywhere;font-weight:600}.prx-rich-editor__preview{max-height:260px;overflow:auto;border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:8px;padding:12px;background:var(--md-sys-color-surface, #fff)}.prx-rich-editor__help{margin:-8px 0 0;color:var(--md-sys-color-on-surface-variant, #5f6673);font-size:.85rem}.prx-rich-editor__error{margin:0;padding:10px 12px;border-radius:8px;color:#991b1b;background:color-mix(in srgb,#dc2626 9%,transparent)}.prx-rich-editor__error p{margin:0}.prx-rich-editor__error ul{display:grid;gap:6px;margin:8px 0 0;padding-inline-start:18px}.prx-rich-editor__error code{margin-right:4px;font-family:Cascadia Code,Fira Code,Consolas,monospace}.prx-rich-editor__actions{display:flex;flex-wrap:wrap;gap:10px}button{border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:8px;padding:8px 14px;background:var(--md-sys-color-surface, #fff);color:var(--md-sys-color-primary, #3154e7);font-weight:700;cursor:pointer}button:disabled{cursor:not-allowed;opacity:.55}@media(max-width:760px){.prx-rich-editor__header,.prx-rich-editor__section-header,.prx-rich-editor__node-header,.prx-rich-editor__authoring-shell,.prx-rich-editor__grid,.prx-rich-editor__field-group,.prx-rich-editor__node-grid,.prx-rich-editor__workbench{grid-template-columns:1fr;display:grid}.prx-rich-editor__structure{position:static}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: PraxisRichContent, selector: "praxis-rich-content", inputs: ["document", "nodes", "context", "hostCapabilities", "layout", "rootClassName"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10976
+ `, isInline: true, styles: [":host{display:block;min-width:0;width:100%;max-width:100%;box-sizing:border-box}.prx-rich-editor{display:grid;gap:18px;min-width:0;color:var(--md-sys-color-on-surface, #1f2937)}.prx-rich-editor__header{display:flex;gap:16px;align-items:flex-start;justify-content:space-between}.prx-rich-editor__header h2{margin:0;font-size:1.25rem}.prx-rich-editor__header p{margin:6px 0 0;color:var(--md-sys-color-on-surface-variant, #5f6673)}.prx-rich-editor__eyebrow{margin:0 0 4px;text-transform:uppercase;letter-spacing:.08em;font-size:.72rem;color:var(--md-sys-color-primary, #3154e7)}.prx-rich-editor__status{flex:0 0 auto;padding:6px 10px;border-radius:999px;background:color-mix(in srgb,#16a34a 12%,transparent);color:#166534;font-size:.78rem;font-weight:700}.prx-rich-editor__status.invalid{background:color-mix(in srgb,#dc2626 12%,transparent);color:#991b1b}.prx-rich-editor__grid{display:grid;gap:14px;grid-template-columns:minmax(0,180px) minmax(0,1fr)}.prx-rich-editor__tabs{display:flex;flex-wrap:wrap;gap:8px;align-items:center;border-bottom:1px solid var(--md-sys-color-outline-variant, #d7dce5)}.prx-rich-editor__tab{margin-block-end:-1px;border-radius:8px 8px 0 0;border-bottom-color:transparent;color:var(--md-sys-color-on-surface-variant, #5f6673);background:transparent}.prx-rich-editor__tab--active{border-color:var(--md-sys-color-outline-variant, #d7dce5);border-bottom-color:var(--md-sys-color-surface, #fff);color:var(--md-sys-color-primary, #3154e7);background:var(--md-sys-color-surface, #fff)}.prx-rich-editor__tab-badge,.prx-rich-editor__issue-count{display:inline-flex;min-width:1.35em;min-height:1.35em;padding:0 .38em;border-radius:999px;align-items:center;justify-content:center;background:color-mix(in srgb,#dc2626 14%,transparent);color:#991b1b;font-size:.72rem;font-weight:800;line-height:1}.prx-rich-editor__tab-badge{margin-inline-start:6px}.prx-rich-editor__blocks{display:grid;gap:14px}.prx-rich-editor__authoring-shell{display:grid;gap:14px;grid-template-columns:minmax(220px,.34fr) minmax(0,1fr);align-items:start}.prx-rich-editor__structure,.prx-rich-editor__properties,.prx-rich-editor__document-panel,.prx-rich-editor__preview-panel{min-width:0;display:grid;gap:12px}.prx-rich-editor__structure{position:sticky;top:12px;border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:8px;padding:12px;background:var(--md-sys-color-surface-container-lowest, #fff)}.prx-rich-editor__structure-summary{display:grid;gap:4px}.prx-rich-editor__structure-summary span{color:var(--md-sys-color-on-surface-variant, #5f6673);font-size:.82rem;font-weight:500}.prx-rich-editor__structure-list{display:grid;gap:8px}.prx-rich-editor__structure-item{display:grid;gap:3px;width:100%;padding:10px;text-align:left;color:var(--md-sys-color-on-surface, #1f2937);background:var(--md-sys-color-surface, #fff)}.prx-rich-editor__structure-item--selected{border-color:var(--md-sys-color-primary, #3154e7);background:color-mix(in srgb,var(--md-sys-color-primary, #3154e7) 8%,var(--md-sys-color-surface, #fff))}.prx-rich-editor__structure-item small{color:#991b1b;font-weight:700}.prx-rich-editor__intent-strip{display:grid;gap:8px;grid-template-columns:repeat(3,minmax(0,1fr))}.prx-rich-editor__intent-strip span,.prx-rich-editor__intent-strip button{min-width:0;display:grid;gap:3px;border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:8px;padding:10px 12px;text-align:left;color:var(--md-sys-color-on-surface, #1f2937);background:var(--md-sys-color-surface, #fff)}.prx-rich-editor__intent-strip button{position:relative}.prx-rich-editor__intent-strip small{color:var(--md-sys-color-on-surface-variant, #5f6673);font-size:.78rem;font-weight:600}.prx-rich-editor__intent-strip .prx-rich-editor__issue-count{position:absolute;inset-block-start:8px;inset-inline-end:8px}.prx-rich-editor__section-header,.prx-rich-editor__node-header{display:flex;gap:16px;align-items:flex-start;justify-content:space-between}.prx-rich-editor__section-header h3,.prx-rich-editor__node-header h4{margin:0}.prx-rich-editor__section-header p,.prx-rich-editor__node-note,.prx-rich-editor__empty{margin:6px 0 0;color:var(--md-sys-color-on-surface-variant, #5f6673);font-size:.88rem}.prx-rich-editor__add-block,.prx-rich-editor__node-actions{display:flex;flex-wrap:wrap;gap:10px;align-items:flex-end}.prx-rich-editor__add-block label{min-width:180px}.prx-rich-editor__node-list{display:grid;gap:12px}.prx-rich-editor__node-card{display:grid;gap:14px;border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:8px;padding:14px;background:var(--md-sys-color-surface-container-lowest, #fff)}.prx-rich-editor__node-eyebrow{margin:0 0 4px;color:var(--md-sys-color-on-surface-variant, #5f6673);font-size:.75rem;font-weight:700;text-transform:uppercase}.prx-rich-editor__node-grid{display:grid;gap:12px;grid-template-columns:repeat(2,minmax(0,1fr))}.prx-rich-editor__wide-field,.prx-rich-editor__node-note{grid-column:1 / -1}.prx-rich-editor__field-group,.prx-rich-editor__nested-editor,.prx-rich-editor__nested-node{grid-column:1 / -1;display:grid;gap:12px}.prx-rich-editor__field-group,.prx-rich-editor__nested-node{margin:0;border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:8px;padding:12px}.prx-rich-editor__field-group{grid-template-columns:repeat(2,minmax(0,1fr))}.prx-rich-editor__field-group legend{padding:0 4px;font-weight:700}.prx-rich-editor__advanced-group{grid-column:1 / -1;display:grid;gap:12px;border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:8px;padding:12px;background:color-mix(in srgb,var(--md-sys-color-surface-container-lowest, #fff) 88%,var(--md-sys-color-primary, #3154e7) 4%)}.prx-rich-editor__advanced-group summary{display:flex;gap:10px;align-items:center;justify-content:space-between;cursor:pointer;font-weight:800}.prx-rich-editor__advanced-group summary small{color:var(--md-sys-color-on-surface-variant, #5f6673);font-size:.78rem;font-weight:600}.prx-rich-editor__advanced-group[open]{padding-block-end:14px}.prx-rich-editor__advanced-group[open] summary{margin-block-end:12px}.prx-rich-editor__advanced-group .prx-rich-editor__field-group+.prx-rich-editor__field-group{margin-block-start:12px}.prx-rich-editor__nested-header,.prx-rich-editor__nested-actions{display:flex;flex-wrap:wrap;gap:10px;align-items:center;justify-content:space-between}.prx-rich-editor__quick-actions{display:flex;flex-wrap:wrap;gap:8px}.prx-rich-editor__node-grid textarea{min-height:96px;font-family:inherit}label,.prx-rich-editor__document{display:grid;gap:7px;font-weight:700}.prx-rich-editor__checkbox-field{display:flex;gap:10px;align-items:center;align-self:end}.prx-rich-editor__checkbox-field input[type=checkbox]{width:auto}input,select,textarea{box-sizing:border-box;width:100%;border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:12px;padding:10px 12px;font:inherit;color:inherit;background:var(--md-sys-color-surface, #fff)}textarea{min-height:360px;resize:vertical;font-family:Cascadia Code,Fira Code,Consolas,monospace;font-size:.86rem;line-height:1.45}.prx-rich-editor__workbench{display:grid;gap:14px;grid-template-columns:minmax(0,1.2fr) minmax(260px,.8fr)}.prx-rich-editor__inspector{display:grid;align-content:start;gap:14px;min-width:0;border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:8px;padding:12px;background:var(--md-sys-color-surface-container-lowest, #fff)}.prx-rich-editor__inspector h3{margin:0 0 8px;font-size:.95rem}.prx-rich-editor__inspector dl{display:grid;gap:8px;margin:0}.prx-rich-editor__inspector dl div{display:grid;gap:2px}.prx-rich-editor__inspector dt{color:var(--md-sys-color-on-surface-variant, #5f6673);font-size:.78rem;font-weight:700}.prx-rich-editor__inspector dd{margin:0;overflow-wrap:anywhere;font-weight:600}.prx-rich-editor__preview{max-height:260px;overflow:auto;border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:8px;padding:12px;background:var(--md-sys-color-surface, #fff)}.prx-rich-editor__preview--large{max-height:min(68vh,760px)}.prx-rich-editor__help{margin:-8px 0 0;color:var(--md-sys-color-on-surface-variant, #5f6673);font-size:.85rem}.prx-rich-editor__error{margin:0;padding:10px 12px;border-radius:8px;color:#991b1b;background:color-mix(in srgb,#dc2626 9%,transparent)}.prx-rich-editor__error p{margin:0}.prx-rich-editor__error ul{display:grid;gap:6px;margin:8px 0 0;padding-inline-start:18px}.prx-rich-editor__error code{margin-right:4px;font-family:Cascadia Code,Fira Code,Consolas,monospace}.prx-rich-editor__actions{display:flex;flex-wrap:wrap;gap:10px}button{border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:8px;padding:8px 14px;background:var(--md-sys-color-surface, #fff);color:var(--md-sys-color-primary, #3154e7);font-weight:700;cursor:pointer}button:disabled{cursor:not-allowed;opacity:.55}@media(max-width:760px){.prx-rich-editor__header,.prx-rich-editor__section-header,.prx-rich-editor__node-header,.prx-rich-editor__authoring-shell,.prx-rich-editor__intent-strip,.prx-rich-editor__grid,.prx-rich-editor__field-group,.prx-rich-editor__node-grid,.prx-rich-editor__workbench{grid-template-columns:1fr;display:grid}.prx-rich-editor__structure{position:static}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: PraxisRichContent, selector: "praxis-rich-content", inputs: ["document", "nodes", "context", "hostCapabilities", "layout", "rootClassName"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10807
10977
  }
10808
10978
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisRichContentConfigEditor, decorators: [{
10809
10979
  type: Component,
@@ -11309,7 +11479,59 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
11309
11479
  </div>
11310
11480
  </ng-template>
11311
11481
 
11312
- <section class="prx-rich-editor__blocks">
11482
+ <nav
11483
+ class="prx-rich-editor__tabs"
11484
+ [attr.aria-label]="tx('editor.workbenchTabs', 'Editor sections')"
11485
+ role="tablist"
11486
+ >
11487
+ <button
11488
+ type="button"
11489
+ role="tab"
11490
+ class="prx-rich-editor__tab"
11491
+ [class.prx-rich-editor__tab--active]="activeEditorPanel === 'guided'"
11492
+ [attr.aria-selected]="activeEditorPanel === 'guided'"
11493
+ (click)="selectEditorPanel('guided')"
11494
+ data-testid="rich-content-guided-tab"
11495
+ >
11496
+ {{ tx('editor.guidedEditing', 'Guided editing') }}
11497
+ </button>
11498
+ <button
11499
+ type="button"
11500
+ role="tab"
11501
+ class="prx-rich-editor__tab"
11502
+ [class.prx-rich-editor__tab--active]="activeEditorPanel === 'preview'"
11503
+ [attr.aria-selected]="activeEditorPanel === 'preview'"
11504
+ (click)="selectEditorPanel('preview')"
11505
+ data-testid="rich-content-preview-tab"
11506
+ >
11507
+ {{ tx('editor.preview', 'Preview') }}
11508
+ </button>
11509
+ <button
11510
+ type="button"
11511
+ role="tab"
11512
+ class="prx-rich-editor__tab"
11513
+ [class.prx-rich-editor__tab--active]="activeEditorPanel === 'json'"
11514
+ [attr.aria-selected]="activeEditorPanel === 'json'"
11515
+ (click)="selectEditorPanel('json')"
11516
+ data-testid="rich-content-json-tab"
11517
+ >
11518
+ {{ tx('editor.document', 'Advanced JSON') }}
11519
+ @if (!valid || validationIssues.length) {
11520
+ <span class="prx-rich-editor__tab-badge">
11521
+ {{
11522
+ validationIssues.length || tx('editor.invalidBadge', 'Invalid')
11523
+ }}
11524
+ </span>
11525
+ }
11526
+ </button>
11527
+ </nav>
11528
+
11529
+ @if (activeEditorPanel === 'guided') {
11530
+ <section
11531
+ class="prx-rich-editor__blocks"
11532
+ role="tabpanel"
11533
+ [attr.aria-label]="tx('editor.guidedEditing', 'Guided editing')"
11534
+ >
11313
11535
  <header class="prx-rich-editor__section-header">
11314
11536
  <div>
11315
11537
  <h3>{{ tx('editor.blocks', 'Blocks') }}</h3>
@@ -11373,8 +11595,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
11373
11595
  {{ tx('editor.block', 'Block') }} {{ nodeIndex + 1 }}
11374
11596
  </span>
11375
11597
  <strong>{{ tx('editor.nodeType.' + node.type, node.type) }}</strong>
11376
- @if (getIssueMessages('$.nodes[' + nodeIndex + ']').length) {
11377
- <small>{{ tx('editor.hasIssues', 'Needs attention') }}</small>
11598
+ @if (getIssueCount('$.nodes[' + nodeIndex + ']'); as issueCount) {
11599
+ <small>
11600
+ {{ tx('editor.hasIssues', 'Needs attention') }}
11601
+ <span class="prx-rich-editor__issue-count">{{ issueCount }}</span>
11602
+ </small>
11378
11603
  }
11379
11604
  </button>
11380
11605
  }
@@ -11395,6 +11620,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
11395
11620
  </p>
11396
11621
  </div>
11397
11622
  </header>
11623
+ <div
11624
+ class="prx-rich-editor__intent-strip"
11625
+ [attr.aria-label]="tx('editor.intentNavigation', 'Property sections')"
11626
+ >
11627
+ <span>
11628
+ <strong>{{ tx('editor.intent.content', 'Content') }}</strong>
11629
+ <small>{{ tx('editor.intent.contentHelp', 'Primary block fields') }}</small>
11630
+ </span>
11631
+ <span>
11632
+ <strong>{{ tx('editor.intent.presentation', 'Presentation') }}</strong>
11633
+ <small>{{ tx('editor.intent.presentationHelp', 'Visual affordances') }}</small>
11634
+ </span>
11635
+ <button type="button" (click)="openAdvancedRules()">
11636
+ <strong>{{ tx('editor.intent.dataRules', 'Data and rules') }}</strong>
11637
+ <small>{{ tx('editor.intent.dataRulesHelp', 'Metadata and gates') }}</small>
11638
+ @if (getSelectedNodeIssueCount(); as selectedIssueCount) {
11639
+ <span class="prx-rich-editor__issue-count">
11640
+ {{ selectedIssueCount }}
11641
+ </span>
11642
+ }
11643
+ </button>
11644
+ </div>
11398
11645
  <div class="prx-rich-editor__node-list">
11399
11646
  @for (node of parsedDocument?.nodes ?? []; track node.id ?? $index; let nodeIndex = $index) {
11400
11647
  @if (selectedNodeIndex === nodeIndex) {
@@ -14050,83 +14297,101 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
14050
14297
  </p>
14051
14298
  }
14052
14299
  }
14053
- <fieldset class="prx-rich-editor__wide-field prx-rich-editor__field-group">
14054
- <legend>{{ tx('editor.commonMetadata', 'Common metadata') }}</legend>
14055
- <label>
14056
- <span>{{ tx('editor.field.testId', 'Test id') }}</span>
14057
- <input
14058
- [ngModel]="getStringField(node, 'testId')"
14059
- (ngModelChange)="setStringField($index, 'testId', $event)"
14060
- [placeholder]="tx('editor.placeholder.testId', 'rich-hero-title')"
14061
- />
14062
- </label>
14063
- <label>
14064
- <span>{{ tx('editor.field.className', 'CSS class') }}</span>
14065
- <input
14066
- [ngModel]="getStringField(node, 'className')"
14067
- (ngModelChange)="setStringField($index, 'className', $event)"
14068
- [placeholder]="tx('editor.placeholder.className', 'hero-title')"
14069
- />
14070
- </label>
14071
- <label>
14072
- <span>{{ tx('editor.field.requiresCapabilities', 'Capabilities') }}</span>
14073
- <input
14074
- [ngModel]="getCapabilityList(node)"
14075
- (ngModelChange)="setCapabilityList($index, $event)"
14076
- [placeholder]="tx('editor.placeholder.requiresCapabilities', 'form.mode.edit, employee.open')"
14077
- />
14078
- </label>
14079
- <label>
14080
- <span>{{ tx('editor.field.capabilityMode', 'Capability mode') }}</span>
14081
- <select
14082
- [ngModel]="getStringField(node, 'capabilityMode') || 'all'"
14083
- (ngModelChange)="setStringField($index, 'capabilityMode', $event)"
14084
- >
14085
- <option value="all">{{ tx('editor.capabilityMode.all', 'All') }}</option>
14086
- <option value="any">{{ tx('editor.capabilityMode.any', 'Any') }}</option>
14087
- </select>
14088
- </label>
14089
- </fieldset>
14300
+ <details
14301
+ class="prx-rich-editor__advanced-group"
14302
+ [open]="advancedRulesOpen"
14303
+ (toggle)="onAdvancedRulesToggle($event)"
14304
+ >
14305
+ <summary>
14306
+ <span>{{ tx('editor.advancedDataRules', 'Advanced data and rules') }}</span>
14307
+ <small>
14308
+ {{
14309
+ tx(
14310
+ 'editor.advancedDataRulesHelp',
14311
+ 'Metadata, capability gates, visibility and safe style.'
14312
+ )
14313
+ }}
14314
+ </small>
14315
+ </summary>
14090
14316
 
14091
- <fieldset class="prx-rich-editor__wide-field prx-rich-editor__field-group">
14092
- <legend>{{ tx('editor.visibilityRule', 'Visibility rule') }}</legend>
14093
- <label>
14094
- <span>{{ tx('editor.field.visibleWhenPath', 'Context path') }}</span>
14095
- <input
14096
- [ngModel]="getVisibleWhenPath(node)"
14097
- (ngModelChange)="setVisibleWhenPath($index, $event)"
14098
- [placeholder]="tx('editor.placeholder.visibleWhenPath', 'row.active')"
14099
- />
14100
- </label>
14101
- <label>
14102
- <span>{{ tx('editor.field.visibleWhenValue', 'Expected value') }}</span>
14103
- <input
14104
- [ngModel]="getVisibleWhenValue(node)"
14105
- (ngModelChange)="setVisibleWhenValue($index, $event)"
14106
- [placeholder]="tx('editor.placeholder.visibleWhenValue', 'true')"
14107
- />
14108
- </label>
14109
- </fieldset>
14317
+ <fieldset class="prx-rich-editor__field-group">
14318
+ <legend>{{ tx('editor.commonMetadata', 'Common metadata') }}</legend>
14319
+ <label>
14320
+ <span>{{ tx('editor.field.testId', 'Test id') }}</span>
14321
+ <input
14322
+ [ngModel]="getStringField(node, 'testId')"
14323
+ (ngModelChange)="setStringField($index, 'testId', $event)"
14324
+ [placeholder]="tx('editor.placeholder.testId', 'rich-hero-title')"
14325
+ />
14326
+ </label>
14327
+ <label>
14328
+ <span>{{ tx('editor.field.className', 'CSS class') }}</span>
14329
+ <input
14330
+ [ngModel]="getStringField(node, 'className')"
14331
+ (ngModelChange)="setStringField($index, 'className', $event)"
14332
+ [placeholder]="tx('editor.placeholder.className', 'hero-title')"
14333
+ />
14334
+ </label>
14335
+ <label>
14336
+ <span>{{ tx('editor.field.requiresCapabilities', 'Capabilities') }}</span>
14337
+ <input
14338
+ [ngModel]="getCapabilityList(node)"
14339
+ (ngModelChange)="setCapabilityList($index, $event)"
14340
+ [placeholder]="tx('editor.placeholder.requiresCapabilities', 'form.mode.edit, employee.open')"
14341
+ />
14342
+ </label>
14343
+ <label>
14344
+ <span>{{ tx('editor.field.capabilityMode', 'Capability mode') }}</span>
14345
+ <select
14346
+ [ngModel]="getStringField(node, 'capabilityMode') || 'all'"
14347
+ (ngModelChange)="setStringField($index, 'capabilityMode', $event)"
14348
+ >
14349
+ <option value="all">{{ tx('editor.capabilityMode.all', 'All') }}</option>
14350
+ <option value="any">{{ tx('editor.capabilityMode.any', 'Any') }}</option>
14351
+ </select>
14352
+ </label>
14353
+ </fieldset>
14110
14354
 
14111
- <fieldset class="prx-rich-editor__wide-field prx-rich-editor__field-group">
14112
- <legend>{{ tx('editor.safeStyle', 'Safe style') }}</legend>
14113
- <label>
14114
- <span>{{ tx('editor.field.styleName', 'CSS property') }}</span>
14115
- <input
14116
- [ngModel]="getFirstStyleName(node)"
14117
- (ngModelChange)="setFirstStyleName($index, $event)"
14118
- [placeholder]="tx('editor.placeholder.styleName', 'color')"
14119
- />
14120
- </label>
14121
- <label>
14122
- <span>{{ tx('editor.field.styleValue', 'CSS value') }}</span>
14123
- <input
14124
- [ngModel]="getFirstStyleValue(node)"
14125
- (ngModelChange)="setFirstStyleValue($index, $event)"
14126
- [placeholder]="tx('editor.placeholder.styleValue', 'var(--md-sys-color-primary)')"
14127
- />
14128
- </label>
14129
- </fieldset>
14355
+ <fieldset class="prx-rich-editor__field-group">
14356
+ <legend>{{ tx('editor.visibilityRule', 'Visibility rule') }}</legend>
14357
+ <label>
14358
+ <span>{{ tx('editor.field.visibleWhenPath', 'Context path') }}</span>
14359
+ <input
14360
+ [ngModel]="getVisibleWhenPath(node)"
14361
+ (ngModelChange)="setVisibleWhenPath($index, $event)"
14362
+ [placeholder]="tx('editor.placeholder.visibleWhenPath', 'row.active')"
14363
+ />
14364
+ </label>
14365
+ <label>
14366
+ <span>{{ tx('editor.field.visibleWhenValue', 'Expected value') }}</span>
14367
+ <input
14368
+ [ngModel]="getVisibleWhenValue(node)"
14369
+ (ngModelChange)="setVisibleWhenValue($index, $event)"
14370
+ [placeholder]="tx('editor.placeholder.visibleWhenValue', 'true')"
14371
+ />
14372
+ </label>
14373
+ </fieldset>
14374
+
14375
+ <fieldset class="prx-rich-editor__field-group">
14376
+ <legend>{{ tx('editor.safeStyle', 'Safe style') }}</legend>
14377
+ <label>
14378
+ <span>{{ tx('editor.field.styleName', 'CSS property') }}</span>
14379
+ <input
14380
+ [ngModel]="getFirstStyleName(node)"
14381
+ (ngModelChange)="setFirstStyleName($index, $event)"
14382
+ [placeholder]="tx('editor.placeholder.styleName', 'color')"
14383
+ />
14384
+ </label>
14385
+ <label>
14386
+ <span>{{ tx('editor.field.styleValue', 'CSS value') }}</span>
14387
+ <input
14388
+ [ngModel]="getFirstStyleValue(node)"
14389
+ (ngModelChange)="setFirstStyleValue($index, $event)"
14390
+ [placeholder]="tx('editor.placeholder.styleValue', 'var(--md-sys-color-primary)')"
14391
+ />
14392
+ </label>
14393
+ </fieldset>
14394
+ </details>
14130
14395
  </div>
14131
14396
  </article>
14132
14397
  }
@@ -14140,7 +14405,44 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
14140
14405
  </p>
14141
14406
  }
14142
14407
  </section>
14408
+ }
14143
14409
 
14410
+ @if (activeEditorPanel === 'preview') {
14411
+ <section
14412
+ class="prx-rich-editor__preview-panel"
14413
+ role="tabpanel"
14414
+ [attr.aria-label]="tx('editor.preview', 'Preview')"
14415
+ >
14416
+ <header class="prx-rich-editor__section-header">
14417
+ <div>
14418
+ <h3>{{ tx('editor.preview', 'Preview') }}</h3>
14419
+ <p>
14420
+ {{
14421
+ tx(
14422
+ 'editor.previewHelp',
14423
+ 'Inspect the materialized document without mixing preview with authoring fields.'
14424
+ )
14425
+ }}
14426
+ </p>
14427
+ </div>
14428
+ </header>
14429
+ @if (parsedDocument) {
14430
+ <div class="prx-rich-editor__preview prx-rich-editor__preview--large">
14431
+ <praxis-rich-content
14432
+ [document]="parsedDocument"
14433
+ [layout]="layout"
14434
+ [rootClassName]="rootClassName"
14435
+ ></praxis-rich-content>
14436
+ </div>
14437
+ } @else {
14438
+ <p class="prx-rich-editor__empty">
14439
+ {{ tx('editor.previewUnavailable', 'Preview is unavailable until the document is valid.') }}
14440
+ </p>
14441
+ }
14442
+ </section>
14443
+ }
14444
+
14445
+ @if (activeEditorPanel === 'json') {
14144
14446
  <section class="prx-rich-editor__document-panel">
14145
14447
  <header class="prx-rich-editor__section-header">
14146
14448
  <div>
@@ -14191,18 +14493,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
14191
14493
  </dl>
14192
14494
  </section>
14193
14495
 
14194
- @if (parsedDocument) {
14195
- <section>
14196
- <h3>{{ tx('editor.preview', 'Preview') }}</h3>
14197
- <div class="prx-rich-editor__preview">
14198
- <praxis-rich-content
14199
- [document]="parsedDocument"
14200
- [layout]="layout"
14201
- [rootClassName]="rootClassName"
14202
- ></praxis-rich-content>
14203
- </div>
14204
- </section>
14205
- }
14206
14496
  </aside>
14207
14497
  </div>
14208
14498
  </section>
@@ -14235,6 +14525,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
14235
14525
  }}
14236
14526
  </p>
14237
14527
  }
14528
+ }
14238
14529
 
14239
14530
  <div class="prx-rich-editor__actions">
14240
14531
  <button type="button" (click)="reset()">
@@ -14245,7 +14536,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
14245
14536
  </button>
14246
14537
  </div>
14247
14538
  </section>
14248
- `, styles: [":host{display:block;min-width:0}.prx-rich-editor{display:grid;gap:18px;color:var(--md-sys-color-on-surface, #1f2937)}.prx-rich-editor__header{display:flex;gap:16px;align-items:flex-start;justify-content:space-between}.prx-rich-editor__header h2{margin:0;font-size:1.25rem}.prx-rich-editor__header p{margin:6px 0 0;color:var(--md-sys-color-on-surface-variant, #5f6673)}.prx-rich-editor__eyebrow{margin:0 0 4px;text-transform:uppercase;letter-spacing:.08em;font-size:.72rem;color:var(--md-sys-color-primary, #3154e7)}.prx-rich-editor__status{flex:0 0 auto;padding:6px 10px;border-radius:999px;background:color-mix(in srgb,#16a34a 12%,transparent);color:#166534;font-size:.78rem;font-weight:700}.prx-rich-editor__status.invalid{background:color-mix(in srgb,#dc2626 12%,transparent);color:#991b1b}.prx-rich-editor__grid{display:grid;gap:14px;grid-template-columns:minmax(0,180px) minmax(0,1fr)}.prx-rich-editor__blocks{display:grid;gap:14px}.prx-rich-editor__authoring-shell{display:grid;gap:14px;grid-template-columns:minmax(220px,.34fr) minmax(0,1fr);align-items:start}.prx-rich-editor__structure,.prx-rich-editor__properties,.prx-rich-editor__document-panel{min-width:0;display:grid;gap:12px}.prx-rich-editor__structure{position:sticky;top:12px;border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:8px;padding:12px;background:var(--md-sys-color-surface-container-lowest, #fff)}.prx-rich-editor__structure-summary{display:grid;gap:4px}.prx-rich-editor__structure-summary span{color:var(--md-sys-color-on-surface-variant, #5f6673);font-size:.82rem;font-weight:500}.prx-rich-editor__structure-list{display:grid;gap:8px}.prx-rich-editor__structure-item{display:grid;gap:3px;width:100%;padding:10px;text-align:left;color:var(--md-sys-color-on-surface, #1f2937);background:var(--md-sys-color-surface, #fff)}.prx-rich-editor__structure-item--selected{border-color:var(--md-sys-color-primary, #3154e7);background:color-mix(in srgb,var(--md-sys-color-primary, #3154e7) 8%,var(--md-sys-color-surface, #fff))}.prx-rich-editor__structure-item small{color:#991b1b;font-weight:700}.prx-rich-editor__section-header,.prx-rich-editor__node-header{display:flex;gap:16px;align-items:flex-start;justify-content:space-between}.prx-rich-editor__section-header h3,.prx-rich-editor__node-header h4{margin:0}.prx-rich-editor__section-header p,.prx-rich-editor__node-note,.prx-rich-editor__empty{margin:6px 0 0;color:var(--md-sys-color-on-surface-variant, #5f6673);font-size:.88rem}.prx-rich-editor__add-block,.prx-rich-editor__node-actions{display:flex;flex-wrap:wrap;gap:10px;align-items:flex-end}.prx-rich-editor__add-block label{min-width:180px}.prx-rich-editor__node-list{display:grid;gap:12px}.prx-rich-editor__node-card{display:grid;gap:14px;border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:8px;padding:14px;background:var(--md-sys-color-surface-container-lowest, #fff)}.prx-rich-editor__node-eyebrow{margin:0 0 4px;color:var(--md-sys-color-on-surface-variant, #5f6673);font-size:.75rem;font-weight:700;text-transform:uppercase}.prx-rich-editor__node-grid{display:grid;gap:12px;grid-template-columns:repeat(2,minmax(0,1fr))}.prx-rich-editor__wide-field,.prx-rich-editor__node-note{grid-column:1 / -1}.prx-rich-editor__field-group,.prx-rich-editor__nested-editor,.prx-rich-editor__nested-node{grid-column:1 / -1;display:grid;gap:12px}.prx-rich-editor__field-group,.prx-rich-editor__nested-node{margin:0;border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:8px;padding:12px}.prx-rich-editor__field-group{grid-template-columns:repeat(2,minmax(0,1fr))}.prx-rich-editor__field-group legend{padding:0 4px;font-weight:700}.prx-rich-editor__nested-header,.prx-rich-editor__nested-actions{display:flex;flex-wrap:wrap;gap:10px;align-items:center;justify-content:space-between}.prx-rich-editor__quick-actions{display:flex;flex-wrap:wrap;gap:8px}.prx-rich-editor__node-grid textarea{min-height:96px;font-family:inherit}label,.prx-rich-editor__document{display:grid;gap:7px;font-weight:700}.prx-rich-editor__checkbox-field{display:flex;gap:10px;align-items:center;align-self:end}.prx-rich-editor__checkbox-field input[type=checkbox]{width:auto}input,select,textarea{box-sizing:border-box;width:100%;border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:12px;padding:10px 12px;font:inherit;color:inherit;background:var(--md-sys-color-surface, #fff)}textarea{min-height:360px;resize:vertical;font-family:Cascadia Code,Fira Code,Consolas,monospace;font-size:.86rem;line-height:1.45}.prx-rich-editor__workbench{display:grid;gap:14px;grid-template-columns:minmax(0,1.2fr) minmax(260px,.8fr)}.prx-rich-editor__inspector{display:grid;align-content:start;gap:14px;min-width:0;border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:8px;padding:12px;background:var(--md-sys-color-surface-container-lowest, #fff)}.prx-rich-editor__inspector h3{margin:0 0 8px;font-size:.95rem}.prx-rich-editor__inspector dl{display:grid;gap:8px;margin:0}.prx-rich-editor__inspector dl div{display:grid;gap:2px}.prx-rich-editor__inspector dt{color:var(--md-sys-color-on-surface-variant, #5f6673);font-size:.78rem;font-weight:700}.prx-rich-editor__inspector dd{margin:0;overflow-wrap:anywhere;font-weight:600}.prx-rich-editor__preview{max-height:260px;overflow:auto;border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:8px;padding:12px;background:var(--md-sys-color-surface, #fff)}.prx-rich-editor__help{margin:-8px 0 0;color:var(--md-sys-color-on-surface-variant, #5f6673);font-size:.85rem}.prx-rich-editor__error{margin:0;padding:10px 12px;border-radius:8px;color:#991b1b;background:color-mix(in srgb,#dc2626 9%,transparent)}.prx-rich-editor__error p{margin:0}.prx-rich-editor__error ul{display:grid;gap:6px;margin:8px 0 0;padding-inline-start:18px}.prx-rich-editor__error code{margin-right:4px;font-family:Cascadia Code,Fira Code,Consolas,monospace}.prx-rich-editor__actions{display:flex;flex-wrap:wrap;gap:10px}button{border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:8px;padding:8px 14px;background:var(--md-sys-color-surface, #fff);color:var(--md-sys-color-primary, #3154e7);font-weight:700;cursor:pointer}button:disabled{cursor:not-allowed;opacity:.55}@media(max-width:760px){.prx-rich-editor__header,.prx-rich-editor__section-header,.prx-rich-editor__node-header,.prx-rich-editor__authoring-shell,.prx-rich-editor__grid,.prx-rich-editor__field-group,.prx-rich-editor__node-grid,.prx-rich-editor__workbench{grid-template-columns:1fr;display:grid}.prx-rich-editor__structure{position:static}}\n"] }]
14539
+ `, styles: [":host{display:block;min-width:0;width:100%;max-width:100%;box-sizing:border-box}.prx-rich-editor{display:grid;gap:18px;min-width:0;color:var(--md-sys-color-on-surface, #1f2937)}.prx-rich-editor__header{display:flex;gap:16px;align-items:flex-start;justify-content:space-between}.prx-rich-editor__header h2{margin:0;font-size:1.25rem}.prx-rich-editor__header p{margin:6px 0 0;color:var(--md-sys-color-on-surface-variant, #5f6673)}.prx-rich-editor__eyebrow{margin:0 0 4px;text-transform:uppercase;letter-spacing:.08em;font-size:.72rem;color:var(--md-sys-color-primary, #3154e7)}.prx-rich-editor__status{flex:0 0 auto;padding:6px 10px;border-radius:999px;background:color-mix(in srgb,#16a34a 12%,transparent);color:#166534;font-size:.78rem;font-weight:700}.prx-rich-editor__status.invalid{background:color-mix(in srgb,#dc2626 12%,transparent);color:#991b1b}.prx-rich-editor__grid{display:grid;gap:14px;grid-template-columns:minmax(0,180px) minmax(0,1fr)}.prx-rich-editor__tabs{display:flex;flex-wrap:wrap;gap:8px;align-items:center;border-bottom:1px solid var(--md-sys-color-outline-variant, #d7dce5)}.prx-rich-editor__tab{margin-block-end:-1px;border-radius:8px 8px 0 0;border-bottom-color:transparent;color:var(--md-sys-color-on-surface-variant, #5f6673);background:transparent}.prx-rich-editor__tab--active{border-color:var(--md-sys-color-outline-variant, #d7dce5);border-bottom-color:var(--md-sys-color-surface, #fff);color:var(--md-sys-color-primary, #3154e7);background:var(--md-sys-color-surface, #fff)}.prx-rich-editor__tab-badge,.prx-rich-editor__issue-count{display:inline-flex;min-width:1.35em;min-height:1.35em;padding:0 .38em;border-radius:999px;align-items:center;justify-content:center;background:color-mix(in srgb,#dc2626 14%,transparent);color:#991b1b;font-size:.72rem;font-weight:800;line-height:1}.prx-rich-editor__tab-badge{margin-inline-start:6px}.prx-rich-editor__blocks{display:grid;gap:14px}.prx-rich-editor__authoring-shell{display:grid;gap:14px;grid-template-columns:minmax(220px,.34fr) minmax(0,1fr);align-items:start}.prx-rich-editor__structure,.prx-rich-editor__properties,.prx-rich-editor__document-panel,.prx-rich-editor__preview-panel{min-width:0;display:grid;gap:12px}.prx-rich-editor__structure{position:sticky;top:12px;border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:8px;padding:12px;background:var(--md-sys-color-surface-container-lowest, #fff)}.prx-rich-editor__structure-summary{display:grid;gap:4px}.prx-rich-editor__structure-summary span{color:var(--md-sys-color-on-surface-variant, #5f6673);font-size:.82rem;font-weight:500}.prx-rich-editor__structure-list{display:grid;gap:8px}.prx-rich-editor__structure-item{display:grid;gap:3px;width:100%;padding:10px;text-align:left;color:var(--md-sys-color-on-surface, #1f2937);background:var(--md-sys-color-surface, #fff)}.prx-rich-editor__structure-item--selected{border-color:var(--md-sys-color-primary, #3154e7);background:color-mix(in srgb,var(--md-sys-color-primary, #3154e7) 8%,var(--md-sys-color-surface, #fff))}.prx-rich-editor__structure-item small{color:#991b1b;font-weight:700}.prx-rich-editor__intent-strip{display:grid;gap:8px;grid-template-columns:repeat(3,minmax(0,1fr))}.prx-rich-editor__intent-strip span,.prx-rich-editor__intent-strip button{min-width:0;display:grid;gap:3px;border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:8px;padding:10px 12px;text-align:left;color:var(--md-sys-color-on-surface, #1f2937);background:var(--md-sys-color-surface, #fff)}.prx-rich-editor__intent-strip button{position:relative}.prx-rich-editor__intent-strip small{color:var(--md-sys-color-on-surface-variant, #5f6673);font-size:.78rem;font-weight:600}.prx-rich-editor__intent-strip .prx-rich-editor__issue-count{position:absolute;inset-block-start:8px;inset-inline-end:8px}.prx-rich-editor__section-header,.prx-rich-editor__node-header{display:flex;gap:16px;align-items:flex-start;justify-content:space-between}.prx-rich-editor__section-header h3,.prx-rich-editor__node-header h4{margin:0}.prx-rich-editor__section-header p,.prx-rich-editor__node-note,.prx-rich-editor__empty{margin:6px 0 0;color:var(--md-sys-color-on-surface-variant, #5f6673);font-size:.88rem}.prx-rich-editor__add-block,.prx-rich-editor__node-actions{display:flex;flex-wrap:wrap;gap:10px;align-items:flex-end}.prx-rich-editor__add-block label{min-width:180px}.prx-rich-editor__node-list{display:grid;gap:12px}.prx-rich-editor__node-card{display:grid;gap:14px;border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:8px;padding:14px;background:var(--md-sys-color-surface-container-lowest, #fff)}.prx-rich-editor__node-eyebrow{margin:0 0 4px;color:var(--md-sys-color-on-surface-variant, #5f6673);font-size:.75rem;font-weight:700;text-transform:uppercase}.prx-rich-editor__node-grid{display:grid;gap:12px;grid-template-columns:repeat(2,minmax(0,1fr))}.prx-rich-editor__wide-field,.prx-rich-editor__node-note{grid-column:1 / -1}.prx-rich-editor__field-group,.prx-rich-editor__nested-editor,.prx-rich-editor__nested-node{grid-column:1 / -1;display:grid;gap:12px}.prx-rich-editor__field-group,.prx-rich-editor__nested-node{margin:0;border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:8px;padding:12px}.prx-rich-editor__field-group{grid-template-columns:repeat(2,minmax(0,1fr))}.prx-rich-editor__field-group legend{padding:0 4px;font-weight:700}.prx-rich-editor__advanced-group{grid-column:1 / -1;display:grid;gap:12px;border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:8px;padding:12px;background:color-mix(in srgb,var(--md-sys-color-surface-container-lowest, #fff) 88%,var(--md-sys-color-primary, #3154e7) 4%)}.prx-rich-editor__advanced-group summary{display:flex;gap:10px;align-items:center;justify-content:space-between;cursor:pointer;font-weight:800}.prx-rich-editor__advanced-group summary small{color:var(--md-sys-color-on-surface-variant, #5f6673);font-size:.78rem;font-weight:600}.prx-rich-editor__advanced-group[open]{padding-block-end:14px}.prx-rich-editor__advanced-group[open] summary{margin-block-end:12px}.prx-rich-editor__advanced-group .prx-rich-editor__field-group+.prx-rich-editor__field-group{margin-block-start:12px}.prx-rich-editor__nested-header,.prx-rich-editor__nested-actions{display:flex;flex-wrap:wrap;gap:10px;align-items:center;justify-content:space-between}.prx-rich-editor__quick-actions{display:flex;flex-wrap:wrap;gap:8px}.prx-rich-editor__node-grid textarea{min-height:96px;font-family:inherit}label,.prx-rich-editor__document{display:grid;gap:7px;font-weight:700}.prx-rich-editor__checkbox-field{display:flex;gap:10px;align-items:center;align-self:end}.prx-rich-editor__checkbox-field input[type=checkbox]{width:auto}input,select,textarea{box-sizing:border-box;width:100%;border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:12px;padding:10px 12px;font:inherit;color:inherit;background:var(--md-sys-color-surface, #fff)}textarea{min-height:360px;resize:vertical;font-family:Cascadia Code,Fira Code,Consolas,monospace;font-size:.86rem;line-height:1.45}.prx-rich-editor__workbench{display:grid;gap:14px;grid-template-columns:minmax(0,1.2fr) minmax(260px,.8fr)}.prx-rich-editor__inspector{display:grid;align-content:start;gap:14px;min-width:0;border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:8px;padding:12px;background:var(--md-sys-color-surface-container-lowest, #fff)}.prx-rich-editor__inspector h3{margin:0 0 8px;font-size:.95rem}.prx-rich-editor__inspector dl{display:grid;gap:8px;margin:0}.prx-rich-editor__inspector dl div{display:grid;gap:2px}.prx-rich-editor__inspector dt{color:var(--md-sys-color-on-surface-variant, #5f6673);font-size:.78rem;font-weight:700}.prx-rich-editor__inspector dd{margin:0;overflow-wrap:anywhere;font-weight:600}.prx-rich-editor__preview{max-height:260px;overflow:auto;border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:8px;padding:12px;background:var(--md-sys-color-surface, #fff)}.prx-rich-editor__preview--large{max-height:min(68vh,760px)}.prx-rich-editor__help{margin:-8px 0 0;color:var(--md-sys-color-on-surface-variant, #5f6673);font-size:.85rem}.prx-rich-editor__error{margin:0;padding:10px 12px;border-radius:8px;color:#991b1b;background:color-mix(in srgb,#dc2626 9%,transparent)}.prx-rich-editor__error p{margin:0}.prx-rich-editor__error ul{display:grid;gap:6px;margin:8px 0 0;padding-inline-start:18px}.prx-rich-editor__error code{margin-right:4px;font-family:Cascadia Code,Fira Code,Consolas,monospace}.prx-rich-editor__actions{display:flex;flex-wrap:wrap;gap:10px}button{border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:8px;padding:8px 14px;background:var(--md-sys-color-surface, #fff);color:var(--md-sys-color-primary, #3154e7);font-weight:700;cursor:pointer}button:disabled{cursor:not-allowed;opacity:.55}@media(max-width:760px){.prx-rich-editor__header,.prx-rich-editor__section-header,.prx-rich-editor__node-header,.prx-rich-editor__authoring-shell,.prx-rich-editor__intent-strip,.prx-rich-editor__grid,.prx-rich-editor__field-group,.prx-rich-editor__node-grid,.prx-rich-editor__workbench{grid-template-columns:1fr;display:grid}.prx-rich-editor__structure{position:static}}\n"] }]
14249
14540
  }], propDecorators: { inputs: [{
14250
14541
  type: Input
14251
14542
  }] } });
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@praxisui/rich-content",
3
- "version": "9.0.0-beta.7",
3
+ "version": "9.0.0-beta.9",
4
4
  "description": "Rich content rendering and authoring primitives for Praxis UI surfaces, including semantic blocks and page-builder integration.",
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^21.0.0",
7
7
  "@angular/core": "^21.0.0",
8
- "@praxisui/core": "^9.0.0-beta.7",
8
+ "@praxisui/core": "^9.0.0-beta.9",
9
9
  "@angular/forms": "^21.0.0",
10
10
  "rxjs": "~7.8.0"
11
11
  },
@@ -280,6 +280,7 @@ type EditableTimelineItemField = 'title' | 'titleExpr' | 'subtitle' | 'subtitleE
280
280
  type DecisionPackageStringField = 'title' | 'titleExpr' | 'subtitle' | 'subtitleExpr' | 'summary' | 'summaryExpr' | 'status' | 'statusExpr' | 'owner' | 'ownerExpr' | 'policySource' | 'policySourceExpr' | 'version' | 'versionExpr' | 'confidenceExpr' | 'risk' | 'riskExpr' | 'lastReviewed' | 'lastReviewedExpr' | 'supersedes' | 'supersedesExpr';
281
281
  type DecisionPackageEvidence = NonNullable<RichDecisionPackageNode['evidence']>[number];
282
282
  type DecisionPackageEvidenceField = 'id' | 'label' | 'labelExpr' | 'source' | 'sourceExpr';
283
+ type RichContentEditorPanel = 'guided' | 'preview' | 'json';
283
284
  interface PraxisRichContentEditorInputs {
284
285
  document?: RichContentDocument | null;
285
286
  layout?: 'block' | 'inline';
@@ -313,6 +314,8 @@ declare class PraxisRichContentConfigEditor implements SettingsValueProvider, On
313
314
  nodeCount: number;
314
315
  nodeTypeSummary: string;
315
316
  selectedNodeIndex: number;
317
+ activeEditorPanel: RichContentEditorPanel;
318
+ advancedRulesOpen: boolean;
316
319
  private initialInputs;
317
320
  private pendingRemoval;
318
321
  ngOnChanges(changes: SimpleChanges): void;
@@ -325,6 +328,9 @@ declare class PraxisRichContentConfigEditor implements SettingsValueProvider, On
325
328
  tx(key: string, fallback: string): string;
326
329
  useEmptyDocument(): void;
327
330
  selectTopLevelNode(index: number): void;
331
+ selectEditorPanel(panel: RichContentEditorPanel): void;
332
+ openAdvancedRules(): void;
333
+ onAdvancedRulesToggle(event: Event): void;
328
334
  addTopLevelNode(): void;
329
335
  removeTopLevelNode(index: number): void;
330
336
  moveTopLevelNode(index: number, delta: number): void;
@@ -486,6 +492,8 @@ declare class PraxisRichContentConfigEditor implements SettingsValueProvider, On
486
492
  getPresetSelection(node: RichBlockNode): string;
487
493
  getPresetOptionValue(ref: CorePresetRef): string;
488
494
  getIssueMessages(path: string): string[];
495
+ getIssueCount(path: string): number;
496
+ getSelectedNodeIssueCount(): number;
489
497
  isEditableNodeType(type: string): type is EditableTopLevelNodeType;
490
498
  private isPresenterNodeType;
491
499
  private load;