@praxisui/rich-content 6.0.0-beta.0 → 8.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,127 @@
1
1
  # PraxisRichContent
2
2
 
3
- This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 20.3.0.
3
+ `PraxisRichContent` renders metadata-driven rich nodes and blocks used by Praxis
4
+ components such as table renderers, list metrics and compact status surfaces.
5
+
6
+ ## Page Builder Integration
7
+
8
+ `@praxisui/rich-content` is a first-class widget for `@praxisui/page-builder`.
9
+ Register the metadata provider so the palette can discover the renderer:
10
+
11
+ ```ts
12
+ import { providePraxisRichContentMetadata } from '@praxisui/rich-content';
13
+
14
+ bootstrapApplication(AppComponent, {
15
+ providers: [
16
+ providePraxisRichContentMetadata(),
17
+ ],
18
+ });
19
+ ```
20
+
21
+ Use the component as a page widget by setting `definition.id` to
22
+ `praxis-rich-content` and passing the canonical document through
23
+ `definition.inputs.document`.
24
+
25
+ ```ts
26
+ const page = {
27
+ widgets: [
28
+ {
29
+ key: 'overview-rich-content',
30
+ definition: {
31
+ id: 'praxis-rich-content',
32
+ inputs: {
33
+ document: {
34
+ kind: 'praxis.rich-content',
35
+ version: '1.0.0',
36
+ nodes: [
37
+ {
38
+ type: 'card',
39
+ title: 'Resumo',
40
+ content: [
41
+ { type: 'text', text: 'Conteudo editorial declarativo' },
42
+ { type: 'badge', label: 'canonico' },
43
+ ],
44
+ },
45
+ ],
46
+ },
47
+ },
48
+ },
49
+ },
50
+ ],
51
+ };
52
+ ```
53
+
54
+ For AI-assisted page authoring, register `RICH_CONTENT_AI_CAPABILITIES` in the
55
+ page-builder widget catalog map for `praxis-rich-content`.
56
+
57
+ ## Visual Authoring
58
+
59
+ The component metadata exposes `PraxisRichContentConfigEditor` as the canonical
60
+ settings-panel editor for `praxis-rich-content`. Hosts such as
61
+ `@praxisui/page-builder` should open this editor from the component metadata
62
+ instead of creating local rich-content JSON editors.
63
+
64
+ The editor receives the widget input envelope and returns the same canonical
65
+ shape expected by the runtime:
66
+
67
+ ```ts
68
+ {
69
+ inputs: {
70
+ document: {
71
+ kind: 'praxis.rich-content',
72
+ version: '1.0.0',
73
+ nodes: []
74
+ },
75
+ layout: 'block',
76
+ rootClassName: 'employee-expansion-rich'
77
+ }
78
+ }
79
+ ```
80
+
81
+ This keeps the authoring round-trip aligned with the renderer:
82
+ `open -> edit -> apply/save -> persist -> reopen -> render`.
83
+
84
+ Authoring labels, helper text and validation messages use the rich-content i18n
85
+ dictionary. Apps can provide overrides with `providePraxisRichContentI18n()` or
86
+ reuse `resolvePraxisRichContentText()` when extending the canonical editor.
87
+
88
+ ## Layout Modes
89
+
90
+ The component defaults to `layout="block"` to preserve document-style rendering.
91
+ Use `layout="inline"` when rendering compact multi-node compositions such as
92
+ `icon + text`, badges, chips, links, buttons or value/caption pairs.
93
+
94
+ ```html
95
+ <praxis-rich-content
96
+ layout="inline"
97
+ rootClassName="status-badge__content"
98
+ [nodes]="[
99
+ { type: 'icon', icon: 'check_circle' },
100
+ { type: 'text', text: 'Ativo' }
101
+ ]"
102
+ ></praxis-rich-content>
103
+ ```
104
+
105
+ Inline mode adds `prx-rich-content-root--inline` to the root and
106
+ `prx-rich-node--inline` to each rendered node, allowing host components to align
107
+ siblings without overriding the default block contract.
108
+
109
+ Consumers can tune inline layout through inherited CSS custom properties on the
110
+ `praxis-rich-content` host or an ancestor:
111
+
112
+ ```css
113
+ .metric-value {
114
+ --prx-rich-content-inline-align-items: baseline;
115
+ --prx-rich-content-inline-flex-wrap: wrap;
116
+ --prx-rich-content-inline-gap: 6px;
117
+ }
118
+ ```
119
+
120
+ Supported inline variables:
121
+
122
+ - `--prx-rich-content-inline-align-items`, default `center`
123
+ - `--prx-rich-content-inline-flex-wrap`, default `nowrap`
124
+ - `--prx-rich-content-inline-gap`, default `6px`
4
125
 
5
126
  ## Code scaffolding
6
127
 
@@ -1,8 +1,11 @@
1
1
  import * as i1 from '@angular/common';
2
2
  import { CommonModule } from '@angular/common';
3
3
  import * as i0 from '@angular/core';
4
- import { InjectionToken, inject, Injectable, input, computed, Input, ChangeDetectionStrategy, Component } from '@angular/core';
5
- import { PraxisJsonLogicService } from '@praxisui/core';
4
+ import { InjectionToken, inject, Injectable, input, computed, Input, ChangeDetectionStrategy, Component, ChangeDetectorRef, ENVIRONMENT_INITIALIZER } from '@angular/core';
5
+ import { PraxisJsonLogicService, providePraxisI18n, PraxisI18nService, SETTINGS_PANEL_DATA, ComponentMetadataRegistry } from '@praxisui/core';
6
+ import * as i1$1 from '@angular/forms';
7
+ import { FormsModule } from '@angular/forms';
8
+ import { BehaviorSubject } from 'rxjs';
6
9
 
7
10
  const PRAXIS_RICH_BLOCK_PRESETS = new InjectionToken('PRAXIS_RICH_BLOCK_PRESETS');
