@praxisui/visual-builder 1.0.0-beta.3 → 1.0.0-beta.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Praxis Visual Builder
2
2
 
3
+ ## 🔰 Exemplos / Quickstart
4
+
5
+ Para ver esta biblioteca em funcionamento em uma aplicação completa, utilize o projeto de exemplo (Quickstart):
6
+
7
+ - Repositório: https://github.com/codexrodrigues/praxis-ui-quickstart
8
+ - O Quickstart demonstra a integração das bibliotecas `@praxisui/*` em um app Angular, incluindo instalação, configuração e uso em telas reais.
9
+
3
10
  A comprehensive Angular library for building visual rule editors with support for mini-DSL expressions and contextual specifications.
4
11
 
5
12
  ## Features
@@ -5520,6 +5520,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
5520
5520
 
5521
5521
  class DslViewerComponent {
5522
5522
  snackBar;
5523
+ cdr;
5523
5524
  dsl = '';
5524
5525
  editable = true;
5525
5526
  language = 'dsl';
@@ -5541,8 +5542,9 @@ class DslViewerComponent {
5541
5542
  showLineNumbers = true;
5542
5543
  wordWrap = false;
5543
5544
  showMinimap = true;
5544
- constructor(snackBar) {
5545
+ constructor(snackBar, cdr) {
5545
5546
  this.snackBar = snackBar;
5547
+ this.cdr = cdr;
5546
5548
  }
5547
5549
  ngOnInit() {
5548
5550
  this.initializeMonacoEditor();
@@ -5573,11 +5575,13 @@ class DslViewerComponent {
5573
5575
  this.updateEditorContent();
5574
5576
  this.updateEditorOptions();
5575
5577
  this.isLoading = false;
5578
+ this.cdr.markForCheck();
5576
5579
  }
5577
5580
  catch (error) {
5578
5581
  console.error('Failed to initialize Monaco Editor:', error);
5579
5582
  this.createFallbackEditor();
5580
5583
  this.isLoading = false;
5584
+ this.cdr.markForCheck();
5581
5585
  }
5582
5586
  }
5583
5587
  async loadMonacoEditor() {
@@ -5656,6 +5660,10 @@ class DslViewerComponent {
5656
5660
  return;
5657
5661
  this.originalDsl = this.dsl || '';
5658
5662
  this.monacoEditor.setValue(this.originalDsl);
5663
+ try {
5664
+ (console.debug || console.log)('[DslViewer] updateEditorContent', { chars: (this.originalDsl || '').length, preview: (this.originalDsl || '').slice(0, 200) });
5665
+ }
5666
+ catch { }
5659
5667
  this.hasChanges = false;
5660
5668
  this.validateSyntax();
5661
5669
  }
@@ -5863,7 +5871,7 @@ class DslViewerComponent {
5863
5871
  });
5864
5872
  return { errors, warnings };
5865
5873
  }
5866
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DslViewerComponent, deps: [{ token: i1$1.MatSnackBar }], target: i0.ɵɵFactoryTarget.Component });
5874
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DslViewerComponent, deps: [{ token: i1$1.MatSnackBar }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
5867
5875
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: DslViewerComponent, isStandalone: true, selector: "praxis-dsl-viewer", inputs: { dsl: "dsl", editable: "editable", language: "language", theme: "theme" }, outputs: { dslChanged: "dslChanged", validationChanged: "validationChanged" }, viewQueries: [{ propertyName: "editorContainer", first: true, predicate: ["editorContainer"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: `
5868
5876
  <div class="dsl-viewer-container">
5869
5877
  <!-- Toolbar -->
@@ -6237,7 +6245,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
6237
6245
  </div>
6238
6246
  </div>
6239
6247
  `, styles: [".dsl-viewer-container{display:flex;flex-direction:column;height:100%;overflow:hidden}.dsl-toolbar{flex-shrink:0;padding:0 16px}.toolbar-title{display:flex;align-items:center;gap:8px;font-weight:500}.toolbar-spacer{flex:1}.toolbar-actions{display:flex;align-items:center;gap:8px}.validation-status{display:flex;align-items:center;gap:4px;padding:4px 8px;border-radius:4px;font-size:12px;font-weight:500}.validation-status.valid{background:var(--md-sys-color-tertiary-container);color:var(--md-sys-color-on-tertiary-container)}.validation-status.invalid{background:var(--md-sys-color-error-container);color:var(--md-sys-color-on-error-container)}.validation-status.warning{background:var(--md-sys-color-secondary-container);color:var(--md-sys-color-on-secondary-container)}.validation-status mat-icon{font-size:16px;width:16px;height:16px}.editor-container{flex:1;position:relative;overflow:hidden}.monaco-editor-container{width:100%;height:100%;position:relative}.monaco-editor-container.loading{opacity:.5}.loading-overlay{position:absolute;inset:0;background:var(--md-sys-color-surface);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;z-index:1000}.loading-text{color:var(--md-sys-color-on-surface-variant);font-size:14px}.readonly-overlay{position:absolute;top:8px;right:8px;z-index:100}.readonly-message{display:flex;align-items:center;gap:4px;background:var(--md-sys-color-surface-container);padding:4px 8px;border-radius:4px;font-size:12px;color:var(--md-sys-color-on-surface-variant);box-shadow:0 2px 4px var(--vb-shadow-low-color, rgba(0,0,0,.1))}.readonly-message mat-icon{font-size:14px;width:14px;height:14px}.status-bar{display:flex;justify-content:space-between;align-items:center;padding:4px 16px;background:var(--md-sys-color-surface-container);border-top:1px solid var(--md-sys-color-outline-variant);font-size:12px;color:var(--md-sys-color-on-surface-variant);flex-shrink:0}.status-left,.status-center,.status-right{display:flex;align-items:center;gap:12px}.cursor-position{font-family:monospace}.syntax-errors,.warnings{display:flex;align-items:center;gap:4px}.syntax-errors mat-icon,.warnings mat-icon{font-size:14px;width:14px;height:14px}.language-mode{font-family:monospace;font-weight:600}.unsaved-indicator{color:var(--md-sys-color-primary);font-weight:700;font-size:16px}.error-panel{background:var(--md-sys-color-surface);border-top:1px solid var(--md-sys-color-outline-variant);max-height:200px;overflow-y:auto;flex-shrink:0}.error-panel-header{display:flex;justify-content:space-between;align-items:center;padding:8px 16px;background:var(--md-sys-color-surface-container);border-bottom:1px solid var(--md-sys-color-outline-variant)}.error-panel-header h4{margin:0;font-size:14px;font-weight:500}.error-list{padding:8px}.error-item{display:flex;align-items:flex-start;gap:8px;padding:8px;border-radius:4px;margin-bottom:4px;cursor:pointer;transition:background .2s ease}.error-item:hover{background:var(--md-sys-color-surface-container)}.error-item.severity-error{border-left:3px solid var(--md-sys-color-error)}.error-item.severity-warning{border-left:3px solid var(--md-sys-color-secondary)}.error-item mat-icon{font-size:16px;width:16px;height:16px;margin-top:2px}.error-item.severity-error mat-icon{color:var(--md-sys-color-error)}.error-item.severity-warning mat-icon{color:var(--md-sys-color-secondary)}.error-content{flex:1}.error-message{font-weight:500;margin-bottom:2px}.error-location{font-size:11px;color:var(--md-sys-color-on-surface-variant);font-family:monospace}@media (max-width: 768px){.toolbar-actions{gap:4px}.status-bar{flex-direction:column;gap:4px;align-items:stretch}.status-left,.status-center,.status-right{justify-content:center}}\n"] }]
6240
- }], ctorParameters: () => [{ type: i1$1.MatSnackBar }], propDecorators: { dsl: [{
6248
+ }], ctorParameters: () => [{ type: i1$1.MatSnackBar }, { type: i0.ChangeDetectorRef }], propDecorators: { dsl: [{
6241
6249
  type: Input
6242
6250
  }], editable: [{
6243
6251
  type: Input
@@ -7167,31 +7175,48 @@ class FieldConditionConverter extends BaseRuleConverter {
7167
7175
  }
7168
7176
  }
7169
7177
  mapOperator(operatorString) {
7170
- const operatorMap = {
7171
- 'eq': ComparisonOperator.EQUALS,
7172
- 'equals': ComparisonOperator.EQUALS,
7173
- 'neq': ComparisonOperator.NOT_EQUALS,
7174
- 'not_equals': ComparisonOperator.NOT_EQUALS,
7175
- 'lt': ComparisonOperator.LESS_THAN,
7176
- 'less_than': ComparisonOperator.LESS_THAN,
7177
- 'lte': ComparisonOperator.LESS_THAN_OR_EQUAL,
7178
- 'less_than_or_equal': ComparisonOperator.LESS_THAN_OR_EQUAL,
7179
- 'gt': ComparisonOperator.GREATER_THAN,
7180
- 'greater_than': ComparisonOperator.GREATER_THAN,
7181
- 'gte': ComparisonOperator.GREATER_THAN_OR_EQUAL,
7182
- 'greater_than_or_equal': ComparisonOperator.GREATER_THAN_OR_EQUAL,
7183
- 'contains': ComparisonOperator.CONTAINS,
7184
- 'startsWith': ComparisonOperator.STARTS_WITH,
7185
- 'starts_with': ComparisonOperator.STARTS_WITH,
7186
- 'endsWith': ComparisonOperator.ENDS_WITH,
7187
- 'ends_with': ComparisonOperator.ENDS_WITH,
7188
- 'in': ComparisonOperator.IN
7189
- };
7190
- const operator = operatorMap[operatorString.toLowerCase()];
7191
- if (!operator) {
7192
- throw new Error(`Unsupported operator: ${operatorString}`);
7178
+ // Normalize: remove underscores/spaces and lowercase
7179
+ const norm = (operatorString || '').replace(/[_\s]/g, '').toLowerCase();
7180
+ switch (norm) {
7181
+ case 'eq':
7182
+ case 'equals':
7183
+ case '==':
7184
+ case '=':
7185
+ case 'istrue': // treat as equals true
7186
+ case 'isfalse': // equals false
7187
+ return ComparisonOperator.EQUALS;
7188
+ case 'neq':
7189
+ case 'notequals':
7190
+ case '!=':
7191
+ case '<>':
7192
+ return ComparisonOperator.NOT_EQUALS;
7193
+ case 'lt':
7194
+ case 'lessthan':
7195
+ case '<':
7196
+ return ComparisonOperator.LESS_THAN;
7197
+ case 'lte':
7198
+ case 'lessthanorequal':
7199
+ case '<=':
7200
+ return ComparisonOperator.LESS_THAN_OR_EQUAL;
7201
+ case 'gt':
7202
+ case 'greaterthan':
7203
+ case '>':
7204
+ return ComparisonOperator.GREATER_THAN;
7205
+ case 'gte':
7206
+ case 'greaterthanorequal':
7207
+ case '>=':
7208
+ return ComparisonOperator.GREATER_THAN_OR_EQUAL;
7209
+ case 'contains':
7210
+ return ComparisonOperator.CONTAINS;
7211
+ case 'startswith':
7212
+ return ComparisonOperator.STARTS_WITH;
7213
+ case 'endswith':
7214
+ return ComparisonOperator.ENDS_WITH;
7215
+ case 'in':
7216
+ return ComparisonOperator.IN;
7217
+ default:
7218
+ throw new Error(`Unsupported operator: ${operatorString}`);
7193
7219
  }
7194
- return operator;
7195
7220
  }
7196
7221
  convertValue(value, valueType) {
7197
7222
  if (value === null || value === undefined) {
@@ -9716,6 +9741,13 @@ class RuleBuilderService {
9716
9741
  rootNodes: json.rootNodes,
9717
9742
  isDirty: true,
9718
9743
  });
9744
+ try {
9745
+ const count = Object.keys(json.nodes || {}).length;
9746
+ const firstKey = Object.keys(json.nodes || {})[0];
9747
+ const firstNode = firstKey ? json.nodes[firstKey] : undefined;
9748
+ (console.debug || console.log)('[RuleBuilderService] import(builder-state)', { nodesCount: count, rootNodes: (json.rootNodes || []).length, firstNode });
9749
+ }
9750
+ catch { }
9719
9751
  this.saveSnapshot('Imported rules');
9720
9752
  this.validateRules();
9721
9753
  return;
@@ -9783,6 +9815,13 @@ class RuleBuilderService {
9783
9815
  isDirty: true,
9784
9816
  });
9785
9817
  }
9818
+ try {
9819
+ const count = Object.keys(ruleNodes.nodes || {}).length;
9820
+ const firstKey = Object.keys(ruleNodes.nodes || {})[0];
9821
+ const firstNode = firstKey ? ruleNodes.nodes[firstKey] : undefined;
9822
+ (console.debug || console.log)('[RuleBuilderService] import(spec)', { nodesCount: count, rootNodes: (ruleNodes.rootNodes || []).length, firstNode });
9823
+ }
9824
+ catch { }
9786
9825
  this.saveSnapshot('Imported rules');
9787
9826
  this.validateRules();
9788
9827
  }
@@ -14960,6 +14999,8 @@ class RuleEditorComponent {
14960
14999
  destroy$ = new Subject();
14961
15000
  // Component state
14962
15001
  currentState = null;
15002
+ // Derived: JSON representation of the builder state (nodes/rootNodes[/selectedNodeId])
15003
+ builderStateJson = null;
14963
15004
  validationErrors = [];
14964
15005
  selectedNode = null;
14965
15006
  fieldSchemas = {};
@@ -14989,12 +15030,23 @@ class RuleEditorComponent {
14989
15030
  this.destroy$.next();
14990
15031
  this.destroy$.complete();
14991
15032
  }
15033
+ ngOnChanges(changes) {
15034
+ if (changes['initialRules'] && !changes['initialRules'].firstChange) {
15035
+ try {
15036
+ this.importInitialRules();
15037
+ }
15038
+ catch (error) {
15039
+ console.error('Failed to re-import rules:', error);
15040
+ }
15041
+ }
15042
+ }
14992
15043
  setupSubscriptions() {
14993
15044
  // Subscribe to rule builder state changes
14994
15045
  this.ruleBuilderService.state$
14995
15046
  .pipe(takeUntil$1(this.destroy$))
14996
15047
  .subscribe((state) => {
14997
15048
  this.currentState = state;
15049
+ this.updateBuilderStateJson();
14998
15050
  this.updateSelectedNode();
14999
15051
  this.rulesChanged.emit(state);
15000
15052
  });
@@ -15040,6 +15092,23 @@ class RuleEditorComponent {
15040
15092
  this.importInitialRules();
15041
15093
  }
15042
15094
  }
15095
+ updateBuilderStateJson() {
15096
+ const state = this.currentState;
15097
+ if (state && Array.isArray(state.rootNodes) && state.rootNodes.length > 0) {
15098
+ // Build minimal, stable view of the builder state for JSON preview
15099
+ const json = {
15100
+ nodes: state.nodes,
15101
+ rootNodes: state.rootNodes,
15102
+ };
15103
+ if (state.selectedNodeId) {
15104
+ json.selectedNodeId = state.selectedNodeId;
15105
+ }
15106
+ this.builderStateJson = json;
15107
+ }
15108
+ else {
15109
+ this.builderStateJson = null;
15110
+ }
15111
+ }
15043
15112
  importInitialRules() {
15044
15113
  try {
15045
15114
  let content;
@@ -15055,6 +15124,10 @@ class RuleEditorComponent {
15055
15124
  else {
15056
15125
  content = JSON.stringify(this.initialRules);
15057
15126
  }
15127
+ try {
15128
+ (console.debug || console.log)('[RuleEditor] importInitialRules', { format, contentPreview: (content || '').slice(0, 200) });
15129
+ }
15130
+ catch { }
15058
15131
  this.ruleBuilderService.import(content, { format });
15059
15132
  }
15060
15133
  catch (error) {
@@ -15360,7 +15433,7 @@ class RuleEditorComponent {
15360
15433
  this.snackBar.open(`Rule "${rule.name}" ${enabled ? 'enabled' : 'disabled'}`, 'Close', { duration: 2000 });
15361
15434
  }
15362
15435
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: RuleEditorComponent, deps: [{ token: RuleBuilderService }, { token: FieldSchemaService }, { token: i1$1.MatSnackBar }, { token: i1.FormBuilder }, { token: i1$2.MatDialog }], target: i0.ɵɵFactoryTarget.Component });
15363
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: RuleEditorComponent, isStandalone: true, selector: "praxis-rule-editor", inputs: { config: "config", initialRules: "initialRules", embedded: "embedded" }, outputs: { rulesChanged: "rulesChanged", exportRequested: "exportRequested", importRequested: "importRequested" }, ngImport: i0, template: `
15436
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: RuleEditorComponent, isStandalone: true, selector: "praxis-rule-editor", inputs: { config: "config", initialRules: "initialRules", embedded: "embedded" }, outputs: { rulesChanged: "rulesChanged", exportRequested: "exportRequested", importRequested: "importRequested" }, usesOnChanges: true, ngImport: i0, template: `
15364
15437
  <div class="rule-editor-container" [class.embedded]="embedded">
15365
15438
  <!-- Header Toolbar -->
15366
15439
  <mat-toolbar class="rule-editor-toolbar">
@@ -15592,7 +15665,7 @@ class RuleEditorComponent {
15592
15665
  <mat-tab label="JSON Preview">
15593
15666
  <div class="json-viewer">
15594
15667
  <praxis-json-viewer
15595
- [json]="currentState?.currentJSON"
15668
+ [json]="builderStateJson"
15596
15669
  [editable]="currentState?.mode === 'json'"
15597
15670
  (jsonChanged)="onJsonChanged($event)"
15598
15671
  >
@@ -15956,7 +16029,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
15956
16029
  <mat-tab label="JSON Preview">
15957
16030
  <div class="json-viewer">
15958
16031
  <praxis-json-viewer
15959
- [json]="currentState?.currentJSON"
16032
+ [json]="builderStateJson"
15960
16033
  [editable]="currentState?.mode === 'json'"
15961
16034
  (jsonChanged)="onJsonChanged($event)"
15962
16035
  >