ngx-form-designer 0.0.28 → 0.0.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.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, InjectionToken, NgModule, inject, signal, computed, EventEmitter, DestroyRef, Injector, afterNextRender, ViewContainerRef, Input, ViewChild, Output, Inject, ChangeDetectionStrategy, Component, effect, ElementRef, NgZone, input, output, HostListener, ContentChildren, untracked, ChangeDetectorRef } from '@angular/core';
2
+ import { Injectable, InjectionToken, NgModule, inject, signal, computed, EventEmitter, DestroyRef, Injector, afterNextRender, ViewContainerRef, Input, ViewChild, Output, Inject, ChangeDetectionStrategy, Component, effect, ElementRef, NgZone, input, output, HostListener, ContentChildren, untracked, ChangeDetectorRef, Pipe } from '@angular/core';
3
3
  import { BehaviorSubject, Subject, merge, of, filter, map, debounceTime as debounceTime$1, skip, firstValueFrom } from 'rxjs';
4
4
  import { v4 } from 'uuid';
5
5
  import * as i1 from '@angular/common';
@@ -13,11 +13,12 @@ import { LucideAngularModule } from 'lucide-angular';
13
13
  import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
14
14
  import * as i5 from 'angular-resizable-element';
15
15
  import { ResizableModule } from 'angular-resizable-element';
16
- import { Router, NavigationEnd } from '@angular/router';
17
16
  import loader from '@monaco-editor/loader';
17
+ import { Router, NavigationEnd } from '@angular/router';
18
18
  import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
19
19
  import { NgSelectComponent } from '@ng-select/ng-select';
20
20
  import Quill from 'quill';
21
+ import { DomSanitizer } from '@angular/platform-browser';
21
22
  import { HttpClient, provideHttpClient } from '@angular/common/http';
22
23
 
23
24
  const CURRENT_SCHEMA_VERSION = '1.0.0';
