@praxisui/rich-content 8.0.0-beta.20 → 8.0.0-beta.22

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
@@ -79,6 +79,12 @@ style declaration can be edited visually. Advanced JSON remains available for
79
79
  less common nested fields and deep structures, but it is no longer the only
80
80
  authoring path.
81
81
 
82
+ Runtime conditional metadata is governed by the shared JSON Logic contract:
83
+ `visibleWhen` and `loadWhen` gate rendering, `disabledWhen` disables interactive
84
+ surfaces, and `classWhen`/`styleWhen` apply declarative visual state. Invalid
85
+ visibility/loading/style rules fail closed by not rendering or not applying the
86
+ rule; invalid `disabledWhen` fails safe by disabling the action surface.
87
+
82
88
  The editor receives the widget input envelope and returns the same canonical
83
89
  shape expected by the runtime:
84
90
 
@@ -140,7 +146,9 @@ platform surfaces:
140
146
  `recordSummary`, `statGroup`, `tabs`, `lookupResult`, `lookupCard`, `relatedRecord`, `actionCard`, `collapsibleCard`,
141
147
  `disclosure` and `accordion`
142
148
  - interactive buttons through `actionButton`, mediated by
143
- `hostCapabilities.dispatchAction(...)`
149
+ `hostCapabilities.dispatchAction(...)`; when `action.confirmMessage` is
150
+ present, runtime first calls `hostCapabilities.confirmAction(...)` and fails
151
+ closed without dispatch if the host does not confirm
144
152
  - governed action surfaces such as `lookupCard`, `relatedRecord`, `actionCard` and `formLauncher`, which keep
145
153
  the document canonical while delegating execution to host capabilities
146
154
  - capability-gated visibility through `requiresCapabilities` and
