@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 +122 -1
- package/fesm2022/praxisui-rich-content.mjs +832 -9
- package/index.d.ts +93 -6
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,127 @@
|
|
|
1
1
|
# PraxisRichContent
|
|
2
2
|
|
|
3
|
-
|
|
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
|
|
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
|
|
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,
|
|
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<
|
|
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
|
-
|
|
79
|
-
|
|
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": "
|
|
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": "^
|
|
7
|
+
"@praxisui/core": "^8.0.0-beta.0"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"tslib": "^2.3.0"
|