@@ -794,6 +795,16 @@ function hasWrapperSurfaceStyles(style) {
794
795
 
795
796
  const WIDGET_EDITOR_CONTEXT = new InjectionToken('WIDGET_EDITOR_CONTEXT');
796
797
 
798
+ const SUPPRESS_API_CALLS_FLAG = '__fdSuppressApiCalls';
799
+ function setEngineApiCallsSuppressed(engine, suppressed) {
800
+ engine[SUPPRESS_API_CALLS_FLAG] = suppressed;
801
+ }
802
+ function areEngineApiCallsSuppressed(engine) {
803
+ if (!engine)
804
+ return false;
805
+ return engine[SUPPRESS_API_CALLS_FLAG] === true;
806
+ }
807
+
797
808
  const SCOPED_NODE_SEPARATOR = '::scope::';
798
809
  function composeScopedNodeId(scopePath, nodeId) {
799
810
  if (scopePath.length === 0)
@@ -2559,7 +2570,7 @@ class LayoutNodeComponent {
2559
2570
  shouldUpdate = true;
2560
2571
  }
2561
2572
  if (readOnlyModeChange && !readOnlyModeChange.firstChange) {
2562
- // Swap engine proxy for preview mode disabled state overrides
2573
+ // Swap engine proxy for preview runtime flags
2563
2574
  shouldUpdate = true;
2564
2575
  }
2565
2576
  if (shouldUpdate) {
@@ -2847,8 +2858,8 @@ class LayoutNodeComponent {
2847
2858
  if (mode === 'design') {
2848
2859
  proxy.isFieldVisible = () => true;
2849
2860
  }
2850
- else {
2851
- proxy.isFieldEnabled = () => false;
2861
+ if (mode === 'preview') {
2862
+ setEngineApiCallsSuppressed(proxy, true);
2852
2863
  }
2853
2864
  this.renderEngineProxy = proxy;
2854
2865
  this.renderEngineSource = this.engine;
@@ -4219,7 +4230,9 @@ class JsonFormRendererComponent {
4219
4230
  if (!this.engine || this.mode === 'design') {
4220
4231
  return;
4221
4232
  }
4222
- const apiExecutor = this.resolveEventApiExecutor();
4233
+ const apiExecutor = this.mode === 'preview'
4234
+ ? undefined
4235
+ : this.resolveEventApiExecutor();
4223
4236
  this.runner = new FormEventRunner(this.engine, {
4224
4237
  logger: this.eventLogger,
4225
4238
  apiExecutor,
@@ -4712,7 +4725,7 @@ class JsonFormRendererComponent {
4712
4725
  </ng-container>
4713
4726
  <ng-template #loading>Loading form...</ng-template>
4714
4727
  </div>
4715
- `, isInline: true, styles: [".form-renderer-container{width:100%;height:100vh;overflow-y:auto}.form-renderer-container.is-design{padding-top:2.25rem;height:auto;overflow:visible}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: LayoutNodeComponent, selector: "app-layout-node", inputs: ["node", "engine", "fields", "designMode", "readOnlyMode", "scopePath", "device", "breakpoint", "connectedDropLists"], outputs: ["nodeDrop", "nodeSelect"] }] });
4728
+ `, isInline: true, styles: [".form-renderer-container{width:100%;height:100vh}.form-renderer-container.is-design{padding-top:2.25rem;height:auto;overflow:visible}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: LayoutNodeComponent, selector: "app-layout-node", inputs: ["node", "engine", "fields", "designMode", "readOnlyMode", "scopePath", "device", "breakpoint", "connectedDropLists"], outputs: ["nodeDrop", "nodeSelect"] }] });
4716
4729
  }
4717
4730
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: JsonFormRendererComponent, decorators: [{
4718
4731
  type: Component,
@@ -4737,7 +4750,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
4737
4750
  </ng-container>
4738
4751
  <ng-template #loading>Loading form...</ng-template>
4739
4752
  </div>
4740
- `, styles: [".form-renderer-container{width:100%;height:100vh;overflow-y:auto}.form-renderer-container.is-design{padding-top:2.25rem;height:auto;overflow:visible}\n"] }]
4753
+ `, styles: [".form-renderer-container{width:100%;height:100vh}.form-renderer-container.is-design{padding-top:2.25rem;height:auto;overflow:visible}\n"] }]
4741
4754
  }], ctorParameters: () => [{ type: DesignerStateService }], propDecorators: { schema: [{
4742
4755
  type: Input
4743
4756
  }], initialValues: [{
@@ -4953,876 +4966,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
4953
4966
  type: Input
4954
4967
  }] } });
4955
4968
 
4956
- class FormViewerReadonlyComponent {
4957
- schema;
4958
- values = {};
4959
- breakpoint = 'xl';
4960
- asRow(node) {
4961
- return node;
4962
- }
4963
- asCol(node) {
4964
- return node;
4965
- }
4966
- asWidget(node) {
4967
- return node;
4968
- }
4969
- getField(fieldId) {
4970
- return this.schema?.fields.find(field => field.id === fieldId);
4971
- }
4972
- getFieldLabel(field) {
4973
- return field.label?.trim() || field.name;
4974
- }
4975
- getFieldValue(field) {
4976
- return this.values[field.name] ?? field.defaultValue ?? null;
4977
- }
4978
- getHeadingText(field) {
4979
- return field.label?.trim() || field.name;
4980
- }
4981
- getRichTextContent(field) {
4982
- const text = field['text'];
4983
- return typeof text === 'string' ? text : '';
4984
- }
4985
- getHeadingLevel(field) {
4986
- const level = field['level'];
4987
- return typeof level === 'string' && /^h[1-6]$/.test(level) ? level : 'h2';
4988
- }
4989
- getParagraphText(field) {
4990
- const text = field['text'];
4991
- if (typeof text === 'string' && text.trim()) {
4992
- return text;
4993
- }
4994
- return field.label?.trim() || '';
4995
- }
4996
- getNodeStyle(node) {
4997
- const style = normalizeStyle(node.style);
4998
- if (!style['width'])
4999
- style['width'] = '100%';
5000
- if (!style['height'])
5001
- style['height'] = 'auto';
5002
- return style;
5003
- }
5004
- getFieldStyle(field) {
5005
- return normalizeStyle(field.style);
5006
- }
5007
- getColClasses(node) {
5008
- if (node.type !== 'col')
5009
- return '';
5010
- const span = this.getEffectiveSpan(node.responsive || { xs: 12 });
5011
- return this.getSpanClass(span);
5012
- }
5013
- formatFieldValue(field, value) {
5014
- if (field.type === 'select' || field.type === 'radio' || field.type === 'search' || field.type === 'tree-select') {
5015
- return this.formatScalarValue(this.resolveOptionLabel(field, value));
5016
- }
5017
- if (field.type === 'date') {
5018
- return this.formatDateValue(value);
5019
- }
5020
- if (field.type === 'time') {
5021
- return this.formatTimeValue(value);
5022
- }
5023
- if (field.type === 'datetime-local') {
5024
- return this.formatDateTimeValue(value);
5025
- }
5026
- return this.formatScalarValue(value, field);
5027
- }
5028
- formatScalarValue(value, field) {
5029
- if (Array.isArray(value)) {
5030
- const scalarItems = value
5031
- .map(item => this.formatScalarValue(item))
5032
- .filter(item => item !== 'No value provided');
5033
- return scalarItems.length ? scalarItems.join(', ') : this.getEmptyValueLabel(field);
5034
- }
5035
- if (value == null || value === '') {
5036
- return this.getEmptyValueLabel(field);
5037
- }
5038
- if (typeof value === 'boolean') {
5039
- return value ? 'Yes' : 'No';
5040
- }
5041
- if (typeof value === 'object') {
5042
- return JSON.stringify(value, null, 2);
5043
- }
5044
- return String(value);
5045
- }
5046
- getDisplayList(field, value) {
5047
- if (!Array.isArray(value))
5048
- return [];
5049
- return value
5050
- .map(item => this.resolveOptionLabel(field, item))
5051
- .map(item => this.formatScalarValue(item))
5052
- .filter(item => item !== 'No value provided');
5053
- }
5054
- getUploadedFiles(value) {
5055
- if (!value)
5056
- return [];
5057
- if (Array.isArray(value)) {
5058
- return value.filter(item => this.isUploadedFileRef(item));
5059
- }
5060
- return this.isUploadedFileRef(value) ? [value] : [];
5061
- }
5062
- getImageSource(field, value) {
5063
- if (typeof value === 'string' && value.trim()) {
5064
- return value;
5065
- }
5066
- const imageSrc = field['imageSrc'];
5067
- if (typeof imageSrc === 'string' && imageSrc.trim()) {
5068
- return imageSrc;
5069
- }
5070
- const staticValue = field.dataConfig?.staticValue;
5071
- return typeof staticValue === 'string' && staticValue.trim() ? staticValue : null;
5072
- }
5073
- getRepeatableItems(value) {
5074
- if (!Array.isArray(value))
5075
- return [];
5076
- return value.filter(item => this.isRecord(item));
5077
- }
5078
- getRepeatableFields(field) {
5079
- return field.repeatable?.itemSchema?.fields ?? [];
5080
- }
5081
- getRepeatableItemTitle(field, index) {
5082
- const itemLabel = field.repeatable?.itemLabel?.trim() || 'Item';
5083
- return `${itemLabel} ${index + 1}`;
5084
- }
5085
- getNestedFieldValue(item, nestedField) {
5086
- return item[nestedField.name] ?? nestedField.defaultValue ?? null;
5087
- }
5088
- getTableRows(value) {
5089
- if (!Array.isArray(value))
5090
- return [];
5091
- return value.filter(item => this.isRecord(item));
5092
- }
5093
- getTableColumns(field, value) {
5094
- const configuredColumns = Array.isArray(field['columns']) ? field['columns'] : [];
5095
- const normalizedConfigured = configuredColumns
5096
- .filter(column => this.isRecord(column) && typeof column['key'] === 'string')
5097
- .map(column => ({
5098
- key: String(column['key']),
5099
- label: typeof column['label'] === 'string' && column['label'].trim()
5100
- ? String(column['label'])
5101
- : String(column['key'])
5102
- }));
5103
- if (normalizedConfigured.length) {
5104
- return normalizedConfigured;
5105
- }
5106
- const [firstRow] = this.getTableRows(value);
5107
- if (!firstRow)
5108
- return [];
5109
- return Object.keys(firstRow).map(key => ({ key, label: key }));
5110
- }
5111
- readPathValue(value, path) {
5112
- if (!path)
5113
- return value;
5114
- return path
5115
- .split('.')
5116
- .reduce((current, segment) => (this.isRecord(current) ? current[segment] : undefined), value);
5117
- }
5118
- isTruthy(value) {
5119
- return !!value;
5120
- }
5121
- getEmptyValueLabel(field) {
5122
- return field?.type === 'file' ? 'No files uploaded' : 'No value provided';
5123
- }
5124
- formatFileSize(size) {
5125
- if (size >= 1024 * 1024) {
5126
- return `${(size / (1024 * 1024)).toFixed(1)} MB`;
5127
- }
5128
- if (size >= 1024) {
5129
- return `${(size / 1024).toFixed(1)} KB`;
5130
- }
5131
- return `${size} B`;
5132
- }
5133
- trackByNodeId(_, node) {
5134
- return node.id;
5135
- }
5136
- trackByIndex(index) {
5137
- return index;
5138
- }
5139
- trackByFieldId(_, field) {
5140
- return field.id;
5141
- }
5142
- trackByTableColumn(_, column) {
5143
- return column.key;
5144
- }
5145
- trackByUploadedFile(_, file) {
5146
- return `${file.id ?? ''}-${file.name ?? ''}-${file.url ?? ''}`;
5147
- }
5148
- getEffectiveSpan(responsive) {
5149
- const breakpoints = ['xs', 'sm', 'md', 'lg', 'xl', '2xl'];
5150
- const currentIndex = breakpoints.indexOf(this.breakpoint);
5151
- if (currentIndex === -1)
5152
- return responsive.xs || 12;
5153
- for (let index = currentIndex; index >= 0; index -= 1) {
5154
- const key = breakpoints[index];
5155
- if (responsive[key] != null) {
5156
- return responsive[key];
5157
- }
5158
- }
5159
- return 12;
5160
- }
5161
- getSpanClass(span) {
5162
- if (span === 12)
5163
- return 'w-full';
5164
- const map = {
5165
- 1: 'w-1/12',
5166
- 2: 'w-2/12',
5167
- 3: 'w-3/12',
5168
- 4: 'w-4/12',
5169
- 5: 'w-5/12',
5170
- 6: 'w-6/12',
5171
- 7: 'w-7/12',
5172
- 8: 'w-8/12',
5173
- 9: 'w-9/12',
5174
- 10: 'w-10/12',
5175
- 11: 'w-11/12'
5176
- };
5177
- return map[span] || 'w-full';
5178
- }
5179
- resolveOptionLabel(field, value) {
5180
- const options = this.getFieldOptions(field);
5181
- const matched = options.find(option => this.optionMatches(option, value));
5182
- return matched?.label ?? value;
5183
- }
5184
- getFieldOptions(field) {
5185
- return field.dataConfig?.staticOptions ?? field.staticOptions ?? [];
5186
- }
5187
- optionMatches(option, value) {
5188
- return option.value === value || String(option.value) === String(value);
5189
- }
5190
- formatDateValue(value) {
5191
- if (typeof value !== 'string' || !value.trim()) {
5192
- return this.getEmptyValueLabel();
5193
- }
5194
- const date = new Date(`${value}T00:00:00`);
5195
- return Number.isNaN(date.getTime()) ? value : date.toLocaleDateString('en-GB');
5196
- }
5197
- formatTimeValue(value) {
5198
- if (typeof value !== 'string' || !value.trim()) {
5199
- return this.getEmptyValueLabel();
5200
- }
5201
- const [hours, minutes] = value.split(':');
5202
- if (!hours || !minutes)
5203
- return value;
5204
- const date = new Date();
5205
- date.setHours(Number(hours), Number(minutes), 0, 0);
5206
- return Number.isNaN(date.getTime())
5207
- ? value
5208
- : date.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' });
5209
- }
5210
- formatDateTimeValue(value) {
5211
- if (typeof value !== 'string' || !value.trim()) {
5212
- return this.getEmptyValueLabel();
5213
- }
5214
- const date = new Date(value);
5215
- return Number.isNaN(date.getTime())
5216
- ? value
5217
- : date.toLocaleString('en-GB', {
5218
- year: 'numeric',
5219
- month: 'short',
5220
- day: 'numeric',
5221
- hour: '2-digit',
5222
- minute: '2-digit'
5223
- });
5224
- }
5225
- isRecord(value) {
5226
- return !!value && typeof value === 'object' && !Array.isArray(value);
5227
- }
5228
- isUploadedFileRef(value) {
5229
- if (!this.isRecord(value))
5230
- return false;
5231
- return typeof value['name'] === 'string'
5232
- || typeof value['url'] === 'string'
5233
- || typeof value['id'] === 'string';
5234
- }
5235
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: FormViewerReadonlyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5236
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: FormViewerReadonlyComponent, isStandalone: true, selector: "app-form-viewer-readonly", inputs: { schema: "schema", values: "values", breakpoint: "breakpoint" }, ngImport: i0, template: `
5237
- <div class="space-y-4" data-fd="form-viewer-readonly">
5238
- <ng-container *ngIf="schema.layout as layout">
5239
- <ng-container
5240
- *ngTemplateOutlet="renderNode; context: { $implicit: layout }">
5241
- </ng-container>
5242
- </ng-container>
5243
- </div>
5244
-
5245
- <ng-template #renderNode let-node>
5246
- <ng-container [ngSwitch]="node.type">
5247
- <div
5248
- *ngSwitchCase="'row'"
5249
- class="flex flex-wrap"
5250
- data-fd="form-viewer-row"
5251
- [style]="getNodeStyle(node)">
5252
- <ng-container *ngFor="let child of asRow(node).children; trackBy: trackByNodeId">
5253
- <ng-container
5254
- *ngTemplateOutlet="renderNode; context: { $implicit: child }">
5255
- </ng-container>
5256
- </ng-container>
5257
- </div>
5258
-
5259
- <div
5260
- *ngSwitchCase="'col'"
5261
- class="min-h-[1px] space-y-4"
5262
- data-fd="form-viewer-column"
5263
- [class]="getColClasses(node)"
5264
- [style]="getNodeStyle(node)">
5265
- <ng-container *ngFor="let child of asCol(node).children; trackBy: trackByNodeId">
5266
- <ng-container
5267
- *ngTemplateOutlet="renderNode; context: { $implicit: child }">
5268
- </ng-container>
5269
- </ng-container>
5270
- </div>
5271
-
5272
- <ng-container *ngSwitchCase="'widget'">
5273
- <ng-container *ngIf="getField(asWidget(node).refId) as field">
5274
- <div class="w-full"
5275
- data-fd="form-viewer-widget"
5276
- [attr.data-fd-field-name]="field.name"
5277
- [attr.data-fd-field-type]="field.type"
5278
- [style]="getNodeStyle(node)">
5279
- <ng-container [ngSwitch]="field.type">
5280
- <div *ngSwitchCase="'heading'"
5281
- data-fd="form-viewer-heading"
5282
- [attr.data-fd-field-name]="field.name"
5283
- [attr.data-fd-field-type]="field.type"
5284
- [style]="getFieldStyle(field)">
5285
- <h1
5286
- *ngIf="getHeadingLevel(field) === 'h1'"
5287
- class="text-4xl font-semibold tracking-tight text-slate-950">
5288
- {{ getHeadingText(field) }}
5289
- </h1>
5290
- <h2
5291
- *ngIf="getHeadingLevel(field) === 'h2'"
5292
- class="text-3xl font-semibold tracking-tight text-slate-950">
5293
- {{ getHeadingText(field) }}
5294
- </h2>
5295
- <h3
5296
- *ngIf="getHeadingLevel(field) === 'h3'"
5297
- class="text-2xl font-semibold tracking-tight text-slate-950">
5298
- {{ getHeadingText(field) }}
5299
- </h3>
5300
- <h4
5301
- *ngIf="getHeadingLevel(field) === 'h4'"
5302
- class="text-xl font-semibold tracking-tight text-slate-950">
5303
- {{ getHeadingText(field) }}
5304
- </h4>
5305
- <h5
5306
- *ngIf="getHeadingLevel(field) === 'h5'"
5307
- class="text-lg font-semibold tracking-tight text-slate-950">
5308
- {{ getHeadingText(field) }}
5309
- </h5>
5310
- <h6
5311
- *ngIf="getHeadingLevel(field) === 'h6'"
5312
- class="text-base font-semibold tracking-tight text-slate-950">
5313
- {{ getHeadingText(field) }}
5314
- </h6>
5315
- </div>
5316
-
5317
- <p
5318
- *ngSwitchCase="'paragraph'"
5319
- data-fd="form-viewer-paragraph"
5320
- [attr.data-fd-field-name]="field.name"
5321
- [attr.data-fd-field-type]="field.type"
5322
- class="whitespace-pre-wrap text-sm leading-6 text-slate-700"
5323
- [style]="getFieldStyle(field)">
5324
- {{ getParagraphText(field) }}
5325
- </p>
5326
-
5327
- <div
5328
- *ngSwitchCase="'rich-text'"
5329
- data-fd="form-viewer-rich-text"
5330
- [attr.data-fd-field-name]="field.name"
5331
- [attr.data-fd-field-type]="field.type"
5332
- class="whitespace-pre-wrap text-sm leading-6 text-slate-700"
5333
- [style]="getFieldStyle(field)"
5334
- [innerHTML]="getRichTextContent(field)"></div>
5335
-
5336
- <div *ngSwitchCase="'cta-button'"
5337
- data-fd="form-viewer-button"
5338
- [attr.data-fd-field-name]="field.name"
5339
- [attr.data-fd-field-type]="field.type"
5340
- [style]="getFieldStyle(field)">
5341
- <button
5342
- type="button"
5343
- disabled
5344
- class="inline-flex cursor-default items-center justify-center rounded-lg border border-slate-300 bg-slate-100 px-4 py-2 text-sm font-medium text-slate-700 shadow-sm">
5345
- {{ field.label || 'Action' }}
5346
- </button>
5347
- </div>
5348
-
5349
- <div *ngSwitchDefault
5350
- data-fd="form-viewer-field-card"
5351
- [attr.data-fd-field-name]="field.name"
5352
- [attr.data-fd-field-type]="field.type"
5353
- class="rounded-xl border border-slate-200 bg-white px-4 py-3 shadow-sm"
5354
- [style]="getFieldStyle(field)">
5355
- <div class="mb-3 flex items-start justify-between gap-3" *ngIf="field.helpText || field.html5?.required">
5356
- <div *ngIf="field.helpText">
5357
- <p data-fd="form-viewer-field-help" class="text-xs text-slate-500">
5358
- {{ field.helpText }}
5359
- </p>
5360
- </div>
5361
- <span
5362
- *ngIf="field.html5?.required"
5363
- class="rounded-full bg-rose-50 px-2 py-1 text-[11px] font-medium text-rose-600">
5364
- Required
5365
- </span>
5366
- </div>
5367
-
5368
- <ng-container [ngSwitch]="field.type">
5369
- <div *ngSwitchCase="'checkbox'"
5370
- data-fd="form-viewer-field-value"
5371
- class="inline-flex items-center rounded-full px-3 py-1 text-sm font-medium"
5372
- [class.bg-emerald-50]="isTruthy(getFieldValue(field))"
5373
- [class.text-emerald-700]="isTruthy(getFieldValue(field))"
5374
- [class.bg-slate-100]="!isTruthy(getFieldValue(field))"
5375
- [class.text-slate-600]="!isTruthy(getFieldValue(field))">
5376
- {{ isTruthy(getFieldValue(field)) ? 'Checked' : 'Not checked' }}
5377
- </div>
5378
-
5379
- <div *ngSwitchCase="'multiselect'" data-fd="form-viewer-chip-list" class="flex flex-wrap gap-2">
5380
- <span
5381
- *ngFor="let item of getDisplayList(field, getFieldValue(field)); trackBy: trackByIndex"
5382
- data-fd="form-viewer-chip"
5383
- class="rounded-full bg-slate-100 px-3 py-1 text-sm text-slate-700">
5384
- {{ item }}
5385
- </span>
5386
- <span *ngIf="!getDisplayList(field, getFieldValue(field)).length" data-fd="form-viewer-field-value-empty" class="text-sm text-slate-400">
5387
- {{ getEmptyValueLabel(field) }}
5388
- </span>
5389
- </div>
5390
-
5391
- <div *ngSwitchCase="'checkbox-group'" data-fd="form-viewer-chip-list" class="flex flex-wrap gap-2">
5392
- <span
5393
- *ngFor="let item of getDisplayList(field, getFieldValue(field)); trackBy: trackByIndex"
5394
- data-fd="form-viewer-chip"
5395
- class="rounded-full bg-slate-100 px-3 py-1 text-sm text-slate-700">
5396
- {{ item }}
5397
- </span>
5398
- <span *ngIf="!getDisplayList(field, getFieldValue(field)).length" data-fd="form-viewer-field-value-empty" class="text-sm text-slate-400">
5399
- {{ getEmptyValueLabel(field) }}
5400
- </span>
5401
- </div>
5402
-
5403
- <div *ngSwitchCase="'file'" data-fd="form-viewer-file-list" class="space-y-2">
5404
- <ng-container *ngIf="getUploadedFiles(getFieldValue(field)).length; else emptyFileState">
5405
- <div
5406
- *ngFor="let file of getUploadedFiles(getFieldValue(field)); trackBy: trackByUploadedFile"
5407
- data-fd="form-viewer-file-item"
5408
- class="flex items-center justify-between gap-3 rounded-lg border border-slate-200 bg-slate-50 px-3 py-2">
5409
- <div class="min-w-0">
5410
- <a
5411
- *ngIf="file.url; else fileNameText"
5412
- data-fd="form-viewer-file-link"
5413
- [href]="file.url"
5414
- target="_blank"
5415
- rel="noopener noreferrer"
5416
- class="block truncate text-sm font-medium text-sky-700 underline">
5417
- {{ file.name || file.id || 'Uploaded file' }}
5418
- </a>
5419
- <ng-template #fileNameText>
5420
- <div data-fd="form-viewer-file-name" class="truncate text-sm font-medium text-slate-900">
5421
- {{ file.name || file.id || 'Uploaded file' }}
5422
- </div>
5423
- </ng-template>
5424
- <div *ngIf="file.type" data-fd="form-viewer-file-type" class="text-xs text-slate-500">{{ file.type }}</div>
5425
- </div>
5426
- <div *ngIf="file.size != null" data-fd="form-viewer-file-size" class="text-xs text-slate-500">
5427
- {{ formatFileSize(file.size) }}
5428
- </div>
5429
- </div>
5430
- </ng-container>
5431
- <ng-template #emptyFileState>
5432
- <span data-fd="form-viewer-field-value-empty" class="text-sm text-slate-400">{{ getEmptyValueLabel(field) }}</span>
5433
- </ng-template>
5434
- </div>
5435
-
5436
- <div *ngSwitchCase="'image'" data-fd="form-viewer-image" class="space-y-3">
5437
- <ng-container *ngIf="getImageSource(field, getFieldValue(field)) as imageSrc; else emptyImageState">
5438
- <img
5439
- data-fd="form-viewer-image-element"
5440
- [src]="imageSrc"
5441
- [alt]="field.label || field.name"
5442
- class="max-h-64 rounded-lg border border-slate-200 object-contain">
5443
- </ng-container>
5444
- <ng-template #emptyImageState>
5445
- <span data-fd="form-viewer-field-value-empty" class="text-sm text-slate-400">{{ getEmptyValueLabel(field) }}</span>
5446
- </ng-template>
5447
- </div>
5448
-
5449
- <div *ngSwitchCase="'repeatable-group'" data-fd="form-viewer-repeatable-group" class="space-y-3">
5450
- <ng-container *ngIf="getRepeatableItems(getFieldValue(field)).length; else emptyRepeatableState">
5451
- <section
5452
- *ngFor="let item of getRepeatableItems(getFieldValue(field)); let index = index; trackBy: trackByIndex"
5453
- data-fd="form-viewer-repeatable-item"
5454
- class="rounded-lg border border-slate-200 bg-slate-50 px-3 py-3">
5455
- <div data-fd="form-viewer-repeatable-item-title" class="mb-3 text-xs font-semibold uppercase tracking-wide text-slate-500">
5456
- {{ getRepeatableItemTitle(field, index) }}
5457
- </div>
5458
- <dl class="grid gap-3 sm:grid-cols-2">
5459
- <div
5460
- *ngFor="let nestedField of getRepeatableFields(field); trackBy: trackByFieldId"
5461
- data-fd="form-viewer-repeatable-field"
5462
- class="rounded-md border border-slate-200 bg-white px-3 py-2">
5463
- <dt data-fd="form-viewer-repeatable-field-label" class="text-xs font-medium uppercase tracking-wide text-slate-500">
5464
- {{ getFieldLabel(nestedField) }}
5465
- </dt>
5466
- <dd data-fd="form-viewer-repeatable-field-value" class="mt-1 whitespace-pre-wrap text-sm text-slate-900">
5467
- {{ formatScalarValue(getNestedFieldValue(item, nestedField)) }}
5468
- </dd>
5469
- </div>
5470
- </dl>
5471
- </section>
5472
- </ng-container>
5473
- <ng-template #emptyRepeatableState>
5474
- <span data-fd="form-viewer-field-value-empty" class="text-sm text-slate-400">{{ getEmptyValueLabel(field) }}</span>
5475
- </ng-template>
5476
- </div>
5477
-
5478
- <div *ngSwitchCase="'table'" data-fd="form-viewer-table-wrapper" class="overflow-x-auto rounded-lg border border-slate-200">
5479
- <table data-fd="form-viewer-table" class="min-w-full divide-y divide-slate-200 text-sm">
5480
- <thead data-fd="form-viewer-table-head" class="bg-slate-50">
5481
- <tr>
5482
- <th
5483
- *ngFor="let column of getTableColumns(field, getFieldValue(field)); trackBy: trackByTableColumn"
5484
- data-fd="form-viewer-table-header"
5485
- class="px-3 py-2 text-left font-semibold text-slate-600">
5486
- {{ column.label }}
5487
- </th>
5488
- </tr>
5489
- </thead>
5490
- <tbody data-fd="form-viewer-table-body" class="divide-y divide-slate-200 bg-white">
5491
- <tr *ngFor="let row of getTableRows(getFieldValue(field)); trackBy: trackByIndex" data-fd="form-viewer-table-row">
5492
- <td
5493
- *ngFor="let column of getTableColumns(field, getFieldValue(field)); trackBy: trackByTableColumn"
5494
- data-fd="form-viewer-table-cell"
5495
- class="px-3 py-2 align-top text-slate-700">
5496
- {{ formatScalarValue(readPathValue(row, column.key)) }}
5497
- </td>
5498
- </tr>
5499
- <tr *ngIf="!getTableRows(getFieldValue(field)).length" data-fd="form-viewer-table-empty-row">
5500
- <td data-fd="form-viewer-table-empty"
5501
- class="px-3 py-3 text-slate-400"
5502
- [attr.colspan]="getTableColumns(field, getFieldValue(field)).length || 1">
5503
- {{ getEmptyValueLabel(field) }}
5504
- </td>
5505
- </tr>
5506
- </tbody>
5507
- </table>
5508
- </div>
5509
-
5510
- <div *ngSwitchDefault data-fd="form-viewer-field-value" class="whitespace-pre-wrap text-sm leading-6 text-slate-900">
5511
- {{ formatFieldValue(field, getFieldValue(field)) }}
5512
- </div>
5513
- </ng-container>
5514
- </div>
5515
- </ng-container>
5516
- </div>
5517
- </ng-container>
5518
- </ng-container>
5519
- </ng-container>
5520
- </ng-template>
5521
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i1.NgSwitchDefault, selector: "[ngSwitchDefault]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5522
- }
5523
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: FormViewerReadonlyComponent, decorators: [{
5524
- type: Component,
5525
- args: [{
5526
- selector: 'app-form-viewer-readonly',
5527
- standalone: true,
5528
- imports: [CommonModule],
5529
- changeDetection: ChangeDetectionStrategy.OnPush,
5530
- template: `
5531
- <div class="space-y-4" data-fd="form-viewer-readonly">
5532
- <ng-container *ngIf="schema.layout as layout">
5533
- <ng-container
5534
- *ngTemplateOutlet="renderNode; context: { $implicit: layout }">
5535
- </ng-container>
5536
- </ng-container>
5537
- </div>
5538
-
5539
- <ng-template #renderNode let-node>
5540
- <ng-container [ngSwitch]="node.type">
5541
- <div
5542
- *ngSwitchCase="'row'"
5543
- class="flex flex-wrap"
5544
- data-fd="form-viewer-row"
5545
- [style]="getNodeStyle(node)">
5546
- <ng-container *ngFor="let child of asRow(node).children; trackBy: trackByNodeId">
5547
- <ng-container
5548
- *ngTemplateOutlet="renderNode; context: { $implicit: child }">
5549
- </ng-container>
5550
- </ng-container>
5551
- </div>
5552
-
5553
- <div
5554
- *ngSwitchCase="'col'"
5555
- class="min-h-[1px] space-y-4"
5556
- data-fd="form-viewer-column"
5557
- [class]="getColClasses(node)"
5558
- [style]="getNodeStyle(node)">
5559
- <ng-container *ngFor="let child of asCol(node).children; trackBy: trackByNodeId">
5560
- <ng-container
5561
- *ngTemplateOutlet="renderNode; context: { $implicit: child }">
5562
- </ng-container>
5563
- </ng-container>
5564
- </div>
5565
-
5566
- <ng-container *ngSwitchCase="'widget'">
5567
- <ng-container *ngIf="getField(asWidget(node).refId) as field">
5568
- <div class="w-full"
5569
- data-fd="form-viewer-widget"
5570
- [attr.data-fd-field-name]="field.name"
5571
- [attr.data-fd-field-type]="field.type"
5572
- [style]="getNodeStyle(node)">
5573
- <ng-container [ngSwitch]="field.type">
5574
- <div *ngSwitchCase="'heading'"
5575
- data-fd="form-viewer-heading"
5576
- [attr.data-fd-field-name]="field.name"
5577
- [attr.data-fd-field-type]="field.type"
5578
- [style]="getFieldStyle(field)">
5579
- <h1
5580
- *ngIf="getHeadingLevel(field) === 'h1'"
5581
- class="text-4xl font-semibold tracking-tight text-slate-950">
5582
- {{ getHeadingText(field) }}
5583
- </h1>
5584
- <h2
5585
- *ngIf="getHeadingLevel(field) === 'h2'"
5586
- class="text-3xl font-semibold tracking-tight text-slate-950">
5587
- {{ getHeadingText(field) }}
5588
- </h2>
5589
- <h3
5590
- *ngIf="getHeadingLevel(field) === 'h3'"
5591
- class="text-2xl font-semibold tracking-tight text-slate-950">
5592
- {{ getHeadingText(field) }}
5593
- </h3>
5594
- <h4
5595
- *ngIf="getHeadingLevel(field) === 'h4'"
5596
- class="text-xl font-semibold tracking-tight text-slate-950">
5597
- {{ getHeadingText(field) }}
5598
- </h4>
5599
- <h5
5600
- *ngIf="getHeadingLevel(field) === 'h5'"
5601
- class="text-lg font-semibold tracking-tight text-slate-950">
5602
- {{ getHeadingText(field) }}
5603
- </h5>
5604
- <h6
5605
- *ngIf="getHeadingLevel(field) === 'h6'"
5606
- class="text-base font-semibold tracking-tight text-slate-950">
5607
- {{ getHeadingText(field) }}
5608
- </h6>
5609
- </div>
5610
-
5611
- <p
5612
- *ngSwitchCase="'paragraph'"
5613
- data-fd="form-viewer-paragraph"
5614
- [attr.data-fd-field-name]="field.name"
5615
- [attr.data-fd-field-type]="field.type"
5616
- class="whitespace-pre-wrap text-sm leading-6 text-slate-700"
5617
- [style]="getFieldStyle(field)">
5618
- {{ getParagraphText(field) }}
5619
- </p>
5620
-
5621
- <div
5622
- *ngSwitchCase="'rich-text'"
5623
- data-fd="form-viewer-rich-text"
5624
- [attr.data-fd-field-name]="field.name"
5625
- [attr.data-fd-field-type]="field.type"
5626
- class="whitespace-pre-wrap text-sm leading-6 text-slate-700"
5627
- [style]="getFieldStyle(field)"
5628
- [innerHTML]="getRichTextContent(field)"></div>
5629
-
5630
- <div *ngSwitchCase="'cta-button'"
5631
- data-fd="form-viewer-button"
5632
- [attr.data-fd-field-name]="field.name"
5633
- [attr.data-fd-field-type]="field.type"
5634
- [style]="getFieldStyle(field)">
5635
- <button
5636
- type="button"
5637
- disabled
5638
- class="inline-flex cursor-default items-center justify-center rounded-lg border border-slate-300 bg-slate-100 px-4 py-2 text-sm font-medium text-slate-700 shadow-sm">
5639
- {{ field.label || 'Action' }}
5640
- </button>
5641
- </div>
5642
-
5643
- <div *ngSwitchDefault
5644
- data-fd="form-viewer-field-card"
5645
- [attr.data-fd-field-name]="field.name"
5646
- [attr.data-fd-field-type]="field.type"
5647
- class="rounded-xl border border-slate-200 bg-white px-4 py-3 shadow-sm"
5648
- [style]="getFieldStyle(field)">
5649
- <div class="mb-3 flex items-start justify-between gap-3" *ngIf="field.helpText || field.html5?.required">
5650
- <div *ngIf="field.helpText">
5651
- <p data-fd="form-viewer-field-help" class="text-xs text-slate-500">
5652
- {{ field.helpText }}
5653
- </p>
5654
- </div>
5655
- <span
5656
- *ngIf="field.html5?.required"
5657
- class="rounded-full bg-rose-50 px-2 py-1 text-[11px] font-medium text-rose-600">
5658
- Required
5659
- </span>
5660
- </div>
5661
-
5662
- <ng-container [ngSwitch]="field.type">
5663
- <div *ngSwitchCase="'checkbox'"
5664
- data-fd="form-viewer-field-value"
5665
- class="inline-flex items-center rounded-full px-3 py-1 text-sm font-medium"
5666
- [class.bg-emerald-50]="isTruthy(getFieldValue(field))"
5667
- [class.text-emerald-700]="isTruthy(getFieldValue(field))"
5668
- [class.bg-slate-100]="!isTruthy(getFieldValue(field))"
5669
- [class.text-slate-600]="!isTruthy(getFieldValue(field))">
5670
- {{ isTruthy(getFieldValue(field)) ? 'Checked' : 'Not checked' }}
5671
- </div>
5672
-
5673
- <div *ngSwitchCase="'multiselect'" data-fd="form-viewer-chip-list" class="flex flex-wrap gap-2">
5674
- <span
5675
- *ngFor="let item of getDisplayList(field, getFieldValue(field)); trackBy: trackByIndex"
5676
- data-fd="form-viewer-chip"
5677
- class="rounded-full bg-slate-100 px-3 py-1 text-sm text-slate-700">
5678
- {{ item }}
5679
- </span>
5680
- <span *ngIf="!getDisplayList(field, getFieldValue(field)).length" data-fd="form-viewer-field-value-empty" class="text-sm text-slate-400">
5681
- {{ getEmptyValueLabel(field) }}
5682
- </span>
5683
- </div>
5684
-
5685
- <div *ngSwitchCase="'checkbox-group'" data-fd="form-viewer-chip-list" class="flex flex-wrap gap-2">
5686
- <span
5687
- *ngFor="let item of getDisplayList(field, getFieldValue(field)); trackBy: trackByIndex"
5688
- data-fd="form-viewer-chip"
5689
- class="rounded-full bg-slate-100 px-3 py-1 text-sm text-slate-700">
5690
- {{ item }}
5691
- </span>
5692
- <span *ngIf="!getDisplayList(field, getFieldValue(field)).length" data-fd="form-viewer-field-value-empty" class="text-sm text-slate-400">
5693
- {{ getEmptyValueLabel(field) }}
5694
- </span>
5695
- </div>
5696
-
5697
- <div *ngSwitchCase="'file'" data-fd="form-viewer-file-list" class="space-y-2">
5698
- <ng-container *ngIf="getUploadedFiles(getFieldValue(field)).length; else emptyFileState">
5699
- <div
5700
- *ngFor="let file of getUploadedFiles(getFieldValue(field)); trackBy: trackByUploadedFile"
5701
- data-fd="form-viewer-file-item"
5702
- class="flex items-center justify-between gap-3 rounded-lg border border-slate-200 bg-slate-50 px-3 py-2">
5703
- <div class="min-w-0">
5704
- <a
5705
- *ngIf="file.url; else fileNameText"
5706
- data-fd="form-viewer-file-link"
5707
- [href]="file.url"
5708
- target="_blank"
5709
- rel="noopener noreferrer"
5710
- class="block truncate text-sm font-medium text-sky-700 underline">
5711
- {{ file.name || file.id || 'Uploaded file' }}
5712
- </a>
5713
- <ng-template #fileNameText>
5714
- <div data-fd="form-viewer-file-name" class="truncate text-sm font-medium text-slate-900">
5715
- {{ file.name || file.id || 'Uploaded file' }}
5716
- </div>
5717
- </ng-template>
5718
- <div *ngIf="file.type" data-fd="form-viewer-file-type" class="text-xs text-slate-500">{{ file.type }}</div>
5719
- </div>
5720
- <div *ngIf="file.size != null" data-fd="form-viewer-file-size" class="text-xs text-slate-500">
5721
- {{ formatFileSize(file.size) }}
5722
- </div>
5723
- </div>
5724
- </ng-container>
5725
- <ng-template #emptyFileState>
5726
- <span data-fd="form-viewer-field-value-empty" class="text-sm text-slate-400">{{ getEmptyValueLabel(field) }}</span>
5727
- </ng-template>
5728
- </div>
5729
-
5730
- <div *ngSwitchCase="'image'" data-fd="form-viewer-image" class="space-y-3">
5731
- <ng-container *ngIf="getImageSource(field, getFieldValue(field)) as imageSrc; else emptyImageState">
5732
- <img
5733
- data-fd="form-viewer-image-element"
5734
- [src]="imageSrc"
5735
- [alt]="field.label || field.name"
5736
- class="max-h-64 rounded-lg border border-slate-200 object-contain">
5737
- </ng-container>
5738
- <ng-template #emptyImageState>
5739
- <span data-fd="form-viewer-field-value-empty" class="text-sm text-slate-400">{{ getEmptyValueLabel(field) }}</span>
5740
- </ng-template>
5741
- </div>
5742
-
5743
- <div *ngSwitchCase="'repeatable-group'" data-fd="form-viewer-repeatable-group" class="space-y-3">
5744
- <ng-container *ngIf="getRepeatableItems(getFieldValue(field)).length; else emptyRepeatableState">
5745
- <section
5746
- *ngFor="let item of getRepeatableItems(getFieldValue(field)); let index = index; trackBy: trackByIndex"
5747
- data-fd="form-viewer-repeatable-item"
5748
- class="rounded-lg border border-slate-200 bg-slate-50 px-3 py-3">
5749
- <div data-fd="form-viewer-repeatable-item-title" class="mb-3 text-xs font-semibold uppercase tracking-wide text-slate-500">
5750
- {{ getRepeatableItemTitle(field, index) }}
5751
- </div>
5752
- <dl class="grid gap-3 sm:grid-cols-2">
5753
- <div
5754
- *ngFor="let nestedField of getRepeatableFields(field); trackBy: trackByFieldId"
5755
- data-fd="form-viewer-repeatable-field"
5756
- class="rounded-md border border-slate-200 bg-white px-3 py-2">
5757
- <dt data-fd="form-viewer-repeatable-field-label" class="text-xs font-medium uppercase tracking-wide text-slate-500">
5758
- {{ getFieldLabel(nestedField) }}
5759
- </dt>
5760
- <dd data-fd="form-viewer-repeatable-field-value" class="mt-1 whitespace-pre-wrap text-sm text-slate-900">
5761
- {{ formatScalarValue(getNestedFieldValue(item, nestedField)) }}
5762
- </dd>
5763
- </div>
5764
- </dl>
5765
- </section>
5766
- </ng-container>
5767
- <ng-template #emptyRepeatableState>
5768
- <span data-fd="form-viewer-field-value-empty" class="text-sm text-slate-400">{{ getEmptyValueLabel(field) }}</span>
5769
- </ng-template>
5770
- </div>
5771
-
5772
- <div *ngSwitchCase="'table'" data-fd="form-viewer-table-wrapper" class="overflow-x-auto rounded-lg border border-slate-200">
5773
- <table data-fd="form-viewer-table" class="min-w-full divide-y divide-slate-200 text-sm">
5774
- <thead data-fd="form-viewer-table-head" class="bg-slate-50">
5775
- <tr>
5776
- <th
5777
- *ngFor="let column of getTableColumns(field, getFieldValue(field)); trackBy: trackByTableColumn"
5778
- data-fd="form-viewer-table-header"
5779
- class="px-3 py-2 text-left font-semibold text-slate-600">
5780
- {{ column.label }}
5781
- </th>
5782
- </tr>
5783
- </thead>
5784
- <tbody data-fd="form-viewer-table-body" class="divide-y divide-slate-200 bg-white">
5785
- <tr *ngFor="let row of getTableRows(getFieldValue(field)); trackBy: trackByIndex" data-fd="form-viewer-table-row">
5786
- <td
5787
- *ngFor="let column of getTableColumns(field, getFieldValue(field)); trackBy: trackByTableColumn"
5788
- data-fd="form-viewer-table-cell"
5789
- class="px-3 py-2 align-top text-slate-700">
5790
- {{ formatScalarValue(readPathValue(row, column.key)) }}
5791
- </td>
5792
- </tr>
5793
- <tr *ngIf="!getTableRows(getFieldValue(field)).length" data-fd="form-viewer-table-empty-row">
5794
- <td data-fd="form-viewer-table-empty"
5795
- class="px-3 py-3 text-slate-400"
5796
- [attr.colspan]="getTableColumns(field, getFieldValue(field)).length || 1">
5797
- {{ getEmptyValueLabel(field) }}
5798
- </td>
5799
- </tr>
5800
- </tbody>
5801
- </table>
5802
- </div>
5803
-
5804
- <div *ngSwitchDefault data-fd="form-viewer-field-value" class="whitespace-pre-wrap text-sm leading-6 text-slate-900">
5805
- {{ formatFieldValue(field, getFieldValue(field)) }}
5806
- </div>
5807
- </ng-container>
5808
- </div>
5809
- </ng-container>
5810
- </div>
5811
- </ng-container>
5812
- </ng-container>
5813
- </ng-container>
5814
- </ng-template>
5815
- `
5816
- }]
5817
- }], propDecorators: { schema: [{
5818
- type: Input,
5819
- args: [{ required: true }]
5820
- }], values: [{
5821
- type: Input
5822
- }], breakpoint: [{
5823
- type: Input
5824
- }] } });
5825
-
5826
4969
  class FormViewerComponent {
5827
4970
  schema;
5828
4971
  set data(value) {
@@ -5993,34 +5136,25 @@ class FormViewerComponent {
5993
5136
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: FormViewerComponent, isStandalone: true, selector: "app-form-viewer", inputs: { schema: "schema", data: "data", options: "options", viewOnly: "viewOnly", eventApis: "eventApis", eventApiExecutor: "eventApiExecutor", fieldDataAccessMap: "fieldDataAccessMap", fieldDataAccessApi: "fieldDataAccessApi", formContentId: "formContentId", formContentVersion: "formContentVersion" }, outputs: { formDataChange: "formDataChange", formValidationChange: "formValidationChange", uploadedFilesChange: "uploadedFilesChange", submit: "submit" }, viewQueries: [{ propertyName: "renderer", first: true, predicate: JsonFormRendererComponent, descendants: true }], ngImport: i0, template: `
5994
5137
  <div class="form-viewer-container flex flex-col h-full"
5995
5138
  data-fd="form-viewer"
5996
- [attr.data-fd-mode]="viewOnly ? 'readonly' : 'live'">
5997
- <div class="flex-1 min-h-0 overflow-y-auto" data-fd="form-viewer-surface">
5998
- <app-form-viewer-readonly
5999
- *ngIf="viewOnly; else liveRenderer"
5139
+ [attr.data-fd-mode]="viewOnly ? 'preview' : 'live'">
5140
+ <div class="flex-1 min-h-0" data-fd="form-viewer-surface">
5141
+ <app-json-form-renderer
6000
5142
  [schema]="schema"
6001
- [values]="normalizedInitialValues"
6002
- [breakpoint]="breakpoint()">
6003
- </app-form-viewer-readonly>
6004
-
6005
- <ng-template #liveRenderer>
6006
- <app-json-form-renderer
6007
- [schema]="schema"
6008
- [initialValues]="normalizedInitialValues"
6009
- [mode]="'live'"
6010
- [breakpoint]="breakpoint()"
6011
- [uploadOnSubmit]="true"
6012
- [eventApis]="eventApis"
6013
- [eventApiExecutor]="eventApiExecutor"
6014
- [fieldDataAccessMap]="fieldDataAccessMap"
6015
- [fieldDataAccessApi]="fieldDataAccessApi"
6016
- [formContentId]="formContentId"
6017
- [formContentVersion]="formContentVersion"
6018
- (valueChange)="onValueChange($event)"
6019
- (validationChange)="onValidationChange($event)"
6020
- (uploadedFilesChange)="onUploadedFilesChange($event)"
6021
- (formSubmit)="onRendererSubmit($event)">
6022
- </app-json-form-renderer>
6023
- </ng-template>
5143
+ [initialValues]="normalizedInitialValues"
5144
+ [mode]="viewOnly ? 'preview' : 'live'"
5145
+ [breakpoint]="breakpoint()"
5146
+ [uploadOnSubmit]="true"
5147
+ [eventApis]="eventApis"
5148
+ [eventApiExecutor]="eventApiExecutor"
5149
+ [fieldDataAccessMap]="fieldDataAccessMap"
5150
+ [fieldDataAccessApi]="fieldDataAccessApi"
5151
+ [formContentId]="formContentId"
5152
+ [formContentVersion]="formContentVersion"
5153
+ (valueChange)="onValueChange($event)"
5154
+ (validationChange)="onValidationChange($event)"
5155
+ (uploadedFilesChange)="onUploadedFilesChange($event)"
5156
+ (formSubmit)="onRendererSubmit($event)">
5157
+ </app-json-form-renderer>
6024
5158
  </div>
6025
5159
 
6026
5160
  <div *ngIf="!viewOnly && options.showSubmitButton"
@@ -6035,41 +5169,32 @@ class FormViewerComponent {
6035
5169
  </button>
6036
5170
  </div>
6037
5171
  </div>
6038
- `, isInline: true, styles: [":host{display:block;height:100%;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: JsonFormRendererComponent, selector: "app-json-form-renderer", inputs: ["schema", "initialValues", "mode", "device", "breakpoint", "eventLogger", "eventApis", "eventApiExecutor", "uploadOnSubmit", "fieldDataAccessMap", "fieldDataAccessApi", "formContentId", "formContentVersion"], outputs: ["valueChange", "groupedValueChange", "combinedValueChange", "validationChange", "uploadedFilesChange", "formSubmit"] }, { kind: "component", type: FormViewerReadonlyComponent, selector: "app-form-viewer-readonly", inputs: ["schema", "values", "breakpoint"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5172
+ `, isInline: true, styles: [":host{display:block;height:100%;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: JsonFormRendererComponent, selector: "app-json-form-renderer", inputs: ["schema", "initialValues", "mode", "device", "breakpoint", "eventLogger", "eventApis", "eventApiExecutor", "uploadOnSubmit", "fieldDataAccessMap", "fieldDataAccessApi", "formContentId", "formContentVersion"], outputs: ["valueChange", "groupedValueChange", "combinedValueChange", "validationChange", "uploadedFilesChange", "formSubmit"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6039
5173
  }
6040
5174
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: FormViewerComponent, decorators: [{
6041
5175
  type: Component,
6042
- args: [{ selector: 'app-form-viewer', standalone: true, imports: [CommonModule, JsonFormRendererComponent, FormViewerReadonlyComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
5176
+ args: [{ selector: 'app-form-viewer', standalone: true, imports: [CommonModule, JsonFormRendererComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
6043
5177
  <div class="form-viewer-container flex flex-col h-full"
6044
5178
  data-fd="form-viewer"
6045
- [attr.data-fd-mode]="viewOnly ? 'readonly' : 'live'">
6046
- <div class="flex-1 min-h-0 overflow-y-auto" data-fd="form-viewer-surface">
6047
- <app-form-viewer-readonly
6048
- *ngIf="viewOnly; else liveRenderer"
5179
+ [attr.data-fd-mode]="viewOnly ? 'preview' : 'live'">
5180
+ <div class="flex-1 min-h-0" data-fd="form-viewer-surface">
5181
+ <app-json-form-renderer
6049
5182
  [schema]="schema"
6050
- [values]="normalizedInitialValues"
6051
- [breakpoint]="breakpoint()">
6052
- </app-form-viewer-readonly>
6053
-
6054
- <ng-template #liveRenderer>
6055
- <app-json-form-renderer
6056
- [schema]="schema"
6057
- [initialValues]="normalizedInitialValues"
6058
- [mode]="'live'"
6059
- [breakpoint]="breakpoint()"
6060
- [uploadOnSubmit]="true"
6061
- [eventApis]="eventApis"
6062
- [eventApiExecutor]="eventApiExecutor"
6063
- [fieldDataAccessMap]="fieldDataAccessMap"
6064
- [fieldDataAccessApi]="fieldDataAccessApi"
6065
- [formContentId]="formContentId"
6066
- [formContentVersion]="formContentVersion"
6067
- (valueChange)="onValueChange($event)"
6068
- (validationChange)="onValidationChange($event)"
6069
- (uploadedFilesChange)="onUploadedFilesChange($event)"
6070
- (formSubmit)="onRendererSubmit($event)">
6071
- </app-json-form-renderer>
6072
- </ng-template>
5183
+ [initialValues]="normalizedInitialValues"
5184
+ [mode]="viewOnly ? 'preview' : 'live'"
5185
+ [breakpoint]="breakpoint()"
5186
+ [uploadOnSubmit]="true"
5187
+ [eventApis]="eventApis"
5188
+ [eventApiExecutor]="eventApiExecutor"
5189
+ [fieldDataAccessMap]="fieldDataAccessMap"
5190
+ [fieldDataAccessApi]="fieldDataAccessApi"
5191
+ [formContentId]="formContentId"
5192
+ [formContentVersion]="formContentVersion"
5193
+ (valueChange)="onValueChange($event)"
5194
+ (validationChange)="onValidationChange($event)"
5195
+ (uploadedFilesChange)="onUploadedFilesChange($event)"
5196
+ (formSubmit)="onRendererSubmit($event)">
5197
+ </app-json-form-renderer>
6073
5198
  </div>
6074
5199
 
6075
5200
  <div *ngIf="!viewOnly && options.showSubmitButton"
@@ -7322,6 +6447,101 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
7322
6447
  args: [WIDGET_DEFINITIONS]
7323
6448
  }] }, { type: DesignerStateService }] });
7324
6449
 
6450
+ class MonacoEditorComponent {
6451
+ value = input('');
6452
+ language = input('html');
6453
+ theme = input('vs');
6454
+ readOnly = input(false);
6455
+ minimap = input(false);
6456
+ options = input(null);
6457
+ valueChange = output();
6458
+ containerRef;
6459
+ static configured = false;
6460
+ monaco;
6461
+ editor;
6462
+ suppressChange = false;
6463
+ subscriptions = [];
6464
+ resizeObserver;
6465
+ editorReady = signal(false);
6466
+ syncInputs = effect(() => {
6467
+ if (!this.editorReady())
6468
+ return;
6469
+ if (!this.editor || !this.monaco)
6470
+ return;
6471
+ const nextValue = this.value();
6472
+ if (this.editor.getValue() !== nextValue) {
6473
+ this.suppressChange = true;
6474
+ this.editor.setValue(nextValue ?? '');
6475
+ this.suppressChange = false;
6476
+ }
6477
+ const model = this.editor.getModel();
6478
+ if (model) {
6479
+ this.monaco.editor.setModelLanguage(model, this.language());
6480
+ }
6481
+ this.monaco.editor.setTheme(this.theme());
6482
+ const options = this.options() ?? {};
6483
+ this.editor.updateOptions({
6484
+ ...options,
6485
+ readOnly: this.readOnly(),
6486
+ minimap: { enabled: this.minimap() }
6487
+ });
6488
+ });
6489
+ async ngAfterViewInit() {
6490
+ if (!MonacoEditorComponent.configured) {
6491
+ loader.config({
6492
+ paths: { vs: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs' }
6493
+ });
6494
+ MonacoEditorComponent.configured = true;
6495
+ }
6496
+ this.monaco = (await loader.init());
6497
+ this.editor = this.monaco.editor.create(this.containerRef.nativeElement, {
6498
+ value: this.value() ?? '',
6499
+ language: this.language(),
6500
+ theme: this.theme(),
6501
+ readOnly: this.readOnly(),
6502
+ minimap: { enabled: this.minimap() },
6503
+ automaticLayout: false,
6504
+ ...(this.options() ?? {})
6505
+ });
6506
+ const model = this.editor.getModel();
6507
+ if (model && this.language()) {
6508
+ this.monaco.editor.setModelLanguage(model, this.language());
6509
+ }
6510
+ this.subscriptions.push(this.editor.onDidChangeModelContent(() => {
6511
+ if (this.suppressChange)
6512
+ return;
6513
+ this.valueChange.emit(this.editor?.getValue() ?? '');
6514
+ }));
6515
+ if (typeof ResizeObserver !== 'undefined') {
6516
+ this.resizeObserver = new ResizeObserver(() => this.editor?.layout());
6517
+ this.resizeObserver.observe(this.containerRef.nativeElement);
6518
+ }
6519
+ this.editorReady.set(true);
6520
+ }
6521
+ ngOnDestroy() {
6522
+ this.resizeObserver?.disconnect();
6523
+ this.subscriptions.forEach(subscription => subscription.dispose());
6524
+ this.subscriptions = [];
6525
+ this.editor?.dispose();
6526
+ this.editor = undefined;
6527
+ }
6528
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: MonacoEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6529
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.17", type: MonacoEditorComponent, isStandalone: true, selector: "app-monaco-editor", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, language: { classPropertyName: "language", publicName: "language", isSignal: true, isRequired: false, transformFunction: null }, theme: { classPropertyName: "theme", publicName: "theme", isSignal: true, isRequired: false, transformFunction: null }, readOnly: { classPropertyName: "readOnly", publicName: "readOnly", isSignal: true, isRequired: false, transformFunction: null }, minimap: { classPropertyName: "minimap", publicName: "minimap", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, host: { classAttribute: "block h-full w-full" }, viewQueries: [{ propertyName: "containerRef", first: true, predicate: ["container"], descendants: true, static: true }], ngImport: i0, template: `<div class="h-full w-full" #container></div>`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
6530
+ }
6531
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: MonacoEditorComponent, decorators: [{
6532
+ type: Component,
6533
+ args: [{
6534
+ selector: 'app-monaco-editor',
6535
+ standalone: true,
6536
+ changeDetection: ChangeDetectionStrategy.OnPush,
6537
+ template: `<div class="h-full w-full" #container></div>`,
6538
+ host: { class: 'block h-full w-full' }
6539
+ }]
6540
+ }], propDecorators: { containerRef: [{
6541
+ type: ViewChild,
6542
+ args: ['container', { static: true }]
6543
+ }] } });
6544
+
7325
6545
  const DEFAULT_PAGE_STYLE = {
7326
6546
  paddingTop: 32,
7327
6547
  paddingRight: 32,
@@ -7347,6 +6567,15 @@ class LayoutCanvasComponent {
7347
6567
  get device() { return this.state.activeDevice(); } // 'mobile' | 'desktop' for basic passing
7348
6568
  get breakpoint() { return this.state.activeBreakpoint(); }
7349
6569
  zoom = signal(1);
6570
+ showLiveSchemaEditor = signal(false);
6571
+ liveSchemaEditorText = signal('');
6572
+ liveSchemaEditorError = signal('');
6573
+ liveSchemaEditorOptions = {
6574
+ fontSize: 12,
6575
+ lineNumbersMinChars: 3,
6576
+ wordWrap: 'on',
6577
+ scrollBeyondLastLine: false
6578
+ };
7350
6579
  pageBaseSize = signal({ width: 0, height: 0 });
7351
6580
  minZoom = 0.25;
7352
6581
  maxZoom = 3;
@@ -7504,6 +6733,34 @@ class LayoutCanvasComponent {
7504
6733
  }
7505
6734
  this.state.isPreviewMode.set(true);
7506
6735
  }
6736
+ toggleLiveSchemaEditor() {
6737
+ if (this.showLiveSchemaEditor()) {
6738
+ this.closeLiveSchemaEditor();
6739
+ return;
6740
+ }
6741
+ this.liveSchemaEditorText.set(serializeSchema(this.state.schema()));
6742
+ this.liveSchemaEditorError.set('');
6743
+ this.showLiveSchemaEditor.set(true);
6744
+ }
6745
+ closeLiveSchemaEditor() {
6746
+ this.showLiveSchemaEditor.set(false);
6747
+ }
6748
+ onLiveSchemaEditorTextChange(nextText) {
6749
+ this.liveSchemaEditorText.set(nextText);
6750
+ if (this.state.isReadOnly()) {
6751
+ this.liveSchemaEditorError.set('');
6752
+ return;
6753
+ }
6754
+ try {
6755
+ const parsedSchema = parseSchema(nextText);
6756
+ this.state.updateSchema(parsedSchema);
6757
+ this.state.selectNode(null);
6758
+ this.liveSchemaEditorError.set('');
6759
+ }
6760
+ catch {
6761
+ this.liveSchemaEditorError.set('Invalid JSON schema. Changes are not applied until the JSON is valid.');
6762
+ }
6763
+ }
7507
6764
  getCanvasWidth() {
7508
6765
  const bp = this.breakpoint;
7509
6766
  switch (bp) {
@@ -7785,6 +7042,12 @@ class LayoutCanvasComponent {
7785
7042
  <span class="text-[12px] leading-none">Preview</span>
7786
7043
  </button>
7787
7044
 
7045
+ <button class="inline-flex h-7 w-7 items-center justify-center rounded-[4px] border border-transparent transition-colors hover:border-border-default hover:bg-slate-50"
7046
+ (click)="toggleLiveSchemaEditor(); $event.stopPropagation()"
7047
+ title="Live JSON schema editor"
7048
+ aria-label="Live JSON schema editor">
7049
+ <lucide-icon name="braces" class="w-3.5 h-3.5"></lucide-icon>
7050
+ </button>
7788
7051
  <button class="inline-flex h-7 w-7 items-center justify-center rounded-[4px] border border-transparent transition-colors hover:border-border-default hover:bg-slate-50"
7789
7052
  (click)="exportJson(); $event.stopPropagation()" title="Export JSON">
7790
7053
  <lucide-icon name="download" class="w-3.5 h-3.5"></lucide-icon>
@@ -7891,15 +7154,53 @@ class LayoutCanvasComponent {
7891
7154
  <lucide-icon *ngIf="!last" name="chevron-right" class="w-3 h-3 text-ink-600"></lucide-icon>
7892
7155
  </ng-container>
7893
7156
  </div>
7157
+
7158
+ <div *ngIf="showLiveSchemaEditor()"
7159
+ data-testid="live-schema-editor-panel"
7160
+ class="absolute inset-0 z-[70] flex items-center justify-center bg-black/20 p-4 backdrop-blur-[1px]"
7161
+ (click)="closeLiveSchemaEditor()">
7162
+ <section class="h-full w-full max-w-5xl overflow-hidden rounded-[6px] border border-border-default bg-surface-default shadow-popover"
7163
+ (click)="$event.stopPropagation()">
7164
+ <header class="flex items-center justify-between border-b border-border-default px-4 py-2.5">
7165
+ <div class="flex flex-col">
7166
+ <h3 class="text-[13px] font-semibold text-text-strong">Live JSON Schema Editor</h3>
7167
+ <p class="text-[11px] text-text-primary/80">Changes apply instantly when JSON is valid.</p>
7168
+ </div>
7169
+ <button type="button"
7170
+ class="inline-flex h-8 w-8 items-center justify-center rounded-[4px] border border-border-default text-text-primary transition-colors hover:bg-slate-50"
7171
+ aria-label="Close live schema editor"
7172
+ (click)="closeLiveSchemaEditor()">
7173
+ <lucide-icon name="x" class="h-4 w-4"></lucide-icon>
7174
+ </button>
7175
+ </header>
7176
+
7177
+ <div *ngIf="liveSchemaEditorError()"
7178
+ class="border-b border-red-200 bg-red-50 px-4 py-2 text-[11px] text-red-700">
7179
+ {{ liveSchemaEditorError() }}
7180
+ </div>
7181
+
7182
+ <div class="h-[calc(100%-56px)] min-h-0">
7183
+ <app-monaco-editor
7184
+ class="h-full"
7185
+ [value]="liveSchemaEditorText()"
7186
+ language="json"
7187
+ theme="vs"
7188
+ [readOnly]="state.isReadOnly()"
7189
+ [options]="liveSchemaEditorOptions"
7190
+ (valueChange)="onLiveSchemaEditorTextChange($event)">
7191
+ </app-monaco-editor>
7192
+ </div>
7193
+ </section>
7194
+ </div>
7894
7195
  </div>
7895
7196
 
7896
7197
  <!-- Hidden file input for import -->
7897
7198
  <input type="file" #fileInput accept=".json" (change)="onFileSelected($event)" style="display: none;">
7898
- `, isInline: true, styles: [".canvas-grid{background-image:radial-gradient(rgba(148,163,184,.35) 1px,transparent 1px);background-size:18px 18px}.custom-scrollbar::-webkit-scrollbar{width:6px;height:6px}.custom-scrollbar::-webkit-scrollbar-track{background:transparent}.custom-scrollbar::-webkit-scrollbar-thumb{background:transparent;border-radius:10px}.custom-scrollbar:hover::-webkit-scrollbar-thumb{background:#00000026}.custom-scrollbar{scrollbar-width:thin;scrollbar-color:transparent transparent}.custom-scrollbar:hover{scrollbar-color:rgba(0,0,0,.15) transparent}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: JsonFormRendererComponent, selector: "app-json-form-renderer", inputs: ["schema", "initialValues", "mode", "device", "breakpoint", "eventLogger", "eventApis", "eventApiExecutor", "uploadOnSubmit", "fieldDataAccessMap", "fieldDataAccessApi", "formContentId", "formContentVersion"], outputs: ["valueChange", "groupedValueChange", "combinedValueChange", "validationChange", "uploadedFilesChange", "formSubmit"] }, { kind: "ngmodule", type: UiIconModule }, { kind: "component", type: i3$1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }] });
7199
+ `, isInline: true, styles: [".canvas-grid{background-image:radial-gradient(rgba(148,163,184,.35) 1px,transparent 1px);background-size:18px 18px}.custom-scrollbar::-webkit-scrollbar{width:6px;height:6px}.custom-scrollbar::-webkit-scrollbar-track{background:transparent}.custom-scrollbar::-webkit-scrollbar-thumb{background:transparent;border-radius:10px}.custom-scrollbar:hover::-webkit-scrollbar-thumb{background:#00000026}.custom-scrollbar{scrollbar-width:thin;scrollbar-color:transparent transparent}.custom-scrollbar:hover{scrollbar-color:rgba(0,0,0,.15) transparent}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: JsonFormRendererComponent, selector: "app-json-form-renderer", inputs: ["schema", "initialValues", "mode", "device", "breakpoint", "eventLogger", "eventApis", "eventApiExecutor", "uploadOnSubmit", "fieldDataAccessMap", "fieldDataAccessApi", "formContentId", "formContentVersion"], outputs: ["valueChange", "groupedValueChange", "combinedValueChange", "validationChange", "uploadedFilesChange", "formSubmit"] }, { kind: "ngmodule", type: UiIconModule }, { kind: "component", type: i3$1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }, { kind: "component", type: MonacoEditorComponent, selector: "app-monaco-editor", inputs: ["value", "language", "theme", "readOnly", "minimap", "options"], outputs: ["valueChange"] }] });
7899
7200
  }
7900
7201
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: LayoutCanvasComponent, decorators: [{
7901
7202
  type: Component,
7902
- args: [{ selector: 'app-layout-canvas', standalone: true, imports: [CommonModule, JsonFormRendererComponent, UiIconModule], template: `
7203
+ args: [{ selector: 'app-layout-canvas', standalone: true, imports: [CommonModule, JsonFormRendererComponent, UiIconModule, MonacoEditorComponent], template: `
7903
7204
  <div #root class="h-full w-full relative font-sans">
7904
7205
  <!-- Floating Toolbar (fixed to canvas viewport; unaffected by pan/zoom) -->
7905
7206
  <div class="absolute top-4 left-1/2 -translate-x-1/2 z-40">
@@ -7974,6 +7275,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
7974
7275
  <span class="text-[12px] leading-none">Preview</span>
7975
7276
  </button>
7976
7277
 
7278
+ <button class="inline-flex h-7 w-7 items-center justify-center rounded-[4px] border border-transparent transition-colors hover:border-border-default hover:bg-slate-50"
7279
+ (click)="toggleLiveSchemaEditor(); $event.stopPropagation()"
7280
+ title="Live JSON schema editor"
7281
+ aria-label="Live JSON schema editor">
7282
+ <lucide-icon name="braces" class="w-3.5 h-3.5"></lucide-icon>
7283
+ </button>
7977
7284
  <button class="inline-flex h-7 w-7 items-center justify-center rounded-[4px] border border-transparent transition-colors hover:border-border-default hover:bg-slate-50"
7978
7285
  (click)="exportJson(); $event.stopPropagation()" title="Export JSON">
7979
7286
  <lucide-icon name="download" class="w-3.5 h-3.5"></lucide-icon>
@@ -8080,6 +7387,44 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
8080
7387
  <lucide-icon *ngIf="!last" name="chevron-right" class="w-3 h-3 text-ink-600"></lucide-icon>
8081
7388
  </ng-container>
8082
7389
  </div>
7390
+
7391
+ <div *ngIf="showLiveSchemaEditor()"
7392
+ data-testid="live-schema-editor-panel"
7393
+ class="absolute inset-0 z-[70] flex items-center justify-center bg-black/20 p-4 backdrop-blur-[1px]"
7394
+ (click)="closeLiveSchemaEditor()">
7395
+ <section class="h-full w-full max-w-5xl overflow-hidden rounded-[6px] border border-border-default bg-surface-default shadow-popover"
7396
+ (click)="$event.stopPropagation()">
7397
+ <header class="flex items-center justify-between border-b border-border-default px-4 py-2.5">
7398
+ <div class="flex flex-col">
7399
+ <h3 class="text-[13px] font-semibold text-text-strong">Live JSON Schema Editor</h3>
7400
+ <p class="text-[11px] text-text-primary/80">Changes apply instantly when JSON is valid.</p>
7401
+ </div>
7402
+ <button type="button"
7403
+ class="inline-flex h-8 w-8 items-center justify-center rounded-[4px] border border-border-default text-text-primary transition-colors hover:bg-slate-50"
7404
+ aria-label="Close live schema editor"
7405
+ (click)="closeLiveSchemaEditor()">
7406
+ <lucide-icon name="x" class="h-4 w-4"></lucide-icon>
7407
+ </button>
7408
+ </header>
7409
+
7410
+ <div *ngIf="liveSchemaEditorError()"
7411
+ class="border-b border-red-200 bg-red-50 px-4 py-2 text-[11px] text-red-700">
7412
+ {{ liveSchemaEditorError() }}
7413
+ </div>
7414
+
7415
+ <div class="h-[calc(100%-56px)] min-h-0">
7416
+ <app-monaco-editor
7417
+ class="h-full"
7418
+ [value]="liveSchemaEditorText()"
7419
+ language="json"
7420
+ theme="vs"
7421
+ [readOnly]="state.isReadOnly()"
7422
+ [options]="liveSchemaEditorOptions"
7423
+ (valueChange)="onLiveSchemaEditorTextChange($event)">
7424
+ </app-monaco-editor>
7425
+ </div>
7426
+ </section>
7427
+ </div>
8083
7428
  </div>
8084
7429
 
8085
7430
  <!-- Hidden file input for import -->
@@ -25973,101 +25318,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
25973
25318
  args: ['projectFileInput']
25974
25319
  }] } });
25975
25320
 
25976
- class MonacoEditorComponent {
25977
- value = input('');
25978
- language = input('html');
25979
- theme = input('vs');
25980
- readOnly = input(false);
25981
- minimap = input(false);
25982
- options = input(null);
25983
- valueChange = output();
25984
- containerRef;
25985
- static configured = false;
25986
- monaco;
25987
- editor;
25988
- suppressChange = false;
25989
- subscriptions = [];
25990
- resizeObserver;
25991
- editorReady = signal(false);
25992
- syncInputs = effect(() => {
25993
- if (!this.editorReady())
25994
- return;
25995
- if (!this.editor || !this.monaco)
25996
- return;
25997
- const nextValue = this.value();
25998
- if (this.editor.getValue() !== nextValue) {
25999
- this.suppressChange = true;
26000
- this.editor.setValue(nextValue ?? '');
26001
- this.suppressChange = false;
26002
- }
26003
- const model = this.editor.getModel();
26004
- if (model) {
26005
- this.monaco.editor.setModelLanguage(model, this.language());
26006
- }
26007
- this.monaco.editor.setTheme(this.theme());
26008
- const options = this.options() ?? {};
26009
- this.editor.updateOptions({
26010
- ...options,
26011
- readOnly: this.readOnly(),
26012
- minimap: { enabled: this.minimap() }
26013
- });
26014
- });
26015
- async ngAfterViewInit() {
26016
- if (!MonacoEditorComponent.configured) {
26017
- loader.config({
26018
- paths: { vs: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs' }
26019
- });
26020
- MonacoEditorComponent.configured = true;
26021
- }
26022
- this.monaco = (await loader.init());
26023
- this.editor = this.monaco.editor.create(this.containerRef.nativeElement, {
26024
- value: this.value() ?? '',
26025
- language: this.language(),
26026
- theme: this.theme(),
26027
- readOnly: this.readOnly(),
26028
- minimap: { enabled: this.minimap() },
26029
- automaticLayout: false,
26030
- ...(this.options() ?? {})
26031
- });
26032
- const model = this.editor.getModel();
26033
- if (model && this.language()) {
26034
- this.monaco.editor.setModelLanguage(model, this.language());
26035
- }
26036
- this.subscriptions.push(this.editor.onDidChangeModelContent(() => {
26037
- if (this.suppressChange)
26038
- return;
26039
- this.valueChange.emit(this.editor?.getValue() ?? '');
26040
- }));
26041
- if (typeof ResizeObserver !== 'undefined') {
26042
- this.resizeObserver = new ResizeObserver(() => this.editor?.layout());
26043
- this.resizeObserver.observe(this.containerRef.nativeElement);
26044
- }
26045
- this.editorReady.set(true);
26046
- }
26047
- ngOnDestroy() {
26048
- this.resizeObserver?.disconnect();
26049
- this.subscriptions.forEach(subscription => subscription.dispose());
26050
- this.subscriptions = [];
26051
- this.editor?.dispose();
26052
- this.editor = undefined;
26053
- }
26054
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: MonacoEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
26055
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.17", type: MonacoEditorComponent, isStandalone: true, selector: "app-monaco-editor", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, language: { classPropertyName: "language", publicName: "language", isSignal: true, isRequired: false, transformFunction: null }, theme: { classPropertyName: "theme", publicName: "theme", isSignal: true, isRequired: false, transformFunction: null }, readOnly: { classPropertyName: "readOnly", publicName: "readOnly", isSignal: true, isRequired: false, transformFunction: null }, minimap: { classPropertyName: "minimap", publicName: "minimap", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, host: { classAttribute: "block h-full w-full" }, viewQueries: [{ propertyName: "containerRef", first: true, predicate: ["container"], descendants: true, static: true }], ngImport: i0, template: `<div class="h-full w-full" #container></div>`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
26056
- }
26057
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: MonacoEditorComponent, decorators: [{
26058
- type: Component,
26059
- args: [{
26060
- selector: 'app-monaco-editor',
26061
- standalone: true,
26062
- changeDetection: ChangeDetectionStrategy.OnPush,
26063
- template: `<div class="h-full w-full" #container></div>`,
26064
- host: { class: 'block h-full w-full' }
26065
- }]
26066
- }], propDecorators: { containerRef: [{
26067
- type: ViewChild,
26068
- args: ['container', { static: true }]
26069
- }] } });
26070
-
26071
25321
  /// <reference path="../../json-editor.d.ts" />
26072
25322
  const FD_JSON_EDITOR_ICONLIB = 'fdlucide';
26073
25323
  function ensureFdIconLib(jsonEditorCtor) {
@@ -28084,6 +27334,9 @@ class DefaultDataProvider extends DataProvider {
28084
27334
  return super.queryOptions(field, { ...query, term: query.term || '' }, engine);
28085
27335
  }
28086
27336
  if (cfg.type === 'source' || cfg.type === 'global' || cfg.type === 'api') {
27337
+ if (this.shouldSuppressRemoteAccess(cfg, engine)) {
27338
+ return [];
27339
+ }
28087
27340
  if (!cfg.datasourceId)
28088
27341
  return [];
28089
27342
  const filters = this.resolveFilters(cfg, engine);
@@ -28135,6 +27388,9 @@ class DefaultDataProvider extends DataProvider {
28135
27388
  // Determine if source-backed
28136
27389
  const isSource = cfg.type === 'source' || cfg.type === 'global' || (cfg.type === 'api' && !!cfg.datasourceId);
28137
27390
  if (isSource && cfg.datasourceId) {
27391
+ if (this.shouldSuppressRemoteAccess(cfg, engine)) {
27392
+ return { rows: [], total: 0 };
27393
+ }
28138
27394
  // Build DataSourceQuery
28139
27395
  const term = query.term ?? '';
28140
27396
  const search = term ? { term, columns: cfg.searchColumns } : undefined;
@@ -28213,6 +27469,9 @@ class DefaultDataProvider extends DataProvider {
28213
27469
  case 'source':
28214
27470
  case 'global':
28215
27471
  case 'api': // Aliasing API to source if datasourceId present
27472
+ if (this.shouldSuppressRemoteAccess(cfg, engine)) {
27473
+ return [];
27474
+ }
28216
27475
  if (cfg.datasourceId) {
28217
27476
  return this.getGlobalRows(cfg);
28218
27477
  }
@@ -28371,8 +27630,16 @@ class DefaultDataProvider extends DataProvider {
28371
27630
  async getRuntimeOptions(field, engine) {
28372
27631
  if (!engine)
28373
27632
  return undefined;
27633
+ if (areEngineApiCallsSuppressed(engine))
27634
+ return undefined;
28374
27635
  return this.runtimeFieldDataAccessRegistry.getOptions(field, engine);
28375
27636
  }
27637
+ shouldSuppressRemoteAccess(cfg, engine) {
27638
+ if (!engine || !areEngineApiCallsSuppressed(engine)) {
27639
+ return false;
27640
+ }
27641
+ return cfg.type === 'source' || cfg.type === 'global' || cfg.type === 'api';
27642
+ }
28376
27643
  shouldUseLocalResolution(cfg) {
28377
27644
  if (cfg.rowsPath || cfg.labelPath || cfg.valuePath || cfg.rowSelectionMode || cfg.selectionFieldId || cfg.selectionMatchPath || cfg.childRowsPath) {
28378
27645
  return true;
@@ -29440,6 +28707,11 @@ class SelectWidgetComponent {
29440
28707
  runtimeManagedField = false;
29441
28708
  runtimeOptionsLoaded = false;
29442
28709
  dependencyValueSnapshot = new Map();
28710
+ cachedStyleSource;
28711
+ cachedWrapperStyles = { width: '100%' };
28712
+ cachedControlCssVars = this.toCssVarMap({});
28713
+ cachedInputLabel = '';
28714
+ cachedInputAttributes = { 'aria-label': 'Select field' };
29443
28715
  destroyRef = inject(DestroyRef);
29444
28716
  cdr = inject(ChangeDetectorRef);
29445
28717
  dataProvider = inject(DATA_PROVIDER, { optional: true }) || inject(DefaultDataProvider);
@@ -29562,9 +28834,8 @@ class SelectWidgetComponent {
29562
28834
  return this.toSafeNonNegativeInt(this.config['searchMinChars'], 0);
29563
28835
  }
29564
28836
  getInputAttributes() {
29565
- return {
29566
- 'aria-label': this.getAccessibleLabel()
29567
- };
28837
+ this.syncTemplateBindingCaches();
28838
+ return this.cachedInputAttributes;
29568
28839
  }
29569
28840
  pillLabel() {
29570
28841
  return String(this.config['pillLabel'] ?? '').trim();
@@ -29578,16 +28849,22 @@ class SelectWidgetComponent {
29578
28849
  showNativeLabel() {
29579
28850
  return this.config?.showLabel !== false && this.nativeLabel().length > 0;
29580
28851
  }
28852
+ getCompositeClass() {
28853
+ return this.showNativeLabel()
28854
+ ? 'relative fd-select-composite mt-[0.5em]'
28855
+ : 'relative fd-select-composite';
28856
+ }
29581
28857
  getWrapperStyles() {
29582
- const { wrapperStyles } = splitControlSurfaceStyles(this.config.style, SELECT_CONTROL_STYLE_KEYS);
29583
- return mergeAndNormalize({ width: '100%' }, wrapperStyles);
28858
+ this.syncTemplateBindingCaches();
28859
+ return this.cachedWrapperStyles;
29584
28860
  }
29585
28861
  getControlCssVars() {
29586
- const { controlStyles } = splitControlSurfaceStyles(this.config.style, SELECT_CONTROL_STYLE_KEYS);
29587
- return this.toCssVarMap(controlStyles);
28862
+ this.syncTemplateBindingCaches();
28863
+ return this.cachedControlCssVars;
29588
28864
  }
29589
28865
  getPillStyles() {
29590
- return this.getControlCssVars();
28866
+ this.syncTemplateBindingCaches();
28867
+ return this.cachedControlCssVars;
29591
28868
  }
29592
28869
  get visible() {
29593
28870
  return this.engine ? this.engine.isFieldVisible(this.config.id) : true;
@@ -29871,6 +29148,20 @@ class SelectWidgetComponent {
29871
29148
  getAccessibleLabel() {
29872
29149
  return this.config?.label?.trim() || this.config?.name || this.config?.placeholder || 'Select field';
29873
29150
  }
29151
+ syncTemplateBindingCaches() {
29152
+ const styleSource = this.config?.style;
29153
+ if (this.cachedStyleSource !== styleSource) {
29154
+ const { wrapperStyles, controlStyles } = splitControlSurfaceStyles(this.config.style, SELECT_CONTROL_STYLE_KEYS);
29155
+ this.cachedWrapperStyles = mergeAndNormalize({ width: '100%' }, wrapperStyles);
29156
+ this.cachedControlCssVars = this.toCssVarMap(controlStyles);
29157
+ this.cachedStyleSource = styleSource;
29158
+ }
29159
+ const accessibleLabel = this.getAccessibleLabel();
29160
+ if (this.cachedInputLabel !== accessibleLabel) {
29161
+ this.cachedInputLabel = accessibleLabel;
29162
+ this.cachedInputAttributes = { 'aria-label': accessibleLabel };
29163
+ }
29164
+ }
29874
29165
  toSafeNonNegativeInt(value, fallback) {
29875
29166
  if (typeof value === 'number' && Number.isFinite(value) && value >= 0) {
29876
29167
  return Math.floor(value);
@@ -29890,7 +29181,7 @@ class SelectWidgetComponent {
29890
29181
  [ngStyle]="getWrapperStyles()"
29891
29182
  data-fd="field"
29892
29183
  [attr.data-fd-field-type]="config.type">
29893
- <div class="relative fd-select-composite" [class.fd-select-composite--with-pill]="hasPillLabel()" [ngClass]="{'mt-[0.5em]': showNativeLabel()}">
29184
+ <div [class]="getCompositeClass()" [class.fd-select-composite--with-pill]="hasPillLabel()">
29894
29185
  @if (showNativeLabel()) {
29895
29186
  <label
29896
29187
  [for]="fieldId"
@@ -29960,7 +29251,7 @@ class SelectWidgetComponent {
29960
29251
  <p [id]="helpTextId" [class]="fieldHelpClass" data-fd="field-help">{{ config.helpText }}</p>
29961
29252
  }
29962
29253
  </div>
29963
- `, isInline: true, styles: [":host ::ng-deep ng-select.fd-select{display:block;min-height:0;color:var(--fd-select-text-color, #1C2431)}.fd-select-composite{display:flex;width:100%;min-width:0}:host ::ng-deep ng-select.fd-select .ng-select-container{height:var(--fd-select-height, 100%);min-height:var(--fd-select-min-height, 20px);border-style:var(--fd-select-border-style, solid);border-width:var(--fd-select-border-width, 1.5px);border-color:var(--fd-select-border-color, #3F8CFF);border-top-style:var(--fd-select-border-top-style, var(--fd-select-border-style, solid));border-right-style:var(--fd-select-border-right-style, var(--fd-select-border-style, solid));border-bottom-style:var(--fd-select-border-bottom-style, var(--fd-select-border-style, solid));border-left-style:var(--fd-select-border-left-style, var(--fd-select-border-style, solid));border-top-width:var(--fd-select-border-top-width, var(--fd-select-border-width, 1.5px));border-right-width:var(--fd-select-border-right-width, var(--fd-select-border-width, 1.5px));border-bottom-width:var(--fd-select-border-bottom-width, var(--fd-select-border-width, 1.5px));border-left-width:var(--fd-select-border-left-width, var(--fd-select-border-width, 1.5px));border-top-color:var(--fd-select-border-top-color, var(--fd-select-border-color, #3F8CFF));border-right-color:var(--fd-select-border-right-color, var(--fd-select-border-color, #3F8CFF));border-bottom-color:var(--fd-select-border-bottom-color, var(--fd-select-border-color, #3F8CFF));border-left-color:var(--fd-select-border-left-color, var(--fd-select-border-color, #3F8CFF));border-radius:var(--fd-select-border-radius, 4px);border-top-left-radius:var(--fd-select-border-top-left-radius, var(--fd-select-border-radius, 4px));border-top-right-radius:var(--fd-select-border-top-right-radius, var(--fd-select-border-radius, 4px));border-bottom-right-radius:var(--fd-select-border-bottom-right-radius, var(--fd-select-border-radius, 4px));border-bottom-left-radius:var(--fd-select-border-bottom-left-radius, var(--fd-select-border-radius, 4px));background:var(--fd-select-background-color, #FFFFFF);box-shadow:var(--fd-select-box-shadow, none);transition:border-color .2s ease,box-shadow .2s ease}:host ::ng-deep ng-select.fd-select:hover .ng-select-container{border-color:var(--fd-select-hover-border-color, var(--fd-select-border-color, #3F8CFF))}:host ::ng-deep ng-select.fd-select.fd-select--with-pill .ng-select-container{border-top-right-radius:0;border-bottom-right-radius:0;border-right-width:0}:host ::ng-deep ng-select.fd-select.ng-select-focused:not(.ng-select-opened) .ng-select-container,:host ::ng-deep ng-select.fd-select.ng-select-opened .ng-select-container{border-width:var(--fd-select-interactive-border-width, var(--fd-select-border-width, 1.5px));border-color:var(--fd-select-interactive-border-color, #7FB2FF)}:host ::ng-deep ng-select.fd-select[data-fd-field-state=invalid] .ng-select-container{border-width:var(--fd-select-invalid-border-width, var(--fd-select-border-width, 1.5px));border-color:var(--fd-select-invalid-border-color, #EF4444)}:host ::ng-deep ng-select.fd-select .ng-value-container{align-items:center;min-height:100%;padding-top:var(--fd-select-padding-top, 2px);padding-right:var(--fd-select-padding-right, 12px);padding-bottom:var(--fd-select-padding-bottom, 2px);padding-left:var(--fd-select-padding-left, 12px);gap:4px}:host ::ng-deep ng-select.fd-select .ng-placeholder,:host ::ng-deep ng-select.fd-select .ng-value-label,:host ::ng-deep ng-select.fd-select .ng-input>input{font-family:inherit;color:var(--fd-select-text-color, #1C2431);font-size:var(--fd-select-font-size, 14px);font-weight:var(--fd-select-font-weight, 400);line-height:var(--fd-select-line-height, 20px)}:host ::ng-deep ng-select.fd-select .ng-placeholder{color:#1c243166}:host ::ng-deep ng-select.fd-select .ng-select-container.ng-has-value .ng-placeholder{display:none}:host ::ng-deep ng-select.fd-select .ng-arrow-wrapper{padding-right:var(--fd-select-arrow-padding-right, 12px)}:host ::ng-deep ng-select.fd-select.fd-select--with-pill .ng-arrow-wrapper,:host ::ng-deep ng-select.fd-select.fd-select--with-pill .ng-clear-wrapper{display:none}:host ::ng-deep ng-select.fd-select .ng-arrow{border-color:var(--fd-select-icon-color, rgba(28, 36, 49, .6)) transparent transparent}:host ::ng-deep ng-select.fd-select .ng-clear-wrapper{color:var(--fd-select-icon-color, rgba(28, 36, 49, .6))}:host ::ng-deep .ng-dropdown-panel{border:1px solid #E6EAF0;border-radius:8px;background:#fff;box-shadow:0 10px 30px #0000001f}:host ::ng-deep .ng-dropdown-panel .ng-option{padding:10px 14px;font-size:14px;line-height:20px;background:#fff;color:#1c2431}:host ::ng-deep .ng-dropdown-panel .ng-option.ng-option-marked{background:#7fb2ff14;color:#1c2431}:host ::ng-deep .ng-dropdown-panel .ng-option.ng-option-selected{background:#7fb2ff24;color:#1c2431}.fd-select-pill{display:inline-flex;align-items:center;gap:8px;height:var(--fd-select-height, 100%);min-height:var(--fd-select-min-height, 20px);padding:0 12px;border-style:var(--fd-select-border-style, solid);border-width:var(--fd-select-border-width, 1px);border-color:var(--fd-select-border-color, #3F8CFF);border-left-width:var(--fd-select-border-left-width, 1px);border-left-style:var(--fd-select-border-left-style, solid);border-left-color:var(--fd-select-border-left-color, var(--fd-select-border-color, #3F8CFF));border-top-right-radius:var(--fd-select-border-top-right-radius, var(--fd-select-border-radius, 4px));border-bottom-right-radius:var(--fd-select-border-bottom-right-radius, var(--fd-select-border-radius, 4px));border-top-left-radius:0;border-bottom-left-radius:0;background:var(--fd-select-background-color, #FFFFFF);color:var(--fd-select-text-color, #1C2431);font-size:var(--fd-select-font-size, 14px);font-weight:var(--fd-select-font-weight, 400);line-height:var(--fd-select-line-height, 20px);box-shadow:var(--fd-select-box-shadow, none);white-space:nowrap;cursor:pointer}.fd-select-pill:disabled{cursor:default;opacity:.7}.fd-select-pill__arrow{width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent;border-top:5px solid var(--fd-select-icon-color, rgba(28, 36, 49, .6))}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: NgSelectComponent, selector: "ng-select", inputs: ["bindLabel", "bindValue", "ariaLabel", "markFirst", "placeholder", "fixedPlaceholder", "notFoundText", "typeToSearchText", "preventToggleOnRightClick", "addTagText", "loadingText", "clearAllText", "appearance", "dropdownPosition", "appendTo", "loading", "closeOnSelect", "hideSelected", "selectOnTab", "openOnEnter", "maxSelectedItems", "groupBy", "groupValue", "bufferAmount", "virtualScroll", "selectableGroup", "selectableGroupAsModel", "searchFn", "trackByFn", "clearOnBackspace", "labelForId", "inputAttrs", "tabIndex", "readonly", "searchWhileComposing", "minTermLength", "editableSearchTerm", "ngClass", "typeahead", "multiple", "addTag", "searchable", "clearable", "isOpen", "items", "compareWith", "clearSearchOnAdd", "deselectOnClick", "keyDownFn"], outputs: ["blur", "focus", "change", "open", "close", "search", "clear", "add", "remove", "scroll", "scrollToEnd"] }] });
29254
+ `, isInline: true, styles: [":host ::ng-deep ng-select.fd-select{display:block;min-height:0;color:var(--fd-select-text-color, #1C2431)}.fd-select-composite{display:flex;width:100%;min-width:0}:host ::ng-deep ng-select.fd-select .ng-select-container{height:var(--fd-select-height, 100%);min-height:var(--fd-select-min-height, 20px);border-style:var(--fd-select-border-style, solid);border-width:var(--fd-select-border-width, 1.5px);border-color:var(--fd-select-border-color, #3F8CFF);border-top-style:var(--fd-select-border-top-style, var(--fd-select-border-style, solid));border-right-style:var(--fd-select-border-right-style, var(--fd-select-border-style, solid));border-bottom-style:var(--fd-select-border-bottom-style, var(--fd-select-border-style, solid));border-left-style:var(--fd-select-border-left-style, var(--fd-select-border-style, solid));border-top-width:var(--fd-select-border-top-width, var(--fd-select-border-width, 1.5px));border-right-width:var(--fd-select-border-right-width, var(--fd-select-border-width, 1.5px));border-bottom-width:var(--fd-select-border-bottom-width, var(--fd-select-border-width, 1.5px));border-left-width:var(--fd-select-border-left-width, var(--fd-select-border-width, 1.5px));border-top-color:var(--fd-select-border-top-color, var(--fd-select-border-color, #3F8CFF));border-right-color:var(--fd-select-border-right-color, var(--fd-select-border-color, #3F8CFF));border-bottom-color:var(--fd-select-border-bottom-color, var(--fd-select-border-color, #3F8CFF));border-left-color:var(--fd-select-border-left-color, var(--fd-select-border-color, #3F8CFF));border-radius:var(--fd-select-border-radius, 4px);border-top-left-radius:var(--fd-select-border-top-left-radius, var(--fd-select-border-radius, 4px));border-top-right-radius:var(--fd-select-border-top-right-radius, var(--fd-select-border-radius, 4px));border-bottom-right-radius:var(--fd-select-border-bottom-right-radius, var(--fd-select-border-radius, 4px));border-bottom-left-radius:var(--fd-select-border-bottom-left-radius, var(--fd-select-border-radius, 4px));background:var(--fd-select-background-color, #FFFFFF);box-shadow:var(--fd-select-box-shadow, none);transition:border-color .2s ease,box-shadow .2s ease}:host ::ng-deep ng-select.fd-select:hover .ng-select-container{border-color:var(--fd-select-hover-border-color, var(--fd-select-border-color, #3F8CFF))}:host ::ng-deep ng-select.fd-select.fd-select--with-pill .ng-select-container{border-top-right-radius:0;border-bottom-right-radius:0;border-right-width:0}:host ::ng-deep ng-select.fd-select.ng-select-focused:not(.ng-select-opened) .ng-select-container,:host ::ng-deep ng-select.fd-select.ng-select-opened .ng-select-container{border-width:var(--fd-select-interactive-border-width, var(--fd-select-border-width, 1.5px));border-color:var(--fd-select-interactive-border-color, #7FB2FF)}:host ::ng-deep ng-select.fd-select[data-fd-field-state=invalid] .ng-select-container{border-width:var(--fd-select-invalid-border-width, var(--fd-select-border-width, 1.5px));border-color:var(--fd-select-invalid-border-color, #EF4444)}:host ::ng-deep ng-select.fd-select .ng-value-container{align-items:center;min-height:100%;padding-top:var(--fd-select-padding-top, 2px);padding-right:var(--fd-select-padding-right, 12px);padding-bottom:var(--fd-select-padding-bottom, 2px);padding-left:var(--fd-select-padding-left, 12px);gap:4px}:host ::ng-deep ng-select.fd-select .ng-placeholder,:host ::ng-deep ng-select.fd-select .ng-value-label,:host ::ng-deep ng-select.fd-select .ng-input>input{font-family:inherit;color:var(--fd-select-text-color, #1C2431);font-size:var(--fd-select-font-size, 14px);font-weight:var(--fd-select-font-weight, 400);line-height:var(--fd-select-line-height, 20px)}:host ::ng-deep ng-select.fd-select .ng-placeholder{color:#1c243166}:host ::ng-deep ng-select.fd-select .ng-select-container.ng-has-value .ng-placeholder{display:none}:host ::ng-deep ng-select.fd-select .ng-arrow-wrapper{padding-right:var(--fd-select-arrow-padding-right, 12px)}:host ::ng-deep ng-select.fd-select.fd-select--with-pill .ng-arrow-wrapper,:host ::ng-deep ng-select.fd-select.fd-select--with-pill .ng-clear-wrapper{display:none}:host ::ng-deep ng-select.fd-select .ng-arrow{border-color:var(--fd-select-icon-color, rgba(28, 36, 49, .6)) transparent transparent}:host ::ng-deep ng-select.fd-select .ng-clear-wrapper{color:var(--fd-select-icon-color, rgba(28, 36, 49, .6))}:host ::ng-deep .ng-dropdown-panel{border:1px solid #E6EAF0;border-radius:8px;background:#fff;box-shadow:0 10px 30px #0000001f}:host ::ng-deep .ng-dropdown-panel .ng-option{padding:10px 14px;font-size:14px;line-height:20px;background:#fff;color:#1c2431}:host ::ng-deep .ng-dropdown-panel .ng-option.ng-option-marked{background:#7fb2ff14;color:#1c2431}:host ::ng-deep .ng-dropdown-panel .ng-option.ng-option-selected{background:#7fb2ff24;color:#1c2431}.fd-select-pill{display:inline-flex;align-items:center;gap:8px;height:var(--fd-select-height, 100%);min-height:var(--fd-select-min-height, 20px);padding:0 12px;border-style:var(--fd-select-border-style, solid);border-width:var(--fd-select-border-width, 1px);border-color:var(--fd-select-border-color, #3F8CFF);border-left-width:var(--fd-select-border-left-width, 1px);border-left-style:var(--fd-select-border-left-style, solid);border-left-color:var(--fd-select-border-left-color, var(--fd-select-border-color, #3F8CFF));border-top-right-radius:var(--fd-select-border-top-right-radius, var(--fd-select-border-radius, 4px));border-bottom-right-radius:var(--fd-select-border-bottom-right-radius, var(--fd-select-border-radius, 4px));border-top-left-radius:0;border-bottom-left-radius:0;background:var(--fd-select-background-color, #FFFFFF);color:var(--fd-select-text-color, #1C2431);font-size:var(--fd-select-font-size, 14px);font-weight:var(--fd-select-font-weight, 400);line-height:var(--fd-select-line-height, 20px);box-shadow:var(--fd-select-box-shadow, none);white-space:nowrap;cursor:pointer}.fd-select-pill:disabled{cursor:default;opacity:.7}.fd-select-pill__arrow{width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent;border-top:5px solid var(--fd-select-icon-color, rgba(28, 36, 49, .6))}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: NgSelectComponent, selector: "ng-select", inputs: ["bindLabel", "bindValue", "ariaLabel", "markFirst", "placeholder", "fixedPlaceholder", "notFoundText", "typeToSearchText", "preventToggleOnRightClick", "addTagText", "loadingText", "clearAllText", "appearance", "dropdownPosition", "appendTo", "loading", "closeOnSelect", "hideSelected", "selectOnTab", "openOnEnter", "maxSelectedItems", "groupBy", "groupValue", "bufferAmount", "virtualScroll", "selectableGroup", "selectableGroupAsModel", "searchFn", "trackByFn", "clearOnBackspace", "labelForId", "inputAttrs", "tabIndex", "readonly", "searchWhileComposing", "minTermLength", "editableSearchTerm", "ngClass", "typeahead", "multiple", "addTag", "searchable", "clearable", "isOpen", "items", "compareWith", "clearSearchOnAdd", "deselectOnClick", "keyDownFn"], outputs: ["blur", "focus", "change", "open", "close", "search", "clear", "add", "remove", "scroll", "scrollToEnd"] }] });
29964
29255
  }
29965
29256
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SelectWidgetComponent, decorators: [{
29966
29257
  type: Component,
@@ -29970,7 +29261,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
29970
29261
  [ngStyle]="getWrapperStyles()"
29971
29262
  data-fd="field"
29972
29263
  [attr.data-fd-field-type]="config.type">
29973
- <div class="relative fd-select-composite" [class.fd-select-composite--with-pill]="hasPillLabel()" [ngClass]="{'mt-[0.5em]': showNativeLabel()}">
29264
+ <div [class]="getCompositeClass()" [class.fd-select-composite--with-pill]="hasPillLabel()">
29974
29265
  @if (showNativeLabel()) {
29975
29266
  <label
29976
29267
  [for]="fieldId"
@@ -32538,6 +31829,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
32538
31829
  args: ['editor', { static: true }]
32539
31830
  }] } });
32540
31831
 
31832
+ class SafePipe {
31833
+ // Use inject() for modern Angular
31834
+ sanitizer = inject(DomSanitizer);
31835
+ transform(value, type) {
31836
+ switch (type) {
31837
+ case 'html': return this.sanitizer.bypassSecurityTrustHtml(value);
31838
+ case 'style': return this.sanitizer.bypassSecurityTrustStyle(value);
31839
+ case 'script': return this.sanitizer.bypassSecurityTrustScript(value);
31840
+ case 'url': return this.sanitizer.bypassSecurityTrustUrl(value);
31841
+ case 'resourceUrl': return this.sanitizer.bypassSecurityTrustResourceUrl(value);
31842
+ default: return value;
31843
+ }
31844
+ }
31845
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SafePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
31846
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.17", ngImport: i0, type: SafePipe, isStandalone: true, name: "safe" });
31847
+ }
31848
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SafePipe, decorators: [{
31849
+ type: Pipe,
31850
+ args: [{
31851
+ name: 'safe',
31852
+ standalone: true
31853
+ }]
31854
+ }] });
31855
+
32541
31856
  class RichTextWidgetComponent {
32542
31857
  editor = inject(WIDGET_EDITOR_CONTEXT, { optional: true });
32543
31858
  destroyRef = inject(DestroyRef);
@@ -32670,13 +31985,13 @@ class RichTextWidgetComponent {
32670
31985
  [class]="'rich-text-widget ' + contentClass()"
32671
31986
  data-fd="text-content"
32672
31987
  [ngStyle]="editorStyles()"
32673
- [innerHTML]="contentHtml()"></div>
31988
+ [innerHTML]="contentHtml() | safe:'html'"></div>
32674
31989
  </ng-template>
32675
- `, isInline: true, styles: [":host ::ng-deep .rich-text-widget ol{list-style-type:var(--fd-ordered-list-style, decimal);margin-left:1.25rem;padding-left:.5rem}:host ::ng-deep .rich-text-widget ul{margin-left:1.25rem;padding-left:.5rem;list-style-type:disc}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: InlineQuillEditorComponent, selector: "app-inline-quill-editor", inputs: ["html", "placeholder", "readOnly", "autoFocus", "containerClass", "editorClass", "editorStyle"], outputs: ["htmlChange", "editorBlur", "editorFocus"] }] });
31990
+ `, isInline: true, styles: [":host ::ng-deep .rich-text-widget ol{list-style-type:var(--fd-ordered-list-style, decimal);margin-left:1.25rem;padding-left:.5rem}:host ::ng-deep .rich-text-widget ul{margin-left:1.25rem;padding-left:.5rem;list-style-type:disc}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: InlineQuillEditorComponent, selector: "app-inline-quill-editor", inputs: ["html", "placeholder", "readOnly", "autoFocus", "containerClass", "editorClass", "editorStyle"], outputs: ["htmlChange", "editorBlur", "editorFocus"] }, { kind: "pipe", type: SafePipe, name: "safe" }] });
32676
31991
  }
32677
31992
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: RichTextWidgetComponent, decorators: [{
32678
31993
  type: Component,
32679
- args: [{ selector: 'app-rich-text-widget', standalone: true, imports: [CommonModule, InlineQuillEditorComponent], template: `
31994
+ args: [{ selector: 'app-rich-text-widget', standalone: true, imports: [CommonModule, InlineQuillEditorComponent, SafePipe], template: `
32680
31995
  <ng-container *ngIf="isDesignMode(); else staticContent">
32681
31996
  <app-inline-quill-editor
32682
31997
  [html]="contentHtml()"
@@ -32694,7 +32009,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
32694
32009
  [class]="'rich-text-widget ' + contentClass()"
32695
32010
  data-fd="text-content"
32696
32011
  [ngStyle]="editorStyles()"
32697
- [innerHTML]="contentHtml()"></div>
32012
+ [innerHTML]="contentHtml() | safe:'html'"></div>
32698
32013
  </ng-template>
32699
32014
  `, styles: [":host ::ng-deep .rich-text-widget ol{list-style-type:var(--fd-ordered-list-style, decimal);margin-left:1.25rem;padding-left:.5rem}:host ::ng-deep .rich-text-widget ul{margin-left:1.25rem;padding-left:.5rem;list-style-type:disc}\n"] }]
32700
32015
  }], propDecorators: { config: [{