@@ -134,10 +134,13 @@ class PraxisRichContent {
134
134
  if (!this.supportsRequiredCapabilities(node)) {
135
135
  return false;
136
136
  }
137
- if (!node.visibleWhen) {
138
- return true;
137
+ if (node.visibleWhen && !this.evaluateRule(node.visibleWhen, false)) {
138
+ return false;
139
+ }
140
+ if (node.loadWhen && !this.evaluateRule(node.loadWhen, false)) {
141
+ return false;
139
142
  }
140
- return !!this.jsonLogic.evaluate(node.visibleWhen, this.buildEvaluationContext());
143
+ return true;
141
144
  }
142
145
  resolveNodeClasses(node) {
143
146
  const classes = new Set();
@@ -145,7 +148,7 @@ class PraxisRichContent {
145
148
  classes.add(node.className);
146
149
  }
147
150
  for (const rule of node.classWhen ?? []) {
148
- if (this.jsonLogic.evaluate(rule.expr, this.buildEvaluationContext())) {
151
+ if (this.evaluateRule(rule.expr, false)) {
149
152
  classes.add(rule.className);
150
153
  }
151
154
  }
@@ -154,7 +157,7 @@ class PraxisRichContent {
154
157
  resolveNodeStyles(node) {
155
158
  const styles = { ...(node.style ?? {}) };
156
159
  for (const rule of node.styleWhen ?? []) {
157
- if (this.jsonLogic.evaluate(rule.expr, this.buildEvaluationContext())) {
160
+ if (this.evaluateRule(rule.expr, false)) {
158
161
  Object.assign(styles, rule.style);
159
162
  }
160
163
  }
@@ -297,7 +300,7 @@ class PraxisRichContent {
297
300
  return false;
298
301
  }
299
302
  if (node.disabledWhen) {
300
- return !!this.jsonLogic.evaluate(node.disabledWhen, this.buildEvaluationContext());
303
+ return this.evaluateRule(node.disabledWhen, true);
301
304
  }
302
305
  const action = node.interaction?.action;
303
306
  if (!action) {
@@ -354,17 +357,33 @@ class PraxisRichContent {
354
357
  if (!action) {
355
358
  return;
356
359
  }
357
- const payload = action.payloadExpr != null
360
+ void this.dispatchRichAction(action);
361
+ }
362
+ resolveActionPayload(action) {
363
+ return action.payloadExpr != null
358
364
  ? this.resolveStructuredValue(action.payloadExpr)
359
365
  : action.payload;
360
- void this.hostCapabilities()?.dispatchAction?.(action.actionId, payload);
366
+ }
367
+ async dispatchRichAction(action, payload = this.resolveActionPayload(action)) {
368
+ const host = this.hostCapabilities();
369
+ if (action.confirmMessage) {
370
+ const confirmed = await host?.confirmAction?.({
371
+ actionId: action.actionId,
372
+ message: action.confirmMessage,
373
+ payload,
374
+ });
375
+ if (!confirmed) {
376
+ return;
377
+ }
378
+ }
379
+ await host?.dispatchAction?.(action.actionId, payload);
361
380
  }
362
381
  resolveActionButtonLabel(node) {
363
382
  return this.resolveValue(node.labelExpr) ?? node.label ?? node.action.actionId;
364
383
  }
365
384
  isActionButtonDisabled(node) {
366
385
  if (node.disabledWhen) {
367
- return !!this.jsonLogic.evaluate(node.disabledWhen, this.buildEvaluationContext());
386
+ return this.evaluateRule(node.disabledWhen, true);
368
387
  }
369
388
  const actionId = node.action.actionId;
370
389
  const host = this.hostCapabilities();
@@ -380,10 +399,7 @@ class PraxisRichContent {
380
399
  if (this.isActionButtonDisabled(node)) {
381
400
  return;
382
401
  }
383
- const payload = node.action.payloadExpr != null
384
- ? this.resolveStructuredValue(node.action.payloadExpr)
385
- : node.action.payload;
386
- void this.hostCapabilities()?.dispatchAction?.(node.action.actionId, payload);
402
+ void this.dispatchRichAction(node.action);
387
403
  }
388
404
  resolveCalloutTitle(node) {
389
405
  return this.resolveValue(node.titleExpr) ?? node.title ?? null;
@@ -481,7 +497,7 @@ class PraxisRichContent {
481
497
  if (!item.disabledWhen) {
482
498
  return false;
483
499
  }
484
- return !!this.jsonLogic.evaluate(item.disabledWhen, this.buildEvaluationContext());
500
+ return this.evaluateRule(item.disabledWhen, true);
485
501
  }
486
502
  isTabsItemActive(node, item, index, items) {
487
503
  return this.resolveTabsItemKey(item, index) === this.resolveActiveTabsItemKey(node, items);
@@ -622,7 +638,7 @@ class PraxisRichContent {
622
638
  return false;
623
639
  }
624
640
  if (node.disabledWhen) {
625
- return !!this.jsonLogic.evaluate(node.disabledWhen, this.buildEvaluationContext());
641
+ return this.evaluateRule(node.disabledWhen, true);
626
642
  }
627
643
  if (node.action.availabilityExpr) {
628
644
  return !this.resolveBooleanExpression(node.action.availabilityExpr);
@@ -634,14 +650,11 @@ class PraxisRichContent {
634
650
  if (!node.action || this.isRelatedRecordDisabled(node)) {
635
651
  return;
636
652
  }
637
- const payload = node.action.payloadExpr != null
638
- ? this.resolveStructuredValue(node.action.payloadExpr)
639
- : node.action.payload;
640
- void this.hostCapabilities()?.dispatchAction?.(node.action.actionId, payload);
653
+ void this.dispatchRichAction(node.action);
641
654
  }
642
655
  isLookupCardDisabled(node) {
643
656
  if (node.disabledWhen) {
644
- return !!this.jsonLogic.evaluate(node.disabledWhen, this.buildEvaluationContext());
657
+ return this.evaluateRule(node.disabledWhen, true);
645
658
  }
646
659
  if (node.action.availabilityExpr) {
647
660
  return !this.resolveBooleanExpression(node.action.availabilityExpr);
@@ -653,10 +666,7 @@ class PraxisRichContent {
653
666
  if (this.isLookupCardDisabled(node)) {
654
667
  return;
655
668
  }
656
- const payload = node.action.payloadExpr != null
657
- ? this.resolveStructuredValue(node.action.payloadExpr)
658
- : node.action.payload;
659
- void this.hostCapabilities()?.dispatchAction?.(node.action.actionId, payload);
669
+ void this.dispatchRichAction(node.action);
660
670
  }
661
671
  resolveActionCardTitle(node) {
662
672
  return this.resolveValue(node.titleExpr) ?? node.title ?? null;
@@ -675,7 +685,7 @@ class PraxisRichContent {
675
685
  }
676
686
  isActionCardDisabled(node) {
677
687
  if (node.disabledWhen) {
678
- return !!this.jsonLogic.evaluate(node.disabledWhen, this.buildEvaluationContext());
688
+ return this.evaluateRule(node.disabledWhen, true);
679
689
  }
680
690
  if (node.action.availabilityExpr) {
681
691
  return !this.resolveBooleanExpression(node.action.availabilityExpr);
@@ -687,10 +697,7 @@ class PraxisRichContent {
687
697
  if (this.isActionCardDisabled(node)) {
688
698
  return;
689
699
  }
690
- const payload = node.action.payloadExpr != null
691
- ? this.resolveStructuredValue(node.action.payloadExpr)
692
- : node.action.payload;
693
- void this.hostCapabilities()?.dispatchAction?.(node.action.actionId, payload);
700
+ void this.dispatchRichAction(node.action);
694
701
  }
695
702
  resolveCollapsibleCardTitle(node) {
696
703
  return this.resolveValue(node.titleExpr) ?? node.title ?? null;
@@ -718,7 +725,7 @@ class PraxisRichContent {
718
725
  }
719
726
  isFormLauncherDisabled(node) {
720
727
  if (node.disabledWhen) {
721
- return !!this.jsonLogic.evaluate(node.disabledWhen, this.buildEvaluationContext());
728
+ return this.evaluateRule(node.disabledWhen, true);
722
729
  }
723
730
  if (node.action.availabilityExpr) {
724
731
  return !this.resolveBooleanExpression(node.action.availabilityExpr);
@@ -738,7 +745,7 @@ class PraxisRichContent {
738
745
  const payload = resolvedPayload && typeof resolvedPayload === 'object'
739
746
  ? { formId: node.formId, ...resolvedPayload }
740
747
  : { formId: node.formId, payload: resolvedPayload };
741
- void this.hostCapabilities()?.dispatchAction?.(node.action.actionId, payload);
748
+ void this.dispatchRichAction(node.action, payload);
742
749
  }
743
750
  resolveDisclosureTitle(node) {
744
751
  return this.resolveValue(node.titleExpr) ?? node.title ?? null;
@@ -964,8 +971,16 @@ class PraxisRichContent {
964
971
  }
965
972
  isTabsItemVisible(item) {
966
973
  return (this.supportsItemRequiredCapabilities(item) &&
967
- (!item.visibleWhen ||
968
- !!this.jsonLogic.evaluate(item.visibleWhen, this.buildEvaluationContext())));
974
+ (!item.visibleWhen || this.evaluateRule(item.visibleWhen, false)) &&
975
+ (!item.loadWhen || this.evaluateRule(item.loadWhen, false)));
976
+ }
977
+ evaluateRule(expression, fallback) {
978
+ try {
979
+ return !!this.jsonLogic.evaluate(expression, this.buildEvaluationContext());
980
+ }
981
+ catch {
982
+ return fallback;
983
+ }
969
984
  }
970
985
  supportsItemRequiredCapabilities(item) {
971
986
  const required = item.requiresCapabilities;
@@ -14950,8 +14965,9 @@ const RICH_CONTENT_AI_CAPABILITIES = {
14950
14965
  'Use document como input principal quando o componente for hospedado pelo page-builder.',
14951
14966
  'O documento deve usar kind "praxis.rich-content" e version "1.0.0".',
14952
14967
  'Nodes de rich content representam apresentacao/documento rico; nao substituem field controls de dynamic-fields.',
14953
- 'visibleWhen, classWhen e styleWhen usam Json Logic canonico, nao string DSL.',
14968
+ 'visibleWhen, disabledWhen, loadWhen, classWhen e styleWhen usam Json Logic canonico, nao string DSL.',
14954
14969
  'hostCapabilities e reservado para mediacao governada de preset/action/embed; nao serializar funcoes arbitrarias em contratos publicos.',
14970
+ 'action.confirmMessage exige hostCapabilities.confirmAction em runtime; sem confirmacao governada do host a action falha fechada e nao faz dispatch.',
14955
14971
  'Use actionButton quando a action precisar ser mediada pelo host; action.actionId e o identificador canonico do fluxo.',
14956
14972
  'requiresCapabilities e capabilityMode permitem gating declarativo por snapshot de capability do host, sem inventar DSL local.',
14957
14973
  'Quando praxis-rich-content for hospedado por praxis-dynamic-page ou praxis-dynamic-form, hostCapabilities permanece runtime-only e nao pertence ao JSON persistido.',
@@ -14972,6 +14988,8 @@ const RICH_CONTENT_AI_CAPABILITIES = {
14972
14988
  { path: 'document.nodes[].className', category: 'richNode', valueKind: 'string', description: 'Classe CSS declarativa aplicada ao node.' },
14973
14989
  { path: 'document.nodes[].style', category: 'richNode', valueKind: 'object', description: 'Estilos inline declarativos simples para o node.' },
14974
14990
  { path: 'document.nodes[].visibleWhen', category: 'richRules', valueKind: 'expression', description: 'Regra Json Logic que controla visibilidade do node.' },
14991
+ { path: 'document.nodes[].disabledWhen', category: 'richRules', valueKind: 'expression', description: 'Regra Json Logic que desabilita superficies interativas do node em modo fail-safe.' },
14992
+ { path: 'document.nodes[].loadWhen', category: 'richRules', valueKind: 'expression', description: 'Regra Json Logic que controla se o node deve ser carregado/renderizado.' },
14975
14993
  { path: 'document.nodes[].requiresCapabilities', category: 'richHost', valueKind: 'array', description: 'Lista de capability ids exigidos para renderizar o node.' },
14976
14994
  { path: 'document.nodes[].capabilityMode', category: 'richHost', valueKind: 'enum', allowedValues: ['all', 'any'], description: 'Controla se todas ou qualquer capability exigida deve estar presente.' },
14977
14995
  { path: 'document.nodes[].classWhen', category: 'richRules', valueKind: 'array', description: 'Lista de regras Json Logic que aplicam classes.' },
@@ -15100,6 +15118,7 @@ const RICH_CONTENT_AI_CAPABILITIES = {
15100
15118
  { path: 'rootClassName', category: 'richContent', valueKind: 'string', description: 'Classe aplicada ao root do renderer.' },
15101
15119
  { path: 'hostCapabilities', category: 'richHost', valueKind: 'object', description: 'Capacidades mediadas pelo host. Nao usar funcoes serializadas em JSON publico.' },
15102
15120
  { path: 'hostCapabilities.dispatchAction', category: 'richHost', valueKind: 'object', description: 'Canal runtime-only para dispatch de actionId no host.' },
15121
+ { path: 'hostCapabilities.confirmAction', category: 'richHost', valueKind: 'object', description: 'Canal runtime-only para confirmar actions com confirmMessage antes do dispatch.' },
15103
15122
  { path: 'hostCapabilities.hasCapability', category: 'richHost', valueKind: 'object', description: 'Canal runtime-only para gating por capability no host.' },
15104
15123
  { path: 'hostCapabilities.isActionAvailable', category: 'richHost', valueKind: 'object', description: 'Canal runtime-only para disponibilidade de actions mediadas pelo host.' },
15105
15124
  ],
@@ -15249,6 +15268,10 @@ const blockPatchSchema = {
15249
15268
  className: { type: 'string' },
15250
15269
  style: { type: 'object' },
15251
15270
  visibleWhen: { type: 'object' },
15271
+ disabledWhen: { type: 'object' },
15272
+ loadWhen: { type: 'object' },
15273
+ classWhen: { type: 'array', items: { type: 'object' } },
15274
+ styleWhen: { type: 'array', items: { type: 'object' } },
15252
15275
  requiresCapabilities: { type: 'array', items: { type: 'string' } },
15253
15276
  capabilityMode: { enum: ['all', 'any'] },
15254
15277
  text: { type: 'string' },
@@ -15348,7 +15371,7 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
15348
15371
  { name: 'document', type: 'RichContentDocument | null', description: 'Canonical praxis.rich-content document rendered by the component.' },
15349
15372
  { name: 'nodes', type: 'RichBlockNode[] | null', description: 'Direct node list used only when the host controls the document envelope.' },
15350
15373
  { name: 'context', type: 'JsonLogicDataRecord | null', description: 'External context used by expression paths and Json Logic rules.' },
15351
- { name: 'hostCapabilities', type: 'RichBlockHostCapabilities | null', description: 'Host-mediated preset, action, data and embed capabilities; functions are not serialized in public contracts.' },
15374
+ { name: 'hostCapabilities', type: 'RichBlockHostCapabilities | null', description: 'Host-mediated preset, action, action confirmation, data and embed capabilities; functions are not serialized in public contracts.' },
15352
15375
  { name: 'layout', type: "'block' | 'inline'", allowedValues: ['block', 'inline'], description: 'Renderer layout mode.' },
15353
15376
  { name: 'rootClassName', type: 'string', description: 'CSS class applied to the renderer root.' },
15354
15377
  ],
@@ -15430,6 +15453,10 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
15430
15453
  'document.nodes[].className',
15431
15454
  'document.nodes[].style',
15432
15455
  'document.nodes[].visibleWhen',
15456
+ 'document.nodes[].disabledWhen',
15457
+ 'document.nodes[].loadWhen',
15458
+ 'document.nodes[].classWhen',
15459
+ 'document.nodes[].styleWhen',
15433
15460
  'document.nodes[].requiresCapabilities',
15434
15461
  'document.nodes[].capabilityMode',
15435
15462
  'document.nodes[].text',
@@ -15599,12 +15626,12 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
15599
15626
  scope: 'templating',
15600
15627
  targetKind: 'metadata',
15601
15628
  target: { kind: 'metadata', resolver: 'rich-block-by-id-or-index', ambiguityPolicy: 'fail', required: true },
15602
- inputSchema: { type: 'object', minProperties: 1, properties: { blockId: { type: 'string' }, testId: { type: 'string' }, className: { type: 'string' }, visibleWhen: { type: 'object' }, style: { type: 'object' }, requiresCapabilities: { type: 'array', items: { type: 'string' } }, capabilityMode: { enum: ['all', 'any'] } } },
15629
+ inputSchema: { type: 'object', minProperties: 1, properties: { blockId: { type: 'string' }, testId: { type: 'string' }, className: { type: 'string' }, visibleWhen: { type: 'object' }, disabledWhen: { type: 'object' }, loadWhen: { type: 'object' }, classWhen: { type: 'array', items: { type: 'object' } }, styleWhen: { type: 'array', items: { type: 'object' } }, style: { type: 'object' }, requiresCapabilities: { type: 'array', items: { type: 'string' } }, capabilityMode: { enum: ['all', 'any'] } } },
15603
15630
  effects: [{ kind: 'merge-by-key', path: 'document.nodes[]', key: 'id' }],
15604
15631
  destructive: false,
15605
15632
  requiresConfirmation: false,
15606
15633
  validators: ['block-exists', 'class-name-safe', 'visible-when-json-logic', 'unsafe-style-rejected', 'document-shape-canonical', 'editor-runtime-round-trip'],
15607
- affectedPaths: ['document.nodes[].testId', 'document.nodes[].className', 'document.nodes[].visibleWhen', 'document.nodes[].style', 'document.nodes[].requiresCapabilities', 'document.nodes[].capabilityMode'],
15634
+ affectedPaths: ['document.nodes[].testId', 'document.nodes[].className', 'document.nodes[].visibleWhen', 'document.nodes[].disabledWhen', 'document.nodes[].loadWhen', 'document.nodes[].classWhen', 'document.nodes[].styleWhen', 'document.nodes[].style', 'document.nodes[].requiresCapabilities', 'document.nodes[].capabilityMode'],
15608
15635
  submissionImpact: 'config-only',
15609
15636
  preconditions: ['config-initialized', 'target-block-exists'],
15610
15637
  },
package/index.d.ts CHANGED
@@ -85,6 +85,8 @@ declare class PraxisRichContent {
85
85
  onCardSurfaceKeydown(node: RichCardNode, event: KeyboardEvent): void;
86
86
  private shouldDispatchCardInteraction;
87
87
  private dispatchCardInteraction;
88
+ private resolveActionPayload;
89
+ private dispatchRichAction;
88
90
  resolveActionButtonLabel(node: RichActionButtonNode): string;
89
91
  isActionButtonDisabled(node: RichActionButtonNode): boolean;
90
92
  onActionButtonClick(node: RichActionButtonNode): void;
@@ -220,6 +222,7 @@ declare class PraxisRichContent {
220
222
  private resolveBooleanExpression;
221
223
  private supportsRequiredCapabilities;
222
224
  private isTabsItemVisible;
225
+ private evaluateRule;
223
226
  private supportsItemRequiredCapabilities;
224
227
  private resolveActiveTabsItemKey;
225
228
  private resolveTabsItemKey;
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@praxisui/rich-content",
3
- "version": "8.0.0-beta.20",
3
+ "version": "8.0.0-beta.22",
4
4
  "peerDependencies": {
5
5
  "@angular/common": "^20.3.0",
6
6
  "@angular/core": "^20.3.0",
7
- "@praxisui/core": "^8.0.0-beta.20",
7
+ "@praxisui/core": "^8.0.0-beta.22",
8
8
  "@angular/forms": "^20.3.0",
9
9
  "rxjs": "~7.8.0"
10
10
  },