8
11
  class RichContentPresetRegistryService {
@@ -31,6 +34,7 @@ class PraxisRichContent {
31
34
  nodes = input(null, ...(ngDevMode ? [{ debugName: "nodes" }] : []));
32
35
  context = input(null, ...(ngDevMode ? [{ debugName: "context" }] : []));
33
36
  hostCapabilities = input(null, ...(ngDevMode ? [{ debugName: "hostCapabilities" }] : []));
37
+ layout = input('block', ...(ngDevMode ? [{ debugName: "layout" }] : []));
34
38
  rootClassName = '';
35
39
  renderableNodes = computed(() => {
36
40
  const explicitNodes = this.nodes();
@@ -130,6 +134,42 @@ class PraxisRichContent {
130
134
  resolveCardSubtitle(node) {
131
135
  return this.resolveValue(node.subtitleExpr) ?? node.subtitle ?? null;
132
136
  }
137
+ resolveMediaBlockAvatarNodes(node) {
138
+ return node.avatar ? [node.avatar] : [];
139
+ }
140
+ resolveMediaBlockTitleNodes(node) {
141
+ return node.title ? [node.title] : [];
142
+ }
143
+ resolveMediaBlockSubtitleNodes(node) {
144
+ return node.subtitle ? [node.subtitle] : [];
145
+ }
146
+ resolveMediaBlockMetaNodes(node) {
147
+ return node.meta ? [node.meta] : [];
148
+ }
149
+ resolveMediaBlockTrailingNodes(node) {
150
+ return (node.trailing ?? []);
151
+ }
152
+ resolveTimelineTitle(node) {
153
+ return this.resolveValue(node.titleExpr) ?? node.title ?? null;
154
+ }
155
+ resolveTimelineItems(node) {
156
+ return Array.isArray(node.items) ? node.items : [];
157
+ }
158
+ resolveTimelineItemTitle(item) {
159
+ return this.resolveValue(item.titleExpr) ?? item.title ?? null;
160
+ }
161
+ resolveTimelineItemSubtitle(item) {
162
+ return this.resolveValue(item.subtitleExpr) ?? item.subtitle ?? null;
163
+ }
164
+ resolveTimelineItemMeta(item) {
165
+ return this.resolveValue(item.metaExpr) ?? item.meta ?? null;
166
+ }
167
+ resolveTimelineItemIcon(item) {
168
+ return this.resolveValue(item.iconExpr) ?? item.icon ?? null;
169
+ }
170
+ resolveTimelineItemBadge(item) {
171
+ return this.resolveValue(item.badgeExpr) ?? item.badge ?? null;
172
+ }
133
173
  resolvePresetNodes(node) {
134
174
  const localPreset = this.presetRegistry.get(node.ref);
135
175
  if (localPreset) {
@@ -167,8 +207,12 @@ class PraxisRichContent {
167
207
  .join('');
168
208
  }
169
209
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisRichContent, deps: [], target: i0.ɵɵFactoryTarget.Component });
170
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PraxisRichContent, isStandalone: true, selector: "praxis-rich-content", inputs: { document: { classPropertyName: "document", publicName: "document", isSignal: true, isRequired: false, transformFunction: null }, nodes: { classPropertyName: "nodes", publicName: "nodes", isSignal: true, isRequired: false, transformFunction: null }, context: { classPropertyName: "context", publicName: "context", isSignal: true, isRequired: false, transformFunction: null }, hostCapabilities: { classPropertyName: "hostCapabilities", publicName: "hostCapabilities", isSignal: true, isRequired: false, transformFunction: null }, rootClassName: { classPropertyName: "rootClassName", publicName: "rootClassName", isSignal: false, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
171
- <div class="prx-rich-content-root" [ngClass]="rootClassName">
210
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PraxisRichContent, isStandalone: true, selector: "praxis-rich-content", inputs: { document: { classPropertyName: "document", publicName: "document", isSignal: true, isRequired: false, transformFunction: null }, nodes: { classPropertyName: "nodes", publicName: "nodes", isSignal: true, isRequired: false, transformFunction: null }, context: { classPropertyName: "context", publicName: "context", isSignal: true, isRequired: false, transformFunction: null }, hostCapabilities: { classPropertyName: "hostCapabilities", publicName: "hostCapabilities", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null }, rootClassName: { classPropertyName: "rootClassName", publicName: "rootClassName", isSignal: false, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
211
+ <div
212
+ class="prx-rich-content-root"
213
+ [class.prx-rich-content-root--inline]="layout() === 'inline'"
214
+ [ngClass]="rootClassName"
215
+ >
172
216
  <ng-container
173
217
  *ngTemplateOutlet="renderNodes; context: { $implicit: renderableNodes() }"
174
218
  ></ng-container>
@@ -179,6 +223,7 @@ class PraxisRichContent {
179
223
  @if (isNodeVisible(node)) {
180
224
  <div
181
225
  class="prx-rich-node"
226
+ [class.prx-rich-node--inline]="layout() === 'inline'"
182
227
  [attr.data-rich-node-type]="node.type"
183
228
  [attr.data-rich-node-id]="node.id || null"
184
229
  [attr.data-testid]="node.testId || null"
@@ -262,6 +307,99 @@ class PraxisRichContent {
262
307
  ></ng-container>
263
308
  </div>
264
309
  }
310
+ @case ('mediaBlock') {
311
+ <div class="prx-rich-media-block">
312
+ @if (resolveMediaBlockAvatarNodes(node); as avatarNodes) {
313
+ @if (avatarNodes.length) {
314
+ <div class="prx-rich-media-block__avatar">
315
+ <ng-container
316
+ *ngTemplateOutlet="renderNodes; context: { $implicit: avatarNodes }"
317
+ ></ng-container>
318
+ </div>
319
+ }
320
+ }
321
+ <div class="prx-rich-media-block__body">
322
+ @if (resolveMediaBlockTitleNodes(node); as titleNodes) {
323
+ @if (titleNodes.length) {
324
+ <div class="prx-rich-media-block__title">
325
+ <ng-container
326
+ *ngTemplateOutlet="renderNodes; context: { $implicit: titleNodes }"
327
+ ></ng-container>
328
+ </div>
329
+ }
330
+ }
331
+ @if (resolveMediaBlockSubtitleNodes(node); as subtitleNodes) {
332
+ @if (subtitleNodes.length) {
333
+ <div class="prx-rich-media-block__subtitle">
334
+ <ng-container
335
+ *ngTemplateOutlet="renderNodes; context: { $implicit: subtitleNodes }"
336
+ ></ng-container>
337
+ </div>
338
+ }
339
+ }
340
+ @if (resolveMediaBlockMetaNodes(node); as metaNodes) {
341
+ @if (metaNodes.length) {
342
+ <div class="prx-rich-media-block__meta">
343
+ <ng-container
344
+ *ngTemplateOutlet="renderNodes; context: { $implicit: metaNodes }"
345
+ ></ng-container>
346
+ </div>
347
+ }
348
+ }
349
+ </div>
350
+ @if (resolveMediaBlockTrailingNodes(node); as trailingNodes) {
351
+ @if (trailingNodes.length) {
352
+ <div class="prx-rich-media-block__trailing">
353
+ <ng-container
354
+ *ngTemplateOutlet="renderNodes; context: { $implicit: trailingNodes }"
355
+ ></ng-container>
356
+ </div>
357
+ }
358
+ }
359
+ </div>
360
+ }
361
+ @case ('timeline') {
362
+ <div class="prx-rich-timeline">
363
+ @if (resolveTimelineTitle(node); as timelineTitle) {
364
+ <div class="prx-rich-timeline__title">{{ timelineTitle }}</div>
365
+ }
366
+ @if (resolveTimelineItems(node).length) {
367
+ <div class="prx-rich-timeline__items">
368
+ @for (item of resolveTimelineItems(node); track item.id ?? $index) {
369
+ <article class="prx-rich-timeline__item">
370
+ <div class="prx-rich-timeline__item-marker">
371
+ @if (resolveTimelineItemIcon(item); as icon) {
372
+ <span class="prx-rich-timeline__item-icon material-symbols-outlined">
373
+ {{ icon }}
374
+ </span>
375
+ } @else {
376
+ <span class="prx-rich-timeline__item-dot"></span>
377
+ }
378
+ </div>
379
+ <div class="prx-rich-timeline__item-body">
380
+ @if (resolveTimelineItemTitle(item); as itemTitle) {
381
+ <div class="prx-rich-timeline__item-title">{{ itemTitle }}</div>
382
+ }
383
+ @if (resolveTimelineItemSubtitle(item); as itemSubtitle) {
384
+ <div class="prx-rich-timeline__item-subtitle">{{ itemSubtitle }}</div>
385
+ }
386
+ @if (resolveTimelineItemMeta(item); as itemMeta) {
387
+ <div class="prx-rich-timeline__item-meta">{{ itemMeta }}</div>
388
+ }
389
+ </div>
390
+ @if (resolveTimelineItemBadge(item); as itemBadge) {
391
+ <div class="prx-rich-timeline__item-badge">
392
+ <span class="prx-rich-badge">{{ itemBadge }}</span>
393
+ </div>
394
+ }
395
+ </article>
396
+ }
397
+ </div>
398
+ } @else if (node.emptyText) {
399
+ <div class="prx-rich-timeline__empty">{{ node.emptyText }}</div>
400
+ }
401
+ </div>
402
+ }
265
403
  @case ('preset') {
266
404
  @if (resolvePresetNodes(node); as presetNodes) {
267
405
  <ng-container
@@ -274,12 +412,16 @@ class PraxisRichContent {
274
412
  }
275
413
  }
276
414
  </ng-template>
277
- `, isInline: true, styles: [".prx-rich-content-root{display:block}.prx-rich-node{min-width:0}.prx-rich-compose{display:flex;align-items:center;gap:8px}.prx-rich-compose.direction-column{flex-direction:column;align-items:stretch}.prx-rich-compose.wrap{flex-wrap:wrap}.prx-rich-badge{display:inline-flex;align-items:center;padding:2px 10px;border-radius:999px;background:var(--md-sys-color-primary-container, #e8def8);color:var(--md-sys-color-on-primary-container, #21005d);font-size:12px;line-height:20px}.prx-rich-avatar{display:inline-flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:50%;background:var(--md-sys-color-surface-container-high, #ece6f0);overflow:hidden}.prx-rich-avatar-image{width:100%;height:100%;object-fit:cover}.prx-rich-avatar-fallback{font-weight:600}.prx-rich-card{display:flex;flex-direction:column;gap:8px;padding:16px;border:1px solid var(--md-sys-color-outline-variant, #cac4d0);border-radius:16px;background:var(--md-sys-color-surface, #fff)}.prx-rich-card-title{font-weight:600}.prx-rich-card-subtitle,.prx-rich-metric-caption{color:var(--md-sys-color-on-surface-variant, #49454f);font-size:12px}.prx-rich-metric{display:flex;flex-direction:column;gap:4px}.prx-rich-metric-value{font-size:20px;font-weight:700}.prx-rich-progress{display:flex;flex-direction:column;gap:4px}.prx-rich-progress-bar{width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
415
+ `, isInline: true, styles: [".prx-rich-content-root{display:block}.prx-rich-content-root--inline{display:inline-flex;align-items:var(--prx-rich-content-inline-align-items, center);flex-wrap:var(--prx-rich-content-inline-flex-wrap, nowrap);gap:var(--prx-rich-content-inline-gap, 6px);min-width:0;max-width:100%;vertical-align:middle}.prx-rich-node{min-width:0}.prx-rich-node--inline{display:inline-flex;align-items:center;min-width:0}.prx-rich-compose{display:flex;align-items:center;gap:8px}.prx-rich-compose.direction-column{flex-direction:column;align-items:stretch}.prx-rich-compose.wrap{flex-wrap:wrap}.prx-rich-badge{display:inline-flex;align-items:center;padding:2px 10px;border-radius:999px;background:var(--md-sys-color-primary-container, #e8def8);color:var(--md-sys-color-on-primary-container, #21005d);font-size:12px;line-height:20px}.prx-rich-avatar{display:inline-flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:50%;background:var(--md-sys-color-surface-container-high, #ece6f0);overflow:hidden}.prx-rich-avatar-image{width:100%;height:100%;object-fit:cover}.prx-rich-avatar-fallback{font-weight:600}.prx-rich-card{display:flex;flex-direction:column;gap:8px;padding:16px;border:1px solid var(--md-sys-color-outline-variant, #cac4d0);border-radius:16px;background:var(--md-sys-color-surface, #fff)}.prx-rich-card-title{font-weight:600}.prx-rich-card-subtitle,.prx-rich-metric-caption{color:var(--md-sys-color-on-surface-variant, #49454f);font-size:12px}.prx-rich-metric{display:flex;flex-direction:column;gap:4px}.prx-rich-metric-value{font-size:20px;font-weight:700}.prx-rich-progress{display:flex;flex-direction:column;gap:4px}.prx-rich-progress-bar{width:100%}.prx-rich-media-block{display:grid;grid-template-columns:auto minmax(0,1fr) auto;gap:12px;align-items:center;min-width:0}.prx-rich-media-block__avatar,.prx-rich-media-block__body,.prx-rich-media-block__trailing{min-width:0}.prx-rich-media-block__body{display:flex;flex-direction:column;gap:6px}.prx-rich-media-block__title{font-weight:700}.prx-rich-media-block__subtitle{color:var(--md-sys-color-on-surface-variant, #49454f);font-size:13px}.prx-rich-media-block__meta{color:var(--md-sys-color-on-surface-variant, #49454f);font-size:12px}.prx-rich-media-block__trailing{display:flex;align-items:center;justify-content:flex-end}.prx-rich-timeline{display:flex;flex-direction:column;gap:12px;min-width:0}.prx-rich-timeline__title{font-weight:700}.prx-rich-timeline__items{display:flex;flex-direction:column;gap:12px}.prx-rich-timeline__item{display:grid;grid-template-columns:auto minmax(0,1fr) auto;gap:12px;align-items:flex-start;min-width:0}.prx-rich-timeline__item-marker{display:inline-flex;align-items:center;justify-content:center;width:24px;min-height:24px}.prx-rich-timeline__item-icon{font-size:18px;line-height:18px;color:var(--md-sys-color-primary, #6750a4)}.prx-rich-timeline__item-dot{width:10px;height:10px;border-radius:50%;background:var(--md-sys-color-primary, #6750a4);display:inline-block;margin-top:4px}.prx-rich-timeline__item-body{display:flex;flex-direction:column;gap:4px;min-width:0}.prx-rich-timeline__item-title{font-weight:600}.prx-rich-timeline__item-subtitle,.prx-rich-timeline__item-meta,.prx-rich-timeline__empty{color:var(--md-sys-color-on-surface-variant, #49454f);font-size:12px}.prx-rich-timeline__item-badge{display:inline-flex;align-items:flex-start;justify-content:flex-end}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
278
416
  }
279
417
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisRichContent, decorators: [{
280
418
  type: Component,
281
419
  args: [{ selector: 'praxis-rich-content', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
282
- <div class="prx-rich-content-root" [ngClass]="rootClassName">
420
+ <div
421
+ class="prx-rich-content-root"
422
+ [class.prx-rich-content-root--inline]="layout() === 'inline'"
423
+ [ngClass]="rootClassName"
424
+ >
283
425
  <ng-container
284
426
  *ngTemplateOutlet="renderNodes; context: { $implicit: renderableNodes() }"
285
427
  ></ng-container>
@@ -290,6 +432,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
290
432
  @if (isNodeVisible(node)) {
291
433
  <div
292
434
  class="prx-rich-node"
435
+ [class.prx-rich-node--inline]="layout() === 'inline'"
293
436
  [attr.data-rich-node-type]="node.type"
294
437
  [attr.data-rich-node-id]="node.id || null"
295
438
  [attr.data-testid]="node.testId || null"
@@ -373,6 +516,99 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
373
516
  ></ng-container>
374
517
  </div>
375
518
  }
519
+ @case ('mediaBlock') {
520
+ <div class="prx-rich-media-block">
521
+ @if (resolveMediaBlockAvatarNodes(node); as avatarNodes) {
522
+ @if (avatarNodes.length) {
523
+ <div class="prx-rich-media-block__avatar">
524
+ <ng-container
525
+ *ngTemplateOutlet="renderNodes; context: { $implicit: avatarNodes }"
526
+ ></ng-container>
527
+ </div>
528
+ }
529
+ }
530
+ <div class="prx-rich-media-block__body">
531
+ @if (resolveMediaBlockTitleNodes(node); as titleNodes) {
532
+ @if (titleNodes.length) {
533
+ <div class="prx-rich-media-block__title">
534
+ <ng-container
535
+ *ngTemplateOutlet="renderNodes; context: { $implicit: titleNodes }"
536
+ ></ng-container>
537
+ </div>
538
+ }
539
+ }
540
+ @if (resolveMediaBlockSubtitleNodes(node); as subtitleNodes) {
541
+ @if (subtitleNodes.length) {
542
+ <div class="prx-rich-media-block__subtitle">
543
+ <ng-container
544
+ *ngTemplateOutlet="renderNodes; context: { $implicit: subtitleNodes }"
545
+ ></ng-container>
546
+ </div>
547
+ }
548
+ }
549
+ @if (resolveMediaBlockMetaNodes(node); as metaNodes) {
550
+ @if (metaNodes.length) {
551
+ <div class="prx-rich-media-block__meta">
552
+ <ng-container
553
+ *ngTemplateOutlet="renderNodes; context: { $implicit: metaNodes }"
554
+ ></ng-container>
555
+ </div>
556
+ }
557
+ }
558
+ </div>
559
+ @if (resolveMediaBlockTrailingNodes(node); as trailingNodes) {
560
+ @if (trailingNodes.length) {
561
+ <div class="prx-rich-media-block__trailing">
562
+ <ng-container
563
+ *ngTemplateOutlet="renderNodes; context: { $implicit: trailingNodes }"
564
+ ></ng-container>
565
+ </div>
566
+ }
567
+ }
568
+ </div>
569
+ }
570
+ @case ('timeline') {
571
+ <div class="prx-rich-timeline">
572
+ @if (resolveTimelineTitle(node); as timelineTitle) {
573
+ <div class="prx-rich-timeline__title">{{ timelineTitle }}</div>
574
+ }
575
+ @if (resolveTimelineItems(node).length) {
576
+ <div class="prx-rich-timeline__items">
577
+ @for (item of resolveTimelineItems(node); track item.id ?? $index) {
578
+ <article class="prx-rich-timeline__item">
579
+ <div class="prx-rich-timeline__item-marker">
580
+ @if (resolveTimelineItemIcon(item); as icon) {
581
+ <span class="prx-rich-timeline__item-icon material-symbols-outlined">
582
+ {{ icon }}
583
+ </span>
584
+ } @else {
585
+ <span class="prx-rich-timeline__item-dot"></span>
586
+ }
587
+ </div>
588
+ <div class="prx-rich-timeline__item-body">
589
+ @if (resolveTimelineItemTitle(item); as itemTitle) {
590
+ <div class="prx-rich-timeline__item-title">{{ itemTitle }}</div>
591
+ }
592
+ @if (resolveTimelineItemSubtitle(item); as itemSubtitle) {
593
+ <div class="prx-rich-timeline__item-subtitle">{{ itemSubtitle }}</div>
594
+ }
595
+ @if (resolveTimelineItemMeta(item); as itemMeta) {
596
+ <div class="prx-rich-timeline__item-meta">{{ itemMeta }}</div>
597
+ }
598
+ </div>
599
+ @if (resolveTimelineItemBadge(item); as itemBadge) {
600
+ <div class="prx-rich-timeline__item-badge">
601
+ <span class="prx-rich-badge">{{ itemBadge }}</span>
602
+ </div>
603
+ }
604
+ </article>
605
+ }
606
+ </div>
607
+ } @else if (node.emptyText) {
608
+ <div class="prx-rich-timeline__empty">{{ node.emptyText }}</div>
609
+ }
610
+ </div>
611
+ }
376
612
  @case ('preset') {
377
613
  @if (resolvePresetNodes(node); as presetNodes) {
378
614
  <ng-container
@@ -385,11 +621,598 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
385
621
  }
386
622
  }
387
623
  </ng-template>
388
- `, styles: [".prx-rich-content-root{display:block}.prx-rich-node{min-width:0}.prx-rich-compose{display:flex;align-items:center;gap:8px}.prx-rich-compose.direction-column{flex-direction:column;align-items:stretch}.prx-rich-compose.wrap{flex-wrap:wrap}.prx-rich-badge{display:inline-flex;align-items:center;padding:2px 10px;border-radius:999px;background:var(--md-sys-color-primary-container, #e8def8);color:var(--md-sys-color-on-primary-container, #21005d);font-size:12px;line-height:20px}.prx-rich-avatar{display:inline-flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:50%;background:var(--md-sys-color-surface-container-high, #ece6f0);overflow:hidden}.prx-rich-avatar-image{width:100%;height:100%;object-fit:cover}.prx-rich-avatar-fallback{font-weight:600}.prx-rich-card{display:flex;flex-direction:column;gap:8px;padding:16px;border:1px solid var(--md-sys-color-outline-variant, #cac4d0);border-radius:16px;background:var(--md-sys-color-surface, #fff)}.prx-rich-card-title{font-weight:600}.prx-rich-card-subtitle,.prx-rich-metric-caption{color:var(--md-sys-color-on-surface-variant, #49454f);font-size:12px}.prx-rich-metric{display:flex;flex-direction:column;gap:4px}.prx-rich-metric-value{font-size:20px;font-weight:700}.prx-rich-progress{display:flex;flex-direction:column;gap:4px}.prx-rich-progress-bar{width:100%}\n"] }]
389
- }], propDecorators: { document: [{ type: i0.Input, args: [{ isSignal: true, alias: "document", required: false }] }], nodes: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodes", required: false }] }], context: [{ type: i0.Input, args: [{ isSignal: true, alias: "context", required: false }] }], hostCapabilities: [{ type: i0.Input, args: [{ isSignal: true, alias: "hostCapabilities", required: false }] }], rootClassName: [{
624
+ `, styles: [".prx-rich-content-root{display:block}.prx-rich-content-root--inline{display:inline-flex;align-items:var(--prx-rich-content-inline-align-items, center);flex-wrap:var(--prx-rich-content-inline-flex-wrap, nowrap);gap:var(--prx-rich-content-inline-gap, 6px);min-width:0;max-width:100%;vertical-align:middle}.prx-rich-node{min-width:0}.prx-rich-node--inline{display:inline-flex;align-items:center;min-width:0}.prx-rich-compose{display:flex;align-items:center;gap:8px}.prx-rich-compose.direction-column{flex-direction:column;align-items:stretch}.prx-rich-compose.wrap{flex-wrap:wrap}.prx-rich-badge{display:inline-flex;align-items:center;padding:2px 10px;border-radius:999px;background:var(--md-sys-color-primary-container, #e8def8);color:var(--md-sys-color-on-primary-container, #21005d);font-size:12px;line-height:20px}.prx-rich-avatar{display:inline-flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:50%;background:var(--md-sys-color-surface-container-high, #ece6f0);overflow:hidden}.prx-rich-avatar-image{width:100%;height:100%;object-fit:cover}.prx-rich-avatar-fallback{font-weight:600}.prx-rich-card{display:flex;flex-direction:column;gap:8px;padding:16px;border:1px solid var(--md-sys-color-outline-variant, #cac4d0);border-radius:16px;background:var(--md-sys-color-surface, #fff)}.prx-rich-card-title{font-weight:600}.prx-rich-card-subtitle,.prx-rich-metric-caption{color:var(--md-sys-color-on-surface-variant, #49454f);font-size:12px}.prx-rich-metric{display:flex;flex-direction:column;gap:4px}.prx-rich-metric-value{font-size:20px;font-weight:700}.prx-rich-progress{display:flex;flex-direction:column;gap:4px}.prx-rich-progress-bar{width:100%}.prx-rich-media-block{display:grid;grid-template-columns:auto minmax(0,1fr) auto;gap:12px;align-items:center;min-width:0}.prx-rich-media-block__avatar,.prx-rich-media-block__body,.prx-rich-media-block__trailing{min-width:0}.prx-rich-media-block__body{display:flex;flex-direction:column;gap:6px}.prx-rich-media-block__title{font-weight:700}.prx-rich-media-block__subtitle{color:var(--md-sys-color-on-surface-variant, #49454f);font-size:13px}.prx-rich-media-block__meta{color:var(--md-sys-color-on-surface-variant, #49454f);font-size:12px}.prx-rich-media-block__trailing{display:flex;align-items:center;justify-content:flex-end}.prx-rich-timeline{display:flex;flex-direction:column;gap:12px;min-width:0}.prx-rich-timeline__title{font-weight:700}.prx-rich-timeline__items{display:flex;flex-direction:column;gap:12px}.prx-rich-timeline__item{display:grid;grid-template-columns:auto minmax(0,1fr) auto;gap:12px;align-items:flex-start;min-width:0}.prx-rich-timeline__item-marker{display:inline-flex;align-items:center;justify-content:center;width:24px;min-height:24px}.prx-rich-timeline__item-icon{font-size:18px;line-height:18px;color:var(--md-sys-color-primary, #6750a4)}.prx-rich-timeline__item-dot{width:10px;height:10px;border-radius:50%;background:var(--md-sys-color-primary, #6750a4);display:inline-block;margin-top:4px}.prx-rich-timeline__item-body{display:flex;flex-direction:column;gap:4px;min-width:0}.prx-rich-timeline__item-title{font-weight:600}.prx-rich-timeline__item-subtitle,.prx-rich-timeline__item-meta,.prx-rich-timeline__empty{color:var(--md-sys-color-on-surface-variant, #49454f);font-size:12px}.prx-rich-timeline__item-badge{display:inline-flex;align-items:flex-start;justify-content:flex-end}\n"] }]
625
+ }], propDecorators: { document: [{ type: i0.Input, args: [{ isSignal: true, alias: "document", required: false }] }], nodes: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodes", required: false }] }], context: [{ type: i0.Input, args: [{ isSignal: true, alias: "context", required: false }] }], hostCapabilities: [{ type: i0.Input, args: [{ isSignal: true, alias: "hostCapabilities", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], rootClassName: [{
390
626
  type: Input
391
627
  }] } });
392
628
 
629
+ const PRAXIS_RICH_CONTENT_EN_US = {
630
+ 'praxis.richContent.editor.eyebrow': 'RichContentDocument',
631
+ 'praxis.richContent.editor.title': 'Configure rich content',
632
+ 'praxis.richContent.editor.subtitle': 'Edit the canonical document consumed by the renderer. The saved payload preserves the remaining widget inputs.',
633
+ 'praxis.richContent.editor.valid': 'Valid document',
634
+ 'praxis.richContent.editor.invalid': 'Invalid JSON',
635
+ 'praxis.richContent.editor.layout': 'Layout',
636
+ 'praxis.richContent.editor.layout.block': 'Block',
637
+ 'praxis.richContent.editor.layout.inline': 'Inline',
638
+ 'praxis.richContent.editor.rootClass': 'Root class',
639
+ 'praxis.richContent.editor.rootClassPlaceholder': 'Example: employee-expansion-rich',
640
+ 'praxis.richContent.editor.document': 'Document JSON',
641
+ 'praxis.richContent.editor.documentHelp': 'Paste or edit a RichContentDocument with kind "praxis.rich-content" and a nodes array.',
642
+ 'praxis.richContent.editor.restore': 'Restore',
643
+ 'praxis.richContent.editor.emptyDocument': 'Empty document',
644
+ 'praxis.richContent.editor.validation.documentShape': 'The document must have kind "praxis.rich-content" and nodes[].',
645
+ 'praxis.richContent.editor.validation.invalidJson': 'Invalid JSON document.',
646
+ };
647
+
648
+ const PRAXIS_RICH_CONTENT_PT_BR = {
649
+ 'praxis.richContent.editor.eyebrow': 'RichContentDocument',
650
+ 'praxis.richContent.editor.title': 'Configurar conteudo rico',
651
+ 'praxis.richContent.editor.subtitle': 'Edite o documento canonico consumido pelo renderer. O payload salvo preserva os demais inputs do widget.',
652
+ 'praxis.richContent.editor.valid': 'Documento valido',
653
+ 'praxis.richContent.editor.invalid': 'JSON invalido',
654
+ 'praxis.richContent.editor.layout': 'Layout',
655
+ 'praxis.richContent.editor.layout.block': 'Bloco',
656
+ 'praxis.richContent.editor.layout.inline': 'Inline',
657
+ 'praxis.richContent.editor.rootClass': 'Classe raiz',
658
+ 'praxis.richContent.editor.rootClassPlaceholder': 'Exemplo: employee-expansion-rich',
659
+ 'praxis.richContent.editor.document': 'JSON do documento',
660
+ 'praxis.richContent.editor.documentHelp': 'Cole ou edite um RichContentDocument com kind "praxis.rich-content" e um array nodes.',
661
+ 'praxis.richContent.editor.restore': 'Restaurar',
662
+ 'praxis.richContent.editor.emptyDocument': 'Documento vazio',
663
+ 'praxis.richContent.editor.validation.documentShape': 'O documento deve ter kind "praxis.rich-content" e nodes[].',
664
+ 'praxis.richContent.editor.validation.invalidJson': 'Documento JSON invalido.',
665
+ };
666
+
667
+ function createPraxisRichContentI18nConfig(options = {}) {
668
+ const dictionaries = {
669
+ 'pt-BR': {
670
+ ...PRAXIS_RICH_CONTENT_PT_BR,
671
+ ...(options.dictionaries?.['pt-BR'] ?? {}),
672
+ },
673
+ 'en-US': {
674
+ ...PRAXIS_RICH_CONTENT_EN_US,
675
+ ...(options.dictionaries?.['en-US'] ?? {}),
676
+ },
677
+ };
678
+ for (const [locale, dictionary] of Object.entries(options.dictionaries ?? {})) {
679
+ if (locale === 'pt-BR' || locale === 'en-US')
680
+ continue;
681
+ dictionaries[locale] = { ...(dictionaries[locale] ?? {}), ...dictionary };
682
+ }
683
+ return {
684
+ locale: options.locale,
685
+ fallbackLocale: options.fallbackLocale ?? 'en-US',
686
+ dictionaries,
687
+ };
688
+ }
689
+ function providePraxisRichContentI18n(options = {}) {
690
+ return providePraxisI18n(createPraxisRichContentI18nConfig(options));
691
+ }
692
+ function resolvePraxisRichContentText(i18n, key, fallback) {
693
+ const namespacedKey = key.startsWith('praxis.richContent.')
694
+ ? key
695
+ : `praxis.richContent.${key}`;
696
+ return i18n.t(namespacedKey, undefined, fallback) || fallback;
697
+ }
698
+
699
+ const EMPTY_DOCUMENT = {
700
+ kind: 'praxis.rich-content',
701
+ version: '1.0.0',
702
+ nodes: [],
703
+ };
704
+ class PraxisRichContentConfigEditor {
705
+ cdr = inject(ChangeDetectorRef);
706
+ i18n = inject(PraxisI18nService);
707
+ injectedData = inject(SETTINGS_PANEL_DATA, {
708
+ optional: true,
709
+ });
710
+ inputs = this.injectedData?.inputs ?? null;
711
+ isDirty$ = new BehaviorSubject(false);
712
+ isValid$ = new BehaviorSubject(true);
713
+ isBusy$ = new BehaviorSubject(false);
714
+ documentJson = '';
715
+ layout = 'block';
716
+ rootClassName = '';
717
+ errorMessage = '';
718
+ valid = true;
719
+ initialInputs = this.normalizeInputs(this.inputs);
720
+ ngOnChanges(changes) {
721
+ if (changes['inputs']) {
722
+ this.load(this.normalizeInputs(this.inputs), false);
723
+ }
724
+ }
725
+ ngOnInit() {
726
+ if (!this.documentJson) {
727
+ this.load(this.normalizeInputs(this.inputs), false);
728
+ }
729
+ }
730
+ getSettingsValue() {
731
+ const document = this.parseDocument();
732
+ if (!document)
733
+ return null;
734
+ return {
735
+ inputs: {
736
+ ...this.initialInputs,
737
+ document,
738
+ layout: this.layout,
739
+ rootClassName: this.rootClassName.trim() || null,
740
+ },
741
+ };
742
+ }
743
+ onSave() {
744
+ const value = this.getSettingsValue();
745
+ if (value) {
746
+ this.initialInputs = this.cloneInputs(value.inputs);
747
+ this.isDirty$.next(false);
748
+ }
749
+ return value;
750
+ }
751
+ reset() {
752
+ this.load(this.initialInputs, false);
753
+ this.cdr.detectChanges();
754
+ }
755
+ markDirty() {
756
+ this.isDirty$.next(!this.matchesInitialState());
757
+ }
758
+ onDocumentJsonChange() {
759
+ this.parseDocument();
760
+ this.markDirty();
761
+ }
762
+ tx(key, fallback) {
763
+ return resolvePraxisRichContentText(this.i18n, key, fallback);
764
+ }
765
+ useEmptyDocument() {
766
+ this.documentJson = this.stringify(EMPTY_DOCUMENT);
767
+ this.parseDocument();
768
+ this.markDirty();
769
+ }
770
+ load(inputs, dirty) {
771
+ const document = inputs.document ?? EMPTY_DOCUMENT;
772
+ const layout = inputs.layout === 'inline' ? 'inline' : 'block';
773
+ const rootClassName = typeof inputs.rootClassName === 'string' ? inputs.rootClassName : '';
774
+ this.initialInputs = {
775
+ ...this.cloneInputs(inputs),
776
+ document: this.cloneValue(document),
777
+ layout,
778
+ rootClassName: rootClassName.trim() || null,
779
+ };
780
+ this.layout = layout;
781
+ this.rootClassName = rootClassName;
782
+ this.documentJson = this.stringify(document);
783
+ this.parseDocument();
784
+ this.isDirty$.next(dirty);
785
+ }
786
+ normalizeInputs(inputs) {
787
+ return this.cloneInputs(inputs ?? {});
788
+ }
789
+ parseDocument() {
790
+ try {
791
+ const parsed = JSON.parse(this.documentJson);
792
+ if (!parsed ||
793
+ typeof parsed !== 'object' ||
794
+ parsed.kind !== 'praxis.rich-content' ||
795
+ !Array.isArray(parsed.nodes)) {
796
+ throw new Error(this.tx('editor.validation.documentShape', 'The document must have kind "praxis.rich-content" and nodes[].'));
797
+ }
798
+ this.valid = true;
799
+ this.errorMessage = '';
800
+ this.isValid$.next(true);
801
+ return parsed;
802
+ }
803
+ catch (error) {
804
+ this.valid = false;
805
+ this.errorMessage =
806
+ error instanceof Error
807
+ ? error.message
808
+ : this.tx('editor.validation.invalidJson', 'Invalid JSON document.');
809
+ this.isValid$.next(false);
810
+ return null;
811
+ }
812
+ }
813
+ matchesInitialState() {
814
+ const current = this.getSettingsValue();
815
+ if (!current)
816
+ return false;
817
+ return (JSON.stringify(current.inputs) === JSON.stringify(this.initialInputs));
818
+ }
819
+ cloneInputs(value) {
820
+ return this.cloneValue(value);
821
+ }
822
+ cloneValue(value) {
823
+ return JSON.parse(JSON.stringify(value));
824
+ }
825
+ stringify(value) {
826
+ return JSON.stringify(value, null, 2);
827
+ }
828
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisRichContentConfigEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
829
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PraxisRichContentConfigEditor, isStandalone: true, selector: "praxis-rich-content-config-editor", inputs: { inputs: "inputs" }, providers: [providePraxisRichContentI18n()], usesOnChanges: true, ngImport: i0, template: `
830
+ <section class="prx-rich-editor" data-testid="rich-content-config-editor">
831
+ <header class="prx-rich-editor__header">
832
+ <div>
833
+ <p class="prx-rich-editor__eyebrow">
834
+ {{ tx('editor.eyebrow', 'RichContentDocument') }}
835
+ </p>
836
+ <h2>{{ tx('editor.title', 'Configure rich content') }}</h2>
837
+ <p>
838
+ {{
839
+ tx(
840
+ 'editor.subtitle',
841
+ 'Edit the canonical document consumed by the renderer. The saved payload preserves the remaining widget inputs.'
842
+ )
843
+ }}
844
+ </p>
845
+ </div>
846
+ <span
847
+ class="prx-rich-editor__status"
848
+ [class.invalid]="!valid"
849
+ aria-live="polite"
850
+ >
851
+ {{
852
+ valid
853
+ ? tx('editor.valid', 'Valid document')
854
+ : tx('editor.invalid', 'Invalid JSON')
855
+ }}
856
+ </span>
857
+ </header>
858
+
859
+ <div class="prx-rich-editor__grid">
860
+ <label>
861
+ <span>{{ tx('editor.layout', 'Layout') }}</span>
862
+ <select
863
+ name="rich-content-layout"
864
+ [(ngModel)]="layout"
865
+ (ngModelChange)="markDirty()"
866
+ data-testid="rich-content-layout-input"
867
+ >
868
+ <option value="block">
869
+ {{ tx('editor.layout.block', 'Block') }}
870
+ </option>
871
+ <option value="inline">
872
+ {{ tx('editor.layout.inline', 'Inline') }}
873
+ </option>
874
+ </select>
875
+ </label>
876
+
877
+ <label>
878
+ <span>{{ tx('editor.rootClass', 'Root class') }}</span>
879
+ <input
880
+ name="rich-content-root-class"
881
+ [(ngModel)]="rootClassName"
882
+ (ngModelChange)="markDirty()"
883
+ [placeholder]="
884
+ tx('editor.rootClassPlaceholder', 'Example: employee-expansion-rich')
885
+ "
886
+ data-testid="rich-content-root-class-input"
887
+ />
888
+ </label>
889
+ </div>
890
+
891
+ <label class="prx-rich-editor__document">
892
+ <span>{{ tx('editor.document', 'Document JSON') }}</span>
893
+ <textarea
894
+ name="rich-content-document"
895
+ [(ngModel)]="documentJson"
896
+ (ngModelChange)="onDocumentJsonChange()"
897
+ [attr.aria-describedby]="
898
+ errorMessage
899
+ ? 'rich-content-config-error'
900
+ : 'rich-content-document-help'
901
+ "
902
+ [attr.aria-invalid]="!valid"
903
+ spellcheck="false"
904
+ data-testid="rich-content-document-input"
905
+ ></textarea>
906
+ </label>
907
+
908
+ @if (errorMessage) {
909
+ <p
910
+ id="rich-content-config-error"
911
+ class="prx-rich-editor__error"
912
+ data-testid="rich-content-config-error"
913
+ >
914
+ {{ errorMessage }}
915
+ </p>
916
+ } @else {
917
+ <p id="rich-content-document-help" class="prx-rich-editor__help">
918
+ {{
919
+ tx(
920
+ 'editor.documentHelp',
921
+ 'Paste or edit a RichContentDocument with kind "praxis.rich-content" and a nodes array.'
922
+ )
923
+ }}
924
+ </p>
925
+ }
926
+
927
+ <div class="prx-rich-editor__actions">
928
+ <button type="button" (click)="reset()">
929
+ {{ tx('editor.restore', 'Restore') }}
930
+ </button>
931
+ <button type="button" (click)="useEmptyDocument()">
932
+ {{ tx('editor.emptyDocument', 'Empty document') }}
933
+ </button>
934
+ </div>
935
+ </section>
936
+ `, 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)}label,.prx-rich-editor__document{display:grid;gap:7px;font-weight:700}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__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:12px;color:#991b1b;background:color-mix(in srgb,#dc2626 9%,transparent)}.prx-rich-editor__actions{display:flex;flex-wrap:wrap;gap:10px}button{border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:999px;padding:8px 14px;background:var(--md-sys-color-surface, #fff);color:var(--md-sys-color-primary, #3154e7);font-weight:700;cursor:pointer}@media(max-width:760px){.prx-rich-editor__header,.prx-rich-editor__grid{grid-template-columns:1fr;display:grid}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.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: i1$1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
937
+ }
938
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisRichContentConfigEditor, decorators: [{
939
+ type: Component,
940
+ args: [{ selector: 'praxis-rich-content-config-editor', standalone: true, imports: [CommonModule, FormsModule], providers: [providePraxisRichContentI18n()], changeDetection: ChangeDetectionStrategy.OnPush, template: `
941
+ <section class="prx-rich-editor" data-testid="rich-content-config-editor">
942
+ <header class="prx-rich-editor__header">
943
+ <div>
944
+ <p class="prx-rich-editor__eyebrow">
945
+ {{ tx('editor.eyebrow', 'RichContentDocument') }}
946
+ </p>
947
+ <h2>{{ tx('editor.title', 'Configure rich content') }}</h2>
948
+ <p>
949
+ {{
950
+ tx(
951
+ 'editor.subtitle',
952
+ 'Edit the canonical document consumed by the renderer. The saved payload preserves the remaining widget inputs.'
953
+ )
954
+ }}
955
+ </p>
956
+ </div>
957
+ <span
958
+ class="prx-rich-editor__status"
959
+ [class.invalid]="!valid"
960
+ aria-live="polite"
961
+ >
962
+ {{
963
+ valid
964
+ ? tx('editor.valid', 'Valid document')
965
+ : tx('editor.invalid', 'Invalid JSON')
966
+ }}
967
+ </span>
968
+ </header>
969
+
970
+ <div class="prx-rich-editor__grid">
971
+ <label>
972
+ <span>{{ tx('editor.layout', 'Layout') }}</span>
973
+ <select
974
+ name="rich-content-layout"
975
+ [(ngModel)]="layout"
976
+ (ngModelChange)="markDirty()"
977
+ data-testid="rich-content-layout-input"
978
+ >
979
+ <option value="block">
980
+ {{ tx('editor.layout.block', 'Block') }}
981
+ </option>
982
+ <option value="inline">
983
+ {{ tx('editor.layout.inline', 'Inline') }}
984
+ </option>
985
+ </select>
986
+ </label>
987
+
988
+ <label>
989
+ <span>{{ tx('editor.rootClass', 'Root class') }}</span>
990
+ <input
991
+ name="rich-content-root-class"
992
+ [(ngModel)]="rootClassName"
993
+ (ngModelChange)="markDirty()"
994
+ [placeholder]="
995
+ tx('editor.rootClassPlaceholder', 'Example: employee-expansion-rich')
996
+ "
997
+ data-testid="rich-content-root-class-input"
998
+ />
999
+ </label>
1000
+ </div>
1001
+
1002
+ <label class="prx-rich-editor__document">
1003
+ <span>{{ tx('editor.document', 'Document JSON') }}</span>
1004
+ <textarea
1005
+ name="rich-content-document"
1006
+ [(ngModel)]="documentJson"
1007
+ (ngModelChange)="onDocumentJsonChange()"
1008
+ [attr.aria-describedby]="
1009
+ errorMessage
1010
+ ? 'rich-content-config-error'
1011
+ : 'rich-content-document-help'
1012
+ "
1013
+ [attr.aria-invalid]="!valid"
1014
+ spellcheck="false"
1015
+ data-testid="rich-content-document-input"
1016
+ ></textarea>
1017
+ </label>
1018
+
1019
+ @if (errorMessage) {
1020
+ <p
1021
+ id="rich-content-config-error"
1022
+ class="prx-rich-editor__error"
1023
+ data-testid="rich-content-config-error"
1024
+ >
1025
+ {{ errorMessage }}
1026
+ </p>
1027
+ } @else {
1028
+ <p id="rich-content-document-help" class="prx-rich-editor__help">
1029
+ {{
1030
+ tx(
1031
+ 'editor.documentHelp',
1032
+ 'Paste or edit a RichContentDocument with kind "praxis.rich-content" and a nodes array.'
1033
+ )
1034
+ }}
1035
+ </p>
1036
+ }
1037
+
1038
+ <div class="prx-rich-editor__actions">
1039
+ <button type="button" (click)="reset()">
1040
+ {{ tx('editor.restore', 'Restore') }}
1041
+ </button>
1042
+ <button type="button" (click)="useEmptyDocument()">
1043
+ {{ tx('editor.emptyDocument', 'Empty document') }}
1044
+ </button>
1045
+ </div>
1046
+ </section>
1047
+ `, 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)}label,.prx-rich-editor__document{display:grid;gap:7px;font-weight:700}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__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:12px;color:#991b1b;background:color-mix(in srgb,#dc2626 9%,transparent)}.prx-rich-editor__actions{display:flex;flex-wrap:wrap;gap:10px}button{border:1px solid var(--md-sys-color-outline-variant, #d7dce5);border-radius:999px;padding:8px 14px;background:var(--md-sys-color-surface, #fff);color:var(--md-sys-color-primary, #3154e7);font-weight:700;cursor:pointer}@media(max-width:760px){.prx-rich-editor__header,.prx-rich-editor__grid{grid-template-columns:1fr;display:grid}}\n"] }]
1048
+ }], propDecorators: { inputs: [{
1049
+ type: Input
1050
+ }] } });
1051
+
1052
+ const DEFAULT_RICH_CONTENT_DOCUMENT = {
1053
+ kind: 'praxis.rich-content',
1054
+ version: '1.0.0',
1055
+ nodes: [
1056
+ {
1057
+ type: 'card',
1058
+ title: 'Rich content',
1059
+ content: [
1060
+ {
1061
+ type: 'text',
1062
+ text: 'Configure este bloco com um RichContentDocument canonico.',
1063
+ },
1064
+ {
1065
+ type: 'badge',
1066
+ label: 'praxis.rich-content',
1067
+ },
1068
+ ],
1069
+ },
1070
+ ],
1071
+ };
1072
+ const PRAXIS_RICH_CONTENT_COMPONENT_METADATA = {
1073
+ id: 'praxis-rich-content',
1074
+ selector: 'praxis-rich-content',
1075
+ component: PraxisRichContent,
1076
+ friendlyName: 'Praxis Rich Content',
1077
+ description: 'Renderer canonico de RichContentDocument para blocos editoriais, cards, media blocks, timelines e primitives de apresentacao.',
1078
+ icon: 'dashboard_customize',
1079
+ inputs: [
1080
+ {
1081
+ name: 'document',
1082
+ type: 'RichContentDocument | null',
1083
+ label: 'Rich content document',
1084
+ description: 'Documento canonico praxis.rich-content renderizado pelo componente.',
1085
+ default: DEFAULT_RICH_CONTENT_DOCUMENT,
1086
+ },
1087
+ {
1088
+ name: 'nodes',
1089
+ type: 'RichBlockNode[] | null',
1090
+ label: 'Nodes',
1091
+ description: 'Lista direta de nodes quando o host ja controla o envelope do documento.',
1092
+ },
1093
+ {
1094
+ name: 'context',
1095
+ type: 'JsonLogicDataRecord | null',
1096
+ label: 'Context',
1097
+ description: 'Contexto usado para resolver Json Logic, expressoes e bindings dos nodes.',
1098
+ },
1099
+ {
1100
+ name: 'hostCapabilities',
1101
+ type: 'RichBlockHostCapabilities | null',
1102
+ label: 'Host capabilities',
1103
+ description: 'Capacidades mediadas pelo host para presets, dados, actions e embeds governados.',
1104
+ },
1105
+ {
1106
+ name: 'layout',
1107
+ type: "'block' | 'inline'",
1108
+ label: 'Layout',
1109
+ default: 'block',
1110
+ description: 'Modo de renderizacao do documento: bloco ou inline.',
1111
+ },
1112
+ {
1113
+ name: 'rootClassName',
1114
+ type: 'string',
1115
+ label: 'Root class',
1116
+ description: 'Classe CSS aplicada ao root do renderer para surfaces de host.',
1117
+ },
1118
+ ],
1119
+ outputs: [],
1120
+ tags: ['widget', 'rich-content', 'editorial', 'presentation', 'cards', 'timeline'],
1121
+ lib: '@praxisui/rich-content',
1122
+ configEditor: {
1123
+ component: PraxisRichContentConfigEditor,
1124
+ title: 'Configurar rich content',
1125
+ },
1126
+ layoutHints: {
1127
+ recommendedCols: 6,
1128
+ recommendedRows: 3,
1129
+ minCols: 2,
1130
+ minRows: 1,
1131
+ },
1132
+ };
1133
+ function providePraxisRichContentMetadata() {
1134
+ return {
1135
+ provide: ENVIRONMENT_INITIALIZER,
1136
+ multi: true,
1137
+ useFactory: (registry) => () => {
1138
+ registry.register(PRAXIS_RICH_CONTENT_COMPONENT_METADATA);
1139
+ },
1140
+ deps: [ComponentMetadataRegistry],
1141
+ };
1142
+ }
1143
+
1144
+ const RICH_CONTENT_AI_CAPABILITIES = {
1145
+ version: 'v1.0',
1146
+ enums: {
1147
+ richContentNodeType: [
1148
+ 'text',
1149
+ 'icon',
1150
+ 'image',
1151
+ 'badge',
1152
+ 'avatar',
1153
+ 'metric',
1154
+ 'progress',
1155
+ 'compose',
1156
+ 'card',
1157
+ 'mediaBlock',
1158
+ 'timeline',
1159
+ 'preset',
1160
+ ],
1161
+ richContentLayout: ['block', 'inline'],
1162
+ richContentGap: ['xs', 'sm', 'md', 'lg', 'xl'],
1163
+ richContentDirection: ['row', 'column'],
1164
+ },
1165
+ targets: [
1166
+ 'praxis-rich-content',
1167
+ 'rich-content',
1168
+ 'page-builder-rich-content-widget',
1169
+ 'dynamic-form-editorial',
1170
+ 'stepper-editorial',
1171
+ ],
1172
+ notes: [
1173
+ 'Use document como input principal quando o componente for hospedado pelo page-builder.',
1174
+ 'O documento deve usar kind "praxis.rich-content" e version "1.0.0".',
1175
+ 'Nodes de rich content representam apresentacao/documento rico; nao substituem field controls de dynamic-fields.',
1176
+ 'visibleWhen, classWhen e styleWhen usam Json Logic canonico, nao string DSL.',
1177
+ 'hostCapabilities e reservado para mediacao governada de preset/action/embed; nao serializar funcoes arbitrarias em contratos publicos.',
1178
+ ],
1179
+ capabilities: [
1180
+ { path: 'document', category: 'richContent', valueKind: 'object', description: 'RichContentDocument canonico renderizado pelo componente.' },
1181
+ { path: 'document.kind', category: 'richContent', valueKind: 'string', description: 'Identificador fixo do documento. Use "praxis.rich-content".' },
1182
+ { path: 'document.version', category: 'richContent', valueKind: 'string', description: 'Versao do contrato do documento. Use "1.0.0".' },
1183
+ { path: 'document.context', category: 'richContent', valueKind: 'object', description: 'Config de escopos e aliases de contexto para bindings.' },
1184
+ { path: 'document.nodes', category: 'richNode', valueKind: 'array', description: 'Lista declarativa de nodes ricos.' },
1185
+ { path: 'document.nodes[]', category: 'richNode', valueKind: 'object', description: 'Node rich-content; o shape depende de document.nodes[].type.' },
1186
+ { path: 'document.nodes[].type', category: 'richNode', valueKind: 'enum', allowedValues: ['text', 'icon', 'image', 'badge', 'avatar', 'metric', 'progress', 'compose', 'card', 'mediaBlock', 'timeline', 'preset'], description: 'Tipo canonico do node.' },
1187
+ { path: 'document.nodes[].id', category: 'richNode', valueKind: 'string', description: 'Identificador estavel opcional para diffs, testes e telemetria.' },
1188
+ { path: 'document.nodes[].testId', category: 'richNode', valueKind: 'string', description: 'Test id opcional para E2E.' },
1189
+ { path: 'document.nodes[].className', category: 'richNode', valueKind: 'string', description: 'Classe CSS declarativa aplicada ao node.' },
1190
+ { path: 'document.nodes[].style', category: 'richNode', valueKind: 'object', description: 'Estilos inline declarativos simples para o node.' },
1191
+ { path: 'document.nodes[].visibleWhen', category: 'richRules', valueKind: 'expression', description: 'Regra Json Logic que controla visibilidade do node.' },
1192
+ { path: 'document.nodes[].classWhen', category: 'richRules', valueKind: 'array', description: 'Lista de regras Json Logic que aplicam classes.' },
1193
+ { path: 'document.nodes[].styleWhen', category: 'richRules', valueKind: 'array', description: 'Lista de regras Json Logic que aplicam estilos.' },
1194
+ { path: 'document.nodes[].text', category: 'richNode', valueKind: 'string', description: 'Texto literal para node text.' },
1195
+ { path: 'document.nodes[].textExpr', category: 'richNode', valueKind: 'string', description: 'Path simples resolvido contra o contexto para node text.' },
1196
+ { path: 'document.nodes[].icon', category: 'richNode', valueKind: 'string', description: 'Nome do icone Material Symbols para node icon ou badge.' },
1197
+ { path: 'document.nodes[].label', category: 'richNode', valueKind: 'string', description: 'Label literal para badge, metric ou progress.' },
1198
+ { path: 'document.nodes[].labelExpr', category: 'richNode', valueKind: 'string', description: 'Path simples resolvido contra o contexto para label.' },
1199
+ { path: 'document.nodes[].src', category: 'richNode', valueKind: 'string', description: 'URL literal para image.' },
1200
+ { path: 'document.nodes[].srcExpr', category: 'richNode', valueKind: 'string', description: 'Path simples resolvido contra o contexto para image src.' },
1201
+ { path: 'document.nodes[].alt', category: 'richNode', valueKind: 'string', description: 'Texto alternativo literal para image.' },
1202
+ { path: 'document.nodes[].name', category: 'richNode', valueKind: 'string', description: 'Nome literal para avatar.' },
1203
+ { path: 'document.nodes[].nameExpr', category: 'richNode', valueKind: 'string', description: 'Path simples resolvido contra o contexto para avatar.' },
1204
+ { path: 'document.nodes[].valueExpr', category: 'richNode', valueKind: 'string', description: 'Path simples resolvido contra o contexto para metric/progress.' },
1205
+ { path: 'document.nodes[].items', category: 'richNode', valueKind: 'array', description: 'Itens internos de compose.' },
1206
+ { path: 'document.nodes[].content', category: 'richNode', valueKind: 'array', description: 'Conteudo interno de card.' },
1207
+ { path: 'document.nodes[].trailing', category: 'richNode', valueKind: 'array', description: 'Conteudo trailing de mediaBlock.' },
1208
+ { path: 'document.nodes[].ref', category: 'richNode', valueKind: 'object', description: 'Referencia registravel de preset rich-block.' },
1209
+ { path: 'context', category: 'richContent', valueKind: 'object', description: 'Contexto externo injetado pelo host ou page-builder.' },
1210
+ { path: 'layout', category: 'richContent', valueKind: 'enum', allowedValues: ['block', 'inline'], description: 'Modo de layout do renderer.' },
1211
+ { path: 'rootClassName', category: 'richContent', valueKind: 'string', description: 'Classe aplicada ao root do renderer.' },
1212
+ { path: 'hostCapabilities', category: 'richHost', valueKind: 'object', description: 'Capacidades mediadas pelo host. Nao usar funcoes serializadas em JSON publico.' },
1213
+ ],
1214
+ };
1215
+
393
1216
  /*
394
1217
  * Public API Surface of praxis-rich-content
395
1218
  */
@@ -398,4 +1221,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
398
1221
  * Generated bundle index. Do not edit.
399
1222
  */
400
1223
 
401
- export { PRAXIS_RICH_BLOCK_PRESETS, PraxisRichContent, RichContentPresetRegistryService };
1224
+ export { PRAXIS_RICH_BLOCK_PRESETS, PRAXIS_RICH_CONTENT_COMPONENT_METADATA, PraxisRichContent, PraxisRichContentConfigEditor, RICH_CONTENT_AI_CAPABILITIES, RichContentPresetRegistryService, createPraxisRichContentI18nConfig, providePraxisRichContentI18n, providePraxisRichContentMetadata, resolvePraxisRichContentText };
package/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as _angular_core from '@angular/core';
2
- import { InjectionToken } from '@angular/core';
3
- import { RichContentDocument, RichPrimitiveNode, RichPresetReferenceNode, JsonLogicRecord, RichBlockHostCapabilities, RichBlockNode, RichPresenterNode, RichComposeNode, RichCardNode, CorePresetDescriptor, CorePresetRef } from '@praxisui/core';
2
+ import { OnChanges, OnInit, SimpleChanges, InjectionToken, Provider } from '@angular/core';
3
+ import { RichContentDocument, RichPrimitiveNode, RichPresetReferenceNode, JsonLogicDataRecord, RichBlockHostCapabilities, RichBlockNode, RichPresenterNode, RichComposeNode, RichCardNode, RichMediaBlockNode, RichTimelineNode, RichTimelineItem, SettingsValueProvider, CorePresetDescriptor, CorePresetRef, ComponentDocMeta, AiCapabilityCategory, AiValueKind, AiCapability, AiCapabilityCatalog, PraxisI18nDictionary, PraxisI18nConfig, PraxisI18nService } from '@praxisui/core';
4
+ import { BehaviorSubject } from 'rxjs';
4
5
 
5
6
  type RenderableNode = RichPrimitiveNode | RichPresetReferenceNode;
6
7
  declare class PraxisRichContent {
@@ -8,8 +9,9 @@ declare class PraxisRichContent {
8
9
  private readonly presetRegistry;
9
10
  readonly document: _angular_core.InputSignal<RichContentDocument>;
10
11
  readonly nodes: _angular_core.InputSignal<RenderableNode[]>;
11
- readonly context: _angular_core.InputSignal<JsonLogicRecord>;
12
+ readonly context: _angular_core.InputSignal<JsonLogicDataRecord>;
12
13
  readonly hostCapabilities: _angular_core.InputSignal<RichBlockHostCapabilities>;
14
+ readonly layout: _angular_core.InputSignal<"block" | "inline">;
13
15
  rootClassName: string;
14
16
  readonly renderableNodes: _angular_core.Signal<RenderableNode[]>;
15
17
  isNodeVisible(node: RichBlockNode): boolean;
@@ -51,13 +53,68 @@ declare class PraxisRichContent {
51
53
  resolveComposeGap(node: RichComposeNode): string;
52
54
  resolveCardTitle(node: RichCardNode): string | null;
53
55
  resolveCardSubtitle(node: RichCardNode): string | null;
56
+ resolveMediaBlockAvatarNodes(node: RichMediaBlockNode): RenderableNode[];
57
+ resolveMediaBlockTitleNodes(node: RichMediaBlockNode): RenderableNode[];
58
+ resolveMediaBlockSubtitleNodes(node: RichMediaBlockNode): RenderableNode[];
59
+ resolveMediaBlockMetaNodes(node: RichMediaBlockNode): RenderableNode[];
60
+ resolveMediaBlockTrailingNodes(node: RichMediaBlockNode): RenderableNode[];
61
+ resolveTimelineTitle(node: RichTimelineNode): string | null;
62
+ resolveTimelineItems(node: RichTimelineNode): RichTimelineItem[];
63
+ resolveTimelineItemTitle(item: RichTimelineItem): string | null;
64
+ resolveTimelineItemSubtitle(item: RichTimelineItem): string | null;
65
+ resolveTimelineItemMeta(item: RichTimelineItem): string | null;
66
+ resolveTimelineItemIcon(item: RichTimelineItem): string | null;
67
+ resolveTimelineItemBadge(item: RichTimelineItem): string | null;
54
68
  resolvePresetNodes(node: RichPresetReferenceNode): RenderableNode[] | null;
55
69
  private buildEvaluationContext;
56
70
  private resolveValue;
57
71
  private getByPath;
58
72
  private resolveInitials;
59
73
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<PraxisRichContent, never>;
60
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<PraxisRichContent, "praxis-rich-content", never, { "document": { "alias": "document"; "required": false; "isSignal": true; }; "nodes": { "alias": "nodes"; "required": false; "isSignal": true; }; "context": { "alias": "context"; "required": false; "isSignal": true; }; "hostCapabilities": { "alias": "hostCapabilities"; "required": false; "isSignal": true; }; "rootClassName": { "alias": "rootClassName"; "required": false; }; }, {}, never, never, true, never>;
74
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<PraxisRichContent, "praxis-rich-content", never, { "document": { "alias": "document"; "required": false; "isSignal": true; }; "nodes": { "alias": "nodes"; "required": false; "isSignal": true; }; "context": { "alias": "context"; "required": false; "isSignal": true; }; "hostCapabilities": { "alias": "hostCapabilities"; "required": false; "isSignal": true; }; "layout": { "alias": "layout"; "required": false; "isSignal": true; }; "rootClassName": { "alias": "rootClassName"; "required": false; }; }, {}, never, never, true, never>;
75
+ }
76
+
77
+ interface PraxisRichContentEditorInputs {
78
+ document?: RichContentDocument | null;
79
+ layout?: 'block' | 'inline';
80
+ rootClassName?: string | null;
81
+ [key: string]: unknown;
82
+ }
83
+ interface PraxisRichContentEditorValue {
84
+ inputs: PraxisRichContentEditorInputs;
85
+ }
86
+ declare class PraxisRichContentConfigEditor implements SettingsValueProvider, OnChanges, OnInit {
87
+ private readonly cdr;
88
+ private readonly i18n;
89
+ private readonly injectedData;
90
+ inputs: PraxisRichContentEditorInputs | null;
91
+ readonly isDirty$: BehaviorSubject<boolean>;
92
+ readonly isValid$: BehaviorSubject<boolean>;
93
+ readonly isBusy$: BehaviorSubject<boolean>;
94
+ documentJson: string;
95
+ layout: 'block' | 'inline';
96
+ rootClassName: string;
97
+ errorMessage: string;
98
+ valid: boolean;
99
+ private initialInputs;
100
+ ngOnChanges(changes: SimpleChanges): void;
101
+ ngOnInit(): void;
102
+ getSettingsValue(): PraxisRichContentEditorValue | null;
103
+ onSave(): PraxisRichContentEditorValue | null;
104
+ reset(): void;
105
+ markDirty(): void;
106
+ onDocumentJsonChange(): void;
107
+ tx(key: string, fallback: string): string;
108
+ useEmptyDocument(): void;
109
+ private load;
110
+ private normalizeInputs;
111
+ private parseDocument;
112
+ private matchesInitialState;
113
+ private cloneInputs;
114
+ private cloneValue;
115
+ private stringify;
116
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<PraxisRichContentConfigEditor, never>;
117
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<PraxisRichContentConfigEditor, "praxis-rich-content-config-editor", never, { "inputs": { "alias": "inputs"; "required": false; }; }, {}, never, never, true, never>;
61
118
  }
62
119
 
63
120
  interface RichBlockPresetDefinition extends CorePresetDescriptor {
@@ -75,5 +132,35 @@ declare class RichContentPresetRegistryService {
75
132
  static ɵprov: _angular_core.ɵɵInjectableDeclaration<RichContentPresetRegistryService>;
76
133
  }
77
134
 
78
- export { PRAXIS_RICH_BLOCK_PRESETS, PraxisRichContent, RichContentPresetRegistryService };
79
- export type { RichBlockPresetDefinition };
135
+ declare const PRAXIS_RICH_CONTENT_COMPONENT_METADATA: ComponentDocMeta;
136
+ declare function providePraxisRichContentMetadata(): Provider;
137
+
138
+ declare module '@praxisui/core' {
139
+ interface AiCapabilityCategoryMap {
140
+ richContent: true;
141
+ richNode: true;
142
+ richRules: true;
143
+ richHost: true;
144
+ }
145
+ }
146
+ type RichContentCapabilityCategory = AiCapabilityCategory;
147
+ type RichContentValueKind = AiValueKind;
148
+ interface RichContentCapability extends AiCapability {
149
+ category: RichContentCapabilityCategory;
150
+ }
151
+ interface RichContentCapabilityCatalog extends AiCapabilityCatalog {
152
+ capabilities: RichContentCapability[];
153
+ }
154
+ declare const RICH_CONTENT_AI_CAPABILITIES: RichContentCapabilityCatalog;
155
+
156
+ interface PraxisRichContentI18nOptions {
157
+ locale?: string;
158
+ fallbackLocale?: string;
159
+ dictionaries?: Record<string, PraxisI18nDictionary>;
160
+ }
161
+ declare function createPraxisRichContentI18nConfig(options?: PraxisRichContentI18nOptions): Partial<PraxisI18nConfig>;
162
+ declare function providePraxisRichContentI18n(options?: PraxisRichContentI18nOptions): _angular_core.Provider;
163
+ declare function resolvePraxisRichContentText(i18n: PraxisI18nService, key: string, fallback: string): string;
164
+
165
+ export { PRAXIS_RICH_BLOCK_PRESETS, PRAXIS_RICH_CONTENT_COMPONENT_METADATA, PraxisRichContent, PraxisRichContentConfigEditor, RICH_CONTENT_AI_CAPABILITIES, RichContentPresetRegistryService, createPraxisRichContentI18nConfig, providePraxisRichContentI18n, providePraxisRichContentMetadata, resolvePraxisRichContentText };
166
+ export type { PraxisRichContentEditorInputs, PraxisRichContentEditorValue, PraxisRichContentI18nOptions, RichBlockPresetDefinition, RichContentCapability, RichContentCapabilityCatalog, RichContentCapabilityCategory, RichContentValueKind };
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@praxisui/rich-content",
3
- "version": "6.0.0-beta.0",
3
+ "version": "8.0.0-beta.0",
4
4
  "peerDependencies": {
5
5
  "@angular/common": "^20.3.0",
6
6
  "@angular/core": "^20.3.0",
7
- "@praxisui/core": "^6.0.0-beta.0"
7
+ "@praxisui/core": "^8.0.0-beta.0"
8
8
  },
9
9
  "dependencies": {
10
10
  "tslib": "^2.3.0"