@yourself.create/ngx-form-designer 0.0.1 → 0.0.4

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.
Files changed (42) hide show
  1. package/README.md +8 -8
  2. package/fesm2022/{notdefined-ngx-form-designer.mjs → uch-web-ngx-form-designer.mjs} +2867 -409
  3. package/fesm2022/uch-web-ngx-form-designer.mjs.map +1 -0
  4. package/index.d.ts +1 -1
  5. package/lib/ai/ai-tool-registry.service.d.ts +1 -1
  6. package/lib/ai/plugins/form-designer-tool-plugin.d.ts +1 -1
  7. package/lib/ai/provide-form-designer-angai-feature.d.ts +16 -0
  8. package/lib/ai/tools/designer-read-tools.d.ts +1 -1
  9. package/lib/ai/tools/designer-write-tools.d.ts +1 -1
  10. package/lib/ai/tools/schema-patch-tool.d.ts +1 -1
  11. package/lib/form-core/form-engine.d.ts +7 -3
  12. package/lib/form-core/form-event-runner.d.ts +14 -0
  13. package/lib/form-core/form-journey.models.d.ts +30 -0
  14. package/lib/form-core/models.d.ts +7 -1
  15. package/lib/form-designer/designer-state.service.d.ts +1 -1
  16. package/lib/form-designer/events-workspace.component.d.ts +28 -3
  17. package/lib/form-designer/form-designer-shell.component.d.ts +30 -5
  18. package/lib/form-designer/form-journey-state.service.d.ts +28 -0
  19. package/lib/form-designer/form-preview.component.d.ts +21 -2
  20. package/lib/form-designer/inspector-sections/inspector-backgrounds-section.component.d.ts +5 -0
  21. package/lib/form-designer/inspector-sections/inspector-typography-section.component.d.ts +1 -0
  22. package/lib/form-designer/json-form-designer.component.d.ts +21 -2
  23. package/lib/form-designer/layout-canvas.component.d.ts +4 -2
  24. package/lib/form-designer/template-library.d.ts +2 -0
  25. package/lib/form-designer/widget-inspector.component.d.ts +1 -1
  26. package/lib/form-renderer/form-journey-viewer.component.d.ts +51 -0
  27. package/lib/form-renderer/form-viewer/form-viewer.component.d.ts +16 -3
  28. package/lib/form-renderer/json-form-renderer.component.d.ts +10 -2
  29. package/lib/form-renderer/layout-node.component.d.ts +2 -1
  30. package/lib/ui/ui-color-swatch.component.d.ts +1 -0
  31. package/lib/website/website-designer-shell.component.d.ts +4 -4
  32. package/lib/website/website-preview-shell.component.d.ts +4 -4
  33. package/lib/widgets/field-widgets/checkbox-group/checkbox-group-widget.component.d.ts +5 -0
  34. package/lib/widgets/field-widgets/option-field-labels.d.ts +3 -0
  35. package/lib/widgets/field-widgets/radio/radio-widget.component.d.ts +5 -0
  36. package/lib/widgets/field-widgets/search/search-widget.component.d.ts +4 -0
  37. package/lib/widgets/field-widgets/select/select-widget.component.d.ts +9 -0
  38. package/lib/widgets/field-widgets/text-field/text-field.component.d.ts +3 -0
  39. package/lib/widgets/page-widgets/brick-settings.component.d.ts +1 -1
  40. package/package.json +6 -5
  41. package/public-api.d.ts +4 -0
  42. package/fesm2022/notdefined-ngx-form-designer.mjs.map +0 -1
@@ -1,7 +1,7 @@
1
+ import { v4 } from 'uuid';
1
2
  import * as i0 from '@angular/core';
2
3
  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
4
  import { BehaviorSubject, Subject, merge, of, filter, map, debounceTime as debounceTime$1, skip, firstValueFrom } from 'rxjs';
4
- import { v4 } from 'uuid';
5
5
  import * as i1 from '@angular/common';
6
6
  import { CommonModule, DOCUMENT } from '@angular/common';
7
7
  import * as i2 from '@angular/forms';
@@ -14,7 +14,8 @@ 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
16
  import loader from '@monaco-editor/loader';
17
- import { AI_BACKEND_CLIENT, AiChatDrawerComponent, AiWorkspaceComponent } from '@notdefined/angai';
17
+ import { ColorPickerDirective } from 'ngx-color-picker';
18
+ import { defineAiTool, provideAngaiFeature, AI_BACKEND_CLIENT, AiChatDrawerComponent, AiWorkspaceComponent } from '@uch-web/angai';
18
19
  import { Router, NavigationEnd } from '@angular/router';
19
20
  import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
20
21
  import { NgSelectComponent } from '@ng-select/ng-select';
@@ -24,6 +25,243 @@ import { HttpClient, provideHttpClient } from '@angular/common/http';
24
25
 
25
26
  const CURRENT_SCHEMA_VERSION = '1.0.0';
26
27
 
28
+ /**
29
+ * Automatically computes and sets `hierarchyAccess` flags
30
+ * on each field's dataConfig based on dependency relationships.
31
+ *
32
+ * Logic:
33
+ * - hierarchyAccess = true if field has dependsOn OR other fields depend on it
34
+ */
35
+ function computeAccessFlags(schema) {
36
+ if (!schema.fields || schema.fields.length === 0) {
37
+ return;
38
+ }
39
+ const fieldByIdOrName = new Map();
40
+ for (const field of schema.fields) {
41
+ fieldByIdOrName.set(field.id, field);
42
+ if (field.name) {
43
+ fieldByIdOrName.set(field.name, field);
44
+ }
45
+ }
46
+ // Build a map of canonical field ids that are depended upon
47
+ const dependedUponFieldIds = new Set();
48
+ for (const field of schema.fields) {
49
+ if (field.dataConfig?.dependsOn && field.dataConfig.dependsOn.length > 0) {
50
+ for (const dep of field.dataConfig.dependsOn) {
51
+ const dependedField = fieldByIdOrName.get(dep.fieldId);
52
+ if (dependedField) {
53
+ dependedUponFieldIds.add(dependedField.id);
54
+ }
55
+ }
56
+ }
57
+ }
58
+ // Compute flags for each field
59
+ for (const field of schema.fields) {
60
+ const hasDependsOn = !!field.dataConfig?.dependsOn?.length;
61
+ const isDependedUpon = dependedUponFieldIds.has(field.id);
62
+ const hierarchyAccess = hasDependsOn || isDependedUpon;
63
+ if (!field.dataConfig) {
64
+ if (!hierarchyAccess) {
65
+ continue;
66
+ }
67
+ // Persist hierarchy participation for fields that act as dependency parents
68
+ // even when they did not have a dataConfig previously.
69
+ field.dataConfig = {
70
+ type: 'static',
71
+ hierarchyAccess
72
+ };
73
+ continue;
74
+ }
75
+ // hierarchyAccess: true if field participates in hierarchy (either direction)
76
+ field.dataConfig.hierarchyAccess = hierarchyAccess;
77
+ }
78
+ for (const field of schema.fields) {
79
+ const itemSchema = field.repeatable?.itemSchema;
80
+ if (!itemSchema)
81
+ continue;
82
+ computeAccessFlags(itemSchema);
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Metadata field is intended for AI/LLM and project-level information (prompts, generation parameters, tags).
88
+ * SchemaVersion will be used for future migrations as more flavors (audio/video/image editors, tables, etc.) are added.
89
+ */
90
+ function createEmptySchema(flavor = 'form', opts) {
91
+ return {
92
+ id: opts?.id || v4(),
93
+ version: '1.0',
94
+ schemaVersion: CURRENT_SCHEMA_VERSION,
95
+ flavor,
96
+ title: opts?.title || 'New Form',
97
+ metadata: {},
98
+ fields: [],
99
+ layout: {
100
+ id: v4(),
101
+ type: 'col', // Root is a vertical Page/Column
102
+ responsive: { xs: 12 },
103
+ children: [
104
+ {
105
+ id: v4(),
106
+ type: 'row', // Initial Row
107
+ children: [
108
+ {
109
+ id: v4(),
110
+ type: 'col',
111
+ responsive: { xs: 12 },
112
+ children: []
113
+ }
114
+ ]
115
+ }
116
+ ]
117
+ }
118
+ };
119
+ }
120
+ function serializeSchema(schema) {
121
+ const schemaForSerialization = JSON.parse(JSON.stringify(schema));
122
+ computeAccessFlags(schemaForSerialization);
123
+ return JSON.stringify(schemaForSerialization, null, 2);
124
+ }
125
+ function parseSchema(json) {
126
+ let schema;
127
+ try {
128
+ schema = JSON.parse(json);
129
+ }
130
+ catch (e) {
131
+ throw new Error('Invalid JSON');
132
+ }
133
+ if (!schema.id || !schema.layout) {
134
+ throw new Error('Invalid Schema Structure');
135
+ }
136
+ // Normalization / Migration Logic
137
+ if (!schema.schemaVersion) {
138
+ schema.schemaVersion = CURRENT_SCHEMA_VERSION;
139
+ }
140
+ // Default flavor if missing (legacy support)
141
+ if (!schema.flavor) {
142
+ schema.flavor = 'form';
143
+ }
144
+ // TODO: Future migrations based on schemaVersion can go here.
145
+ // Auto-compute access flags based on dependencies
146
+ computeAccessFlags(schema);
147
+ return schema;
148
+ }
149
+
150
+ function normalizePageName(name, schema) {
151
+ const explicitName = (name ?? '').trim();
152
+ if (explicitName)
153
+ return explicitName;
154
+ const schemaTitle = (schema.title ?? '').trim();
155
+ if (schemaTitle)
156
+ return schemaTitle;
157
+ return 'Page';
158
+ }
159
+ function toRouteSegment(input) {
160
+ return input
161
+ .trim()
162
+ .toLowerCase()
163
+ .replace(/[^a-z0-9]+/g, '-')
164
+ .replace(/^-+|-+$/g, '');
165
+ }
166
+ function normalizeRoute(route, name) {
167
+ const raw = (route ?? '').trim().toLowerCase();
168
+ if (!raw) {
169
+ const segment = toRouteSegment(name);
170
+ return segment ? `/${segment}` : '/step';
171
+ }
172
+ return raw.startsWith('/') ? raw : `/${raw}`;
173
+ }
174
+ function normalizePage(page) {
175
+ const schema = page.schema;
176
+ const name = normalizePageName(page.name, schema);
177
+ return {
178
+ id: page.id || v4(),
179
+ name,
180
+ route: normalizeRoute(page.route, name),
181
+ schema: {
182
+ ...schema,
183
+ title: schema.title ?? name
184
+ }
185
+ };
186
+ }
187
+ function createFormJourneyPage(opts) {
188
+ const schema = opts?.schema ?? createEmptySchema('form');
189
+ const name = normalizePageName(opts?.name, schema);
190
+ return {
191
+ id: opts?.id ?? v4(),
192
+ name,
193
+ route: normalizeRoute(opts?.route, name),
194
+ schema: {
195
+ ...schema,
196
+ title: schema.title ?? name
197
+ }
198
+ };
199
+ }
200
+ function createFormJourneyProject(opts) {
201
+ const pages = (opts?.pages?.length ? opts.pages : [
202
+ createFormJourneyPage({
203
+ name: 'Step 1',
204
+ schema: createEmptySchema('form', { title: 'Step 1' })
205
+ })
206
+ ]).map(normalizePage);
207
+ const startPageId = pages.some(page => page.id === opts?.startPageId)
208
+ ? opts?.startPageId
209
+ : pages[0].id;
210
+ return {
211
+ id: opts?.id ?? v4(),
212
+ title: opts?.title ?? pages[0]?.schema.title ?? 'New Journey',
213
+ pages,
214
+ startPageId,
215
+ metadata: opts?.metadata ?? {}
216
+ };
217
+ }
218
+ function isFormJourneyProject(value) {
219
+ if (!value || typeof value !== 'object' || Array.isArray(value))
220
+ return false;
221
+ const candidate = value;
222
+ if (!Array.isArray(candidate.pages))
223
+ return false;
224
+ if (typeof candidate.startPageId !== 'string' || candidate.startPageId.trim().length === 0)
225
+ return false;
226
+ return candidate.pages.every(page => {
227
+ if (!page || typeof page !== 'object')
228
+ return false;
229
+ const p = page;
230
+ if (typeof p.id !== 'string' || typeof p.name !== 'string')
231
+ return false;
232
+ if (!p.schema || typeof p.schema !== 'object' || Array.isArray(p.schema))
233
+ return false;
234
+ return 'layout' in p.schema && 'fields' in p.schema;
235
+ });
236
+ }
237
+ function normalizeToJourney(value) {
238
+ if (isFormJourneyProject(value)) {
239
+ return createFormJourneyProject({
240
+ id: value.id,
241
+ title: value.title,
242
+ pages: value.pages.map(page => normalizePage(page)),
243
+ startPageId: value.startPageId,
244
+ metadata: value.metadata
245
+ });
246
+ }
247
+ const singlePage = createFormJourneyPage({
248
+ name: value.title ?? 'Step 1',
249
+ schema: value
250
+ });
251
+ return createFormJourneyProject({
252
+ title: value.title ?? 'New Journey',
253
+ pages: [singlePage],
254
+ startPageId: singlePage.id,
255
+ metadata: value.metadata
256
+ });
257
+ }
258
+ function unwrapSinglePageJourney(value) {
259
+ if (value.pages.length === 1) {
260
+ return value.pages[0].schema;
261
+ }
262
+ return value;
263
+ }
264
+
27
265
  class RuleEvaluationService {
28
266
  constructor() { }
29
267
  /**
@@ -145,6 +383,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
145
383
  class FormEngine {
146
384
  schema;
147
385
  values = {};
386
+ fieldLabels = {};
148
387
  errors = {};
149
388
  ruleEvaluator = new RuleEvaluationService();
150
389
  // Reactive Streams
@@ -163,9 +402,10 @@ class FormEngine {
163
402
  valueChanges: this.valueChanges$
164
403
  };
165
404
  }
166
- constructor(schema, initialValues = {}) {
405
+ constructor(schema, initialValues = {}, initialFieldLabels = {}) {
167
406
  this.schema = schema;
168
407
  this.values = { ...initialValues };
408
+ this.fieldLabels = { ...initialFieldLabels };
169
409
  // Initialize default values if missing
170
410
  this.schema.fields.forEach(field => {
171
411
  if (this.values[field.name] === undefined) {
@@ -212,6 +452,19 @@ class FormEngine {
212
452
  getValues() {
213
453
  return { ...this.values };
214
454
  }
455
+ getFieldLabel(fieldName) {
456
+ return this.fieldLabels[fieldName];
457
+ }
458
+ getFieldLabels() {
459
+ return { ...this.fieldLabels };
460
+ }
461
+ setFieldLabel(fieldName, label) {
462
+ if (label === undefined) {
463
+ delete this.fieldLabels[fieldName];
464
+ return;
465
+ }
466
+ this.fieldLabels[fieldName] = Array.isArray(label) ? [...label] : label;
467
+ }
215
468
  getErrors() {
216
469
  return { ...this.errors };
217
470
  }
@@ -411,130 +664,8 @@ class FormEngine {
411
664
  return currentState;
412
665
  }
413
666
  }
414
- function createFormEngine(schema, initialValues = {}) {
415
- return new FormEngine(schema, initialValues);
416
- }
417
-
418
- /**
419
- * Automatically computes and sets `hierarchyAccess` flags
420
- * on each field's dataConfig based on dependency relationships.
421
- *
422
- * Logic:
423
- * - hierarchyAccess = true if field has dependsOn OR other fields depend on it
424
- */
425
- function computeAccessFlags(schema) {
426
- if (!schema.fields || schema.fields.length === 0) {
427
- return;
428
- }
429
- const fieldByIdOrName = new Map();
430
- for (const field of schema.fields) {
431
- fieldByIdOrName.set(field.id, field);
432
- if (field.name) {
433
- fieldByIdOrName.set(field.name, field);
434
- }
435
- }
436
- // Build a map of canonical field ids that are depended upon
437
- const dependedUponFieldIds = new Set();
438
- for (const field of schema.fields) {
439
- if (field.dataConfig?.dependsOn && field.dataConfig.dependsOn.length > 0) {
440
- for (const dep of field.dataConfig.dependsOn) {
441
- const dependedField = fieldByIdOrName.get(dep.fieldId);
442
- if (dependedField) {
443
- dependedUponFieldIds.add(dependedField.id);
444
- }
445
- }
446
- }
447
- }
448
- // Compute flags for each field
449
- for (const field of schema.fields) {
450
- const hasDependsOn = !!field.dataConfig?.dependsOn?.length;
451
- const isDependedUpon = dependedUponFieldIds.has(field.id);
452
- const hierarchyAccess = hasDependsOn || isDependedUpon;
453
- if (!field.dataConfig) {
454
- if (!hierarchyAccess) {
455
- continue;
456
- }
457
- // Persist hierarchy participation for fields that act as dependency parents
458
- // even when they did not have a dataConfig previously.
459
- field.dataConfig = {
460
- type: 'static',
461
- hierarchyAccess
462
- };
463
- continue;
464
- }
465
- // hierarchyAccess: true if field participates in hierarchy (either direction)
466
- field.dataConfig.hierarchyAccess = hierarchyAccess;
467
- }
468
- for (const field of schema.fields) {
469
- const itemSchema = field.repeatable?.itemSchema;
470
- if (!itemSchema)
471
- continue;
472
- computeAccessFlags(itemSchema);
473
- }
474
- }
475
-
476
- /**
477
- * Metadata field is intended for AI/LLM and project-level information (prompts, generation parameters, tags).
478
- * SchemaVersion will be used for future migrations as more flavors (audio/video/image editors, tables, etc.) are added.
479
- */
480
- function createEmptySchema(flavor = 'form', opts) {
481
- return {
482
- id: opts?.id || v4(),
483
- version: '1.0',
484
- schemaVersion: CURRENT_SCHEMA_VERSION,
485
- flavor,
486
- title: opts?.title || 'New Form',
487
- metadata: {},
488
- fields: [],
489
- layout: {
490
- id: v4(),
491
- type: 'col', // Root is a vertical Page/Column
492
- responsive: { xs: 12 },
493
- children: [
494
- {
495
- id: v4(),
496
- type: 'row', // Initial Row
497
- children: [
498
- {
499
- id: v4(),
500
- type: 'col',
501
- responsive: { xs: 12 },
502
- children: []
503
- }
504
- ]
505
- }
506
- ]
507
- }
508
- };
509
- }
510
- function serializeSchema(schema) {
511
- const schemaForSerialization = JSON.parse(JSON.stringify(schema));
512
- computeAccessFlags(schemaForSerialization);
513
- return JSON.stringify(schemaForSerialization, null, 2);
514
- }
515
- function parseSchema(json) {
516
- let schema;
517
- try {
518
- schema = JSON.parse(json);
519
- }
520
- catch (e) {
521
- throw new Error('Invalid JSON');
522
- }
523
- if (!schema.id || !schema.layout) {
524
- throw new Error('Invalid Schema Structure');
525
- }
526
- // Normalization / Migration Logic
527
- if (!schema.schemaVersion) {
528
- schema.schemaVersion = CURRENT_SCHEMA_VERSION;
529
- }
530
- // Default flavor if missing (legacy support)
531
- if (!schema.flavor) {
532
- schema.flavor = 'form';
533
- }
534
- // TODO: Future migrations based on schemaVersion can go here.
535
- // Auto-compute access flags based on dependencies
536
- computeAccessFlags(schema);
537
- return schema;
667
+ function createFormEngine(schema, initialValues = {}, initialFieldLabels = {}) {
668
+ return new FormEngine(schema, initialValues, initialFieldLabels);
538
669
  }
539
670
 
540
671
  /**
@@ -2692,6 +2823,7 @@ class LayoutNodeComponent {
2692
2823
  engine;
2693
2824
  fields = [];
2694
2825
  designMode = false;
2826
+ showLayoutGuides = true;
2695
2827
  readOnlyMode = false;
2696
2828
  scopePath = [];
2697
2829
  device = 'desktop';
@@ -3154,7 +3286,9 @@ class LayoutNodeComponent {
3154
3286
  getColContainerClasses(node) {
3155
3287
  const widthClasses = this.getColClasses(node);
3156
3288
  const interactionClasses = this.designMode
3157
- ? 'min-h-[50px] relative transition-all cursor-pointer border border-transparent hover:border-blue-200/50'
3289
+ ? this.showLayoutGuides
3290
+ ? 'min-h-[50px] relative transition-all cursor-pointer border border-transparent hover:border-blue-200/50'
3291
+ : 'min-h-[50px] relative transition-all cursor-pointer'
3158
3292
  : 'min-h-[50px] relative transition-all';
3159
3293
  return `${interactionClasses} ${widthClasses}`.trim();
3160
3294
  }
@@ -3294,7 +3428,7 @@ class LayoutNodeComponent {
3294
3428
  return this.fields.find(field => field.id === widget.refId);
3295
3429
  }
3296
3430
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: LayoutNodeComponent, deps: [{ token: WIDGET_DEFINITIONS }, { token: DesignerStateService }, { token: i0.ChangeDetectorRef }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
3297
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: LayoutNodeComponent, isStandalone: true, selector: "app-layout-node", inputs: { node: "node", engine: "engine", fields: "fields", designMode: "designMode", readOnlyMode: "readOnlyMode", scopePath: "scopePath", device: "device", breakpoint: "breakpoint", connectedDropLists: "connectedDropLists" }, outputs: { nodeDrop: "nodeDrop", nodeSelect: "nodeSelect" }, viewQueries: [{ propertyName: "widgetContainer", first: true, predicate: ["widgetContainer"], descendants: true, read: ViewContainerRef }], usesOnChanges: true, ngImport: i0, template: `
3431
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: LayoutNodeComponent, isStandalone: true, selector: "app-layout-node", inputs: { node: "node", engine: "engine", fields: "fields", designMode: "designMode", showLayoutGuides: "showLayoutGuides", readOnlyMode: "readOnlyMode", scopePath: "scopePath", device: "device", breakpoint: "breakpoint", connectedDropLists: "connectedDropLists" }, outputs: { nodeDrop: "nodeDrop", nodeSelect: "nodeSelect" }, viewQueries: [{ propertyName: "widgetContainer", first: true, predicate: ["widgetContainer"], descendants: true, read: ViewContainerRef }], usesOnChanges: true, ngImport: i0, template: `
3298
3432
  <ng-container [ngSwitch]="node.type">
3299
3433
 
3300
3434
  <!-- ROW -->
@@ -3308,9 +3442,9 @@ class LayoutNodeComponent {
3308
3442
  (resizeEnd)="onHeightResizeEnd(node, $event)"
3309
3443
  (click)="onRowClick($event)"
3310
3444
  (contextmenu)="onRowContextMenu($event)"
3311
- [class.outline-dashed]="designMode"
3312
- [class.outline-1]="designMode"
3313
- [class.outline-blue-200]="designMode && !isSelected"
3445
+ [class.outline-dashed]="designMode && showLayoutGuides"
3446
+ [class.outline-1]="designMode && showLayoutGuides"
3447
+ [class.outline-blue-200]="designMode && showLayoutGuides && !isSelected"
3314
3448
  [class.ring-2]="designMode && isSelected"
3315
3449
  [class.ring-blue-500]="designMode && isSelected"
3316
3450
  [class.bg-blue-50]="designMode && isSelected">
@@ -3337,7 +3471,7 @@ class LayoutNodeComponent {
3337
3471
  </div>
3338
3472
 
3339
3473
  <!-- Visual Label for Row when hovered (not selected) -->
3340
- <div *ngIf="designMode && !isSelected" class="absolute -top-3 left-0 bg-blue-500 text-white text-[10px] px-1 rounded rounded-b-none opacity-0 group-hover:opacity-100 transition-opacity z-10">
3474
+ <div *ngIf="designMode && showLayoutGuides && !isSelected" class="absolute -top-3 left-0 bg-blue-500 text-white text-[10px] px-1 rounded rounded-b-none opacity-0 group-hover:opacity-100 transition-opacity z-10">
3341
3475
  ROW
3342
3476
  </div>
3343
3477
 
@@ -3346,6 +3480,7 @@ class LayoutNodeComponent {
3346
3480
  [engine]="engine"
3347
3481
  [fields]="fields"
3348
3482
  [designMode]="designMode"
3483
+ [showLayoutGuides]="showLayoutGuides"
3349
3484
  [readOnlyMode]="readOnlyMode"
3350
3485
  [scopePath]="scopePath"
3351
3486
  [device]="device"
@@ -3382,9 +3517,9 @@ class LayoutNodeComponent {
3382
3517
  [id]="getScopedNodeId(node.id)"
3383
3518
  [cdkDropListConnectedTo]="connectedDropLists"
3384
3519
  (cdkDropListDropped)="onDrop($event)"
3385
- [class.outline-dashed]="designMode"
3386
- [class.outline-1]="designMode"
3387
- [class.outline-gray-300]="designMode && !isSelected"
3520
+ [class.outline-dashed]="designMode && showLayoutGuides"
3521
+ [class.outline-1]="designMode && showLayoutGuides"
3522
+ [class.outline-gray-300]="designMode && showLayoutGuides && !isSelected"
3388
3523
  [class.ring-2]="designMode && isSelected"
3389
3524
  [class.ring-blue-500]="designMode && isSelected"
3390
3525
  [class.bg-blue-50]="designMode && isSelected && asCol(node).children.length === 0">
@@ -3433,6 +3568,7 @@ class LayoutNodeComponent {
3433
3568
  [engine]="engine"
3434
3569
  [fields]="fields"
3435
3570
  [designMode]="designMode"
3571
+ [showLayoutGuides]="showLayoutGuides"
3436
3572
  [readOnlyMode]="readOnlyMode"
3437
3573
  [scopePath]="scopePath"
3438
3574
  [device]="device"
@@ -3537,7 +3673,7 @@ class LayoutNodeComponent {
3537
3673
  </ng-container>
3538
3674
 
3539
3675
  </ng-container>
3540
- `, isInline: true, dependencies: [{ kind: "component", type: LayoutNodeComponent, selector: "app-layout-node", inputs: ["node", "engine", "fields", "designMode", "readOnlyMode", "scopePath", "device", "breakpoint", "connectedDropLists"], outputs: ["nodeDrop", "nodeSelect"] }, { 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.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i3.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i3.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i3.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: i3.CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }, { 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: "ngmodule", type: ResizableModule }, { kind: "directive", type: i5.ResizableDirective, selector: "[mwlResizable]", inputs: ["validateResize", "enableGhostResize", "resizeSnapGrid", "resizeCursors", "ghostElementPositioning", "allowNegativeResizes", "mouseMoveThrottleMS"], outputs: ["resizeStart", "resizing", "resizeEnd"], exportAs: ["mwlResizable"] }, { kind: "directive", type: i5.ResizeHandleDirective, selector: "[mwlResizeHandle]", inputs: ["resizeEdges", "resizableContainer"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3676
+ `, isInline: true, dependencies: [{ kind: "component", type: LayoutNodeComponent, selector: "app-layout-node", inputs: ["node", "engine", "fields", "designMode", "showLayoutGuides", "readOnlyMode", "scopePath", "device", "breakpoint", "connectedDropLists"], outputs: ["nodeDrop", "nodeSelect"] }, { 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.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i3.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i3.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i3.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: i3.CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }, { 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: "ngmodule", type: ResizableModule }, { kind: "directive", type: i5.ResizableDirective, selector: "[mwlResizable]", inputs: ["validateResize", "enableGhostResize", "resizeSnapGrid", "resizeCursors", "ghostElementPositioning", "allowNegativeResizes", "mouseMoveThrottleMS"], outputs: ["resizeStart", "resizing", "resizeEnd"], exportAs: ["mwlResizable"] }, { kind: "directive", type: i5.ResizeHandleDirective, selector: "[mwlResizeHandle]", inputs: ["resizeEdges", "resizableContainer"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3541
3677
  }
3542
3678
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: LayoutNodeComponent, decorators: [{
3543
3679
  type: Component,
@@ -3560,9 +3696,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
3560
3696
  (resizeEnd)="onHeightResizeEnd(node, $event)"
3561
3697
  (click)="onRowClick($event)"
3562
3698
  (contextmenu)="onRowContextMenu($event)"
3563
- [class.outline-dashed]="designMode"
3564
- [class.outline-1]="designMode"
3565
- [class.outline-blue-200]="designMode && !isSelected"
3699
+ [class.outline-dashed]="designMode && showLayoutGuides"
3700
+ [class.outline-1]="designMode && showLayoutGuides"
3701
+ [class.outline-blue-200]="designMode && showLayoutGuides && !isSelected"
3566
3702
  [class.ring-2]="designMode && isSelected"
3567
3703
  [class.ring-blue-500]="designMode && isSelected"
3568
3704
  [class.bg-blue-50]="designMode && isSelected">
@@ -3589,7 +3725,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
3589
3725
  </div>
3590
3726
 
3591
3727
  <!-- Visual Label for Row when hovered (not selected) -->
3592
- <div *ngIf="designMode && !isSelected" class="absolute -top-3 left-0 bg-blue-500 text-white text-[10px] px-1 rounded rounded-b-none opacity-0 group-hover:opacity-100 transition-opacity z-10">
3728
+ <div *ngIf="designMode && showLayoutGuides && !isSelected" class="absolute -top-3 left-0 bg-blue-500 text-white text-[10px] px-1 rounded rounded-b-none opacity-0 group-hover:opacity-100 transition-opacity z-10">
3593
3729
  ROW
3594
3730
  </div>
3595
3731
 
@@ -3598,6 +3734,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
3598
3734
  [engine]="engine"
3599
3735
  [fields]="fields"
3600
3736
  [designMode]="designMode"
3737
+ [showLayoutGuides]="showLayoutGuides"
3601
3738
  [readOnlyMode]="readOnlyMode"
3602
3739
  [scopePath]="scopePath"
3603
3740
  [device]="device"
@@ -3634,9 +3771,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
3634
3771
  [id]="getScopedNodeId(node.id)"
3635
3772
  [cdkDropListConnectedTo]="connectedDropLists"
3636
3773
  (cdkDropListDropped)="onDrop($event)"
3637
- [class.outline-dashed]="designMode"
3638
- [class.outline-1]="designMode"
3639
- [class.outline-gray-300]="designMode && !isSelected"
3774
+ [class.outline-dashed]="designMode && showLayoutGuides"
3775
+ [class.outline-1]="designMode && showLayoutGuides"
3776
+ [class.outline-gray-300]="designMode && showLayoutGuides && !isSelected"
3640
3777
  [class.ring-2]="designMode && isSelected"
3641
3778
  [class.ring-blue-500]="designMode && isSelected"
3642
3779
  [class.bg-blue-50]="designMode && isSelected && asCol(node).children.length === 0">
@@ -3685,6 +3822,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
3685
3822
  [engine]="engine"
3686
3823
  [fields]="fields"
3687
3824
  [designMode]="designMode"
3825
+ [showLayoutGuides]="showLayoutGuides"
3688
3826
  [readOnlyMode]="readOnlyMode"
3689
3827
  [scopePath]="scopePath"
3690
3828
  [device]="device"
@@ -3802,6 +3940,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
3802
3940
  type: Input
3803
3941
  }], designMode: [{
3804
3942
  type: Input
3943
+ }], showLayoutGuides: [{
3944
+ type: Input
3805
3945
  }], readOnlyMode: [{
3806
3946
  type: Input
3807
3947
  }], scopePath: [{
@@ -3892,6 +4032,7 @@ class FormEventRunner {
3892
4032
  logger;
3893
4033
  apiExecutor;
3894
4034
  dataSourceWriter;
4035
+ navigateToPage;
3895
4036
  constructor(engine, loggerOrOptions = NOOP_LOGGER) {
3896
4037
  this.engine = engine;
3897
4038
  if (isEventLogger(loggerOrOptions)) {
@@ -3901,6 +4042,7 @@ class FormEventRunner {
3901
4042
  this.logger = loggerOrOptions.logger ?? NOOP_LOGGER;
3902
4043
  this.apiExecutor = loggerOrOptions.apiExecutor;
3903
4044
  this.dataSourceWriter = loggerOrOptions.dataSourceWriter;
4045
+ this.navigateToPage = loggerOrOptions.navigateToPage;
3904
4046
  }
3905
4047
  this.sub = this.engine.events$.subscribe(evt => {
3906
4048
  void this.handleEvent(evt);
@@ -3925,7 +4067,10 @@ class FormEventRunner {
3925
4067
  const bindings = field.events.filter(b => b.on === evt.type && b.enabled !== false);
3926
4068
  for (const binding of bindings) {
3927
4069
  for (const action of binding.actions) {
3928
- await this.executeAction(action, binding.id, evt, schema.fields);
4070
+ const stop = await this.executeAction(action, binding.id, evt, schema.fields);
4071
+ if (stop) {
4072
+ return;
4073
+ }
3929
4074
  }
3930
4075
  }
3931
4076
  }
@@ -3936,13 +4081,17 @@ class FormEventRunner {
3936
4081
  async executeAction(action, eventId, evt, fields) {
3937
4082
  if (action.type === 'log') {
3938
4083
  this.logger.log(action.message || 'Event fired', { event: evt });
3939
- return;
4084
+ return false;
3940
4085
  }
3941
4086
  if (action.type === 'setValue') {
3942
4087
  this.handleSetValue(action, evt, fields);
3943
- return;
4088
+ return false;
4089
+ }
4090
+ if (action.type === 'navigate') {
4091
+ return this.handleNavigateAction(action, fields);
3944
4092
  }
3945
4093
  await this.handleApiAction(action, eventId, evt, fields);
4094
+ return false;
3946
4095
  }
3947
4096
  handleSetValue(action, evt, fields) {
3948
4097
  const target = fields.find(f => f.id === action.targetFieldId);
@@ -4019,6 +4168,119 @@ class FormEventRunner {
4019
4168
  });
4020
4169
  }
4021
4170
  }
4171
+ async handleNavigateAction(action, fields) {
4172
+ const targetPageId = action.targetPageId?.trim();
4173
+ if (!targetPageId) {
4174
+ this.logger.warn('Navigate action skipped because targetPageId is missing.', { action });
4175
+ return false;
4176
+ }
4177
+ if (!this.evaluateNavigateCondition(action.when, fields)) {
4178
+ return false;
4179
+ }
4180
+ if (!this.navigateToPage) {
4181
+ this.logger.warn('Navigate action skipped because no navigateToPage handler was provided.', { action });
4182
+ return false;
4183
+ }
4184
+ try {
4185
+ const result = await this.navigateToPage(targetPageId);
4186
+ if (!result?.ok) {
4187
+ this.logger.warn('Navigate action was blocked by navigateToPage handler.', { action, result });
4188
+ return false;
4189
+ }
4190
+ return true;
4191
+ }
4192
+ catch (error) {
4193
+ this.logger.warn('Navigate action execution failed.', { action, error });
4194
+ return false;
4195
+ }
4196
+ }
4197
+ evaluateNavigateCondition(group, fields) {
4198
+ if (!group)
4199
+ return true;
4200
+ return this.evaluateLogicGroup(group, fields);
4201
+ }
4202
+ evaluateLogicGroup(group, fields) {
4203
+ const results = group.conditions.map(condition => this.isLogicCondition(condition)
4204
+ ? this.evaluateLogicCondition(condition, fields)
4205
+ : this.evaluateLogicGroup(condition, fields));
4206
+ return group.operator === 'AND'
4207
+ ? results.every(Boolean)
4208
+ : results.some(Boolean);
4209
+ }
4210
+ isLogicCondition(value) {
4211
+ return 'fieldId' in value;
4212
+ }
4213
+ evaluateLogicCondition(condition, fields) {
4214
+ const left = this.getFieldValue(condition.fieldId, fields);
4215
+ const right = condition.valueSource === 'field'
4216
+ ? this.getFieldValue(condition.valueFieldId ?? '', fields)
4217
+ : condition.value;
4218
+ switch (condition.operator) {
4219
+ case 'eq':
4220
+ return left === right;
4221
+ case 'neq':
4222
+ return left !== right;
4223
+ case 'gt':
4224
+ return this.compareValues(left, right) > 0;
4225
+ case 'lt':
4226
+ return this.compareValues(left, right) < 0;
4227
+ case 'gte':
4228
+ return this.compareValues(left, right) >= 0;
4229
+ case 'lte':
4230
+ return this.compareValues(left, right) <= 0;
4231
+ case 'contains':
4232
+ return this.containsValue(left, right);
4233
+ case 'startsWith':
4234
+ return String(left ?? '').startsWith(String(right ?? ''));
4235
+ case 'endsWith':
4236
+ return String(left ?? '').endsWith(String(right ?? ''));
4237
+ case 'empty':
4238
+ return this.isEmptyValue(left);
4239
+ case 'notEmpty':
4240
+ return !this.isEmptyValue(left);
4241
+ case 'truthy':
4242
+ return !!left;
4243
+ default:
4244
+ return false;
4245
+ }
4246
+ }
4247
+ compareValues(left, right) {
4248
+ const leftNumber = Number(left);
4249
+ const rightNumber = Number(right);
4250
+ const leftIsNumeric = Number.isFinite(leftNumber);
4251
+ const rightIsNumeric = Number.isFinite(rightNumber);
4252
+ if (leftIsNumeric && rightIsNumeric) {
4253
+ if (leftNumber === rightNumber)
4254
+ return 0;
4255
+ return leftNumber > rightNumber ? 1 : -1;
4256
+ }
4257
+ const leftText = String(left ?? '');
4258
+ const rightText = String(right ?? '');
4259
+ if (leftText === rightText)
4260
+ return 0;
4261
+ return leftText > rightText ? 1 : -1;
4262
+ }
4263
+ containsValue(left, right) {
4264
+ if (Array.isArray(left)) {
4265
+ return left.includes(right);
4266
+ }
4267
+ return String(left ?? '').includes(String(right ?? ''));
4268
+ }
4269
+ isEmptyValue(value) {
4270
+ if (value === null || value === undefined)
4271
+ return true;
4272
+ if (typeof value === 'string')
4273
+ return value.trim().length === 0;
4274
+ if (Array.isArray(value))
4275
+ return value.length === 0;
4276
+ return false;
4277
+ }
4278
+ getFieldValue(fieldId, fields) {
4279
+ const field = fields.find(item => item.id === fieldId);
4280
+ if (!field)
4281
+ return undefined;
4282
+ return this.engine.getValue(field.name);
4283
+ }
4022
4284
  collectSourceValues(action, fields) {
4023
4285
  const values = {};
4024
4286
  const fieldIds = action.sourceFieldIds ?? [];
@@ -4495,12 +4757,15 @@ class JsonFormRendererComponent {
4495
4757
  designerState;
4496
4758
  schema;
4497
4759
  initialValues = {};
4760
+ initialFieldLabels = {};
4498
4761
  mode = 'live';
4499
4762
  device = 'desktop';
4763
+ showLayoutGuides = true;
4500
4764
  breakpoint = 'xl';
4501
4765
  eventLogger;
4502
4766
  eventApis = [];
4503
4767
  eventApiExecutor;
4768
+ navigateToPage;
4504
4769
  uploadOnSubmit = true;
4505
4770
  fieldDataAccessMap;
4506
4771
  fieldDataAccessApi;
@@ -4545,7 +4810,8 @@ class JsonFormRendererComponent {
4545
4810
  }
4546
4811
  if (changes['eventLogger']
4547
4812
  || changes['eventApiExecutor']
4548
- || changes['eventApis']) {
4813
+ || changes['eventApis']
4814
+ || changes['navigateToPage']) {
4549
4815
  this.configureRunner();
4550
4816
  }
4551
4817
  }
@@ -4564,7 +4830,7 @@ class JsonFormRendererComponent {
4564
4830
  console.warn('[ngx-form-designer] API-Only Architecture Warnings:', issues);
4565
4831
  }
4566
4832
  }
4567
- this.engine = createFormEngine(this.schema, this.initialValues);
4833
+ this.engine = createFormEngine(this.schema, this.initialValues, this.initialFieldLabels);
4568
4834
  this.syncRuntimeFieldDataAccessContext();
4569
4835
  this.configureRunner();
4570
4836
  // Subscribe to value changes
@@ -4617,7 +4883,8 @@ class JsonFormRendererComponent {
4617
4883
  this.runner = new FormEventRunner(this.engine, {
4618
4884
  logger: this.eventLogger,
4619
4885
  apiExecutor,
4620
- dataSourceWriter: request => this.writeEventDatasource(request)
4886
+ dataSourceWriter: request => this.writeEventDatasource(request),
4887
+ navigateToPage: this.navigateToPage
4621
4888
  });
4622
4889
  }
4623
4890
  resolveEventApiExecutor() {
@@ -4916,7 +5183,8 @@ class JsonFormRendererComponent {
4916
5183
  }
4917
5184
  mapped[field.id] = {
4918
5185
  fieldName: field.name,
4919
- fieldValue: rawValue
5186
+ fieldValue: rawValue,
5187
+ ...this.buildFieldLabelMetadata(field.name)
4920
5188
  };
4921
5189
  }
4922
5190
  return mapped;
@@ -5003,6 +5271,12 @@ class JsonFormRendererComponent {
5003
5271
  }
5004
5272
  return [];
5005
5273
  }
5274
+ buildFieldLabelMetadata(fieldName) {
5275
+ const fieldLabel = typeof this.engine?.getFieldLabel === 'function'
5276
+ ? this.engine.getFieldLabel(fieldName)
5277
+ : undefined;
5278
+ return fieldLabel === undefined ? {} : { fieldLabel };
5279
+ }
5006
5280
  mergeFileMetadata(target, source) {
5007
5281
  const merged = { ...target };
5008
5282
  for (const fieldId of Object.keys(source)) {
@@ -5085,7 +5359,7 @@ class JsonFormRendererComponent {
5085
5359
  || typeof record['type'] === 'string';
5086
5360
  }
5087
5361
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: JsonFormRendererComponent, deps: [{ token: DesignerStateService }], target: i0.ɵɵFactoryTarget.Component });
5088
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: JsonFormRendererComponent, isStandalone: true, selector: "app-json-form-renderer", inputs: { schema: "schema", initialValues: "initialValues", mode: "mode", device: "device", breakpoint: "breakpoint", eventLogger: "eventLogger", eventApis: "eventApis", eventApiExecutor: "eventApiExecutor", uploadOnSubmit: "uploadOnSubmit", fieldDataAccessMap: "fieldDataAccessMap", fieldDataAccessApi: "fieldDataAccessApi", formContentId: "formContentId", formContentVersion: "formContentVersion" }, outputs: { valueChange: "valueChange", groupedValueChange: "groupedValueChange", combinedValueChange: "combinedValueChange", validationChange: "validationChange", uploadedFilesChange: "uploadedFilesChange", formSubmit: "formSubmit" }, usesOnChanges: true, ngImport: i0, template: `
5362
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: JsonFormRendererComponent, isStandalone: true, selector: "app-json-form-renderer", inputs: { schema: "schema", initialValues: "initialValues", initialFieldLabels: "initialFieldLabels", mode: "mode", device: "device", showLayoutGuides: "showLayoutGuides", breakpoint: "breakpoint", eventLogger: "eventLogger", eventApis: "eventApis", eventApiExecutor: "eventApiExecutor", navigateToPage: "navigateToPage", uploadOnSubmit: "uploadOnSubmit", fieldDataAccessMap: "fieldDataAccessMap", fieldDataAccessApi: "fieldDataAccessApi", formContentId: "formContentId", formContentVersion: "formContentVersion" }, outputs: { valueChange: "valueChange", groupedValueChange: "groupedValueChange", combinedValueChange: "combinedValueChange", validationChange: "validationChange", uploadedFilesChange: "uploadedFilesChange", formSubmit: "formSubmit" }, usesOnChanges: true, ngImport: i0, template: `
5089
5363
  <div class="form-renderer-container"
5090
5364
  data-fd="renderer"
5091
5365
  [class.is-mobile]="device === 'mobile'"
@@ -5097,6 +5371,7 @@ class JsonFormRendererComponent {
5097
5371
  [engine]="engine"
5098
5372
  [fields]="schema.fields"
5099
5373
  [designMode]="mode === 'design'"
5374
+ [showLayoutGuides]="showLayoutGuides"
5100
5375
  [readOnlyMode]="mode === 'preview'"
5101
5376
  [device]="device"
5102
5377
  [breakpoint]="breakpoint"
@@ -5106,7 +5381,7 @@ class JsonFormRendererComponent {
5106
5381
  </ng-container>
5107
5382
  <ng-template #loading>Loading form...</ng-template>
5108
5383
  </div>
5109
- `, 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"] }] });
5384
+ `, 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", "showLayoutGuides", "readOnlyMode", "scopePath", "device", "breakpoint", "connectedDropLists"], outputs: ["nodeDrop", "nodeSelect"] }] });
5110
5385
  }
5111
5386
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: JsonFormRendererComponent, decorators: [{
5112
5387
  type: Component,
@@ -5122,6 +5397,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
5122
5397
  [engine]="engine"
5123
5398
  [fields]="schema.fields"
5124
5399
  [designMode]="mode === 'design'"
5400
+ [showLayoutGuides]="showLayoutGuides"
5125
5401
  [readOnlyMode]="mode === 'preview'"
5126
5402
  [device]="device"
5127
5403
  [breakpoint]="breakpoint"
@@ -5136,10 +5412,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
5136
5412
  type: Input
5137
5413
  }], initialValues: [{
5138
5414
  type: Input
5415
+ }], initialFieldLabels: [{
5416
+ type: Input
5139
5417
  }], mode: [{
5140
5418
  type: Input
5141
5419
  }], device: [{
5142
5420
  type: Input
5421
+ }], showLayoutGuides: [{
5422
+ type: Input
5143
5423
  }], breakpoint: [{
5144
5424
  type: Input
5145
5425
  }], eventLogger: [{
@@ -5148,6 +5428,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
5148
5428
  type: Input
5149
5429
  }], eventApiExecutor: [{
5150
5430
  type: Input
5431
+ }], navigateToPage: [{
5432
+ type: Input
5151
5433
  }], uploadOnSubmit: [{
5152
5434
  type: Input
5153
5435
  }], fieldDataAccessMap: [{
@@ -5347,8 +5629,262 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
5347
5629
  type: Input
5348
5630
  }] } });
5349
5631
 
5632
+ class FormJourneyViewerComponent {
5633
+ journey;
5634
+ viewOnly = false;
5635
+ eventApis = [];
5636
+ eventApiExecutor;
5637
+ initialValues;
5638
+ initialFieldLabels;
5639
+ fieldDataAccessMap;
5640
+ fieldDataAccessApi;
5641
+ formContentId;
5642
+ formContentVersion;
5643
+ formDataChange = new EventEmitter();
5644
+ formDataByPageChange = new EventEmitter();
5645
+ formValidationChange = new EventEmitter();
5646
+ uploadedFilesChange = new EventEmitter();
5647
+ submit = new EventEmitter();
5648
+ activePageIdChange = new EventEmitter();
5649
+ renderer;
5650
+ activePageId = signal('');
5651
+ values = signal({});
5652
+ fieldLabels = signal({});
5653
+ activePage = computed(() => this.journey.pages.find(page => page.id === this.activePageId()) ?? null);
5654
+ activePageIndex = computed(() => {
5655
+ const id = this.activePageId();
5656
+ const index = this.journey.pages.findIndex(page => page.id === id);
5657
+ return index >= 0 ? index : 0;
5658
+ });
5659
+ ngOnInit() {
5660
+ this.activePageId.set(this.journey.startPageId || this.journey.pages[0]?.id || '');
5661
+ this.values.set(this.normalizeInitialValues(this.initialValues));
5662
+ this.fieldLabels.set(this.normalizeInitialFieldLabels(this.initialFieldLabels));
5663
+ }
5664
+ ngOnChanges(changes) {
5665
+ if (changes['initialValues']) {
5666
+ this.values.set(this.normalizeInitialValues(this.initialValues));
5667
+ }
5668
+ if (changes['initialFieldLabels']) {
5669
+ this.fieldLabels.set(this.normalizeInitialFieldLabels(this.initialFieldLabels));
5670
+ }
5671
+ const current = this.activePageId();
5672
+ if (current && this.journey.pages.some(page => page.id === current))
5673
+ return;
5674
+ this.activePageId.set(this.journey.startPageId || this.journey.pages[0]?.id || '');
5675
+ }
5676
+ navigateToPage = async (pageId) => {
5677
+ if (!this.journey.pages.some(page => page.id === pageId)) {
5678
+ return { ok: false, reason: 'unknown-page' };
5679
+ }
5680
+ if (!this.viewOnly && this.renderer?.engine) {
5681
+ const errors = this.renderer.engine.validate();
5682
+ const validation = {
5683
+ errors: { ...errors },
5684
+ isValid: Object.keys(errors).length === 0
5685
+ };
5686
+ this.formValidationChange.emit(validation);
5687
+ if (!validation.isValid) {
5688
+ return { ok: false, reason: 'validation' };
5689
+ }
5690
+ }
5691
+ this.activePageId.set(pageId);
5692
+ this.activePageIdChange.emit(pageId);
5693
+ return { ok: true };
5694
+ };
5695
+ onValueChange(values) {
5696
+ const mergedScope = this.mergeValueScope(this.values(), this.toValueScope(values));
5697
+ const mergedFieldLabels = this.mergeFieldLabelScope(this.fieldLabels(), this.toFieldLabelScope(values));
5698
+ this.values.set(mergedScope);
5699
+ this.fieldLabels.set(mergedFieldLabels);
5700
+ const valuesByPage = this.toJourneyValueMapByPage(mergedScope, mergedFieldLabels);
5701
+ this.formDataByPageChange.emit(valuesByPage);
5702
+ this.formDataChange.emit(this.flattenJourneyValueMapByPage(valuesByPage));
5703
+ }
5704
+ onValidationChange(result) {
5705
+ this.formValidationChange.emit(result);
5706
+ }
5707
+ onUploadedFilesChange(result) {
5708
+ this.uploadedFilesChange.emit(result);
5709
+ }
5710
+ onSubmit(result) {
5711
+ this.submit.emit(result);
5712
+ }
5713
+ normalizeInitialValues(value) {
5714
+ if (!value)
5715
+ return {};
5716
+ return structuredClone(value);
5717
+ }
5718
+ normalizeInitialFieldLabels(value) {
5719
+ if (!value)
5720
+ return {};
5721
+ return structuredClone(value);
5722
+ }
5723
+ toValueScope(values) {
5724
+ const scope = {};
5725
+ for (const fieldValue of Object.values(values)) {
5726
+ scope[fieldValue.fieldName] = fieldValue.fieldValue;
5727
+ }
5728
+ return scope;
5729
+ }
5730
+ toFieldLabelScope(values) {
5731
+ const scope = {};
5732
+ for (const fieldValue of Object.values(values)) {
5733
+ if (fieldValue.fieldLabel === undefined)
5734
+ continue;
5735
+ scope[fieldValue.fieldName] = fieldValue.fieldLabel;
5736
+ }
5737
+ return scope;
5738
+ }
5739
+ mergeValueScope(currentScope, incomingScope) {
5740
+ return {
5741
+ ...currentScope,
5742
+ ...incomingScope
5743
+ };
5744
+ }
5745
+ mergeFieldLabelScope(currentScope, incomingScope) {
5746
+ return {
5747
+ ...currentScope,
5748
+ ...incomingScope
5749
+ };
5750
+ }
5751
+ toJourneyValueMapByPage(scope, fieldLabels) {
5752
+ const valuesByPage = {};
5753
+ for (const page of this.journey.pages) {
5754
+ const pageValues = {};
5755
+ for (const field of page.schema.fields) {
5756
+ if (!Object.prototype.hasOwnProperty.call(scope, field.name))
5757
+ continue;
5758
+ pageValues[field.id] = {
5759
+ fieldName: field.name,
5760
+ fieldValue: scope[field.name],
5761
+ ...(fieldLabels[field.name] === undefined ? {} : { fieldLabel: fieldLabels[field.name] })
5762
+ };
5763
+ }
5764
+ valuesByPage[page.id] = pageValues;
5765
+ }
5766
+ return valuesByPage;
5767
+ }
5768
+ flattenJourneyValueMapByPage(valuesByPage) {
5769
+ const flattened = {};
5770
+ for (const page of this.journey.pages) {
5771
+ const pageValues = valuesByPage[page.id];
5772
+ if (!pageValues)
5773
+ continue;
5774
+ for (const [fieldId, value] of Object.entries(pageValues)) {
5775
+ flattened[fieldId] = value;
5776
+ }
5777
+ }
5778
+ return flattened;
5779
+ }
5780
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: FormJourneyViewerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5781
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: FormJourneyViewerComponent, isStandalone: true, selector: "app-form-journey-viewer", inputs: { journey: "journey", viewOnly: "viewOnly", eventApis: "eventApis", eventApiExecutor: "eventApiExecutor", initialValues: "initialValues", initialFieldLabels: "initialFieldLabels", fieldDataAccessMap: "fieldDataAccessMap", fieldDataAccessApi: "fieldDataAccessApi", formContentId: "formContentId", formContentVersion: "formContentVersion" }, outputs: { formDataChange: "formDataChange", formDataByPageChange: "formDataByPageChange", formValidationChange: "formValidationChange", uploadedFilesChange: "uploadedFilesChange", submit: "submit", activePageIdChange: "activePageIdChange" }, viewQueries: [{ propertyName: "renderer", first: true, predicate: JsonFormRendererComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: `
5782
+ <div class="form-journey-viewer flex h-full w-full flex-col" data-fd="form-journey-viewer">
5783
+ <div class="border-b border-gray-200 bg-white px-4 py-2 text-xs text-gray-600" data-fd="journey-progress">
5784
+ {{ activePageIndex() + 1 }} / {{ journey.pages.length }}
5785
+ <span class="ml-2 font-semibold text-gray-800">{{ activePage()?.name }}</span>
5786
+ </div>
5787
+
5788
+ <div class="min-h-0 flex-1" data-fd="journey-surface">
5789
+ <app-json-form-renderer
5790
+ *ngIf="activePage() as page"
5791
+ [schema]="page.schema"
5792
+ [initialValues]="values()"
5793
+ [initialFieldLabels]="fieldLabels()"
5794
+ [mode]="viewOnly ? 'preview' : 'live'"
5795
+ [eventApis]="eventApis"
5796
+ [eventApiExecutor]="eventApiExecutor"
5797
+ [fieldDataAccessMap]="fieldDataAccessMap"
5798
+ [fieldDataAccessApi]="fieldDataAccessApi"
5799
+ [formContentId]="formContentId"
5800
+ [formContentVersion]="formContentVersion"
5801
+ [navigateToPage]="navigateToPage"
5802
+ (valueChange)="onValueChange($event)"
5803
+ (validationChange)="onValidationChange($event)"
5804
+ (uploadedFilesChange)="onUploadedFilesChange($event)"
5805
+ (formSubmit)="onSubmit($event)">
5806
+ </app-json-form-renderer>
5807
+ </div>
5808
+ </div>
5809
+ `, isInline: true, 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", "initialFieldLabels", "mode", "device", "showLayoutGuides", "breakpoint", "eventLogger", "eventApis", "eventApiExecutor", "navigateToPage", "uploadOnSubmit", "fieldDataAccessMap", "fieldDataAccessApi", "formContentId", "formContentVersion"], outputs: ["valueChange", "groupedValueChange", "combinedValueChange", "validationChange", "uploadedFilesChange", "formSubmit"] }] });
5810
+ }
5811
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: FormJourneyViewerComponent, decorators: [{
5812
+ type: Component,
5813
+ args: [{
5814
+ selector: 'app-form-journey-viewer',
5815
+ standalone: true,
5816
+ imports: [CommonModule, JsonFormRendererComponent],
5817
+ template: `
5818
+ <div class="form-journey-viewer flex h-full w-full flex-col" data-fd="form-journey-viewer">
5819
+ <div class="border-b border-gray-200 bg-white px-4 py-2 text-xs text-gray-600" data-fd="journey-progress">
5820
+ {{ activePageIndex() + 1 }} / {{ journey.pages.length }}
5821
+ <span class="ml-2 font-semibold text-gray-800">{{ activePage()?.name }}</span>
5822
+ </div>
5823
+
5824
+ <div class="min-h-0 flex-1" data-fd="journey-surface">
5825
+ <app-json-form-renderer
5826
+ *ngIf="activePage() as page"
5827
+ [schema]="page.schema"
5828
+ [initialValues]="values()"
5829
+ [initialFieldLabels]="fieldLabels()"
5830
+ [mode]="viewOnly ? 'preview' : 'live'"
5831
+ [eventApis]="eventApis"
5832
+ [eventApiExecutor]="eventApiExecutor"
5833
+ [fieldDataAccessMap]="fieldDataAccessMap"
5834
+ [fieldDataAccessApi]="fieldDataAccessApi"
5835
+ [formContentId]="formContentId"
5836
+ [formContentVersion]="formContentVersion"
5837
+ [navigateToPage]="navigateToPage"
5838
+ (valueChange)="onValueChange($event)"
5839
+ (validationChange)="onValidationChange($event)"
5840
+ (uploadedFilesChange)="onUploadedFilesChange($event)"
5841
+ (formSubmit)="onSubmit($event)">
5842
+ </app-json-form-renderer>
5843
+ </div>
5844
+ </div>
5845
+ `
5846
+ }]
5847
+ }], propDecorators: { journey: [{
5848
+ type: Input,
5849
+ args: [{ required: true }]
5850
+ }], viewOnly: [{
5851
+ type: Input
5852
+ }], eventApis: [{
5853
+ type: Input
5854
+ }], eventApiExecutor: [{
5855
+ type: Input
5856
+ }], initialValues: [{
5857
+ type: Input
5858
+ }], initialFieldLabels: [{
5859
+ type: Input
5860
+ }], fieldDataAccessMap: [{
5861
+ type: Input
5862
+ }], fieldDataAccessApi: [{
5863
+ type: Input
5864
+ }], formContentId: [{
5865
+ type: Input
5866
+ }], formContentVersion: [{
5867
+ type: Input
5868
+ }], formDataChange: [{
5869
+ type: Output
5870
+ }], formDataByPageChange: [{
5871
+ type: Output
5872
+ }], formValidationChange: [{
5873
+ type: Output
5874
+ }], uploadedFilesChange: [{
5875
+ type: Output
5876
+ }], submit: [{
5877
+ type: Output
5878
+ }], activePageIdChange: [{
5879
+ type: Output
5880
+ }], renderer: [{
5881
+ type: ViewChild,
5882
+ args: [JsonFormRendererComponent]
5883
+ }] } });
5884
+
5350
5885
  class FormViewerComponent {
5351
5886
  schema;
5887
+ journey;
5352
5888
  set data(value) {
5353
5889
  this._data = value ?? {};
5354
5890
  this.refreshNormalizedInitialValues();
@@ -5376,10 +5912,13 @@ class FormViewerComponent {
5376
5912
  formContentId;
5377
5913
  formContentVersion;
5378
5914
  formDataChange = new EventEmitter();
5915
+ formDataByPageChange = new EventEmitter();
5379
5916
  formValidationChange = new EventEmitter();
5380
5917
  uploadedFilesChange = new EventEmitter();
5381
5918
  submit = new EventEmitter();
5919
+ activePageIdChange = new EventEmitter();
5382
5920
  renderer;
5921
+ journeyViewer;
5383
5922
  // Responsive State
5384
5923
  breakpoint = signal('xl');
5385
5924
  el = inject(ElementRef);
@@ -5389,6 +5928,7 @@ class FormViewerComponent {
5389
5928
  _data = {};
5390
5929
  _dataUsesFieldNameKeys = false;
5391
5930
  normalizedInitialValues = {};
5931
+ normalizedInitialFieldLabels = {};
5392
5932
  ngOnInit() {
5393
5933
  this.setupResizeObserver();
5394
5934
  }
@@ -5436,6 +5976,11 @@ class FormViewerComponent {
5436
5976
  onValidationChange(result) {
5437
5977
  this.formValidationChange.emit(result);
5438
5978
  }
5979
+ onJourneyValueByPageChange(valuesByPage) {
5980
+ if (this.options.emitOnChange !== false) {
5981
+ this.formDataByPageChange.emit(valuesByPage);
5982
+ }
5983
+ }
5439
5984
  onUploadedFilesChange(result) {
5440
5985
  this.uploadedFilesChange.emit(result);
5441
5986
  }
@@ -5447,14 +5992,34 @@ class FormViewerComponent {
5447
5992
  // But `onSubmit` is public, let's just call it with a fake event.
5448
5993
  if (this.renderer) {
5449
5994
  this.renderer.onSubmit(new Event('submit'));
5995
+ return;
5996
+ }
5997
+ if (this.journeyViewer?.renderer) {
5998
+ this.journeyViewer.renderer.onSubmit(new Event('submit'));
5450
5999
  }
5451
6000
  }
5452
6001
  onRendererSubmit(result) {
5453
6002
  // Re-emit upwards
5454
6003
  this.submit.emit(result);
5455
6004
  }
6005
+ onActivePageIdChange(pageId) {
6006
+ this.activePageIdChange.emit(pageId);
6007
+ }
6008
+ hasJourney() {
6009
+ return !!this.journey && Array.isArray(this.journey.pages) && this.journey.pages.length > 0;
6010
+ }
6011
+ resolvedSchema() {
6012
+ if (this.schema) {
6013
+ return this.schema;
6014
+ }
6015
+ if (!this.hasJourney()) {
6016
+ return null;
6017
+ }
6018
+ return this.journey.pages[0]?.schema ?? null;
6019
+ }
5456
6020
  refreshNormalizedInitialValues() {
5457
6021
  this.normalizedInitialValues = this.normalizeInitialValues(this._data);
6022
+ this.normalizedInitialFieldLabels = this.normalizeInitialFieldLabels(this._data);
5458
6023
  }
5459
6024
  normalizeInitialValues(input) {
5460
6025
  if (!this.isRecord(input))
@@ -5477,6 +6042,29 @@ class FormViewerComponent {
5477
6042
  }
5478
6043
  return normalized;
5479
6044
  }
6045
+ normalizeInitialFieldLabels(input) {
6046
+ if (!this.isRecord(input))
6047
+ return {};
6048
+ if (this._dataUsesFieldNameKeys) {
6049
+ const normalizedFieldNameKeys = this.normalizeFieldNameKeyedLabels(input);
6050
+ if (normalizedFieldNameKeys) {
6051
+ return normalizedFieldNameKeys;
6052
+ }
6053
+ }
6054
+ if (!this.isOutputShapeCandidate(input))
6055
+ return {};
6056
+ const outputValues = new Map();
6057
+ this.collectOutputShapeFields(input, outputValues);
6058
+ if (outputValues.size === 0)
6059
+ return {};
6060
+ const normalized = {};
6061
+ for (const [fieldName, value] of outputValues.entries()) {
6062
+ if (value.fieldLabel === undefined)
6063
+ continue;
6064
+ normalized[fieldName] = value.fieldLabel;
6065
+ }
6066
+ return normalized;
6067
+ }
5480
6068
  normalizeFieldNameKeyedValues(input) {
5481
6069
  if (!this.isFieldNameKeyedOutputCandidate(input)) {
5482
6070
  return input;
@@ -5495,6 +6083,19 @@ class FormViewerComponent {
5495
6083
  }
5496
6084
  return normalized;
5497
6085
  }
6086
+ normalizeFieldNameKeyedLabels(input) {
6087
+ if (!this.isFieldNameKeyedOutputCandidate(input)) {
6088
+ return null;
6089
+ }
6090
+ const normalized = {};
6091
+ for (const [fieldName, value] of Object.entries(input)) {
6092
+ if (!this.isFieldIdValue(value) || value.fieldLabel === undefined) {
6093
+ continue;
6094
+ }
6095
+ normalized[fieldName] = value.fieldLabel;
6096
+ }
6097
+ return normalized;
6098
+ }
5498
6099
  collectOutputShapeFields(value, output) {
5499
6100
  if (this.isFormFieldValue(value)) {
5500
6101
  output.set(value.fieldName, value);
@@ -5585,31 +6186,56 @@ class FormViewerComponent {
5585
6186
  return !!value && typeof value === 'object';
5586
6187
  }
5587
6188
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: FormViewerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5588
- 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", dataUsesFieldNameKeys: "dataUsesFieldNameKeys", 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: `
6189
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: FormViewerComponent, isStandalone: true, selector: "app-form-viewer", inputs: { schema: "schema", journey: "journey", data: "data", dataUsesFieldNameKeys: "dataUsesFieldNameKeys", options: "options", viewOnly: "viewOnly", eventApis: "eventApis", eventApiExecutor: "eventApiExecutor", fieldDataAccessMap: "fieldDataAccessMap", fieldDataAccessApi: "fieldDataAccessApi", formContentId: "formContentId", formContentVersion: "formContentVersion" }, outputs: { formDataChange: "formDataChange", formDataByPageChange: "formDataByPageChange", formValidationChange: "formValidationChange", uploadedFilesChange: "uploadedFilesChange", submit: "submit", activePageIdChange: "activePageIdChange" }, viewQueries: [{ propertyName: "renderer", first: true, predicate: JsonFormRendererComponent, descendants: true }, { propertyName: "journeyViewer", first: true, predicate: FormJourneyViewerComponent, descendants: true }], ngImport: i0, template: `
5589
6190
  <div class="form-viewer-container flex flex-col h-full"
5590
6191
  data-fd="form-viewer"
5591
6192
  [attr.data-fd-mode]="viewOnly ? 'preview' : 'live'">
5592
6193
  <div class="flex-1 min-h-0" data-fd="form-viewer-surface">
5593
- <app-json-form-renderer
5594
- [schema]="schema"
5595
- [initialValues]="normalizedInitialValues"
5596
- [mode]="viewOnly ? 'preview' : 'live'"
5597
- [breakpoint]="breakpoint()"
5598
- [uploadOnSubmit]="true"
5599
- [eventApis]="eventApis"
5600
- [eventApiExecutor]="eventApiExecutor"
5601
- [fieldDataAccessMap]="fieldDataAccessMap"
5602
- [fieldDataAccessApi]="fieldDataAccessApi"
5603
- [formContentId]="formContentId"
5604
- [formContentVersion]="formContentVersion"
5605
- (valueChange)="onValueChange($event)"
5606
- (validationChange)="onValidationChange($event)"
5607
- (uploadedFilesChange)="onUploadedFilesChange($event)"
5608
- (formSubmit)="onRendererSubmit($event)">
5609
- </app-json-form-renderer>
6194
+ <ng-container *ngIf="hasJourney(); else singleFormRenderer">
6195
+ <app-form-journey-viewer
6196
+ data-fd="form-viewer-journey"
6197
+ [journey]="journey!"
6198
+ [viewOnly]="viewOnly"
6199
+ [eventApis]="eventApis"
6200
+ [eventApiExecutor]="eventApiExecutor"
6201
+ [initialValues]="normalizedInitialValues"
6202
+ [initialFieldLabels]="normalizedInitialFieldLabels"
6203
+ [fieldDataAccessMap]="fieldDataAccessMap"
6204
+ [fieldDataAccessApi]="fieldDataAccessApi"
6205
+ [formContentId]="formContentId"
6206
+ [formContentVersion]="formContentVersion"
6207
+ (formDataChange)="onValueChange($event)"
6208
+ (formDataByPageChange)="onJourneyValueByPageChange($event)"
6209
+ (formValidationChange)="onValidationChange($event)"
6210
+ (submit)="onRendererSubmit($event)"
6211
+ (activePageIdChange)="onActivePageIdChange($event)">
6212
+ </app-form-journey-viewer>
6213
+ </ng-container>
6214
+
6215
+ <ng-template #singleFormRenderer>
6216
+ <app-json-form-renderer
6217
+ *ngIf="resolvedSchema() as currentSchema"
6218
+ [schema]="currentSchema"
6219
+ [initialValues]="normalizedInitialValues"
6220
+ [initialFieldLabels]="normalizedInitialFieldLabels"
6221
+ [mode]="viewOnly ? 'preview' : 'live'"
6222
+ [breakpoint]="breakpoint()"
6223
+ [uploadOnSubmit]="true"
6224
+ [eventApis]="eventApis"
6225
+ [eventApiExecutor]="eventApiExecutor"
6226
+ [fieldDataAccessMap]="fieldDataAccessMap"
6227
+ [fieldDataAccessApi]="fieldDataAccessApi"
6228
+ [formContentId]="formContentId"
6229
+ [formContentVersion]="formContentVersion"
6230
+ (valueChange)="onValueChange($event)"
6231
+ (validationChange)="onValidationChange($event)"
6232
+ (uploadedFilesChange)="onUploadedFilesChange($event)"
6233
+ (formSubmit)="onRendererSubmit($event)">
6234
+ </app-json-form-renderer>
6235
+ </ng-template>
5610
6236
  </div>
5611
6237
 
5612
- <div *ngIf="!viewOnly && options.showSubmitButton"
6238
+ <div *ngIf="!viewOnly && options.showSubmitButton && !hasJourney()"
5613
6239
  data-fd="form-viewer-submit-bar"
5614
6240
  class="p-4 border-t border-gray-200 bg-white shadow-sm flex justify-end shrink-0 z-10">
5615
6241
  <button
@@ -5621,35 +6247,60 @@ class FormViewerComponent {
5621
6247
  </button>
5622
6248
  </div>
5623
6249
  </div>
5624
- `, 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 });
6250
+ `, 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", "initialFieldLabels", "mode", "device", "showLayoutGuides", "breakpoint", "eventLogger", "eventApis", "eventApiExecutor", "navigateToPage", "uploadOnSubmit", "fieldDataAccessMap", "fieldDataAccessApi", "formContentId", "formContentVersion"], outputs: ["valueChange", "groupedValueChange", "combinedValueChange", "validationChange", "uploadedFilesChange", "formSubmit"] }, { kind: "component", type: FormJourneyViewerComponent, selector: "app-form-journey-viewer", inputs: ["journey", "viewOnly", "eventApis", "eventApiExecutor", "initialValues", "initialFieldLabels", "fieldDataAccessMap", "fieldDataAccessApi", "formContentId", "formContentVersion"], outputs: ["formDataChange", "formDataByPageChange", "formValidationChange", "uploadedFilesChange", "submit", "activePageIdChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5625
6251
  }
5626
6252
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: FormViewerComponent, decorators: [{
5627
6253
  type: Component,
5628
- args: [{ selector: 'app-form-viewer', standalone: true, imports: [CommonModule, JsonFormRendererComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
6254
+ args: [{ selector: 'app-form-viewer', standalone: true, imports: [CommonModule, JsonFormRendererComponent, FormJourneyViewerComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
5629
6255
  <div class="form-viewer-container flex flex-col h-full"
5630
6256
  data-fd="form-viewer"
5631
6257
  [attr.data-fd-mode]="viewOnly ? 'preview' : 'live'">
5632
6258
  <div class="flex-1 min-h-0" data-fd="form-viewer-surface">
5633
- <app-json-form-renderer
5634
- [schema]="schema"
5635
- [initialValues]="normalizedInitialValues"
5636
- [mode]="viewOnly ? 'preview' : 'live'"
5637
- [breakpoint]="breakpoint()"
5638
- [uploadOnSubmit]="true"
5639
- [eventApis]="eventApis"
5640
- [eventApiExecutor]="eventApiExecutor"
5641
- [fieldDataAccessMap]="fieldDataAccessMap"
5642
- [fieldDataAccessApi]="fieldDataAccessApi"
5643
- [formContentId]="formContentId"
5644
- [formContentVersion]="formContentVersion"
5645
- (valueChange)="onValueChange($event)"
5646
- (validationChange)="onValidationChange($event)"
5647
- (uploadedFilesChange)="onUploadedFilesChange($event)"
5648
- (formSubmit)="onRendererSubmit($event)">
5649
- </app-json-form-renderer>
6259
+ <ng-container *ngIf="hasJourney(); else singleFormRenderer">
6260
+ <app-form-journey-viewer
6261
+ data-fd="form-viewer-journey"
6262
+ [journey]="journey!"
6263
+ [viewOnly]="viewOnly"
6264
+ [eventApis]="eventApis"
6265
+ [eventApiExecutor]="eventApiExecutor"
6266
+ [initialValues]="normalizedInitialValues"
6267
+ [initialFieldLabels]="normalizedInitialFieldLabels"
6268
+ [fieldDataAccessMap]="fieldDataAccessMap"
6269
+ [fieldDataAccessApi]="fieldDataAccessApi"
6270
+ [formContentId]="formContentId"
6271
+ [formContentVersion]="formContentVersion"
6272
+ (formDataChange)="onValueChange($event)"
6273
+ (formDataByPageChange)="onJourneyValueByPageChange($event)"
6274
+ (formValidationChange)="onValidationChange($event)"
6275
+ (submit)="onRendererSubmit($event)"
6276
+ (activePageIdChange)="onActivePageIdChange($event)">
6277
+ </app-form-journey-viewer>
6278
+ </ng-container>
6279
+
6280
+ <ng-template #singleFormRenderer>
6281
+ <app-json-form-renderer
6282
+ *ngIf="resolvedSchema() as currentSchema"
6283
+ [schema]="currentSchema"
6284
+ [initialValues]="normalizedInitialValues"
6285
+ [initialFieldLabels]="normalizedInitialFieldLabels"
6286
+ [mode]="viewOnly ? 'preview' : 'live'"
6287
+ [breakpoint]="breakpoint()"
6288
+ [uploadOnSubmit]="true"
6289
+ [eventApis]="eventApis"
6290
+ [eventApiExecutor]="eventApiExecutor"
6291
+ [fieldDataAccessMap]="fieldDataAccessMap"
6292
+ [fieldDataAccessApi]="fieldDataAccessApi"
6293
+ [formContentId]="formContentId"
6294
+ [formContentVersion]="formContentVersion"
6295
+ (valueChange)="onValueChange($event)"
6296
+ (validationChange)="onValidationChange($event)"
6297
+ (uploadedFilesChange)="onUploadedFilesChange($event)"
6298
+ (formSubmit)="onRendererSubmit($event)">
6299
+ </app-json-form-renderer>
6300
+ </ng-template>
5650
6301
  </div>
5651
6302
 
5652
- <div *ngIf="!viewOnly && options.showSubmitButton"
6303
+ <div *ngIf="!viewOnly && options.showSubmitButton && !hasJourney()"
5653
6304
  data-fd="form-viewer-submit-bar"
5654
6305
  class="p-4 border-t border-gray-200 bg-white shadow-sm flex justify-end shrink-0 z-10">
5655
6306
  <button
@@ -5663,8 +6314,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
5663
6314
  </div>
5664
6315
  `, styles: [":host{display:block;height:100%;width:100%}\n"] }]
5665
6316
  }], propDecorators: { schema: [{
5666
- type: Input,
5667
- args: [{ required: true }]
6317
+ type: Input
6318
+ }], journey: [{
6319
+ type: Input
5668
6320
  }], data: [{
5669
6321
  type: Input
5670
6322
  }], dataUsesFieldNameKeys: [{
@@ -5687,15 +6339,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
5687
6339
  type: Input
5688
6340
  }], formDataChange: [{
5689
6341
  type: Output
6342
+ }], formDataByPageChange: [{
6343
+ type: Output
5690
6344
  }], formValidationChange: [{
5691
6345
  type: Output
5692
6346
  }], uploadedFilesChange: [{
5693
6347
  type: Output
5694
6348
  }], submit: [{
5695
6349
  type: Output
6350
+ }], activePageIdChange: [{
6351
+ type: Output
5696
6352
  }], renderer: [{
5697
6353
  type: ViewChild,
5698
6354
  args: [JsonFormRendererComponent]
6355
+ }], journeyViewer: [{
6356
+ type: ViewChild,
6357
+ args: [FormJourneyViewerComponent]
5699
6358
  }] } });
5700
6359
 
5701
6360
  class LayerTreeNodeComponent {
@@ -7021,6 +7680,7 @@ class LayoutCanvasComponent {
7021
7680
  get device() { return this.state.activeDevice(); } // 'mobile' | 'desktop' for basic passing
7022
7681
  get breakpoint() { return this.state.activeBreakpoint(); }
7023
7682
  zoom = signal(1);
7683
+ showLayoutGuides = signal(true);
7024
7684
  showLiveSchemaEditor = signal(false);
7025
7685
  liveSchemaEditorText = signal('');
7026
7686
  liveSchemaEditorError = signal('');
@@ -7187,6 +7847,9 @@ class LayoutCanvasComponent {
7187
7847
  }
7188
7848
  this.state.isPreviewMode.set(true);
7189
7849
  }
7850
+ toggleLayoutGuides() {
7851
+ this.showLayoutGuides.update(current => !current);
7852
+ }
7190
7853
  toggleLiveSchemaEditor() {
7191
7854
  if (this.showLiveSchemaEditor()) {
7192
7855
  this.closeLiveSchemaEditor();
@@ -7489,6 +8152,18 @@ class LayoutCanvasComponent {
7489
8152
 
7490
8153
  <div class="w-px h-3.5 bg-border-default mx-0.5"></div>
7491
8154
 
8155
+ <button type="button"
8156
+ class="inline-flex h-7 items-center rounded-[4px] border border-transparent px-2 text-[11px] font-medium transition-colors hover:border-border-default hover:bg-slate-50"
8157
+ [class.bg-slate-100]="!showLayoutGuides()"
8158
+ [attr.aria-pressed]="!showLayoutGuides()"
8159
+ aria-label="Toggle layout guides"
8160
+ title="Toggle row and column outlines"
8161
+ (click)="toggleLayoutGuides(); $event.stopPropagation()">
8162
+ Guides
8163
+ </button>
8164
+
8165
+ <div class="w-px h-3.5 bg-border-default mx-0.5"></div>
8166
+
7492
8167
  <button class="inline-flex h-7 items-center gap-1 rounded-[4px] bg-transparent px-1.5 transition-colors hover:bg-slate-50"
7493
8168
  style="font-family: 'Lato', sans-serif; font-weight: 400; color: #1c2431;"
7494
8169
  (click)="preview(); $event.stopPropagation()" title="Preview Form">
@@ -7534,6 +8209,7 @@ class LayoutCanvasComponent {
7534
8209
  [schema]="schema()"
7535
8210
  mode="design"
7536
8211
  [device]="device"
8212
+ [showLayoutGuides]="showLayoutGuides()"
7537
8213
  [breakpoint]="breakpoint">
7538
8214
  </app-json-form-renderer>
7539
8215
  </div>
@@ -7650,7 +8326,7 @@ class LayoutCanvasComponent {
7650
8326
 
7651
8327
  <!-- Hidden file input for import -->
7652
8328
  <input type="file" #fileInput accept=".json" (change)="onFileSelected($event)" style="display: none;">
7653
- `, 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"] }] });
8329
+ `, 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", "initialFieldLabels", "mode", "device", "showLayoutGuides", "breakpoint", "eventLogger", "eventApis", "eventApiExecutor", "navigateToPage", "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"] }] });
7654
8330
  }
7655
8331
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: LayoutCanvasComponent, decorators: [{
7656
8332
  type: Component,
@@ -7722,6 +8398,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
7722
8398
 
7723
8399
  <div class="w-px h-3.5 bg-border-default mx-0.5"></div>
7724
8400
 
8401
+ <button type="button"
8402
+ class="inline-flex h-7 items-center rounded-[4px] border border-transparent px-2 text-[11px] font-medium transition-colors hover:border-border-default hover:bg-slate-50"
8403
+ [class.bg-slate-100]="!showLayoutGuides()"
8404
+ [attr.aria-pressed]="!showLayoutGuides()"
8405
+ aria-label="Toggle layout guides"
8406
+ title="Toggle row and column outlines"
8407
+ (click)="toggleLayoutGuides(); $event.stopPropagation()">
8408
+ Guides
8409
+ </button>
8410
+
8411
+ <div class="w-px h-3.5 bg-border-default mx-0.5"></div>
8412
+
7725
8413
  <button class="inline-flex h-7 items-center gap-1 rounded-[4px] bg-transparent px-1.5 transition-colors hover:bg-slate-50"
7726
8414
  style="font-family: 'Lato', sans-serif; font-weight: 400; color: #1c2431;"
7727
8415
  (click)="preview(); $event.stopPropagation()" title="Preview Form">
@@ -7767,6 +8455,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
7767
8455
  [schema]="schema()"
7768
8456
  mode="design"
7769
8457
  [device]="device"
8458
+ [showLayoutGuides]="showLayoutGuides()"
7770
8459
  [breakpoint]="breakpoint">
7771
8460
  </app-json-form-renderer>
7772
8461
  </div>
@@ -8678,6 +9367,10 @@ class UiColorSwatchComponent {
8678
9367
  value = '#ffffff';
8679
9368
  valueChange = new EventEmitter();
8680
9369
  blur = new EventEmitter();
9370
+ onPickerChange(color) {
9371
+ this.value = color;
9372
+ this.valueChange.emit(color);
9373
+ }
8681
9374
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: UiColorSwatchComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8682
9375
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: UiColorSwatchComponent, isStandalone: true, selector: "ui-color-swatch", inputs: { label: "label", hint: "hint", helpText: "helpText", value: "value" }, outputs: { valueChange: "valueChange", blur: "blur" }, ngImport: i0, template: `
8683
9376
  <ui-field-wrapper [label]="label" [hint]="hint" [helpText]="helpText">
@@ -8693,18 +9386,28 @@ class UiColorSwatchComponent {
8693
9386
  />
8694
9387
  <lucide-icon name="paintbrush" class="w-4 h-4 absolute right-2 top-1/2 -translate-y-1/2 text-gray-400"></lucide-icon>
8695
9388
  </div>
8696
- <input type="color" class="w-9 h-9 border border-gray-200 rounded cursor-pointer"
8697
- [(ngModel)]="value" (ngModelChange)="valueChange.emit($event)">
9389
+ <button
9390
+ type="button"
9391
+ data-testid="color-swatch-picker-trigger"
9392
+ class="h-9 w-9 shrink-0 cursor-pointer rounded border border-gray-200"
9393
+ [style.background]="value || '#ffffff'"
9394
+ [colorPicker]="value || '#ffffff'"
9395
+ [cpAlphaChannel]="'disabled'"
9396
+ [cpOutputFormat]="'hex'"
9397
+ [cpFallbackColor]="'#ffffff'"
9398
+ (colorPickerChange)="onPickerChange($event)"
9399
+ aria-label="Open color picker">
9400
+ </button>
8698
9401
  </div>
8699
9402
  </ui-field-wrapper>
8700
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: UiFieldWrapperComponent, selector: "ui-field-wrapper", inputs: ["label", "hint", "helpText"] }, { 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"] }] });
9403
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: ColorPickerDirective, selector: "[colorPicker]", inputs: ["colorPicker", "cpWidth", "cpHeight", "cpToggle", "cpDisabled", "cpIgnoredElements", "cpFallbackColor", "cpColorMode", "cpCmykEnabled", "cpOutputFormat", "cpAlphaChannel", "cpDisableInput", "cpDialogDisplay", "cpSaveClickOutside", "cpCloseClickOutside", "cpUseRootViewContainer", "cpPosition", "cpPositionOffset", "cpPositionRelativeToArrow", "cpOKButton", "cpOKButtonText", "cpOKButtonClass", "cpCancelButton", "cpCancelButtonText", "cpCancelButtonClass", "cpEyeDropper", "cpPresetLabel", "cpPresetColors", "cpPresetColorsClass", "cpMaxPresetColorsLength", "cpPresetEmptyMessage", "cpPresetEmptyMessageClass", "cpAddColorButton", "cpAddColorButtonText", "cpAddColorButtonClass", "cpRemoveColorButtonClass", "cpArrowPosition", "cpExtraTemplate"], outputs: ["cpInputChange", "cpToggleChange", "cpSliderChange", "cpSliderDragEnd", "cpSliderDragStart", "colorPickerOpen", "colorPickerClose", "colorPickerCancel", "colorPickerSelect", "colorPickerChange", "cpCmykColorChange", "cpPresetColorsChange"], exportAs: ["ngxColorPicker"] }, { kind: "component", type: UiFieldWrapperComponent, selector: "ui-field-wrapper", inputs: ["label", "hint", "helpText"] }, { 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"] }] });
8701
9404
  }
8702
9405
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: UiColorSwatchComponent, decorators: [{
8703
9406
  type: Component,
8704
9407
  args: [{
8705
9408
  selector: 'ui-color-swatch',
8706
9409
  standalone: true,
8707
- imports: [CommonModule, FormsModule, UiFieldWrapperComponent, UiIconModule],
9410
+ imports: [CommonModule, FormsModule, ColorPickerDirective, UiFieldWrapperComponent, UiIconModule],
8708
9411
  template: `
8709
9412
  <ui-field-wrapper [label]="label" [hint]="hint" [helpText]="helpText">
8710
9413
  <div class="flex items-center gap-2">
@@ -8719,8 +9422,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
8719
9422
  />
8720
9423
  <lucide-icon name="paintbrush" class="w-4 h-4 absolute right-2 top-1/2 -translate-y-1/2 text-gray-400"></lucide-icon>
8721
9424
  </div>
8722
- <input type="color" class="w-9 h-9 border border-gray-200 rounded cursor-pointer"
8723
- [(ngModel)]="value" (ngModelChange)="valueChange.emit($event)">
9425
+ <button
9426
+ type="button"
9427
+ data-testid="color-swatch-picker-trigger"
9428
+ class="h-9 w-9 shrink-0 cursor-pointer rounded border border-gray-200"
9429
+ [style.background]="value || '#ffffff'"
9430
+ [colorPicker]="value || '#ffffff'"
9431
+ [cpAlphaChannel]="'disabled'"
9432
+ [cpOutputFormat]="'hex'"
9433
+ [cpFallbackColor]="'#ffffff'"
9434
+ (colorPickerChange)="onPickerChange($event)"
9435
+ aria-label="Open color picker">
9436
+ </button>
8724
9437
  </div>
8725
9438
  </ui-field-wrapper>
8726
9439
  `
@@ -10884,6 +11597,13 @@ class InspectorTypographySectionComponent {
10884
11597
  const textDecoration = String(this.style()['textDecoration'] || '');
10885
11598
  return textDecoration.includes('underline');
10886
11599
  }
11600
+ colorValue() {
11601
+ const color = this.style()['color'];
11602
+ if (typeof color === 'string' && color.trim().length > 0) {
11603
+ return color;
11604
+ }
11605
+ return '#1a1b1f';
11606
+ }
10887
11607
  updateStyle(key, value) {
10888
11608
  this.styleChange.emit({ ...this.style(), [key]: value });
10889
11609
  }
@@ -10917,14 +11637,21 @@ class InspectorTypographySectionComponent {
10917
11637
  <div class="grid grid-cols-[60px_1fr] gap-2 items-center">
10918
11638
  <label class="text-[12px] text-highlight-orange">Color</label>
10919
11639
  <div class="flex items-center bg-input-dark border border-border-dark rounded px-2 py-1">
10920
- <input
10921
- type="color"
10922
- [ngModel]="style()['color'] || '#1a1b1f'"
10923
- (ngModelChange)="updateStyle('color', $event)"
10924
- class="w-4 h-4 border border-gray-300 rounded-sm mr-2 cursor-pointer">
11640
+ <button
11641
+ type="button"
11642
+ data-testid="typography-color-picker-trigger"
11643
+ class="mr-2 h-4 w-4 cursor-pointer rounded-sm border border-gray-300"
11644
+ [style.background]="colorValue()"
11645
+ [colorPicker]="colorValue()"
11646
+ [cpAlphaChannel]="'disabled'"
11647
+ [cpOutputFormat]="'hex'"
11648
+ [cpFallbackColor]="'#000000'"
11649
+ (colorPickerChange)="updateStyle('color', $event)"
11650
+ aria-label="Typography color picker">
11651
+ </button>
10925
11652
  <input
10926
11653
  type="text"
10927
- [ngModel]="style()['color'] || '#1a1b1f'"
11654
+ [ngModel]="colorValue()"
10928
11655
  (ngModelChange)="updateStyle('color', $event)"
10929
11656
  class="bg-transparent border-none p-0 text-sm text-gray-700 focus:ring-0 w-full">
10930
11657
  </div>
@@ -11010,11 +11737,11 @@ class InspectorTypographySectionComponent {
11010
11737
  </div>
11011
11738
  </div>
11012
11739
  </div>
11013
- `, isInline: true, styles: [".inspector-select{height:2rem;padding-left:.5rem;padding-right:.5rem;font-size:.875rem;line-height:1.25rem;background-color:var(--color-input-dark);border:1px solid var(--color-border-dark);border-radius:var(--radius-md);color:var(--color-ink-700);appearance:none;cursor:pointer}.inspector-select:focus{outline:none;border-color:var(--color-primary-blue)}.inspector-input-with-unit{display:flex;align-items:center;background-color:var(--color-input-dark);border:1px solid var(--color-border-dark);border-radius:var(--radius-md);overflow:hidden}.inspector-number-input{height:2rem;padding-left:.5rem;padding-right:.5rem;background-color:transparent;border:none;font-size:.875rem;line-height:1.25rem;color:var(--color-ink-700)}.inspector-number-input:focus{outline:none}.inspector-unit{font-size:11px;color:var(--color-ink-400);padding-left:.25rem;padding-right:.25rem;background-color:var(--color-input-dark);height:2rem;display:flex;align-items:center;border-left:1px solid var(--color-border-dark)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { 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"] }] });
11740
+ `, isInline: true, styles: [".inspector-select{height:2rem;padding-left:.5rem;padding-right:.5rem;font-size:.875rem;line-height:1.25rem;background-color:var(--color-input-dark);border:1px solid var(--color-border-dark);border-radius:var(--radius-md);color:var(--color-ink-700);appearance:none;cursor:pointer}.inspector-select:focus{outline:none;border-color:var(--color-primary-blue)}.inspector-input-with-unit{display:flex;align-items:center;background-color:var(--color-input-dark);border:1px solid var(--color-border-dark);border-radius:var(--radius-md);overflow:hidden}.inspector-number-input{height:2rem;padding-left:.5rem;padding-right:.5rem;background-color:transparent;border:none;font-size:.875rem;line-height:1.25rem;color:var(--color-ink-700)}.inspector-number-input:focus{outline:none}.inspector-unit{font-size:11px;color:var(--color-ink-400);padding-left:.25rem;padding-right:.25rem;background-color:var(--color-input-dark);height:2rem;display:flex;align-items:center;border-left:1px solid var(--color-border-dark)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: ColorPickerDirective, selector: "[colorPicker]", inputs: ["colorPicker", "cpWidth", "cpHeight", "cpToggle", "cpDisabled", "cpIgnoredElements", "cpFallbackColor", "cpColorMode", "cpCmykEnabled", "cpOutputFormat", "cpAlphaChannel", "cpDisableInput", "cpDialogDisplay", "cpSaveClickOutside", "cpCloseClickOutside", "cpUseRootViewContainer", "cpPosition", "cpPositionOffset", "cpPositionRelativeToArrow", "cpOKButton", "cpOKButtonText", "cpOKButtonClass", "cpCancelButton", "cpCancelButtonText", "cpCancelButtonClass", "cpEyeDropper", "cpPresetLabel", "cpPresetColors", "cpPresetColorsClass", "cpMaxPresetColorsLength", "cpPresetEmptyMessage", "cpPresetEmptyMessageClass", "cpAddColorButton", "cpAddColorButtonText", "cpAddColorButtonClass", "cpRemoveColorButtonClass", "cpArrowPosition", "cpExtraTemplate"], outputs: ["cpInputChange", "cpToggleChange", "cpSliderChange", "cpSliderDragEnd", "cpSliderDragStart", "colorPickerOpen", "colorPickerClose", "colorPickerCancel", "colorPickerSelect", "colorPickerChange", "cpCmykColorChange", "cpPresetColorsChange"], exportAs: ["ngxColorPicker"] }, { 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"] }] });
11014
11741
  }
11015
11742
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: InspectorTypographySectionComponent, decorators: [{
11016
11743
  type: Component,
11017
- args: [{ selector: 'inspector-typography-section', standalone: true, imports: [CommonModule, FormsModule, UiIconModule], template: `
11744
+ args: [{ selector: 'inspector-typography-section', standalone: true, imports: [CommonModule, FormsModule, ColorPickerDirective, UiIconModule], template: `
11018
11745
  <div class="flex flex-col gap-3">
11019
11746
  <div class="grid grid-cols-[60px_1fr] gap-2 items-center">
11020
11747
  <label class="text-[12px] text-highlight-orange">Family</label>
@@ -11043,14 +11770,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
11043
11770
  <div class="grid grid-cols-[60px_1fr] gap-2 items-center">
11044
11771
  <label class="text-[12px] text-highlight-orange">Color</label>
11045
11772
  <div class="flex items-center bg-input-dark border border-border-dark rounded px-2 py-1">
11046
- <input
11047
- type="color"
11048
- [ngModel]="style()['color'] || '#1a1b1f'"
11049
- (ngModelChange)="updateStyle('color', $event)"
11050
- class="w-4 h-4 border border-gray-300 rounded-sm mr-2 cursor-pointer">
11773
+ <button
11774
+ type="button"
11775
+ data-testid="typography-color-picker-trigger"
11776
+ class="mr-2 h-4 w-4 cursor-pointer rounded-sm border border-gray-300"
11777
+ [style.background]="colorValue()"
11778
+ [colorPicker]="colorValue()"
11779
+ [cpAlphaChannel]="'disabled'"
11780
+ [cpOutputFormat]="'hex'"
11781
+ [cpFallbackColor]="'#000000'"
11782
+ (colorPickerChange)="updateStyle('color', $event)"
11783
+ aria-label="Typography color picker">
11784
+ </button>
11051
11785
  <input
11052
11786
  type="text"
11053
- [ngModel]="style()['color'] || '#1a1b1f'"
11787
+ [ngModel]="colorValue()"
11054
11788
  (ngModelChange)="updateStyle('color', $event)"
11055
11789
  class="bg-transparent border-none p-0 text-sm text-gray-700 focus:ring-0 w-full">
11056
11790
  </div>
@@ -11413,12 +12147,19 @@ class InspectorBordersSectionComponent {
11413
12147
  <div class="grid grid-cols-[52px_1fr] gap-2 items-center">
11414
12148
  <label class="section-label text-[12px]" for="inspector-border-color">Color</label>
11415
12149
  <div class="flex min-w-0 items-center gap-2 rounded-md border border-border-dark bg-input-dark px-2 py-1">
11416
- <input
12150
+ <button
11417
12151
  id="inspector-border-color"
11418
- type="color"
11419
- [ngModel]="getColorInputValue()"
11420
- (ngModelChange)="updateStyle('borderColor', $event)"
11421
- class="h-7 w-7 shrink-0 cursor-pointer rounded border border-border-dark bg-white">
12152
+ type="button"
12153
+ data-testid="border-color-picker-trigger"
12154
+ class="h-7 w-7 shrink-0 cursor-pointer rounded border border-border-dark bg-white"
12155
+ [style.background]="getColorInputValue()"
12156
+ [colorPicker]="getColorInputValue()"
12157
+ [cpAlphaChannel]="'disabled'"
12158
+ [cpOutputFormat]="'hex'"
12159
+ [cpFallbackColor]="'#000000'"
12160
+ (colorPickerChange)="updateStyle('borderColor', $event)"
12161
+ aria-label="Border color picker">
12162
+ </button>
11422
12163
 
11423
12164
  <input
11424
12165
  data-testid="border-color-input"
@@ -11433,11 +12174,11 @@ class InspectorBordersSectionComponent {
11433
12174
  </div>
11434
12175
  </div>
11435
12176
  </div>
11436
- `, isInline: true, styles: [".section-label{font-size:12px;color:var(--color-ink-400)}.inspector-input-with-unit{display:flex;align-items:center;min-width:0;background-color:var(--color-input-dark);border:1px solid var(--color-border-dark);border-radius:var(--radius-md);overflow:hidden}.inspector-number-input{width:100%;min-width:0;height:2rem;padding-left:.5rem;padding-right:.5rem;background-color:transparent;border:none;color:var(--color-ink-700);font-size:.875rem;line-height:1.25rem}.inspector-number-input:focus{outline:none}.inspector-unit{display:flex;align-items:center;height:2rem;padding-left:.5rem;padding-right:.5rem;border-left:1px solid var(--color-border-dark);font-size:11px;color:var(--color-ink-400);background-color:var(--color-input-dark)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.RangeValueAccessor, selector: "input[type=range][formControlName],input[type=range][formControl],input[type=range][ngModel]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
12177
+ `, isInline: true, styles: [".section-label{font-size:12px;color:var(--color-ink-400)}.inspector-input-with-unit{display:flex;align-items:center;min-width:0;background-color:var(--color-input-dark);border:1px solid var(--color-border-dark);border-radius:var(--radius-md);overflow:hidden}.inspector-number-input{width:100%;min-width:0;height:2rem;padding-left:.5rem;padding-right:.5rem;background-color:transparent;border:none;color:var(--color-ink-700);font-size:.875rem;line-height:1.25rem}.inspector-number-input:focus{outline:none}.inspector-unit{display:flex;align-items:center;height:2rem;padding-left:.5rem;padding-right:.5rem;border-left:1px solid var(--color-border-dark);font-size:11px;color:var(--color-ink-400);background-color:var(--color-input-dark)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.RangeValueAccessor, selector: "input[type=range][formControlName],input[type=range][formControl],input[type=range][ngModel]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: ColorPickerDirective, selector: "[colorPicker]", inputs: ["colorPicker", "cpWidth", "cpHeight", "cpToggle", "cpDisabled", "cpIgnoredElements", "cpFallbackColor", "cpColorMode", "cpCmykEnabled", "cpOutputFormat", "cpAlphaChannel", "cpDisableInput", "cpDialogDisplay", "cpSaveClickOutside", "cpCloseClickOutside", "cpUseRootViewContainer", "cpPosition", "cpPositionOffset", "cpPositionRelativeToArrow", "cpOKButton", "cpOKButtonText", "cpOKButtonClass", "cpCancelButton", "cpCancelButtonText", "cpCancelButtonClass", "cpEyeDropper", "cpPresetLabel", "cpPresetColors", "cpPresetColorsClass", "cpMaxPresetColorsLength", "cpPresetEmptyMessage", "cpPresetEmptyMessageClass", "cpAddColorButton", "cpAddColorButtonText", "cpAddColorButtonClass", "cpRemoveColorButtonClass", "cpArrowPosition", "cpExtraTemplate"], outputs: ["cpInputChange", "cpToggleChange", "cpSliderChange", "cpSliderDragEnd", "cpSliderDragStart", "colorPickerOpen", "colorPickerClose", "colorPickerCancel", "colorPickerSelect", "colorPickerChange", "cpCmykColorChange", "cpPresetColorsChange"], exportAs: ["ngxColorPicker"] }] });
11437
12178
  }
11438
12179
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: InspectorBordersSectionComponent, decorators: [{
11439
12180
  type: Component,
11440
- args: [{ selector: 'inspector-borders-section', standalone: true, imports: [CommonModule, FormsModule], template: `
12181
+ args: [{ selector: 'inspector-borders-section', standalone: true, imports: [CommonModule, FormsModule, ColorPickerDirective], template: `
11441
12182
  <div class="flex flex-col gap-4">
11442
12183
  <div class="grid grid-cols-[56px_1fr] gap-3 items-start">
11443
12184
  <span class="section-label text-[12px] pt-2">Radius</span>
@@ -11530,12 +12271,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
11530
12271
  <div class="grid grid-cols-[52px_1fr] gap-2 items-center">
11531
12272
  <label class="section-label text-[12px]" for="inspector-border-color">Color</label>
11532
12273
  <div class="flex min-w-0 items-center gap-2 rounded-md border border-border-dark bg-input-dark px-2 py-1">
11533
- <input
12274
+ <button
11534
12275
  id="inspector-border-color"
11535
- type="color"
11536
- [ngModel]="getColorInputValue()"
11537
- (ngModelChange)="updateStyle('borderColor', $event)"
11538
- class="h-7 w-7 shrink-0 cursor-pointer rounded border border-border-dark bg-white">
12276
+ type="button"
12277
+ data-testid="border-color-picker-trigger"
12278
+ class="h-7 w-7 shrink-0 cursor-pointer rounded border border-border-dark bg-white"
12279
+ [style.background]="getColorInputValue()"
12280
+ [colorPicker]="getColorInputValue()"
12281
+ [cpAlphaChannel]="'disabled'"
12282
+ [cpOutputFormat]="'hex'"
12283
+ [cpFallbackColor]="'#000000'"
12284
+ (colorPickerChange)="updateStyle('borderColor', $event)"
12285
+ aria-label="Border color picker">
12286
+ </button>
11539
12287
 
11540
12288
  <input
11541
12289
  data-testid="border-color-input"
@@ -12066,19 +12814,57 @@ class InspectorBackgroundsSectionComponent {
12066
12814
  { label: 'Right', value: 'right' }
12067
12815
  ];
12068
12816
  showImageUrl = false;
12817
+ backgroundColorValue() {
12818
+ return this.normalizeBackgroundColor(this.style()['backgroundColor']);
12819
+ }
12069
12820
  getBackgroundPreview() {
12070
- const bg = this.style()['backgroundColor'];
12071
- if (!bg || bg === 'transparent') {
12072
- return `linear-gradient(45deg, #ccc 25%, transparent 25%),
12073
- linear-gradient(-45deg, #ccc 25%, transparent 25%),
12074
- linear-gradient(45deg, transparent 75%, #ccc 75%),
12075
- linear-gradient(-45deg, transparent 75%, #ccc 75%)`;
12076
- }
12077
- return bg;
12821
+ return this.backgroundColorValue();
12822
+ }
12823
+ onBackgroundPickerChange(value) {
12824
+ this.updateStyle('backgroundColor', this.normalizeBackgroundColor(value));
12825
+ }
12826
+ onBackgroundColorInputChange(value) {
12827
+ this.updateStyle('backgroundColor', this.normalizeBackgroundColor(value));
12078
12828
  }
12079
12829
  updateStyle(key, value) {
12080
12830
  this.styleChange.emit({ ...this.style(), [key]: value });
12081
12831
  }
12832
+ normalizeBackgroundColor(value) {
12833
+ if (typeof value !== 'string')
12834
+ return '#ffffff';
12835
+ const trimmed = value.trim();
12836
+ if (!trimmed)
12837
+ return '#ffffff';
12838
+ if (this.isTransparentColor(trimmed))
12839
+ return '#ffffff';
12840
+ return trimmed;
12841
+ }
12842
+ isTransparentColor(value) {
12843
+ const compact = value.replace(/\s+/g, '').toLowerCase();
12844
+ if (compact === 'transparent')
12845
+ return true;
12846
+ if (compact.startsWith('rgba(') && compact.endsWith(')')) {
12847
+ const parts = compact.slice(5, -1).split(',');
12848
+ if (parts.length === 4) {
12849
+ const alpha = Number.parseFloat(parts[3]);
12850
+ return Number.isFinite(alpha) && alpha === 0;
12851
+ }
12852
+ }
12853
+ if (compact.startsWith('hsla(') && compact.endsWith(')')) {
12854
+ const parts = compact.slice(5, -1).split(',');
12855
+ if (parts.length === 4) {
12856
+ const alpha = Number.parseFloat(parts[3]);
12857
+ return Number.isFinite(alpha) && alpha === 0;
12858
+ }
12859
+ }
12860
+ if (/^#[0-9a-f]{4}$/i.test(compact)) {
12861
+ return compact[4] === '0';
12862
+ }
12863
+ if (/^#[0-9a-f]{8}$/i.test(compact)) {
12864
+ return compact.endsWith('00');
12865
+ }
12866
+ return false;
12867
+ }
12082
12868
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: InspectorBackgroundsSectionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
12083
12869
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: InspectorBackgroundsSectionComponent, isStandalone: true, selector: "inspector-backgrounds-section", inputs: { style: { classPropertyName: "style", publicName: "style", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { styleChange: "styleChange" }, ngImport: i0, template: `
12084
12870
  <div class="flex flex-col gap-3">
@@ -12092,13 +12878,22 @@ class InspectorBackgroundsSectionComponent {
12092
12878
  <div class="grid grid-cols-[60px_1fr] gap-2 items-center">
12093
12879
  <label class="text-[11px] text-gray-500">Color</label>
12094
12880
  <div class="flex items-center bg-input-dark border border-border-dark rounded px-2 py-1">
12095
- <div class="w-4 h-4 rounded-sm mr-2 border border-gray-300 overflow-hidden"
12096
- [style.background]="getBackgroundPreview()">
12097
- </div>
12881
+ <button
12882
+ type="button"
12883
+ data-testid="background-color-picker-trigger"
12884
+ class="mr-2 h-4 w-4 cursor-pointer rounded-sm border border-gray-300 overflow-hidden"
12885
+ [style.background]="getBackgroundPreview()"
12886
+ [colorPicker]="backgroundColorValue()"
12887
+ [cpAlphaChannel]="'disabled'"
12888
+ [cpOutputFormat]="'hex'"
12889
+ [cpFallbackColor]="'#ffffff'"
12890
+ (colorPickerChange)="onBackgroundPickerChange($event)"
12891
+ aria-label="Background color picker">
12892
+ </button>
12098
12893
  <input
12099
12894
  type="text"
12100
- [ngModel]="style()['backgroundColor'] || 'transparent'"
12101
- (ngModelChange)="updateStyle('backgroundColor', $event)"
12895
+ [ngModel]="backgroundColorValue()"
12896
+ (ngModelChange)="onBackgroundColorInputChange($event)"
12102
12897
  class="bg-transparent border-none p-0 text-xs text-gray-700 focus:ring-0 w-full">
12103
12898
  </div>
12104
12899
  </div>
@@ -12135,11 +12930,11 @@ class InspectorBackgroundsSectionComponent {
12135
12930
  </div>
12136
12931
  }
12137
12932
  </div>
12138
- `, isInline: true, styles: [".inspector-input{height:1.75rem;padding-left:.5rem;padding-right:.5rem;font-size:.75rem;line-height:1rem;background-color:var(--color-input-dark);border:1px solid var(--color-border-dark);border-radius:var(--radius-md);color:var(--color-ink-700)}.inspector-input:focus{outline:none;border-color:var(--color-primary-blue)}.inspector-select{height:1.75rem;padding-left:.5rem;padding-right:.5rem;font-size:.75rem;line-height:1rem;background-color:var(--color-input-dark);border:1px solid var(--color-border-dark);border-radius:var(--radius-md);color:var(--color-ink-700);appearance:none;cursor:pointer}.inspector-select:focus{outline:none;border-color:var(--color-primary-blue)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { 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"] }] });
12933
+ `, isInline: true, styles: [".inspector-input{height:1.75rem;padding-left:.5rem;padding-right:.5rem;font-size:.75rem;line-height:1rem;background-color:var(--color-input-dark);border:1px solid var(--color-border-dark);border-radius:var(--radius-md);color:var(--color-ink-700)}.inspector-input:focus{outline:none;border-color:var(--color-primary-blue)}.inspector-select{height:1.75rem;padding-left:.5rem;padding-right:.5rem;font-size:.75rem;line-height:1rem;background-color:var(--color-input-dark);border:1px solid var(--color-border-dark);border-radius:var(--radius-md);color:var(--color-ink-700);appearance:none;cursor:pointer}.inspector-select:focus{outline:none;border-color:var(--color-primary-blue)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: ColorPickerDirective, selector: "[colorPicker]", inputs: ["colorPicker", "cpWidth", "cpHeight", "cpToggle", "cpDisabled", "cpIgnoredElements", "cpFallbackColor", "cpColorMode", "cpCmykEnabled", "cpOutputFormat", "cpAlphaChannel", "cpDisableInput", "cpDialogDisplay", "cpSaveClickOutside", "cpCloseClickOutside", "cpUseRootViewContainer", "cpPosition", "cpPositionOffset", "cpPositionRelativeToArrow", "cpOKButton", "cpOKButtonText", "cpOKButtonClass", "cpCancelButton", "cpCancelButtonText", "cpCancelButtonClass", "cpEyeDropper", "cpPresetLabel", "cpPresetColors", "cpPresetColorsClass", "cpMaxPresetColorsLength", "cpPresetEmptyMessage", "cpPresetEmptyMessageClass", "cpAddColorButton", "cpAddColorButtonText", "cpAddColorButtonClass", "cpRemoveColorButtonClass", "cpArrowPosition", "cpExtraTemplate"], outputs: ["cpInputChange", "cpToggleChange", "cpSliderChange", "cpSliderDragEnd", "cpSliderDragStart", "colorPickerOpen", "colorPickerClose", "colorPickerCancel", "colorPickerSelect", "colorPickerChange", "cpCmykColorChange", "cpPresetColorsChange"], exportAs: ["ngxColorPicker"] }, { 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"] }] });
12139
12934
  }
12140
12935
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: InspectorBackgroundsSectionComponent, decorators: [{
12141
12936
  type: Component,
12142
- args: [{ selector: 'inspector-backgrounds-section', standalone: true, imports: [CommonModule, FormsModule, UiIconModule], template: `
12937
+ args: [{ selector: 'inspector-backgrounds-section', standalone: true, imports: [CommonModule, FormsModule, ColorPickerDirective, UiIconModule], template: `
12143
12938
  <div class="flex flex-col gap-3">
12144
12939
  <!-- Image & Gradient -->
12145
12940
  <div class="flex justify-between items-center group cursor-pointer py-1">
@@ -12151,13 +12946,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
12151
12946
  <div class="grid grid-cols-[60px_1fr] gap-2 items-center">
12152
12947
  <label class="text-[11px] text-gray-500">Color</label>
12153
12948
  <div class="flex items-center bg-input-dark border border-border-dark rounded px-2 py-1">
12154
- <div class="w-4 h-4 rounded-sm mr-2 border border-gray-300 overflow-hidden"
12155
- [style.background]="getBackgroundPreview()">
12156
- </div>
12949
+ <button
12950
+ type="button"
12951
+ data-testid="background-color-picker-trigger"
12952
+ class="mr-2 h-4 w-4 cursor-pointer rounded-sm border border-gray-300 overflow-hidden"
12953
+ [style.background]="getBackgroundPreview()"
12954
+ [colorPicker]="backgroundColorValue()"
12955
+ [cpAlphaChannel]="'disabled'"
12956
+ [cpOutputFormat]="'hex'"
12957
+ [cpFallbackColor]="'#ffffff'"
12958
+ (colorPickerChange)="onBackgroundPickerChange($event)"
12959
+ aria-label="Background color picker">
12960
+ </button>
12157
12961
  <input
12158
12962
  type="text"
12159
- [ngModel]="style()['backgroundColor'] || 'transparent'"
12160
- (ngModelChange)="updateStyle('backgroundColor', $event)"
12963
+ [ngModel]="backgroundColorValue()"
12964
+ (ngModelChange)="onBackgroundColorInputChange($event)"
12161
12965
  class="bg-transparent border-none p-0 text-xs text-gray-700 focus:ring-0 w-full">
12162
12966
  </div>
12163
12967
  </div>
@@ -18135,6 +18939,9 @@ class FormPreviewComponent {
18135
18939
  fieldDataAccessApi;
18136
18940
  formContentId;
18137
18941
  formContentVersion;
18942
+ pages = [];
18943
+ activePageId = null;
18944
+ pageSelect = new EventEmitter();
18138
18945
  breakpoints = ['xs', 'sm', 'md', 'lg', 'xl', '2xl'];
18139
18946
  breakpoint = signal('xl');
18140
18947
  // Derived device type for renderer compatibility
@@ -18149,6 +18956,25 @@ class FormPreviewComponent {
18149
18956
  setBreakpoint(bp) {
18150
18957
  this.breakpoint.set(bp);
18151
18958
  }
18959
+ hasJourneyPages() {
18960
+ return this.pages.length > 1;
18961
+ }
18962
+ isPageActive(pageId) {
18963
+ return this.resolveActivePageId() === pageId;
18964
+ }
18965
+ requestPageSelect(pageId) {
18966
+ if (this.resolveActivePageId() === pageId)
18967
+ return;
18968
+ this.pageSelect.emit(pageId);
18969
+ }
18970
+ navigateToPage = async (pageId) => {
18971
+ const targetPageId = pageId?.trim();
18972
+ if (!targetPageId || !this.pages.some(page => page.id === targetPageId)) {
18973
+ return { ok: false, reason: 'unknown-page' };
18974
+ }
18975
+ this.requestPageSelect(targetPageId);
18976
+ return { ok: true };
18977
+ };
18152
18978
  getContainerWidth() {
18153
18979
  const bp = this.breakpoint();
18154
18980
  switch (bp) {
@@ -18237,8 +19063,16 @@ class FormPreviewComponent {
18237
19063
  return false;
18238
19064
  return /^[A-Za-z0-9+/]+={0,2}$/.test(value);
18239
19065
  }
19066
+ trackPageById(_, page) {
19067
+ return page.id;
19068
+ }
19069
+ resolveActivePageId() {
19070
+ if (this.activePageId)
19071
+ return this.activePageId;
19072
+ return this.pages[0]?.id ?? '';
19073
+ }
18240
19074
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: FormPreviewComponent, deps: [{ token: DesignerStateService }], target: i0.ɵɵFactoryTarget.Component });
18241
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: FormPreviewComponent, isStandalone: true, selector: "app-form-preview", inputs: { eventApis: "eventApis", eventApiExecutor: "eventApiExecutor", fieldDataAccessMap: "fieldDataAccessMap", fieldDataAccessApi: "fieldDataAccessApi", formContentId: "formContentId", formContentVersion: "formContentVersion" }, ngImport: i0, template: `
19075
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: FormPreviewComponent, isStandalone: true, selector: "app-form-preview", inputs: { eventApis: "eventApis", eventApiExecutor: "eventApiExecutor", fieldDataAccessMap: "fieldDataAccessMap", fieldDataAccessApi: "fieldDataAccessApi", formContentId: "formContentId", formContentVersion: "formContentVersion", pages: "pages", activePageId: "activePageId" }, outputs: { pageSelect: "pageSelect" }, ngImport: i0, template: `
18242
19076
  <div class="fixed inset-0 bg-white z-[100] flex flex-col animate-in fade-in duration-200">
18243
19077
  <!-- Toolbar -->
18244
19078
  <div class="h-14 border-b border-gray-200 flex items-center justify-between px-4 bg-white shadow-sm shrink-0">
@@ -18269,6 +19103,28 @@ class FormPreviewComponent {
18269
19103
 
18270
19104
  <!-- Content Area -->
18271
19105
  <div class="flex-1 flex overflow-hidden bg-gray-50">
19106
+ <aside *ngIf="hasJourneyPages()" class="w-64 shrink-0 border-r border-gray-200 bg-white flex flex-col">
19107
+ <div class="px-4 py-3 border-b border-gray-200">
19108
+ <div class="text-xs font-semibold uppercase tracking-wide text-gray-500">Journey</div>
19109
+ <div class="mt-1 text-xs text-gray-500">Switch preview page</div>
19110
+ </div>
19111
+ <div class="flex-1 overflow-y-auto p-2">
19112
+ <button
19113
+ *ngFor="let page of pages; trackBy: trackPageById"
19114
+ type="button"
19115
+ class="mb-2 w-full rounded-md border px-3 py-2 text-left transition-colors"
19116
+ [class.border-blue-200]="isPageActive(page.id)"
19117
+ [class.bg-blue-50]="isPageActive(page.id)"
19118
+ [class.text-blue-700]="isPageActive(page.id)"
19119
+ [class.border-gray-200]="!isPageActive(page.id)"
19120
+ [class.bg-white]="!isPageActive(page.id)"
19121
+ [class.text-gray-700]="!isPageActive(page.id)"
19122
+ (click)="requestPageSelect(page.id)">
19123
+ <div class="text-xs font-semibold truncate">{{ page.name }}</div>
19124
+ <div class="mt-0.5 text-[10px] text-gray-500 truncate">{{ page.route }}</div>
19125
+ </button>
19126
+ </div>
19127
+ </aside>
18272
19128
 
18273
19129
  <!-- LEFT: Form Render Area -->
18274
19130
  <div class="flex-1 overflow-auto flex justify-center items-start p-8 relative">
@@ -18290,6 +19146,7 @@ class FormPreviewComponent {
18290
19146
  [breakpoint]="breakpoint()"
18291
19147
  [eventApis]="eventApis"
18292
19148
  [eventApiExecutor]="eventApiExecutor"
19149
+ [navigateToPage]="navigateToPage"
18293
19150
  [fieldDataAccessMap]="fieldDataAccessMap"
18294
19151
  [fieldDataAccessApi]="fieldDataAccessApi"
18295
19152
  [formContentId]="formContentId"
@@ -18318,7 +19175,7 @@ class FormPreviewComponent {
18318
19175
  </div>
18319
19176
  </div>
18320
19177
  </div>
18321
- `, 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: "pipe", type: i1.JsonPipe, name: "json" }, { 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: 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"] }] });
19178
+ `, 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: "pipe", type: i1.JsonPipe, name: "json" }, { 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: JsonFormRendererComponent, selector: "app-json-form-renderer", inputs: ["schema", "initialValues", "initialFieldLabels", "mode", "device", "showLayoutGuides", "breakpoint", "eventLogger", "eventApis", "eventApiExecutor", "navigateToPage", "uploadOnSubmit", "fieldDataAccessMap", "fieldDataAccessApi", "formContentId", "formContentVersion"], outputs: ["valueChange", "groupedValueChange", "combinedValueChange", "validationChange", "uploadedFilesChange", "formSubmit"] }] });
18322
19179
  }
18323
19180
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: FormPreviewComponent, decorators: [{
18324
19181
  type: Component,
@@ -18357,6 +19214,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
18357
19214
 
18358
19215
  <!-- Content Area -->
18359
19216
  <div class="flex-1 flex overflow-hidden bg-gray-50">
19217
+ <aside *ngIf="hasJourneyPages()" class="w-64 shrink-0 border-r border-gray-200 bg-white flex flex-col">
19218
+ <div class="px-4 py-3 border-b border-gray-200">
19219
+ <div class="text-xs font-semibold uppercase tracking-wide text-gray-500">Journey</div>
19220
+ <div class="mt-1 text-xs text-gray-500">Switch preview page</div>
19221
+ </div>
19222
+ <div class="flex-1 overflow-y-auto p-2">
19223
+ <button
19224
+ *ngFor="let page of pages; trackBy: trackPageById"
19225
+ type="button"
19226
+ class="mb-2 w-full rounded-md border px-3 py-2 text-left transition-colors"
19227
+ [class.border-blue-200]="isPageActive(page.id)"
19228
+ [class.bg-blue-50]="isPageActive(page.id)"
19229
+ [class.text-blue-700]="isPageActive(page.id)"
19230
+ [class.border-gray-200]="!isPageActive(page.id)"
19231
+ [class.bg-white]="!isPageActive(page.id)"
19232
+ [class.text-gray-700]="!isPageActive(page.id)"
19233
+ (click)="requestPageSelect(page.id)">
19234
+ <div class="text-xs font-semibold truncate">{{ page.name }}</div>
19235
+ <div class="mt-0.5 text-[10px] text-gray-500 truncate">{{ page.route }}</div>
19236
+ </button>
19237
+ </div>
19238
+ </aside>
18360
19239
 
18361
19240
  <!-- LEFT: Form Render Area -->
18362
19241
  <div class="flex-1 overflow-auto flex justify-center items-start p-8 relative">
@@ -18378,6 +19257,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
18378
19257
  [breakpoint]="breakpoint()"
18379
19258
  [eventApis]="eventApis"
18380
19259
  [eventApiExecutor]="eventApiExecutor"
19260
+ [navigateToPage]="navigateToPage"
18381
19261
  [fieldDataAccessMap]="fieldDataAccessMap"
18382
19262
  [fieldDataAccessApi]="fieldDataAccessApi"
18383
19263
  [formContentId]="formContentId"
@@ -18420,6 +19300,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
18420
19300
  type: Input
18421
19301
  }], formContentVersion: [{
18422
19302
  type: Input
19303
+ }], pages: [{
19304
+ type: Input
19305
+ }], activePageId: [{
19306
+ type: Input
19307
+ }], pageSelect: [{
19308
+ type: Output
18423
19309
  }] } });
18424
19310
 
18425
19311
  class JsonFormDesignerComponent {
@@ -18427,6 +19313,14 @@ class JsonFormDesignerComponent {
18427
19313
  mode = 'edit';
18428
19314
  eventApis = [];
18429
19315
  eventApiExecutor;
19316
+ pages = null;
19317
+ activePageId = null;
19318
+ canRemovePage = false;
19319
+ pageAdd = new EventEmitter();
19320
+ pageSelect = new EventEmitter();
19321
+ pageRemove = new EventEmitter();
19322
+ pageRename = new EventEmitter();
19323
+ pageRouteChange = new EventEmitter();
18430
19324
  state = inject(DesignerStateService);
18431
19325
  ngOnInit() {
18432
19326
  this.state.setFlavor(this.flavor);
@@ -18446,7 +19340,7 @@ class JsonFormDesignerComponent {
18446
19340
  }
18447
19341
  }
18448
19342
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: JsonFormDesignerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
18449
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: JsonFormDesignerComponent, isStandalone: true, selector: "app-json-form-designer", inputs: { flavor: "flavor", mode: "mode", eventApis: "eventApis", eventApiExecutor: "eventApiExecutor" }, host: { classAttribute: "fd-designer-host block h-full w-full" }, usesOnChanges: true, ngImport: i0, template: `
19343
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: JsonFormDesignerComponent, isStandalone: true, selector: "app-json-form-designer", inputs: { flavor: "flavor", mode: "mode", eventApis: "eventApis", eventApiExecutor: "eventApiExecutor", pages: "pages", activePageId: "activePageId", canRemovePage: "canRemovePage" }, outputs: { pageAdd: "pageAdd", pageSelect: "pageSelect", pageRemove: "pageRemove", pageRename: "pageRename", pageRouteChange: "pageRouteChange" }, host: { classAttribute: "fd-designer-host block h-full w-full" }, usesOnChanges: true, ngImport: i0, template: `
18450
19344
  <div
18451
19345
  class="fd-designer flex h-full w-full overflow-hidden relative"
18452
19346
  data-fd="designer"
@@ -18459,7 +19353,16 @@ class JsonFormDesignerComponent {
18459
19353
  [style.opacity]="state.isLeftPanelCollapsed() ? '0' : '1'"
18460
19354
  [class.border-none]="state.isLeftPanelCollapsed()">
18461
19355
  <div class="h-full" [class.pointer-events-none]="state.isLeftPanelCollapsed()">
18462
- <app-field-palette></app-field-palette>
19356
+ <app-field-palette
19357
+ [pages]="pages"
19358
+ [activePageId]="activePageId"
19359
+ [canRemovePage]="canRemovePage"
19360
+ (pageAdd)="pageAdd.emit()"
19361
+ (pageSelect)="pageSelect.emit($event)"
19362
+ (pageRemove)="pageRemove.emit($event)"
19363
+ (pageRename)="pageRename.emit($event)"
19364
+ (pageRouteChange)="pageRouteChange.emit($event)">
19365
+ </app-field-palette>
18463
19366
  </div>
18464
19367
  </div>
18465
19368
 
@@ -18479,11 +19382,14 @@ class JsonFormDesignerComponent {
18479
19382
  class="fd-designer-preview"
18480
19383
  data-fd-region="preview"
18481
19384
  *ngIf="state.isPreviewMode()"
19385
+ [pages]="pages ?? []"
19386
+ [activePageId]="activePageId"
18482
19387
  [eventApis]="eventApis"
18483
- [eventApiExecutor]="eventApiExecutor">
19388
+ [eventApiExecutor]="eventApiExecutor"
19389
+ (pageSelect)="pageSelect.emit($event)">
18484
19390
  </app-form-preview>
18485
19391
  </div>
18486
- `, isInline: true, styles: [".custom-scrollbar::-webkit-scrollbar{width:4px;height:4px}.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.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i3.CdkDropListGroup, selector: "[cdkDropListGroup]", inputs: ["cdkDropListGroupDisabled"], exportAs: ["cdkDropListGroup"] }, { kind: "component", type: FieldPaletteComponent, selector: "app-field-palette", inputs: ["sectionLibrary", "pages", "activePageId", "canRemovePage", "savedSections", "bricks"], outputs: ["sectionInsert", "pageAdd", "pageSelect", "pageRemove", "pageRename", "pageRouteChange", "savedSectionInsert", "savedSectionRemove", "savedSectionCreateRequested"] }, { kind: "component", type: LayoutCanvasComponent, selector: "app-layout-canvas", inputs: ["previewMode"], outputs: ["previewRequested"] }, { kind: "component", type: PropertiesPanelComponent, selector: "app-properties-panel" }, { kind: "component", type: FormPreviewComponent, selector: "app-form-preview", inputs: ["eventApis", "eventApiExecutor", "fieldDataAccessMap", "fieldDataAccessApi", "formContentId", "formContentVersion"] }] });
19392
+ `, isInline: true, styles: [".custom-scrollbar::-webkit-scrollbar{width:4px;height:4px}.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.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i3.CdkDropListGroup, selector: "[cdkDropListGroup]", inputs: ["cdkDropListGroupDisabled"], exportAs: ["cdkDropListGroup"] }, { kind: "component", type: FieldPaletteComponent, selector: "app-field-palette", inputs: ["sectionLibrary", "pages", "activePageId", "canRemovePage", "savedSections", "bricks"], outputs: ["sectionInsert", "pageAdd", "pageSelect", "pageRemove", "pageRename", "pageRouteChange", "savedSectionInsert", "savedSectionRemove", "savedSectionCreateRequested"] }, { kind: "component", type: LayoutCanvasComponent, selector: "app-layout-canvas", inputs: ["previewMode"], outputs: ["previewRequested"] }, { kind: "component", type: PropertiesPanelComponent, selector: "app-properties-panel" }, { kind: "component", type: FormPreviewComponent, selector: "app-form-preview", inputs: ["eventApis", "eventApiExecutor", "fieldDataAccessMap", "fieldDataAccessApi", "formContentId", "formContentVersion", "pages", "activePageId"], outputs: ["pageSelect"] }] });
18487
19393
  }
18488
19394
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: JsonFormDesignerComponent, decorators: [{
18489
19395
  type: Component,
@@ -18502,7 +19408,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
18502
19408
  [style.opacity]="state.isLeftPanelCollapsed() ? '0' : '1'"
18503
19409
  [class.border-none]="state.isLeftPanelCollapsed()">
18504
19410
  <div class="h-full" [class.pointer-events-none]="state.isLeftPanelCollapsed()">
18505
- <app-field-palette></app-field-palette>
19411
+ <app-field-palette
19412
+ [pages]="pages"
19413
+ [activePageId]="activePageId"
19414
+ [canRemovePage]="canRemovePage"
19415
+ (pageAdd)="pageAdd.emit()"
19416
+ (pageSelect)="pageSelect.emit($event)"
19417
+ (pageRemove)="pageRemove.emit($event)"
19418
+ (pageRename)="pageRename.emit($event)"
19419
+ (pageRouteChange)="pageRouteChange.emit($event)">
19420
+ </app-field-palette>
18506
19421
  </div>
18507
19422
  </div>
18508
19423
 
@@ -18522,8 +19437,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
18522
19437
  class="fd-designer-preview"
18523
19438
  data-fd-region="preview"
18524
19439
  *ngIf="state.isPreviewMode()"
19440
+ [pages]="pages ?? []"
19441
+ [activePageId]="activePageId"
18525
19442
  [eventApis]="eventApis"
18526
- [eventApiExecutor]="eventApiExecutor">
19443
+ [eventApiExecutor]="eventApiExecutor"
19444
+ (pageSelect)="pageSelect.emit($event)">
18527
19445
  </app-form-preview>
18528
19446
  </div>
18529
19447
  `, styles: [".custom-scrollbar::-webkit-scrollbar{width:4px;height:4px}.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"] }]
@@ -18535,6 +19453,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
18535
19453
  type: Input
18536
19454
  }], eventApiExecutor: [{
18537
19455
  type: Input
19456
+ }], pages: [{
19457
+ type: Input
19458
+ }], activePageId: [{
19459
+ type: Input
19460
+ }], canRemovePage: [{
19461
+ type: Input
19462
+ }], pageAdd: [{
19463
+ type: Output
19464
+ }], pageSelect: [{
19465
+ type: Output
19466
+ }], pageRemove: [{
19467
+ type: Output
19468
+ }], pageRename: [{
19469
+ type: Output
19470
+ }], pageRouteChange: [{
19471
+ type: Output
18538
19472
  }] } });
18539
19473
 
18540
19474
  // ---------------------------------------------------------------------------
@@ -19734,115 +20668,80 @@ const FORM_DESIGNER_AI_TOOL_PLUGIN = {
19734
20668
  ],
19735
20669
  };
19736
20670
 
19737
- class AiToolRegistryService {
19738
- state;
19739
- widgetDefs;
19740
- eventApis;
19741
- plugins;
19742
- toolByName;
19743
- constructor() {
19744
- this.state = inject(DesignerStateService);
19745
- const injectedWidgetDefs = inject(WIDGET_DEFINITIONS, { optional: true }) ?? [];
19746
- this.widgetDefs = injectedWidgetDefs.flat();
19747
- this.eventApis = [];
19748
- this.plugins = [FORM_DESIGNER_AI_TOOL_PLUGIN];
19749
- this.toolByName = this.buildToolIndex(this.plugins);
19750
- }
19751
- /**
19752
- * Create an instance directly for testing without Angular DI.
19753
- */
19754
- static forTest(state, eventApis, plugins, widgetDefs) {
19755
- const instance = Object.create(AiToolRegistryService.prototype);
19756
- instance.state = state;
19757
- instance.eventApis = eventApis ? [...eventApis] : [];
19758
- instance.widgetDefs = widgetDefs ? [...widgetDefs] : [];
19759
- instance.plugins = plugins?.length ? [...plugins] : [FORM_DESIGNER_AI_TOOL_PLUGIN];
19760
- instance.toolByName = instance.buildToolIndex(instance.plugins);
19761
- return instance;
19762
- }
19763
- describeTools() {
19764
- return this.plugins.flatMap((plugin) => plugin.tools.map((tool) => ({
19765
- name: tool.name,
19766
- description: tool.summary,
19767
- summary: tool.summary,
19768
- domain: tool.domain,
19769
- readOnly: tool.readOnly,
19770
- tags: tool.tags,
19771
- whenToUse: tool.whenToUse,
19772
- whenNotToUse: tool.whenNotToUse,
19773
- exampleIntents: tool.exampleIntents,
19774
- requiresSelection: tool.requiresSelection,
19775
- requiresCapabilities: tool.requiresCapabilities,
19776
- requiredAuthScopes: tool.requiredAuthScopes,
19777
- parametersSchema: tool.parametersSchema,
19778
- })));
20671
+ class FormDesignerAiFeatureStateService {
20672
+ widgetDefinitions = (inject(WIDGET_DEFINITIONS, { optional: true }) ?? []).flat();
20673
+ eventApisSignal = signal([]);
20674
+ setEventApis(apis) {
20675
+ this.eventApisSignal.set([...apis]);
19779
20676
  }
19780
- async execute(name, args) {
19781
- const tool = this.toolByName.get(name);
19782
- if (!tool) {
19783
- return {
19784
- ok: false,
19785
- result: null,
19786
- error: `Unknown tool: "${name}"`,
19787
- };
19788
- }
19789
- const context = this.createContext();
19790
- if (tool.isApplicable && !tool.isApplicable(context)) {
19791
- return {
19792
- ok: false,
19793
- result: null,
19794
- error: `Tool "${name}" is not applicable in current context.`,
19795
- };
19796
- }
19797
- const result = await tool.execute(context, args);
19798
- return result;
20677
+ createToolContext(state) {
20678
+ return {
20679
+ state,
20680
+ eventApis: this.eventApisSignal(),
20681
+ widgetDefs: this.widgetDefinitions,
20682
+ };
19799
20683
  }
19800
- getDiscoveryContext() {
20684
+ createDiscoveryContext(state) {
19801
20685
  return {
19802
20686
  workspace: 'designer',
19803
- readOnly: this.state.isReadOnly(),
19804
- hasSelection: !!this.state.selectedNodeId(),
20687
+ readOnly: state.isReadOnly(),
20688
+ hasSelection: !!state.selectedNodeId(),
19805
20689
  capabilities: {
19806
- eventApis: this.eventApis.length > 0,
19807
- widgetCatalog: this.widgetDefs.length > 0,
20690
+ eventApis: this.eventApisSignal().length > 0,
20691
+ widgetCatalog: this.widgetDefinitions.length > 0,
19808
20692
  },
19809
20693
  };
19810
20694
  }
19811
- /** Allow host/shell to update available event APIs at runtime. */
19812
- setEventApis(apis) {
19813
- this.eventApis = [...apis];
19814
- }
19815
- /** Allow host/shell to update widget catalog at runtime. */
19816
- setWidgetDefinitions(widgetDefs) {
19817
- this.widgetDefs = [...widgetDefs];
19818
- }
19819
- /** Register additional plain TS plugins without requiring Angular DI contracts. */
19820
- registerPlugin(plugin) {
19821
- this.plugins = [...this.plugins, plugin];
19822
- this.toolByName = this.buildToolIndex(this.plugins);
19823
- }
19824
- createContext() {
19825
- return {
19826
- state: this.state,
19827
- eventApis: this.eventApis,
19828
- widgetDefs: this.widgetDefs,
19829
- };
19830
- }
19831
- buildToolIndex(plugins) {
19832
- const index = new Map();
19833
- for (const plugin of plugins) {
19834
- for (const tool of plugin.tools) {
19835
- index.set(tool.name, tool);
19836
- }
19837
- }
19838
- return index;
19839
- }
19840
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: AiToolRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
19841
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: AiToolRegistryService });
20695
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: FormDesignerAiFeatureStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
20696
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: FormDesignerAiFeatureStateService });
19842
20697
  }
19843
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: AiToolRegistryService, decorators: [{
20698
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: FormDesignerAiFeatureStateService, decorators: [{
19844
20699
  type: Injectable
19845
- }], ctorParameters: () => [] });
20700
+ }] });
20701
+ function bindFormDesignerTools(getContext) {
20702
+ return FORM_DESIGNER_AI_TOOL_PLUGIN.tools.map((tool) => defineAiTool({
20703
+ name: tool.name,
20704
+ summary: tool.summary,
20705
+ domain: tool.domain,
20706
+ readOnly: tool.readOnly,
20707
+ tags: tool.tags,
20708
+ whenToUse: tool.whenToUse,
20709
+ whenNotToUse: tool.whenNotToUse,
20710
+ exampleIntents: tool.exampleIntents,
20711
+ requiresSelection: tool.requiresSelection,
20712
+ requiresCapabilities: tool.requiresCapabilities,
20713
+ requiredAuthScopes: tool.requiredAuthScopes,
20714
+ parametersSchema: tool.parametersSchema,
20715
+ isApplicable: tool.isApplicable ? () => tool.isApplicable(getContext()) : undefined,
20716
+ execute: async (args) => {
20717
+ const result = await tool.execute(getContext(), args);
20718
+ return result;
20719
+ },
20720
+ }));
20721
+ }
20722
+ function buildFormDesignerSystemPrompt(state) {
20723
+ const mode = state.isReadOnly() ? 'read-only' : 'editable';
20724
+ return [
20725
+ 'You are assisting with a JSON form designer workspace.',
20726
+ `The current designer session is ${mode}.`,
20727
+ 'Prefer the registered tools for inspecting and editing the form schema instead of describing hypothetical changes.',
20728
+ 'Keep responses concise and grounded in the current schema state.',
20729
+ ].join(' ');
20730
+ }
20731
+ function provideFormDesignerAngaiFeature() {
20732
+ return [
20733
+ FormDesignerAiFeatureStateService,
20734
+ ...provideAngaiFeature(() => {
20735
+ const state = inject(DesignerStateService);
20736
+ const featureState = inject(FormDesignerAiFeatureStateService);
20737
+ return {
20738
+ tools: bindFormDesignerTools(() => featureState.createToolContext(state)),
20739
+ discoveryContext: () => featureState.createDiscoveryContext(state),
20740
+ systemPrompt: () => buildFormDesignerSystemPrompt(state),
20741
+ };
20742
+ }),
20743
+ ];
20744
+ }
19846
20745
 
19847
20746
  const parseTemplateSchema = (schema) => {
19848
20747
  return parseSchema(JSON.stringify(schema));
@@ -21588,6 +22487,622 @@ const TRANSACTION_KYC_REVIEW_TEMPLATE = {
21588
22487
  }
21589
22488
  };
21590
22489
  const TRANSACTION_KYC_REVIEW_SCHEMA = parseTemplateSchema(TRANSACTION_KYC_REVIEW_TEMPLATE);
22490
+ const JOURNEY_BRANCHING_TEMPLATE_STEP_ONE = {
22491
+ "id": "template-journey-branch-step-1",
22492
+ "version": "1.0.0",
22493
+ "schemaVersion": "1.0.0",
22494
+ "flavor": "form",
22495
+ "title": "Step 1 · Intake + Routing",
22496
+ "metadata": {
22497
+ "example": true,
22498
+ "scenario": "journey-branching",
22499
+ "page": "intake"
22500
+ },
22501
+ "fields": [
22502
+ {
22503
+ "id": "fld_search_country",
22504
+ "widgetId": "core.form:search",
22505
+ "name": "branchSearchTerm",
22506
+ "type": "search",
22507
+ "label": "Search Country",
22508
+ "placeholder": "Type country code or name",
22509
+ "dataConfig": {
22510
+ "type": "source",
22511
+ "datasourceId": "journey_branch_ds_country_search",
22512
+ "labelKey": "label",
22513
+ "valueKey": "value",
22514
+ "searchColumns": ["label", "value", "region"],
22515
+ "optionsLimit": 30
22516
+ },
22517
+ "events": [
22518
+ {
22519
+ "id": "evt_search_country",
22520
+ "on": "change",
22521
+ "enabled": true,
22522
+ "actions": [
22523
+ {
22524
+ "type": "api",
22525
+ "apiId": "api.country.search",
22526
+ "datasourceId": "journey_branch_ds_country_search",
22527
+ "sourceFieldIds": ["fld_search_country"],
22528
+ "inputTransformScript": "const query = String(context.formValues.branchSearchTerm ?? \"\").trim(); return { query };",
22529
+ "outputTransformScript": "return Array.isArray(output?.items) ? output.items.map(item => ({ label: item.name, value: item.code, region: item.region })) : [];"
22530
+ }
22531
+ ]
22532
+ }
22533
+ ]
22534
+ },
22535
+ {
22536
+ "id": "fld_country",
22537
+ "widgetId": "core.form:select",
22538
+ "name": "countryCode",
22539
+ "type": "select",
22540
+ "label": "Country",
22541
+ "placeholder": "Select a country",
22542
+ "dataConfig": {
22543
+ "type": "source",
22544
+ "datasourceId": "journey_branch_ds_country_search",
22545
+ "labelKey": "label",
22546
+ "valueKey": "value",
22547
+ "searchEnabled": false,
22548
+ "optionsLimit": 30
22549
+ },
22550
+ "events": [
22551
+ {
22552
+ "id": "evt_country_cities",
22553
+ "on": "change",
22554
+ "enabled": true,
22555
+ "actions": [
22556
+ {
22557
+ "type": "api",
22558
+ "apiId": "api.country.cities",
22559
+ "datasourceId": "journey_branch_ds_country_cities",
22560
+ "sourceFieldIds": ["fld_country"],
22561
+ "inputTransformScript": "const countryCode = String(context.formValues.countryCode ?? context.event.value ?? \"\"); return { countryCode };",
22562
+ "outputTransformScript": "return Array.isArray(output?.cities) ? output.cities.map(city => ({ cityCode: city.code, cityName: city.name, population: city.population, weather: city.weather })) : [];"
22563
+ }
22564
+ ]
22565
+ }
22566
+ ]
22567
+ },
22568
+ {
22569
+ "id": "fld_priority_route",
22570
+ "widgetId": "core.form:checkbox",
22571
+ "name": "priorityRoute",
22572
+ "type": "checkbox",
22573
+ "label": "High priority route (skip to page 3)"
22574
+ },
22575
+ {
22576
+ "id": "btn_continue",
22577
+ "widgetId": "core.form:form-button",
22578
+ "name": "continueWithBranch",
22579
+ "type": "form-button",
22580
+ "label": "Continue",
22581
+ "variant": "primary",
22582
+ "buttonType": "button",
22583
+ "events": [
22584
+ {
22585
+ "id": "evt_continue_with_branch",
22586
+ "on": "click",
22587
+ "enabled": true,
22588
+ "actions": [
22589
+ {
22590
+ "type": "api",
22591
+ "apiId": "api.country.profile",
22592
+ "datasourceId": "journey_branch_ds_country_profile",
22593
+ "sourceFieldIds": ["fld_country"],
22594
+ "inputTransformScript": "const countryCode = String(context.formValues.countryCode ?? \"\"); return { countryCode };",
22595
+ "outputTransformScript": "if (!output || typeof output !== \"object\") return []; return [{ countryCode: output.code, currency: output.currency, timezone: output.timezone, nextHoliday: output.nextHoliday, supportTime: output.supportTime }];"
22596
+ },
22597
+ {
22598
+ "type": "navigate",
22599
+ "targetPageId": "page-details",
22600
+ "when": {
22601
+ "operator": "AND",
22602
+ "conditions": [
22603
+ { "fieldId": "fld_country", "operator": "notEmpty" },
22604
+ { "fieldId": "fld_priority_route", "operator": "truthy" }
22605
+ ]
22606
+ }
22607
+ },
22608
+ {
22609
+ "type": "navigate",
22610
+ "targetPageId": "page-preferences",
22611
+ "when": {
22612
+ "operator": "AND",
22613
+ "conditions": [
22614
+ { "fieldId": "fld_country", "operator": "notEmpty" },
22615
+ { "fieldId": "fld_priority_route", "operator": "eq", "value": false }
22616
+ ]
22617
+ }
22618
+ }
22619
+ ]
22620
+ }
22621
+ ]
22622
+ }
22623
+ ],
22624
+ "layout": {
22625
+ "id": "layout_step_1_root",
22626
+ "type": "col",
22627
+ "responsive": { "xs": 12 },
22628
+ "children": [
22629
+ {
22630
+ "id": "row_step_1_inputs",
22631
+ "type": "row",
22632
+ "children": [
22633
+ {
22634
+ "id": "col_step_1_search",
22635
+ "type": "col",
22636
+ "responsive": { "xs": 12, "md": 6 },
22637
+ "children": [
22638
+ { "id": "w_step_1_search", "type": "widget", "widgetKind": "field", "refId": "fld_search_country" }
22639
+ ]
22640
+ },
22641
+ {
22642
+ "id": "col_step_1_country",
22643
+ "type": "col",
22644
+ "responsive": { "xs": 12, "md": 6 },
22645
+ "children": [
22646
+ { "id": "w_step_1_country", "type": "widget", "widgetKind": "field", "refId": "fld_country" }
22647
+ ]
22648
+ }
22649
+ ]
22650
+ },
22651
+ {
22652
+ "id": "row_step_1_priority",
22653
+ "type": "row",
22654
+ "children": [
22655
+ {
22656
+ "id": "col_step_1_priority",
22657
+ "type": "col",
22658
+ "responsive": { "xs": 12 },
22659
+ "children": [
22660
+ { "id": "w_step_1_priority", "type": "widget", "widgetKind": "field", "refId": "fld_priority_route" }
22661
+ ]
22662
+ }
22663
+ ]
22664
+ },
22665
+ {
22666
+ "id": "row_step_1_continue",
22667
+ "type": "row",
22668
+ "children": [
22669
+ {
22670
+ "id": "col_step_1_continue",
22671
+ "type": "col",
22672
+ "responsive": { "xs": 12 },
22673
+ "children": [
22674
+ { "id": "w_step_1_continue", "type": "widget", "widgetKind": "button", "refId": "btn_continue" }
22675
+ ]
22676
+ }
22677
+ ]
22678
+ }
22679
+ ]
22680
+ }
22681
+ };
22682
+ const JOURNEY_BRANCHING_TEMPLATE_STEP_TWO = {
22683
+ "id": "template-journey-branch-step-2",
22684
+ "version": "1.0.0",
22685
+ "schemaVersion": "1.0.0",
22686
+ "flavor": "form",
22687
+ "title": "Step 2 · Preferences",
22688
+ "metadata": {
22689
+ "example": true,
22690
+ "scenario": "journey-branching",
22691
+ "page": "preferences"
22692
+ },
22693
+ "fields": [
22694
+ {
22695
+ "id": "fld_full_name",
22696
+ "widgetId": "core.form:text",
22697
+ "name": "fullName",
22698
+ "type": "text",
22699
+ "label": "Contact Name",
22700
+ "placeholder": "Enter contact name"
22701
+ },
22702
+ {
22703
+ "id": "fld_email",
22704
+ "widgetId": "core.form:email",
22705
+ "name": "email",
22706
+ "type": "email",
22707
+ "label": "Contact Email",
22708
+ "placeholder": "Enter contact email"
22709
+ },
22710
+ {
22711
+ "id": "fld_contact_method",
22712
+ "widgetId": "core.form:radio",
22713
+ "name": "contactMethod",
22714
+ "type": "radio",
22715
+ "label": "Preferred Contact Method",
22716
+ "staticOptions": [
22717
+ { "label": "Email", "value": "email" },
22718
+ { "label": "Phone", "value": "phone" },
22719
+ { "label": "SMS", "value": "sms" }
22720
+ ]
22721
+ },
22722
+ {
22723
+ "id": "fld_summary_country_code",
22724
+ "widgetId": "core.form:text",
22725
+ "name": "summaryCountryCode",
22726
+ "type": "text",
22727
+ "label": "Country Code",
22728
+ "readonly": true,
22729
+ "dataConfig": {
22730
+ "type": "source",
22731
+ "datasourceId": "journey_branch_ds_country_profile",
22732
+ "valueKey": "countryCode"
22733
+ }
22734
+ },
22735
+ {
22736
+ "id": "tbl_city_snapshot",
22737
+ "widgetId": "core.web:table",
22738
+ "name": "summaryCityTable",
22739
+ "type": "table",
22740
+ "label": "Available Cities",
22741
+ "compact": false,
22742
+ "striped": true,
22743
+ "columns": [
22744
+ { "key": "cityName", "label": "City" },
22745
+ { "key": "population", "label": "Population" },
22746
+ { "key": "weather", "label": "Weather" }
22747
+ ],
22748
+ "dataConfig": {
22749
+ "type": "source",
22750
+ "datasourceId": "journey_branch_ds_country_cities",
22751
+ "searchEnabled": false,
22752
+ "pageSize": 5,
22753
+ "sort": [{ "column": "cityName", "dir": "asc" }]
22754
+ }
22755
+ },
22756
+ {
22757
+ "id": "btn_back_intake",
22758
+ "widgetId": "core.form:form-button",
22759
+ "name": "backToIntake",
22760
+ "type": "form-button",
22761
+ "label": "Back to Intake",
22762
+ "variant": "secondary",
22763
+ "buttonType": "button",
22764
+ "events": [
22765
+ {
22766
+ "id": "evt_back_to_intake",
22767
+ "on": "click",
22768
+ "enabled": true,
22769
+ "actions": [
22770
+ { "type": "navigate", "targetPageId": "page-intake" }
22771
+ ]
22772
+ }
22773
+ ]
22774
+ },
22775
+ {
22776
+ "id": "btn_continue_details",
22777
+ "widgetId": "core.form:form-button",
22778
+ "name": "continueToDetails",
22779
+ "type": "form-button",
22780
+ "label": "Continue to Details",
22781
+ "variant": "primary",
22782
+ "buttonType": "button",
22783
+ "events": [
22784
+ {
22785
+ "id": "evt_continue_to_details",
22786
+ "on": "click",
22787
+ "enabled": true,
22788
+ "actions": [
22789
+ {
22790
+ "type": "navigate",
22791
+ "targetPageId": "page-details",
22792
+ "when": {
22793
+ "operator": "AND",
22794
+ "conditions": [
22795
+ { "fieldId": "fld_full_name", "operator": "notEmpty" },
22796
+ { "fieldId": "fld_email", "operator": "notEmpty" }
22797
+ ]
22798
+ }
22799
+ }
22800
+ ]
22801
+ }
22802
+ ]
22803
+ }
22804
+ ],
22805
+ "layout": {
22806
+ "id": "layout_step_2_root",
22807
+ "type": "col",
22808
+ "responsive": { "xs": 12 },
22809
+ "children": [
22810
+ {
22811
+ "id": "row_step_2_inputs",
22812
+ "type": "row",
22813
+ "children": [
22814
+ {
22815
+ "id": "col_step_2_name",
22816
+ "type": "col",
22817
+ "responsive": { "xs": 12, "md": 6 },
22818
+ "children": [
22819
+ { "id": "w_step_2_name", "type": "widget", "widgetKind": "field", "refId": "fld_full_name" }
22820
+ ]
22821
+ },
22822
+ {
22823
+ "id": "col_step_2_email",
22824
+ "type": "col",
22825
+ "responsive": { "xs": 12, "md": 6 },
22826
+ "children": [
22827
+ { "id": "w_step_2_email", "type": "widget", "widgetKind": "field", "refId": "fld_email" }
22828
+ ]
22829
+ }
22830
+ ]
22831
+ },
22832
+ {
22833
+ "id": "row_step_2_summary",
22834
+ "type": "row",
22835
+ "children": [
22836
+ {
22837
+ "id": "col_step_2_contact_method",
22838
+ "type": "col",
22839
+ "responsive": { "xs": 12, "md": 6 },
22840
+ "children": [
22841
+ { "id": "w_step_2_contact_method", "type": "widget", "widgetKind": "field", "refId": "fld_contact_method" }
22842
+ ]
22843
+ },
22844
+ {
22845
+ "id": "col_step_2_country_code",
22846
+ "type": "col",
22847
+ "responsive": { "xs": 12, "md": 6 },
22848
+ "children": [
22849
+ { "id": "w_step_2_country_code", "type": "widget", "widgetKind": "field", "refId": "fld_summary_country_code" }
22850
+ ]
22851
+ }
22852
+ ]
22853
+ },
22854
+ {
22855
+ "id": "row_step_2_table",
22856
+ "type": "row",
22857
+ "children": [
22858
+ {
22859
+ "id": "col_step_2_table",
22860
+ "type": "col",
22861
+ "responsive": { "xs": 12 },
22862
+ "children": [
22863
+ { "id": "w_step_2_table", "type": "widget", "widgetKind": "table", "refId": "tbl_city_snapshot" }
22864
+ ]
22865
+ }
22866
+ ]
22867
+ },
22868
+ {
22869
+ "id": "row_step_2_actions",
22870
+ "type": "row",
22871
+ "children": [
22872
+ {
22873
+ "id": "col_step_2_back",
22874
+ "type": "col",
22875
+ "responsive": { "xs": 12, "md": 4 },
22876
+ "children": [
22877
+ { "id": "w_step_2_back", "type": "widget", "widgetKind": "button", "refId": "btn_back_intake" }
22878
+ ]
22879
+ },
22880
+ {
22881
+ "id": "col_step_2_continue",
22882
+ "type": "col",
22883
+ "responsive": { "xs": 12, "md": 8 },
22884
+ "children": [
22885
+ { "id": "w_step_2_continue", "type": "widget", "widgetKind": "button", "refId": "btn_continue_details" }
22886
+ ]
22887
+ }
22888
+ ]
22889
+ }
22890
+ ]
22891
+ }
22892
+ };
22893
+ const JOURNEY_BRANCHING_TEMPLATE_STEP_THREE = {
22894
+ "id": "template-journey-branch-step-3",
22895
+ "version": "1.0.0",
22896
+ "schemaVersion": "1.0.0",
22897
+ "flavor": "form",
22898
+ "title": "Step 3 · Details",
22899
+ "metadata": {
22900
+ "example": true,
22901
+ "scenario": "journey-branching",
22902
+ "page": "details"
22903
+ },
22904
+ "fields": [
22905
+ {
22906
+ "id": "fld_follow_up_date",
22907
+ "widgetId": "core.form:date",
22908
+ "name": "followUpDate",
22909
+ "type": "date",
22910
+ "label": "Follow-up Date"
22911
+ },
22912
+ {
22913
+ "id": "fld_follow_up_time",
22914
+ "widgetId": "core.form:time",
22915
+ "name": "followUpTime",
22916
+ "type": "time",
22917
+ "label": "Follow-up Time"
22918
+ },
22919
+ {
22920
+ "id": "fld_notes",
22921
+ "widgetId": "core.form:text",
22922
+ "name": "notes",
22923
+ "type": "text",
22924
+ "label": "Notes",
22925
+ "placeholder": "Add short notes for this request"
22926
+ },
22927
+ {
22928
+ "id": "fld_summary_currency",
22929
+ "widgetId": "core.form:text",
22930
+ "name": "summaryCurrency",
22931
+ "type": "text",
22932
+ "label": "Currency",
22933
+ "readonly": true,
22934
+ "dataConfig": {
22935
+ "type": "source",
22936
+ "datasourceId": "journey_branch_ds_country_profile",
22937
+ "valueKey": "currency"
22938
+ }
22939
+ },
22940
+ {
22941
+ "id": "fld_summary_timezone",
22942
+ "widgetId": "core.form:text",
22943
+ "name": "summaryTimezone",
22944
+ "type": "text",
22945
+ "label": "Timezone",
22946
+ "readonly": true,
22947
+ "dataConfig": {
22948
+ "type": "source",
22949
+ "datasourceId": "journey_branch_ds_country_profile",
22950
+ "valueKey": "timezone"
22951
+ }
22952
+ },
22953
+ {
22954
+ "id": "btn_back_preferences",
22955
+ "widgetId": "core.form:form-button",
22956
+ "name": "backToPreferences",
22957
+ "type": "form-button",
22958
+ "label": "Back to Preferences",
22959
+ "variant": "secondary",
22960
+ "buttonType": "button",
22961
+ "events": [
22962
+ {
22963
+ "id": "evt_back_to_preferences",
22964
+ "on": "click",
22965
+ "enabled": true,
22966
+ "actions": [
22967
+ { "type": "navigate", "targetPageId": "page-preferences" }
22968
+ ]
22969
+ }
22970
+ ]
22971
+ },
22972
+ {
22973
+ "id": "btn_jump_intake",
22974
+ "widgetId": "core.form:form-button",
22975
+ "name": "jumpToIntake",
22976
+ "type": "form-button",
22977
+ "label": "Jump to Intake",
22978
+ "variant": "secondary",
22979
+ "buttonType": "button",
22980
+ "events": [
22981
+ {
22982
+ "id": "evt_jump_to_intake",
22983
+ "on": "click",
22984
+ "enabled": true,
22985
+ "actions": [
22986
+ { "type": "navigate", "targetPageId": "page-intake" }
22987
+ ]
22988
+ }
22989
+ ]
22990
+ }
22991
+ ],
22992
+ "layout": {
22993
+ "id": "layout_step_3_root",
22994
+ "type": "col",
22995
+ "responsive": { "xs": 12 },
22996
+ "children": [
22997
+ {
22998
+ "id": "row_step_3_inputs",
22999
+ "type": "row",
23000
+ "children": [
23001
+ {
23002
+ "id": "col_step_3_date",
23003
+ "type": "col",
23004
+ "responsive": { "xs": 12, "md": 4 },
23005
+ "children": [
23006
+ { "id": "w_step_3_date", "type": "widget", "widgetKind": "field", "refId": "fld_follow_up_date" }
23007
+ ]
23008
+ },
23009
+ {
23010
+ "id": "col_step_3_time",
23011
+ "type": "col",
23012
+ "responsive": { "xs": 12, "md": 4 },
23013
+ "children": [
23014
+ { "id": "w_step_3_time", "type": "widget", "widgetKind": "field", "refId": "fld_follow_up_time" }
23015
+ ]
23016
+ },
23017
+ {
23018
+ "id": "col_step_3_notes",
23019
+ "type": "col",
23020
+ "responsive": { "xs": 12, "md": 4 },
23021
+ "children": [
23022
+ { "id": "w_step_3_notes", "type": "widget", "widgetKind": "field", "refId": "fld_notes" }
23023
+ ]
23024
+ }
23025
+ ]
23026
+ },
23027
+ {
23028
+ "id": "row_step_3_summary",
23029
+ "type": "row",
23030
+ "children": [
23031
+ {
23032
+ "id": "col_step_3_currency",
23033
+ "type": "col",
23034
+ "responsive": { "xs": 12, "md": 6 },
23035
+ "children": [
23036
+ { "id": "w_step_3_currency", "type": "widget", "widgetKind": "field", "refId": "fld_summary_currency" }
23037
+ ]
23038
+ },
23039
+ {
23040
+ "id": "col_step_3_timezone",
23041
+ "type": "col",
23042
+ "responsive": { "xs": 12, "md": 6 },
23043
+ "children": [
23044
+ { "id": "w_step_3_timezone", "type": "widget", "widgetKind": "field", "refId": "fld_summary_timezone" }
23045
+ ]
23046
+ }
23047
+ ]
23048
+ },
23049
+ {
23050
+ "id": "row_step_3_actions",
23051
+ "type": "row",
23052
+ "children": [
23053
+ {
23054
+ "id": "col_step_3_back",
23055
+ "type": "col",
23056
+ "responsive": { "xs": 12, "md": 6 },
23057
+ "children": [
23058
+ { "id": "w_step_3_back", "type": "widget", "widgetKind": "button", "refId": "btn_back_preferences" }
23059
+ ]
23060
+ },
23061
+ {
23062
+ "id": "col_step_3_jump",
23063
+ "type": "col",
23064
+ "responsive": { "xs": 12, "md": 6 },
23065
+ "children": [
23066
+ { "id": "w_step_3_jump", "type": "widget", "widgetKind": "button", "refId": "btn_jump_intake" }
23067
+ ]
23068
+ }
23069
+ ]
23070
+ }
23071
+ ]
23072
+ }
23073
+ };
23074
+ const JOURNEY_BRANCHING_STEP_ONE_SCHEMA = parseTemplateSchema(JOURNEY_BRANCHING_TEMPLATE_STEP_ONE);
23075
+ const JOURNEY_BRANCHING_STEP_TWO_SCHEMA = parseTemplateSchema(JOURNEY_BRANCHING_TEMPLATE_STEP_TWO);
23076
+ const JOURNEY_BRANCHING_STEP_THREE_SCHEMA = parseTemplateSchema(JOURNEY_BRANCHING_TEMPLATE_STEP_THREE);
23077
+ const JOURNEY_BRANCHING_TEMPLATE_PROJECT = {
23078
+ id: 'template-journey-branching',
23079
+ title: 'Journey Branching + Editable Pages',
23080
+ startPageId: 'page-intake',
23081
+ pages: [
23082
+ {
23083
+ id: 'page-intake',
23084
+ name: 'Step 1: Intake + Routing',
23085
+ route: '/journey-intake',
23086
+ schema: JOURNEY_BRANCHING_STEP_ONE_SCHEMA
23087
+ },
23088
+ {
23089
+ id: 'page-preferences',
23090
+ name: 'Step 2: Preferences',
23091
+ route: '/journey-preferences',
23092
+ schema: JOURNEY_BRANCHING_STEP_TWO_SCHEMA
23093
+ },
23094
+ {
23095
+ id: 'page-details',
23096
+ name: 'Step 3: Details',
23097
+ route: '/journey-details',
23098
+ schema: JOURNEY_BRANCHING_STEP_THREE_SCHEMA
23099
+ }
23100
+ ],
23101
+ metadata: {
23102
+ example: true,
23103
+ scenario: 'journey-branching'
23104
+ }
23105
+ };
21591
23106
  const DEFAULT_TEMPLATE_LIBRARY = [
21592
23107
  {
21593
23108
  id: 'basic-contact',
@@ -21616,6 +23131,14 @@ const DEFAULT_TEMPLATE_LIBRARY = [
21616
23131
  description: 'KYC transfer starter with request-channel routing, client lookup, and conditional beneficiary verification.',
21617
23132
  tags: ['operations', 'kyc', 'review', 'dense'],
21618
23133
  schema: TRANSACTION_KYC_REVIEW_SCHEMA
23134
+ },
23135
+ {
23136
+ id: 'journey-branching-3-step',
23137
+ name: 'Journey Branching (3 Steps)',
23138
+ description: 'Three-page journey template with datasource events, conditional page navigation, and editable details step.',
23139
+ tags: ['journey', 'events', 'datasource', 'navigation'],
23140
+ schema: JOURNEY_BRANCHING_STEP_ONE_SCHEMA,
23141
+ journey: JOURNEY_BRANCHING_TEMPLATE_PROJECT
21619
23142
  }
21620
23143
  ];
21621
23144
 
@@ -21631,6 +23154,7 @@ class EventsWorkspaceComponent {
21631
23154
  readOnly = input(false);
21632
23155
  eventApis = input([]);
21633
23156
  eventApiBrowser = input();
23157
+ pages = input([]);
21634
23158
  schemaChange = output();
21635
23159
  eventsSave = output();
21636
23160
  draftSchema = signal(null);
@@ -21647,6 +23171,20 @@ class EventsWorkspaceComponent {
21647
23171
  { label: 'Field', value: 'field' },
21648
23172
  { label: 'Event Payload', value: 'event' }
21649
23173
  ];
23174
+ conditionOperatorOptions = [
23175
+ { label: 'Equals', value: 'eq' },
23176
+ { label: 'Not Equals', value: 'neq' },
23177
+ { label: 'Greater Than', value: 'gt' },
23178
+ { label: 'Less Than', value: 'lt' },
23179
+ { label: 'Greater Or Equals', value: 'gte' },
23180
+ { label: 'Less Or Equals', value: 'lte' },
23181
+ { label: 'Contains', value: 'contains' },
23182
+ { label: 'Starts With', value: 'startsWith' },
23183
+ { label: 'Ends With', value: 'endsWith' },
23184
+ { label: 'Is Empty', value: 'empty' },
23185
+ { label: 'Is Not Empty', value: 'notEmpty' },
23186
+ { label: 'Is Truthy', value: 'truthy' }
23187
+ ];
21650
23188
  currentSchema = computed(() => this.draftSchema() ?? this.schema());
21651
23189
  fields = computed(() => this.flattenFields(this.currentSchema()));
21652
23190
  filteredFields = computed(() => {
@@ -21755,6 +23293,7 @@ class EventsWorkspaceComponent {
21755
23293
  return [
21756
23294
  { label: 'Set Value', value: 'setValue' },
21757
23295
  { label: 'Log', value: 'log' },
23296
+ { label: 'Navigate', value: 'navigate' },
21758
23297
  { label: 'API', value: 'api', disabled: !this.canSelectApiAction(binding, actionIndex) }
21759
23298
  ];
21760
23299
  }
@@ -21834,6 +23373,14 @@ class EventsWorkspaceComponent {
21834
23373
  binding.actions[actionIndex] = nextAction;
21835
23374
  return;
21836
23375
  }
23376
+ if (currentAction.type === 'navigate') {
23377
+ const nextAction = {
23378
+ ...currentAction,
23379
+ ...patch
23380
+ };
23381
+ binding.actions[actionIndex] = nextAction;
23382
+ return;
23383
+ }
21837
23384
  const nextAction = {
21838
23385
  ...currentAction,
21839
23386
  ...patch,
@@ -22039,6 +23586,91 @@ class EventsWorkspaceComponent {
22039
23586
  asApi(action) {
22040
23587
  return action;
22041
23588
  }
23589
+ asNavigate(action) {
23590
+ return action;
23591
+ }
23592
+ updateNavigateTarget(bindingIndex, actionIndex, targetPageId) {
23593
+ this.patchAction(bindingIndex, actionIndex, { targetPageId });
23594
+ }
23595
+ hasNavigateCondition(action) {
23596
+ return !!action.when;
23597
+ }
23598
+ toggleNavigateCondition(bindingIndex, actionIndex, enabled) {
23599
+ if (!enabled) {
23600
+ this.updateNavigateConditionGroup(bindingIndex, actionIndex, undefined);
23601
+ return;
23602
+ }
23603
+ this.updateNavigateConditionGroup(bindingIndex, actionIndex, {
23604
+ operator: 'AND',
23605
+ conditions: []
23606
+ });
23607
+ }
23608
+ updateNavigateConditionGroup(bindingIndex, actionIndex, group) {
23609
+ this.patchAction(bindingIndex, actionIndex, { when: group ? this.clone(group) : undefined });
23610
+ }
23611
+ navigateConditionFieldId(action) {
23612
+ const condition = this.firstNavigateCondition(action);
23613
+ return condition?.fieldId ?? '';
23614
+ }
23615
+ navigateConditionOperator(action) {
23616
+ const condition = this.firstNavigateCondition(action);
23617
+ return condition?.operator ?? 'notEmpty';
23618
+ }
23619
+ navigateConditionValue(action) {
23620
+ const condition = this.firstNavigateCondition(action);
23621
+ if (!condition || condition.value === null || condition.value === undefined)
23622
+ return '';
23623
+ return String(condition.value);
23624
+ }
23625
+ updateNavigateConditionField(bindingIndex, actionIndex, fieldId) {
23626
+ if (!fieldId) {
23627
+ this.updateNavigateConditionGroup(bindingIndex, actionIndex, undefined);
23628
+ return;
23629
+ }
23630
+ const action = this.selectedFieldEvents()[bindingIndex]?.actions[actionIndex];
23631
+ const operator = action && action.type === 'navigate'
23632
+ ? this.navigateConditionOperator(action)
23633
+ : 'notEmpty';
23634
+ const nextCondition = { fieldId, operator };
23635
+ this.updateNavigateConditionGroup(bindingIndex, actionIndex, this.toNavigateLogicGroup(nextCondition));
23636
+ }
23637
+ updateNavigateConditionOperator(bindingIndex, actionIndex, operator) {
23638
+ const action = this.selectedFieldEvents()[bindingIndex]?.actions[actionIndex];
23639
+ if (!action || action.type !== 'navigate')
23640
+ return;
23641
+ const current = this.firstNavigateCondition(action);
23642
+ if (!current?.fieldId)
23643
+ return;
23644
+ const next = { ...current, operator };
23645
+ if (!this.navigateOperatorNeedsValue(operator)) {
23646
+ delete next.value;
23647
+ }
23648
+ this.updateNavigateConditionGroup(bindingIndex, actionIndex, this.toNavigateLogicGroup(next));
23649
+ }
23650
+ updateNavigateConditionValue(bindingIndex, actionIndex, value) {
23651
+ const action = this.selectedFieldEvents()[bindingIndex]?.actions[actionIndex];
23652
+ if (!action || action.type !== 'navigate')
23653
+ return;
23654
+ const current = this.firstNavigateCondition(action);
23655
+ if (!current?.fieldId)
23656
+ return;
23657
+ const next = {
23658
+ ...current,
23659
+ value
23660
+ };
23661
+ this.updateNavigateConditionGroup(bindingIndex, actionIndex, this.toNavigateLogicGroup(next));
23662
+ }
23663
+ navigateOperatorNeedsValue(operator) {
23664
+ return !['empty', 'notEmpty', 'truthy'].includes(operator);
23665
+ }
23666
+ navigateConditionFields() {
23667
+ return this.fields().map(field => ({
23668
+ id: field.id,
23669
+ name: field.name,
23670
+ label: field.label,
23671
+ type: field.type
23672
+ }));
23673
+ }
22042
23674
  trackFieldById(_, field) {
22043
23675
  return field.id;
22044
23676
  }
@@ -22070,6 +23702,22 @@ class EventsWorkspaceComponent {
22070
23702
  const selectedId = this.selectedFieldId();
22071
23703
  return this.fields().filter(field => field.id !== selectedId);
22072
23704
  }
23705
+ firstNavigateCondition(action) {
23706
+ const when = action.when;
23707
+ if (!when?.conditions?.length)
23708
+ return null;
23709
+ const first = when.conditions[0];
23710
+ return this.isLogicCondition(first) ? first : null;
23711
+ }
23712
+ isLogicCondition(value) {
23713
+ return 'fieldId' in value;
23714
+ }
23715
+ toNavigateLogicGroup(condition) {
23716
+ return {
23717
+ operator: 'AND',
23718
+ conditions: [condition]
23719
+ };
23720
+ }
22073
23721
  updateSelectedEvents(mutator) {
22074
23722
  if (this.readOnly())
22075
23723
  return;
@@ -22104,6 +23752,13 @@ class EventsWorkspaceComponent {
22104
23752
  outputTransformScript: 'return output;'
22105
23753
  };
22106
23754
  }
23755
+ if (type === 'navigate') {
23756
+ return {
23757
+ type: 'navigate',
23758
+ targetPageId: '',
23759
+ when: undefined
23760
+ };
23761
+ }
22107
23762
  return {
22108
23763
  type: 'log',
22109
23764
  message: DEFAULT_LOG_ACTION_MESSAGE
@@ -22458,7 +24113,7 @@ class EventsWorkspaceComponent {
22458
24113
  return `evt_ds_${v4().replace(/-/g, '')}`;
22459
24114
  }
22460
24115
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EventsWorkspaceComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
22461
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.17", type: EventsWorkspaceComponent, isStandalone: true, selector: "app-events-workspace", inputs: { schema: { classPropertyName: "schema", publicName: "schema", isSignal: true, isRequired: true, transformFunction: null }, readOnly: { classPropertyName: "readOnly", publicName: "readOnly", isSignal: true, isRequired: false, transformFunction: null }, eventApis: { classPropertyName: "eventApis", publicName: "eventApis", isSignal: true, isRequired: false, transformFunction: null }, eventApiBrowser: { classPropertyName: "eventApiBrowser", publicName: "eventApiBrowser", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { schemaChange: "schemaChange", eventsSave: "eventsSave" }, host: { classAttribute: "fd-events-workspace-host block h-full w-full" }, ngImport: i0, template: `
24116
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.17", type: EventsWorkspaceComponent, isStandalone: true, selector: "app-events-workspace", inputs: { schema: { classPropertyName: "schema", publicName: "schema", isSignal: true, isRequired: true, transformFunction: null }, readOnly: { classPropertyName: "readOnly", publicName: "readOnly", isSignal: true, isRequired: false, transformFunction: null }, eventApis: { classPropertyName: "eventApis", publicName: "eventApis", isSignal: true, isRequired: false, transformFunction: null }, eventApiBrowser: { classPropertyName: "eventApiBrowser", publicName: "eventApiBrowser", isSignal: true, isRequired: false, transformFunction: null }, pages: { classPropertyName: "pages", publicName: "pages", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { schemaChange: "schemaChange", eventsSave: "eventsSave" }, host: { classAttribute: "fd-events-workspace-host block h-full w-full" }, ngImport: i0, template: `
22462
24117
  <div class="fd-events-workspace flex h-full w-full overflow-hidden bg-white font-sans text-sm text-gray-700" data-fd="events-workspace">
22463
24118
  <aside class="fd-events-fields w-72 shrink-0 border-r border-gray-200 bg-gray-50 flex flex-col" data-fd-region="fields">
22464
24119
  <div class="px-4 py-3 border-b border-gray-200 bg-white">
@@ -22637,6 +24292,43 @@ class EventsWorkspaceComponent {
22637
24292
  placeholder="Enter log message">
22638
24293
  </ng-container>
22639
24294
 
24295
+ <ng-container *ngIf="action.type === 'navigate'">
24296
+ <div>
24297
+ <label class="mb-1 block text-[11px] font-medium text-gray-600">Target Page</label>
24298
+ <select
24299
+ class="h-8 w-full rounded-md border border-gray-300 px-2 text-xs focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
24300
+ [disabled]="readOnly()"
24301
+ [ngModel]="asNavigate(action).targetPageId"
24302
+ (ngModelChange)="updateNavigateTarget(bindingIndex, actionIndex, $event)">
24303
+ <option value="">Select page</option>
24304
+ <option *ngFor="let page of pages()" [value]="page.id">{{ page.name }}</option>
24305
+ </select>
24306
+ </div>
24307
+
24308
+ <div class="rounded-md border border-gray-200 bg-white p-3">
24309
+ <label class="inline-flex items-center gap-2 text-[11px] font-medium text-gray-600">
24310
+ <input
24311
+ type="checkbox"
24312
+ class="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
24313
+ [disabled]="readOnly()"
24314
+ [ngModel]="hasNavigateCondition(asNavigate(action))"
24315
+ (ngModelChange)="toggleNavigateCondition(bindingIndex, actionIndex, $event)">
24316
+ <span>Use condition</span>
24317
+ </label>
24318
+ <p class="mt-1 text-[10px] text-gray-500">
24319
+ Reuses the same rules query builder used by widget rules.
24320
+ </p>
24321
+
24322
+ <div *ngIf="hasNavigateCondition(asNavigate(action))" class="mt-3">
24323
+ <app-query-builder
24324
+ [group]="asNavigate(action).when!"
24325
+ [allFields]="navigateConditionFields()"
24326
+ (groupChange)="updateNavigateConditionGroup(bindingIndex, actionIndex, $event)">
24327
+ </app-query-builder>
24328
+ </div>
24329
+ </div>
24330
+ </ng-container>
24331
+
22640
24332
  <ng-container *ngIf="action.type === 'api'">
22641
24333
  <label class="mb-1 block text-[11px] font-medium text-gray-600">API</label>
22642
24334
  <div class="rounded-md border border-gray-200 bg-white">
@@ -22791,7 +24483,7 @@ class EventsWorkspaceComponent {
22791
24483
  </ng-template>
22792
24484
  </section>
22793
24485
  </div>
22794
- `, 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: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
24486
+ `, 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: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: QueryBuilderComponent, selector: "app-query-builder", inputs: ["group", "allFields", "level"], outputs: ["groupChange", "remove"] }] });
22795
24487
  }
22796
24488
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EventsWorkspaceComponent, decorators: [{
22797
24489
  type: Component,
@@ -22801,7 +24493,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
22801
24493
  host: {
22802
24494
  class: 'fd-events-workspace-host block h-full w-full'
22803
24495
  },
22804
- imports: [CommonModule, FormsModule],
24496
+ imports: [CommonModule, FormsModule, QueryBuilderComponent],
22805
24497
  template: `
22806
24498
  <div class="fd-events-workspace flex h-full w-full overflow-hidden bg-white font-sans text-sm text-gray-700" data-fd="events-workspace">
22807
24499
  <aside class="fd-events-fields w-72 shrink-0 border-r border-gray-200 bg-gray-50 flex flex-col" data-fd-region="fields">
@@ -22981,6 +24673,43 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
22981
24673
  placeholder="Enter log message">
22982
24674
  </ng-container>
22983
24675
 
24676
+ <ng-container *ngIf="action.type === 'navigate'">
24677
+ <div>
24678
+ <label class="mb-1 block text-[11px] font-medium text-gray-600">Target Page</label>
24679
+ <select
24680
+ class="h-8 w-full rounded-md border border-gray-300 px-2 text-xs focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
24681
+ [disabled]="readOnly()"
24682
+ [ngModel]="asNavigate(action).targetPageId"
24683
+ (ngModelChange)="updateNavigateTarget(bindingIndex, actionIndex, $event)">
24684
+ <option value="">Select page</option>
24685
+ <option *ngFor="let page of pages()" [value]="page.id">{{ page.name }}</option>
24686
+ </select>
24687
+ </div>
24688
+
24689
+ <div class="rounded-md border border-gray-200 bg-white p-3">
24690
+ <label class="inline-flex items-center gap-2 text-[11px] font-medium text-gray-600">
24691
+ <input
24692
+ type="checkbox"
24693
+ class="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
24694
+ [disabled]="readOnly()"
24695
+ [ngModel]="hasNavigateCondition(asNavigate(action))"
24696
+ (ngModelChange)="toggleNavigateCondition(bindingIndex, actionIndex, $event)">
24697
+ <span>Use condition</span>
24698
+ </label>
24699
+ <p class="mt-1 text-[10px] text-gray-500">
24700
+ Reuses the same rules query builder used by widget rules.
24701
+ </p>
24702
+
24703
+ <div *ngIf="hasNavigateCondition(asNavigate(action))" class="mt-3">
24704
+ <app-query-builder
24705
+ [group]="asNavigate(action).when!"
24706
+ [allFields]="navigateConditionFields()"
24707
+ (groupChange)="updateNavigateConditionGroup(bindingIndex, actionIndex, $event)">
24708
+ </app-query-builder>
24709
+ </div>
24710
+ </div>
24711
+ </ng-container>
24712
+
22984
24713
  <ng-container *ngIf="action.type === 'api'">
22985
24714
  <label class="mb-1 block text-[11px] font-medium text-gray-600">API</label>
22986
24715
  <div class="rounded-md border border-gray-200 bg-white">
@@ -23139,6 +24868,158 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
23139
24868
  }]
23140
24869
  }], ctorParameters: () => [] });
23141
24870
 
24871
+ class FormJourneyStateService {
24872
+ documentSignal = signal(normalizeToJourney(createEmptySchema('form')));
24873
+ sourceWasPlainSchema = signal(true);
24874
+ document = computed(() => this.documentSignal());
24875
+ pages = computed(() => this.documentSignal().pages);
24876
+ activePageId = signal(this.documentSignal().startPageId);
24877
+ activePage = computed(() => this.pages().find(page => page.id === this.activePageId()) ?? null);
24878
+ canRemovePage = computed(() => this.pages().length > 1);
24879
+ isJourneyMode = computed(() => this.pages().length > 1);
24880
+ setDocument(input) {
24881
+ const fromSchema = !this.isJourneyInput(input);
24882
+ const normalized = normalizeToJourney(input);
24883
+ this.documentSignal.set(normalized);
24884
+ this.sourceWasPlainSchema.set(fromSchema);
24885
+ this.activePageId.set(normalized.startPageId);
24886
+ }
24887
+ setActivePage(id) {
24888
+ if (this.pages().some(page => page.id === id)) {
24889
+ this.activePageId.set(id);
24890
+ }
24891
+ }
24892
+ addPage() {
24893
+ const current = this.documentSignal();
24894
+ const name = this.nextPageName();
24895
+ const route = this.ensureUniqueRoute(`/${name.toLowerCase().replace(/\s+/g, '-')}`);
24896
+ const page = createFormJourneyPage({
24897
+ name,
24898
+ route,
24899
+ schema: createEmptySchema('form', { title: name })
24900
+ });
24901
+ this.documentSignal.set({
24902
+ ...current,
24903
+ pages: [...current.pages, page]
24904
+ });
24905
+ this.activePageId.set(page.id);
24906
+ return page;
24907
+ }
24908
+ removePage(id) {
24909
+ if (!this.canRemovePage())
24910
+ return;
24911
+ const current = this.documentSignal();
24912
+ const pages = current.pages.filter(page => page.id !== id);
24913
+ if (pages.length === current.pages.length || pages.length === 0)
24914
+ return;
24915
+ const startPageId = pages.some(page => page.id === current.startPageId)
24916
+ ? current.startPageId
24917
+ : pages[0].id;
24918
+ this.documentSignal.set({
24919
+ ...current,
24920
+ pages,
24921
+ startPageId
24922
+ });
24923
+ if (this.activePageId() === id) {
24924
+ this.activePageId.set(startPageId);
24925
+ }
24926
+ }
24927
+ renamePage(id, name) {
24928
+ const trimmed = name.trim();
24929
+ if (!trimmed)
24930
+ return;
24931
+ const current = this.documentSignal();
24932
+ const pages = current.pages.map(page => {
24933
+ if (page.id !== id)
24934
+ return page;
24935
+ return {
24936
+ ...page,
24937
+ name: trimmed,
24938
+ schema: {
24939
+ ...page.schema,
24940
+ title: trimmed
24941
+ }
24942
+ };
24943
+ });
24944
+ this.documentSignal.set({ ...current, pages });
24945
+ }
24946
+ updateRoute(id, route) {
24947
+ const current = this.documentSignal();
24948
+ const pages = current.pages.map(page => {
24949
+ if (page.id !== id)
24950
+ return page;
24951
+ return {
24952
+ ...page,
24953
+ route: this.ensureUniqueRoute(route, id)
24954
+ };
24955
+ });
24956
+ this.documentSignal.set({ ...current, pages });
24957
+ }
24958
+ updateActivePageSchema(schema) {
24959
+ const activeId = this.activePageId();
24960
+ if (!activeId)
24961
+ return;
24962
+ this.updatePageSchema(activeId, schema);
24963
+ }
24964
+ updatePageSchema(id, schema) {
24965
+ const current = this.documentSignal();
24966
+ const pages = current.pages.map(page => {
24967
+ if (page.id !== id)
24968
+ return page;
24969
+ return {
24970
+ ...page,
24971
+ schema: { ...schema }
24972
+ };
24973
+ });
24974
+ this.documentSignal.set({ ...current, pages });
24975
+ }
24976
+ compatibleOutput() {
24977
+ const current = this.documentSignal();
24978
+ if (current.pages.length === 1 && this.sourceWasPlainSchema()) {
24979
+ return unwrapSinglePageJourney(current);
24980
+ }
24981
+ return current;
24982
+ }
24983
+ isJourneyInput(input) {
24984
+ return 'pages' in input;
24985
+ }
24986
+ nextPageName() {
24987
+ const names = new Set(this.pages().map(page => page.name.toLowerCase()));
24988
+ let i = this.pages().length + 1;
24989
+ while (names.has(`step ${i}`.toLowerCase())) {
24990
+ i += 1;
24991
+ }
24992
+ return `Step ${i}`;
24993
+ }
24994
+ ensureUniqueRoute(route, excludeId) {
24995
+ const normalized = this.normalizeRoute(route);
24996
+ const existing = new Set(this.pages()
24997
+ .filter(page => page.id !== excludeId)
24998
+ .map(page => page.route));
24999
+ if (!existing.has(normalized))
25000
+ return normalized;
25001
+ const base = normalized.replace(/-\d+$/, '');
25002
+ let index = 2;
25003
+ let candidate = `${base}-${index}`;
25004
+ while (existing.has(candidate)) {
25005
+ index += 1;
25006
+ candidate = `${base}-${index}`;
25007
+ }
25008
+ return candidate;
25009
+ }
25010
+ normalizeRoute(route) {
25011
+ const trimmed = route.trim().toLowerCase();
25012
+ if (!trimmed)
25013
+ return '/step';
25014
+ return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
25015
+ }
25016
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: FormJourneyStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
25017
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: FormJourneyStateService });
25018
+ }
25019
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: FormJourneyStateService, decorators: [{
25020
+ type: Injectable
25021
+ }] });
25022
+
23142
25023
  function parseExternalDsProperties(value) {
23143
25024
  if (!value)
23144
25025
  return {};
@@ -23185,9 +25066,11 @@ function normalizeDataSourceType(type) {
23185
25066
  }
23186
25067
  class FormDesignerShellComponent {
23187
25068
  state = inject(DesignerStateService);
25069
+ journeyState = inject(FormJourneyStateService);
23188
25070
  aiAvailable = inject(AI_BACKEND_CLIENT, { optional: true }) !== null;
23189
- aiToolRegistry = inject(AiToolRegistryService, { optional: true });
25071
+ aiFeatureState = inject(FormDesignerAiFeatureStateService);
23190
25072
  schema;
25073
+ journey;
23191
25074
  schemaMetadata;
23192
25075
  flavor = 'form';
23193
25076
  flavors = ['form', 'email'];
@@ -23210,6 +25093,9 @@ class FormDesignerShellComponent {
23210
25093
  save = new EventEmitter();
23211
25094
  push = new EventEmitter();
23212
25095
  eventsSave = new EventEmitter();
25096
+ saveDocument = new EventEmitter();
25097
+ pushDocument = new EventEmitter();
25098
+ editDocument = new EventEmitter();
23213
25099
  catalog = inject(DataCatalog, { optional: true });
23214
25100
  managedSourceIds = new Set();
23215
25101
  managedEventSourceIds = new Set();
@@ -23220,6 +25106,18 @@ class FormDesignerShellComponent {
23220
25106
  selectedTemplateId = null;
23221
25107
  pendingTemplate = null;
23222
25108
  lastSavedSchemaSnapshot = this.serializeSchema(this.state.schema());
25109
+ designerPages = computed(() => this.journeyState.pages().map(page => ({
25110
+ id: page.id,
25111
+ name: page.name,
25112
+ route: page.route
25113
+ })));
25114
+ journeySyncEnabled = signal(false);
25115
+ schemaSync = effect(() => {
25116
+ if (!this.journeySyncEnabled())
25117
+ return;
25118
+ const schema = this.state.schema();
25119
+ untracked(() => this.journeyState.updateActivePageSchema(schema));
25120
+ });
23223
25121
  get engine() {
23224
25122
  return new FormEngine(this.state.schema());
23225
25123
  }
@@ -23229,12 +25127,16 @@ class FormDesignerShellComponent {
23229
25127
  get selectedTemplate() {
23230
25128
  return this.filteredTemplates.find(template => template.id === this.selectedTemplateId) ?? null;
23231
25129
  }
25130
+ constructor() {
25131
+ this.journeyState.setDocument(this.state.schema());
25132
+ this.journeySyncEnabled.set(true);
25133
+ }
23232
25134
  get resolvedHeaderLabel() {
23233
25135
  const explicitLabel = this.headerLabel?.trim();
23234
25136
  if (explicitLabel) {
23235
25137
  return explicitLabel;
23236
25138
  }
23237
- const activeSchema = this.getResolvedExternalSchema() ?? this.state.schema();
25139
+ const activeSchema = this.journeyState.activePage()?.schema ?? this.getResolvedExternalSchema() ?? this.state.schema();
23238
25140
  const schemaTitle = activeSchema?.title?.trim() || 'Untitled Form';
23239
25141
  const schemaVersion = activeSchema?.version?.trim();
23240
25142
  return schemaVersion ? `${schemaTitle} [${schemaVersion}]` : schemaTitle;
@@ -23246,19 +25148,14 @@ class FormDesignerShellComponent {
23246
25148
  if (changes['isReadOnly']) {
23247
25149
  this.state.setIsReadOnly(this.isReadOnly);
23248
25150
  }
23249
- if (changes['schema'] || changes['schemaMetadata']) {
23250
- const resolvedSchema = this.getResolvedExternalSchema();
23251
- if (resolvedSchema) {
23252
- this.state.updateSchema(resolvedSchema);
23253
- this.syncEventDataSources(resolvedSchema);
23254
- this.lastSavedSchemaSnapshot = this.serializeSchema(resolvedSchema);
23255
- }
25151
+ if (changes['schema'] || changes['journey'] || changes['schemaMetadata']) {
25152
+ this.loadExternalDocument();
23256
25153
  }
23257
25154
  if (changes['dataSources']) {
23258
25155
  this.syncDataSources();
23259
25156
  }
23260
25157
  if (changes['eventApis']) {
23261
- this.aiToolRegistry?.setEventApis(this.eventApis ?? []);
25158
+ this.aiFeatureState.setEventApis(this.eventApis ?? []);
23262
25159
  }
23263
25160
  }
23264
25161
  syncDataSources() {
@@ -23412,6 +25309,37 @@ class FormDesignerShellComponent {
23412
25309
  closeAiWorkspace() {
23413
25310
  this.activeWorkspace = 'designer';
23414
25311
  }
25312
+ addPage() {
25313
+ this.persistActivePage();
25314
+ this.journeyState.addPage();
25315
+ this.loadActivePage();
25316
+ }
25317
+ selectPage(id) {
25318
+ if (this.journeyState.activePageId() === id)
25319
+ return;
25320
+ this.persistActivePage();
25321
+ this.journeyState.setActivePage(id);
25322
+ this.loadActivePage();
25323
+ }
25324
+ removePage(id) {
25325
+ if (!this.journeyState.canRemovePage())
25326
+ return;
25327
+ this.persistActivePage();
25328
+ this.journeyState.removePage(id);
25329
+ this.loadActivePage();
25330
+ }
25331
+ renamePage(id, name) {
25332
+ this.journeyState.renamePage(id, name);
25333
+ if (this.journeyState.activePageId() === id) {
25334
+ const current = this.state.schema();
25335
+ if (current.title !== name) {
25336
+ this.state.updateSchema({ ...current, title: name });
25337
+ }
25338
+ }
25339
+ }
25340
+ updatePageRoute(id, route) {
25341
+ this.journeyState.updateRoute(id, route);
25342
+ }
23415
25343
  onEventsSchemaChange(schema) {
23416
25344
  if (this.state.isReadOnly())
23417
25345
  return;
@@ -23449,16 +25377,24 @@ class FormDesignerShellComponent {
23449
25377
  this.pendingTemplate = null;
23450
25378
  }
23451
25379
  onEdit() {
25380
+ this.persistActivePage();
25381
+ const output = this.journeyState.compatibleOutput();
23452
25382
  this.state.setIsReadOnly(false);
23453
- this.edit.emit(this.state.schema());
25383
+ this.edit.emit(output);
25384
+ this.editDocument.emit(output);
23454
25385
  }
23455
25386
  onSave() {
25387
+ this.persistActivePage();
23456
25388
  const schema = this.state.schema();
25389
+ const output = this.journeyState.compatibleOutput();
23457
25390
  this.lastSavedSchemaSnapshot = this.serializeSchema(schema);
23458
- this.save.emit(schema);
25391
+ this.save.emit(output);
25392
+ this.saveDocument.emit(output);
23459
25393
  }
23460
25394
  onPush() {
25395
+ this.persistActivePage();
23461
25396
  const schema = this.state.schema();
25397
+ const output = this.journeyState.compatibleOutput();
23462
25398
  if (this.hasUnsavedChanges(schema)) {
23463
25399
  const confirmed = typeof confirm === 'function'
23464
25400
  ? confirm('You have unsaved changes. Push without saving may publish the previously saved form. Continue?')
@@ -23466,7 +25402,8 @@ class FormDesignerShellComponent {
23466
25402
  if (!confirmed)
23467
25403
  return;
23468
25404
  }
23469
- this.push.emit(schema);
25405
+ this.push.emit(output);
25406
+ this.pushDocument.emit(output);
23470
25407
  }
23471
25408
  trackByTemplateId(_index, template) {
23472
25409
  return template.id;
@@ -23484,6 +25421,18 @@ class FormDesignerShellComponent {
23484
25421
  return JSON.stringify(schema);
23485
25422
  }
23486
25423
  applyTemplate(template) {
25424
+ if (template.journey) {
25425
+ const journey = this.parseTemplateJourney(template.journey);
25426
+ this.journeyState.setDocument(journey);
25427
+ this.loadActivePage();
25428
+ this.selectedTemplateId = template.id;
25429
+ this.closeLibrary();
25430
+ const activeSchema = this.journeyState.activePage()?.schema;
25431
+ if (activeSchema?.flavor && activeSchema.flavor !== this.flavor) {
25432
+ this.switchFlavor(activeSchema.flavor);
25433
+ }
25434
+ return;
25435
+ }
23487
25436
  const schema = parseSchema(JSON.stringify(template.schema));
23488
25437
  schema.id = this.createSchemaId();
23489
25438
  schema.flavor = schema.flavor ?? this.flavor;
@@ -23496,6 +25445,20 @@ class FormDesignerShellComponent {
23496
25445
  this.switchFlavor(resolvedSchema.flavor);
23497
25446
  }
23498
25447
  }
25448
+ parseTemplateJourney(template) {
25449
+ const pages = template.pages.map(page => ({
25450
+ ...page,
25451
+ schema: parseSchema(JSON.stringify(page.schema))
25452
+ }));
25453
+ const startPageId = pages.some(page => page.id === template.startPageId)
25454
+ ? template.startPageId
25455
+ : pages[0]?.id ?? '';
25456
+ return {
25457
+ ...template,
25458
+ pages,
25459
+ startPageId
25460
+ };
25461
+ }
23499
25462
  syncTemplateSelection() {
23500
25463
  const templates = this.filteredTemplates;
23501
25464
  if (!templates.length) {
@@ -23512,7 +25475,31 @@ class FormDesignerShellComponent {
23512
25475
  }
23513
25476
  return `schema-${Date.now()}-${Math.random().toString(16).slice(2)}`;
23514
25477
  }
23515
- getResolvedExternalSchema() {
25478
+ loadExternalDocument() {
25479
+ const resolved = this.getResolvedExternalDocument();
25480
+ if (!resolved)
25481
+ return;
25482
+ this.journeySyncEnabled.set(false);
25483
+ this.journeyState.setDocument(resolved);
25484
+ this.loadActivePage();
25485
+ this.lastSavedSchemaSnapshot = this.serializeSchema(this.state.schema());
25486
+ this.journeySyncEnabled.set(true);
25487
+ }
25488
+ loadActivePage() {
25489
+ const active = this.journeyState.activePage();
25490
+ if (!active)
25491
+ return;
25492
+ this.state.updateSchema(active.schema);
25493
+ this.syncEventDataSources(active.schema);
25494
+ }
25495
+ persistActivePage() {
25496
+ const currentSchema = this.state.schema();
25497
+ this.journeyState.updateActivePageSchema(currentSchema);
25498
+ }
25499
+ getResolvedExternalDocument() {
25500
+ if (this.journey) {
25501
+ return this.journey;
25502
+ }
23516
25503
  if (this.schema) {
23517
25504
  return this.applySchemaMetadata(this.schema);
23518
25505
  }
@@ -23521,6 +25508,17 @@ class FormDesignerShellComponent {
23521
25508
  }
23522
25509
  return undefined;
23523
25510
  }
25511
+ getResolvedExternalSchema() {
25512
+ const external = this.getResolvedExternalDocument();
25513
+ if (!external)
25514
+ return undefined;
25515
+ if (isFormJourneyProject(external)) {
25516
+ return external.pages.find(page => page.id === external.startPageId)?.schema
25517
+ ?? external.pages[0]?.schema
25518
+ ?? undefined;
25519
+ }
25520
+ return external;
25521
+ }
23524
25522
  applySchemaMetadata(schema) {
23525
25523
  const metadata = this.schemaMetadata;
23526
25524
  if (!metadata) {
@@ -23548,7 +25546,7 @@ class FormDesignerShellComponent {
23548
25546
  };
23549
25547
  }
23550
25548
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: FormDesignerShellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
23551
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: FormDesignerShellComponent, isStandalone: true, selector: "app-form-designer-shell", inputs: { schema: "schema", schemaMetadata: "schemaMetadata", flavor: "flavor", flavors: "flavors", templates: "templates", headerLabel: "headerLabel", enableTemplateLibrary: "enableTemplateLibrary", enableGlobalDataManager: "enableGlobalDataManager", enableAiAssistant: "enableAiAssistant", showEditButton: "showEditButton", showSaveButton: "showSaveButton", showPushButton: "showPushButton", showEmailPreview: "showEmailPreview", isReadOnly: "isReadOnly", dataSources: "dataSources", eventApis: "eventApis", eventApiBrowser: "eventApiBrowser", eventApiExecutor: "eventApiExecutor" }, outputs: { flavorChange: "flavorChange", edit: "edit", save: "save", push: "push", eventsSave: "eventsSave" }, host: { classAttribute: "fd-shell-host block h-full w-full" }, usesOnChanges: true, ngImport: i0, template: `
25549
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: FormDesignerShellComponent, isStandalone: true, selector: "app-form-designer-shell", inputs: { schema: "schema", journey: "journey", schemaMetadata: "schemaMetadata", flavor: "flavor", flavors: "flavors", templates: "templates", headerLabel: "headerLabel", enableTemplateLibrary: "enableTemplateLibrary", enableGlobalDataManager: "enableGlobalDataManager", enableAiAssistant: "enableAiAssistant", showEditButton: "showEditButton", showSaveButton: "showSaveButton", showPushButton: "showPushButton", showEmailPreview: "showEmailPreview", isReadOnly: "isReadOnly", dataSources: "dataSources", eventApis: "eventApis", eventApiBrowser: "eventApiBrowser", eventApiExecutor: "eventApiExecutor" }, outputs: { flavorChange: "flavorChange", edit: "edit", save: "save", push: "push", eventsSave: "eventsSave", saveDocument: "saveDocument", pushDocument: "pushDocument", editDocument: "editDocument" }, host: { classAttribute: "fd-shell-host block h-full w-full" }, providers: [FormJourneyStateService, ...provideFormDesignerAngaiFeature()], usesOnChanges: true, ngImport: i0, template: `
23552
25550
  <div
23553
25551
  class="fd-shell flex flex-col h-screen bg-white font-sans"
23554
25552
  data-fd="shell"
@@ -23669,6 +25667,7 @@ class FormDesignerShellComponent {
23669
25667
  data-fd-slot="events-workspace"
23670
25668
  *ngIf="activeWorkspace === 'events'; else designerWorkspace"
23671
25669
  [schema]="state.schema()"
25670
+ [pages]="designerPages()"
23672
25671
  [readOnly]="state.isReadOnly()"
23673
25672
  [eventApis]="eventApis"
23674
25673
  [eventApiBrowser]="eventApiBrowser"
@@ -23683,7 +25682,15 @@ class FormDesignerShellComponent {
23683
25682
  [flavor]="flavor"
23684
25683
  [mode]="state.isReadOnly() ? 'view' : 'edit'"
23685
25684
  [eventApis]="eventApis"
23686
- [eventApiExecutor]="eventApiExecutor">
25685
+ [eventApiExecutor]="eventApiExecutor"
25686
+ [pages]="designerPages()"
25687
+ [activePageId]="journeyState.activePageId()"
25688
+ [canRemovePage]="journeyState.canRemovePage()"
25689
+ (pageAdd)="addPage()"
25690
+ (pageSelect)="selectPage($event)"
25691
+ (pageRemove)="removePage($event)"
25692
+ (pageRename)="renamePage($event.id, $event.name)"
25693
+ (pageRouteChange)="updatePageRoute($event.id, $event.route)">
23687
25694
  </app-json-form-designer>
23688
25695
  </ng-template>
23689
25696
 
@@ -23823,13 +25830,13 @@ class FormDesignerShellComponent {
23823
25830
  </div>
23824
25831
  </section>
23825
25832
  </div>
23826
- `, isInline: true, styles: ["@keyframes slideInRight{0%{transform:translate(100%);opacity:0}to{transform:translate(0);opacity:1}}.animate-slide-in-right{animation:slideInRight .3s cubic-bezier(.16,1,.3,1)}\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: "pipe", type: i1.TitleCasePipe, name: "titlecase" }, { kind: "component", type: JsonFormDesignerComponent, selector: "app-json-form-designer", inputs: ["flavor", "mode", "eventApis", "eventApiExecutor"] }, { kind: "component", type: EventsWorkspaceComponent, selector: "app-events-workspace", inputs: ["schema", "readOnly", "eventApis", "eventApiBrowser"], outputs: ["schemaChange", "eventsSave"] }, { 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: EmailRendererComponent, selector: "app-email-renderer", inputs: ["schema", "engine"] }, { kind: "component", type: GlobalDataManagerComponent, selector: "app-global-data-manager", outputs: ["close"] }, { kind: "component", type: AiChatDrawerComponent, selector: "app-ai-chat-drawer", outputs: ["close", "expand"] }, { kind: "component", type: AiWorkspaceComponent, selector: "app-ai-workspace", outputs: ["close"] }] });
25833
+ `, isInline: true, styles: ["@keyframes slideInRight{0%{transform:translate(100%);opacity:0}to{transform:translate(0);opacity:1}}.animate-slide-in-right{animation:slideInRight .3s cubic-bezier(.16,1,.3,1)}\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: "pipe", type: i1.TitleCasePipe, name: "titlecase" }, { kind: "component", type: JsonFormDesignerComponent, selector: "app-json-form-designer", inputs: ["flavor", "mode", "eventApis", "eventApiExecutor", "pages", "activePageId", "canRemovePage"], outputs: ["pageAdd", "pageSelect", "pageRemove", "pageRename", "pageRouteChange"] }, { kind: "component", type: EventsWorkspaceComponent, selector: "app-events-workspace", inputs: ["schema", "readOnly", "eventApis", "eventApiBrowser", "pages"], outputs: ["schemaChange", "eventsSave"] }, { kind: "component", type: JsonFormRendererComponent, selector: "app-json-form-renderer", inputs: ["schema", "initialValues", "initialFieldLabels", "mode", "device", "showLayoutGuides", "breakpoint", "eventLogger", "eventApis", "eventApiExecutor", "navigateToPage", "uploadOnSubmit", "fieldDataAccessMap", "fieldDataAccessApi", "formContentId", "formContentVersion"], outputs: ["valueChange", "groupedValueChange", "combinedValueChange", "validationChange", "uploadedFilesChange", "formSubmit"] }, { kind: "component", type: EmailRendererComponent, selector: "app-email-renderer", inputs: ["schema", "engine"] }, { kind: "component", type: GlobalDataManagerComponent, selector: "app-global-data-manager", outputs: ["close"] }, { kind: "component", type: AiChatDrawerComponent, selector: "app-ai-chat-drawer", outputs: ["close", "expand"] }, { kind: "component", type: AiWorkspaceComponent, selector: "app-ai-workspace", outputs: ["close"] }] });
23827
25834
  }
23828
25835
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: FormDesignerShellComponent, decorators: [{
23829
25836
  type: Component,
23830
25837
  args: [{ selector: 'app-form-designer-shell', standalone: true, host: {
23831
25838
  class: 'fd-shell-host block h-full w-full'
23832
- }, imports: [
25839
+ }, providers: [FormJourneyStateService, ...provideFormDesignerAngaiFeature()], imports: [
23833
25840
  CommonModule,
23834
25841
  JsonFormDesignerComponent,
23835
25842
  EventsWorkspaceComponent,
@@ -23959,6 +25966,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
23959
25966
  data-fd-slot="events-workspace"
23960
25967
  *ngIf="activeWorkspace === 'events'; else designerWorkspace"
23961
25968
  [schema]="state.schema()"
25969
+ [pages]="designerPages()"
23962
25970
  [readOnly]="state.isReadOnly()"
23963
25971
  [eventApis]="eventApis"
23964
25972
  [eventApiBrowser]="eventApiBrowser"
@@ -23973,7 +25981,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
23973
25981
  [flavor]="flavor"
23974
25982
  [mode]="state.isReadOnly() ? 'view' : 'edit'"
23975
25983
  [eventApis]="eventApis"
23976
- [eventApiExecutor]="eventApiExecutor">
25984
+ [eventApiExecutor]="eventApiExecutor"
25985
+ [pages]="designerPages()"
25986
+ [activePageId]="journeyState.activePageId()"
25987
+ [canRemovePage]="journeyState.canRemovePage()"
25988
+ (pageAdd)="addPage()"
25989
+ (pageSelect)="selectPage($event)"
25990
+ (pageRemove)="removePage($event)"
25991
+ (pageRename)="renamePage($event.id, $event.name)"
25992
+ (pageRouteChange)="updatePageRoute($event.id, $event.route)">
23977
25993
  </app-json-form-designer>
23978
25994
  </ng-template>
23979
25995
 
@@ -24114,7 +26130,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
24114
26130
  </section>
24115
26131
  </div>
24116
26132
  `, styles: ["@keyframes slideInRight{0%{transform:translate(100%);opacity:0}to{transform:translate(0);opacity:1}}.animate-slide-in-right{animation:slideInRight .3s cubic-bezier(.16,1,.3,1)}\n"] }]
24117
- }], propDecorators: { schema: [{
26133
+ }], ctorParameters: () => [], propDecorators: { schema: [{
26134
+ type: Input
26135
+ }], journey: [{
24118
26136
  type: Input
24119
26137
  }], schemaMetadata: [{
24120
26138
  type: Input
@@ -24160,6 +26178,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
24160
26178
  type: Output
24161
26179
  }], eventsSave: [{
24162
26180
  type: Output
26181
+ }], saveDocument: [{
26182
+ type: Output
26183
+ }], pushDocument: [{
26184
+ type: Output
26185
+ }], editDocument: [{
26186
+ type: Output
24163
26187
  }] } });
24164
26188
 
24165
26189
  class EventsPanelComponent {
@@ -27171,7 +29195,7 @@ class WebsiteDesignerShellComponent {
27171
29195
 
27172
29196
  <input type="file" #projectFileInput accept=".json" (change)="onProjectFileSelected($event)" class="hidden">
27173
29197
  </div>
27174
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i3.CdkDropListGroup, selector: "[cdkDropListGroup]", inputs: ["cdkDropListGroupDisabled"], exportAs: ["cdkDropListGroup"] }, { kind: "ngmodule", type: UiIconModule }, { kind: "component", type: FieldPaletteComponent, selector: "app-field-palette", inputs: ["sectionLibrary", "pages", "activePageId", "canRemovePage", "savedSections", "bricks"], outputs: ["sectionInsert", "pageAdd", "pageSelect", "pageRemove", "pageRename", "pageRouteChange", "savedSectionInsert", "savedSectionRemove", "savedSectionCreateRequested"] }, { kind: "component", type: LayoutCanvasComponent, selector: "app-layout-canvas", inputs: ["previewMode"], outputs: ["previewRequested"] }, { kind: "component", type: PropertiesPanelComponent, selector: "app-properties-panel" }, { kind: "component", type: FormPreviewComponent, selector: "app-form-preview", inputs: ["eventApis", "eventApiExecutor", "fieldDataAccessMap", "fieldDataAccessApi", "formContentId", "formContentVersion"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
29198
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i3.CdkDropListGroup, selector: "[cdkDropListGroup]", inputs: ["cdkDropListGroupDisabled"], exportAs: ["cdkDropListGroup"] }, { kind: "ngmodule", type: UiIconModule }, { kind: "component", type: FieldPaletteComponent, selector: "app-field-palette", inputs: ["sectionLibrary", "pages", "activePageId", "canRemovePage", "savedSections", "bricks"], outputs: ["sectionInsert", "pageAdd", "pageSelect", "pageRemove", "pageRename", "pageRouteChange", "savedSectionInsert", "savedSectionRemove", "savedSectionCreateRequested"] }, { kind: "component", type: LayoutCanvasComponent, selector: "app-layout-canvas", inputs: ["previewMode"], outputs: ["previewRequested"] }, { kind: "component", type: PropertiesPanelComponent, selector: "app-properties-panel" }, { kind: "component", type: FormPreviewComponent, selector: "app-form-preview", inputs: ["eventApis", "eventApiExecutor", "fieldDataAccessMap", "fieldDataAccessApi", "formContentId", "formContentVersion", "pages", "activePageId"], outputs: ["pageSelect"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
27175
29199
  }
27176
29200
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: WebsiteDesignerShellComponent, decorators: [{
27177
29201
  type: Component,
@@ -29060,7 +31084,7 @@ class WebsitePreviewShellComponent {
29060
31084
  }
29061
31085
  </main>
29062
31086
  </div>
29063
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { 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"] }] });
31087
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: JsonFormRendererComponent, selector: "app-json-form-renderer", inputs: ["schema", "initialValues", "initialFieldLabels", "mode", "device", "showLayoutGuides", "breakpoint", "eventLogger", "eventApis", "eventApiExecutor", "navigateToPage", "uploadOnSubmit", "fieldDataAccessMap", "fieldDataAccessApi", "formContentId", "formContentVersion"], outputs: ["valueChange", "groupedValueChange", "combinedValueChange", "validationChange", "uploadedFilesChange", "formSubmit"] }] });
29064
31088
  }
29065
31089
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: WebsitePreviewShellComponent, decorators: [{
29066
31090
  type: Component,
@@ -29701,7 +31725,7 @@ function getTextControlClass(options) {
29701
31725
  : 'border-transparent focus:border-transparent focus:ring-0'
29702
31726
  : options?.invalid
29703
31727
  ? TEXT_CONTROL_INVALID_CLASS
29704
- : TEXT_CONTROL_VALID_CLASS, 'h-full min-h-0', multiline ? 'min-h-[96px] py-2 leading-6 resize-y' : compact ? 'min-h-[20px] py-0.5' : 'min-h-[20px] py-0.5', options?.leadingInset && 'pl-10', options?.trailingInset && 'pr-10');
31728
+ : TEXT_CONTROL_VALID_CLASS, 'h-full min-h-0', multiline ? 'min-h-[96px] py-2 leading-6 resize-y' : compact ? 'min-h-[30px] py-0.5' : 'min-h-[30px] py-0.5', options?.leadingInset && 'pl-10', options?.trailingInset && 'pr-10');
29705
31729
  }
29706
31730
  function getChoiceControlClass(options) {
29707
31731
  return joinClasses('mt-0.5 h-[18px] w-[18px] border-2 bg-[#FFFFFF] text-[#7FB2FF] transition focus:ring-2 focus:ring-[#7FB2FF] focus:ring-offset-0', options?.radio ? 'rounded-full' : 'rounded-[2px]', options?.invalid ? 'border-red-500 focus:ring-red-200' : 'border-[#3F8CFF] checked:border-[#7FB2FF]');
@@ -29791,6 +31815,9 @@ class TextFieldWidgetComponent {
29791
31815
  isTextarea() {
29792
31816
  return this.config?.type === 'textarea';
29793
31817
  }
31818
+ isColorField() {
31819
+ return this.config?.type === 'color';
31820
+ }
29794
31821
  getControlClass(multiline = false) {
29795
31822
  return getTextControlClass({
29796
31823
  invalid: !!this.error,
@@ -29896,6 +31923,23 @@ class TextFieldWidgetComponent {
29896
31923
  onMouseLeave() {
29897
31924
  this.isControlHovered = false;
29898
31925
  }
31926
+ getColorPickerValue() {
31927
+ const currentValue = this.control.value;
31928
+ if (typeof currentValue === 'string' && currentValue.trim().length > 0) {
31929
+ return currentValue;
31930
+ }
31931
+ const defaultValue = this.config?.defaultValue;
31932
+ if (typeof defaultValue === 'string' && defaultValue.trim().length > 0) {
31933
+ return defaultValue;
31934
+ }
31935
+ return '#000000';
31936
+ }
31937
+ onColorPickerChange(color) {
31938
+ if (this.control.pristine) {
31939
+ this.control.markAsDirty();
31940
+ }
31941
+ this.control.setValue(color);
31942
+ }
29899
31943
  ngOnDestroy() {
29900
31944
  // Cleanup handled by takeUntilDestroyed
29901
31945
  }
@@ -30074,6 +32118,31 @@ class TextFieldWidgetComponent {
30074
32118
  [class]="getControlClass(true)"
30075
32119
  [ngStyle]="getControlStyles()"
30076
32120
  data-fd="field-control"></textarea>
32121
+ } @else if (isColorField()) {
32122
+ <input
32123
+ [id]="fieldId"
32124
+ type="text"
32125
+ [placeholder]="config.placeholder || ''"
32126
+ [formControl]="control"
32127
+ (click)="onClick()"
32128
+ (focus)="onFocus()"
32129
+ (blur)="onBlur()"
32130
+ (mouseenter)="onMouseEnter()"
32131
+ (mouseleave)="onMouseLeave()"
32132
+ [readonly]="config.readonly"
32133
+ [attr.aria-required]="required"
32134
+ [attr.aria-invalid]="!!error"
32135
+ [attr.aria-describedby]="getAriaDescribedBy()"
32136
+ [attr.aria-label]="getAccessibleLabel()"
32137
+ [class]="getControlClass()"
32138
+ [ngStyle]="getControlStyles()"
32139
+ data-fd="field-control"
32140
+ [colorPicker]="getColorPickerValue()"
32141
+ [cpAlphaChannel]="'disabled'"
32142
+ [cpOutputFormat]="'hex'"
32143
+ [cpFallbackColor]="'#000000'"
32144
+ [cpDisabled]="config.readonly || !enabled"
32145
+ (colorPickerChange)="onColorPickerChange($event)">
30077
32146
  } @else {
30078
32147
  <input
30079
32148
  [id]="fieldId"
@@ -30107,14 +32176,14 @@ class TextFieldWidgetComponent {
30107
32176
  <p [id]="helpTextId" [class]="fieldHelpClass" data-fd="field-help">{{ config.helpText }}</p>
30108
32177
  }
30109
32178
  </div>
30110
- `, isInline: true, 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.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }] });
32179
+ `, isInline: true, 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.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: ColorPickerDirective, selector: "[colorPicker]", inputs: ["colorPicker", "cpWidth", "cpHeight", "cpToggle", "cpDisabled", "cpIgnoredElements", "cpFallbackColor", "cpColorMode", "cpCmykEnabled", "cpOutputFormat", "cpAlphaChannel", "cpDisableInput", "cpDialogDisplay", "cpSaveClickOutside", "cpCloseClickOutside", "cpUseRootViewContainer", "cpPosition", "cpPositionOffset", "cpPositionRelativeToArrow", "cpOKButton", "cpOKButtonText", "cpOKButtonClass", "cpCancelButton", "cpCancelButtonText", "cpCancelButtonClass", "cpEyeDropper", "cpPresetLabel", "cpPresetColors", "cpPresetColorsClass", "cpMaxPresetColorsLength", "cpPresetEmptyMessage", "cpPresetEmptyMessageClass", "cpAddColorButton", "cpAddColorButtonText", "cpAddColorButtonClass", "cpRemoveColorButtonClass", "cpArrowPosition", "cpExtraTemplate"], outputs: ["cpInputChange", "cpToggleChange", "cpSliderChange", "cpSliderDragEnd", "cpSliderDragStart", "colorPickerOpen", "colorPickerClose", "colorPickerCancel", "colorPickerSelect", "colorPickerChange", "cpCmykColorChange", "cpPresetColorsChange"], exportAs: ["ngxColorPicker"] }] });
30111
32180
  }
30112
32181
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: TextFieldWidgetComponent, decorators: [{
30113
32182
  type: Component,
30114
32183
  args: [{
30115
32184
  selector: 'app-text-field-widget',
30116
32185
  standalone: true,
30117
- imports: [CommonModule, ReactiveFormsModule],
32186
+ imports: [CommonModule, ReactiveFormsModule, ColorPickerDirective],
30118
32187
  template: `
30119
32188
  <div [class]="fieldContainerClass"
30120
32189
  [class.hidden]="!visible"
@@ -30153,6 +32222,31 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
30153
32222
  [class]="getControlClass(true)"
30154
32223
  [ngStyle]="getControlStyles()"
30155
32224
  data-fd="field-control"></textarea>
32225
+ } @else if (isColorField()) {
32226
+ <input
32227
+ [id]="fieldId"
32228
+ type="text"
32229
+ [placeholder]="config.placeholder || ''"
32230
+ [formControl]="control"
32231
+ (click)="onClick()"
32232
+ (focus)="onFocus()"
32233
+ (blur)="onBlur()"
32234
+ (mouseenter)="onMouseEnter()"
32235
+ (mouseleave)="onMouseLeave()"
32236
+ [readonly]="config.readonly"
32237
+ [attr.aria-required]="required"
32238
+ [attr.aria-invalid]="!!error"
32239
+ [attr.aria-describedby]="getAriaDescribedBy()"
32240
+ [attr.aria-label]="getAccessibleLabel()"
32241
+ [class]="getControlClass()"
32242
+ [ngStyle]="getControlStyles()"
32243
+ data-fd="field-control"
32244
+ [colorPicker]="getColorPickerValue()"
32245
+ [cpAlphaChannel]="'disabled'"
32246
+ [cpOutputFormat]="'hex'"
32247
+ [cpFallbackColor]="'#000000'"
32248
+ [cpDisabled]="config.readonly || !enabled"
32249
+ (colorPickerChange)="onColorPickerChange($event)">
30156
32250
  } @else {
30157
32251
  <input
30158
32252
  [id]="fieldId"
@@ -30606,6 +32700,62 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
30606
32700
  args: ['fileInput']
30607
32701
  }] } });
30608
32702
 
32703
+ function resolveOptionFieldLabel(value, options, existingLabel) {
32704
+ if (value === undefined || value === null || value === '') {
32705
+ return undefined;
32706
+ }
32707
+ if (Array.isArray(value)) {
32708
+ if (value.length === 0) {
32709
+ return undefined;
32710
+ }
32711
+ const labels = value.map((entry, index) => resolveOptionLabel(entry, options, getExistingLabelEntry(existingLabel, index)));
32712
+ return labels.some(label => label !== undefined)
32713
+ ? labels.map((label, index) => label ?? String(value[index] ?? ''))
32714
+ : undefined;
32715
+ }
32716
+ return resolveOptionLabel(value, options, typeof existingLabel === 'string' ? existingLabel : undefined);
32717
+ }
32718
+ function buildFallbackOptions(value, label) {
32719
+ if (value === undefined || value === null || value === '') {
32720
+ return [];
32721
+ }
32722
+ if (Array.isArray(value)) {
32723
+ return value
32724
+ .filter(entry => entry !== undefined && entry !== null && String(entry).length > 0)
32725
+ .map((entry, index) => ({
32726
+ label: getExistingLabelEntry(label, index) ?? String(entry),
32727
+ value: toOptionValue(entry)
32728
+ }));
32729
+ }
32730
+ return [{
32731
+ label: typeof label === 'string' && label.length > 0 ? label : String(value),
32732
+ value: toOptionValue(value)
32733
+ }];
32734
+ }
32735
+ function resolveOptionLabel(value, options, existingLabel) {
32736
+ const match = options.find(option => Object.is(option.value, value) || String(option.value) === String(value));
32737
+ if (match?.label) {
32738
+ return match.label;
32739
+ }
32740
+ return existingLabel;
32741
+ }
32742
+ function getExistingLabelEntry(label, index) {
32743
+ if (Array.isArray(label)) {
32744
+ const entry = label[index];
32745
+ return typeof entry === 'string' && entry.length > 0 ? entry : undefined;
32746
+ }
32747
+ if (typeof label === 'string' && index === 0 && label.length > 0) {
32748
+ return label;
32749
+ }
32750
+ return undefined;
32751
+ }
32752
+ function toOptionValue(value) {
32753
+ if (typeof value === 'number' && Number.isFinite(value)) {
32754
+ return value;
32755
+ }
32756
+ return String(value ?? '');
32757
+ }
32758
+
30609
32759
  const SELECT_CONTROL_STYLE_KEYS = [
30610
32760
  'height',
30611
32761
  'minHeight',
@@ -30723,6 +32873,7 @@ class SelectWidgetComponent {
30723
32873
  .pipe(takeUntilDestroyed(this.destroyRef))
30724
32874
  .subscribe(val => {
30725
32875
  if (this.engine) {
32876
+ this.syncStoredFieldLabel(val);
30726
32877
  this.engine.setValue(this.config.name, val);
30727
32878
  this.runtimeFieldDataAccessRegistry.invalidateFieldAndDescendants(this.engine, this.config.id);
30728
32879
  this.engine.emitUiEvent({ fieldId: this.config.id, fieldName: this.config.name, type: 'change', value: val });
@@ -30734,13 +32885,24 @@ class SelectWidgetComponent {
30734
32885
  this.syncEnabledState();
30735
32886
  this.runtimeManagedField = this.runtimeFieldDataAccessRegistry.hasFieldAccess(this.config, this.engine);
30736
32887
  if (!this.runtimeManagedField) {
30737
- void this.loadOptions(this.currentSearchTerm);
32888
+ if (this.areApiCallsSuppressed() && this.hasSelectedValue()) {
32889
+ this.options = this.withSelectedValueFallbackOptions([]);
32890
+ }
32891
+ else {
32892
+ void this.loadOptions(this.currentSearchTerm);
32893
+ }
30738
32894
  this.runtimeOptionsLoaded = true;
30739
32895
  }
30740
32896
  else if (this.hasSelectedValue()) {
30741
- void this.loadOptions(this.currentSearchTerm).then(() => {
30742
- this.runtimeOptionsLoaded = !this.loadError;
30743
- });
32897
+ if (this.areApiCallsSuppressed()) {
32898
+ this.options = this.withSelectedValueFallbackOptions([]);
32899
+ this.runtimeOptionsLoaded = true;
32900
+ }
32901
+ else {
32902
+ void this.loadOptions(this.currentSearchTerm).then(() => {
32903
+ this.runtimeOptionsLoaded = !this.loadError;
32904
+ });
32905
+ }
30744
32906
  }
30745
32907
  const dependencyIds = this.getDependencyFieldIds();
30746
32908
  if (this.engine) {
@@ -30750,6 +32912,10 @@ class SelectWidgetComponent {
30750
32912
  .pipe(takeUntilDestroyed(this.destroyRef))
30751
32913
  .subscribe(values => {
30752
32914
  this.syncEnabledState();
32915
+ if (this.areApiCallsSuppressed()) {
32916
+ this.options = this.withSelectedValueFallbackOptions(this.options);
32917
+ this.cdr.markForCheck();
32918
+ }
30753
32919
  if (dependencyIds.length === 0)
30754
32920
  return;
30755
32921
  const schema = this.engine.getSchema();
@@ -30974,7 +33140,8 @@ class SelectWidgetComponent {
30974
33140
  opts = await this.dataProvider.getOptions(this.config, this.engine);
30975
33141
  }
30976
33142
  if (this.requestId === reqId) {
30977
- this.options = opts;
33143
+ this.options = this.withSelectedValueFallbackOptions(opts);
33144
+ this.syncStoredFieldLabel(this.control.value);
30978
33145
  this.loading = false;
30979
33146
  this.loadError = null;
30980
33147
  this.cdr.markForCheck();
@@ -30985,7 +33152,7 @@ class SelectWidgetComponent {
30985
33152
  this.loading = false;
30986
33153
  this.loadError = 'Failed to load options.';
30987
33154
  if (!isSearch) {
30988
- this.options = this.config.staticOptions || [];
33155
+ this.options = this.withSelectedValueFallbackOptions(this.config.staticOptions || []);
30989
33156
  }
30990
33157
  this.cdr.markForCheck();
30991
33158
  }
@@ -31005,6 +33172,11 @@ class SelectWidgetComponent {
31005
33172
  return;
31006
33173
  if (this.runtimeOptionsLoaded && this.options.length > 0)
31007
33174
  return;
33175
+ if (this.areApiCallsSuppressed()) {
33176
+ this.options = this.withSelectedValueFallbackOptions(this.options);
33177
+ this.runtimeOptionsLoaded = true;
33178
+ return;
33179
+ }
31008
33180
  await this.loadOptions(this.currentSearchTerm);
31009
33181
  if (!this.loadError) {
31010
33182
  this.runtimeOptionsLoaded = true;
@@ -31034,14 +33206,7 @@ class SelectWidgetComponent {
31034
33206
  return changed;
31035
33207
  }
31036
33208
  hasSelectedValue() {
31037
- const value = this.control.value;
31038
- if (value === undefined || value === null)
31039
- return false;
31040
- if (typeof value === 'string')
31041
- return value.length > 0;
31042
- if (Array.isArray(value))
31043
- return value.length > 0;
31044
- return true;
33209
+ return this.hasMeaningfulValue(this.resolveSelectedValueForFallback());
31045
33210
  }
31046
33211
  handleConfigChange() {
31047
33212
  this.syncEnabledState();
@@ -31051,6 +33216,10 @@ class SelectWidgetComponent {
31051
33216
  this.loadError = null;
31052
33217
  this.currentSearchTerm = '';
31053
33218
  if (!this.runtimeManagedField) {
33219
+ if (this.areApiCallsSuppressed() && this.hasSelectedValue()) {
33220
+ this.options = this.withSelectedValueFallbackOptions([]);
33221
+ return;
33222
+ }
31054
33223
  void this.loadOptions(this.currentSearchTerm);
31055
33224
  return;
31056
33225
  }
@@ -31140,6 +33309,72 @@ class SelectWidgetComponent {
31140
33309
  }
31141
33310
  return fallback;
31142
33311
  }
33312
+ areApiCallsSuppressed() {
33313
+ return areEngineApiCallsSuppressed(this.engine);
33314
+ }
33315
+ withSelectedValueFallbackOptions(options) {
33316
+ const selected = this.getSelectedFallbackOptions();
33317
+ if (selected.length === 0) {
33318
+ return options;
33319
+ }
33320
+ const merged = [...options];
33321
+ for (const option of selected) {
33322
+ if (this.hasOptionValue(merged, option.value))
33323
+ continue;
33324
+ merged.unshift(option);
33325
+ }
33326
+ return merged;
33327
+ }
33328
+ getSelectedFallbackOptions() {
33329
+ const rawValue = this.resolveSelectedValueForFallback();
33330
+ if (!this.hasMeaningfulValue(rawValue)) {
33331
+ return [];
33332
+ }
33333
+ return buildFallbackOptions(rawValue, this.getStoredFieldLabel());
33334
+ }
33335
+ hasOptionValue(options, value) {
33336
+ return options.some(option => Object.is(option.value, value) || String(option.value) === String(value));
33337
+ }
33338
+ resolveSelectedValueForFallback() {
33339
+ const controlValue = this.control.value;
33340
+ if (this.hasMeaningfulValue(controlValue)) {
33341
+ return controlValue;
33342
+ }
33343
+ if (!this.engine
33344
+ || !this.config?.name
33345
+ || typeof this.engine.getValue !== 'function') {
33346
+ return controlValue;
33347
+ }
33348
+ return this.engine.getValue(this.config.name);
33349
+ }
33350
+ hasMeaningfulValue(value) {
33351
+ if (value === undefined || value === null)
33352
+ return false;
33353
+ if (typeof value === 'string')
33354
+ return value.length > 0;
33355
+ if (Array.isArray(value))
33356
+ return value.length > 0;
33357
+ return true;
33358
+ }
33359
+ syncStoredFieldLabel(value) {
33360
+ if (!this.engine || !this.config?.name) {
33361
+ return;
33362
+ }
33363
+ const label = resolveOptionFieldLabel(value, this.options, this.getStoredFieldLabel());
33364
+ this.setStoredFieldLabel(label);
33365
+ }
33366
+ getStoredFieldLabel() {
33367
+ if (!this.engine || typeof this.engine.getFieldLabel !== 'function') {
33368
+ return undefined;
33369
+ }
33370
+ return this.engine.getFieldLabel(this.config.name);
33371
+ }
33372
+ setStoredFieldLabel(label) {
33373
+ if (!this.engine || typeof this.engine.setFieldLabel !== 'function') {
33374
+ return;
33375
+ }
33376
+ this.engine.setFieldLabel(this.config.name, label);
33377
+ }
31143
33378
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SelectWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
31144
33379
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: SelectWidgetComponent, isStandalone: true, selector: "app-select-widget", inputs: { config: "config", engine: "engine", control: "control" }, viewQueries: [{ propertyName: "ngSelectComponent", first: true, predicate: NgSelectComponent, descendants: true }], ngImport: i0, template: `
31145
33380
  <div [class]="fieldContainerClass"
@@ -31217,7 +33452,7 @@ class SelectWidgetComponent {
31217
33452
  <p [id]="helpTextId" [class]="fieldHelpClass" data-fd="field-help">{{ config.helpText }}</p>
31218
33453
  }
31219
33454
  </div>
31220
- `, 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"] }] });
33455
+ `, 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, 30px);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, 30px);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"] }] });
31221
33456
  }
31222
33457
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SelectWidgetComponent, decorators: [{
31223
33458
  type: Component,
@@ -31297,7 +33532,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
31297
33532
  <p [id]="helpTextId" [class]="fieldHelpClass" data-fd="field-help">{{ config.helpText }}</p>
31298
33533
  }
31299
33534
  </div>
31300
- `, 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"] }]
33535
+ `, 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, 30px);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, 30px);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"] }]
31301
33536
  }], propDecorators: { ngSelectComponent: [{
31302
33537
  type: ViewChild,
31303
33538
  args: [NgSelectComponent]
@@ -31332,6 +33567,7 @@ class SearchWidgetComponent {
31332
33567
  requestId = 0;
31333
33568
  syncingFromQuery = false;
31334
33569
  selectingOption = false;
33570
+ pendingSelectedLabel;
31335
33571
  focused = false;
31336
33572
  hovered = false;
31337
33573
  blurCloseTimeout = null;
@@ -31388,7 +33624,7 @@ class SearchWidgetComponent {
31388
33624
  }
31389
33625
  ngOnInit() {
31390
33626
  this.syncEnabledState();
31391
- this.queryControl.setValue(this.asQueryString(this.control.value), { emitEvent: false });
33627
+ this.queryControl.setValue(this.getDisplayValue(this.control.value), { emitEvent: false });
31392
33628
  this.queryControl.valueChanges
31393
33629
  .pipe(debounceTime(this.getDebounceMs()), distinctUntilChanged(), takeUntilDestroyed(this.destroyRef))
31394
33630
  .subscribe(query => {
@@ -31398,6 +33634,16 @@ class SearchWidgetComponent {
31398
33634
  .pipe(takeUntilDestroyed(this.destroyRef))
31399
33635
  .subscribe(value => {
31400
33636
  if (this.engine) {
33637
+ if (this.pendingSelectedLabel !== undefined) {
33638
+ this.setStoredFieldLabel(this.pendingSelectedLabel);
33639
+ this.pendingSelectedLabel = undefined;
33640
+ }
33641
+ else if (this.syncingFromQuery) {
33642
+ this.setStoredFieldLabel(undefined);
33643
+ }
33644
+ else {
33645
+ this.setStoredFieldLabel(resolveOptionFieldLabel(value, this.options, this.getStoredFieldLabel()));
33646
+ }
31401
33647
  this.engine.setValue(this.config.name, value);
31402
33648
  this.runtimeFieldDataAccessRegistry.invalidateFieldAndDescendants(this.engine, this.config.id);
31403
33649
  this.engine.emitUiEvent({
@@ -31413,7 +33659,7 @@ class SearchWidgetComponent {
31413
33659
  if (this.syncingFromQuery || this.selectingOption) {
31414
33660
  return;
31415
33661
  }
31416
- this.queryControl.setValue(this.asQueryString(value), { emitEvent: false });
33662
+ this.queryControl.setValue(this.getDisplayValue(value), { emitEvent: false });
31417
33663
  });
31418
33664
  if (this.engine) {
31419
33665
  const dataSourceUpdates$ = this.engine.dataSourceUpdates$;
@@ -31529,6 +33775,7 @@ class SearchWidgetComponent {
31529
33775
  }
31530
33776
  selectOption(option) {
31531
33777
  this.selectingOption = true;
33778
+ this.pendingSelectedLabel = String(option.label ?? '');
31532
33779
  this.queryControl.setValue(String(option.label ?? ''), { emitEvent: false });
31533
33780
  this.options = [];
31534
33781
  this.activeOptionIndex = -1;
@@ -31666,6 +33913,25 @@ class SearchWidgetComponent {
31666
33913
  return '';
31667
33914
  return String(value);
31668
33915
  }
33916
+ getDisplayValue(value) {
33917
+ const fieldLabel = this.getStoredFieldLabel();
33918
+ if (typeof fieldLabel === 'string' && fieldLabel.length > 0) {
33919
+ return fieldLabel;
33920
+ }
33921
+ return this.asQueryString(value);
33922
+ }
33923
+ getStoredFieldLabel() {
33924
+ if (!this.engine || typeof this.engine.getFieldLabel !== 'function') {
33925
+ return undefined;
33926
+ }
33927
+ return this.engine.getFieldLabel(this.config.name);
33928
+ }
33929
+ setStoredFieldLabel(label) {
33930
+ if (!this.engine || typeof this.engine.setFieldLabel !== 'function') {
33931
+ return;
33932
+ }
33933
+ this.engine.setFieldLabel(this.config.name, label);
33934
+ }
31669
33935
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SearchWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
31670
33936
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: SearchWidgetComponent, isStandalone: true, selector: "app-search-widget", inputs: { config: "config", engine: "engine", control: "control" }, ngImport: i0, template: `
31671
33937
  <div [class]="fieldContainerClass"
@@ -32428,6 +34694,7 @@ class RadioWidgetComponent {
32428
34694
  .pipe(takeUntilDestroyed(this.destroyRef))
32429
34695
  .subscribe(val => {
32430
34696
  if (this.engine) {
34697
+ this.setStoredFieldLabel(resolveOptionFieldLabel(val, this.options, this.getStoredFieldLabel()));
32431
34698
  this.engine.setValue(this.config.name, val);
32432
34699
  this.engine.emitUiEvent({ fieldId: this.config.id, fieldName: this.config.name, type: 'change', value: val });
32433
34700
  if (this.control.dirty) {
@@ -32436,7 +34703,12 @@ class RadioWidgetComponent {
32436
34703
  }
32437
34704
  });
32438
34705
  this.syncEnabledState();
32439
- await this.loadOptions();
34706
+ if (this.areApiCallsSuppressed() && this.hasSelectedValue()) {
34707
+ this.options = this.withSelectedValueFallbackOptions([]);
34708
+ }
34709
+ else {
34710
+ await this.loadOptions();
34711
+ }
32440
34712
  // Dependencies
32441
34713
  const dependencyIds = this.getDependencyFieldIds();
32442
34714
  if (this.engine) {
@@ -32492,7 +34764,10 @@ class RadioWidgetComponent {
32492
34764
  // Radio doesn't typically support search, so just get options
32493
34765
  opts = await this.dataProvider.getOptions(this.config, this.engine);
32494
34766
  if (this.requestId === reqId) {
32495
- this.options = opts;
34767
+ this.options = this.withSelectedValueFallbackOptions(opts);
34768
+ if (this.engine) {
34769
+ this.setStoredFieldLabel(resolveOptionFieldLabel(this.control.value, this.options, this.getStoredFieldLabel()));
34770
+ }
32496
34771
  this.loading = false;
32497
34772
  this.loadError = null;
32498
34773
  this.cdr.markForCheck();
@@ -32502,7 +34777,7 @@ class RadioWidgetComponent {
32502
34777
  if (this.requestId === reqId) {
32503
34778
  this.loading = false;
32504
34779
  this.loadError = 'Failed to load options.';
32505
- this.options = this.config.staticOptions || [];
34780
+ this.options = this.withSelectedValueFallbackOptions(this.config.staticOptions || []);
32506
34781
  this.cdr.markForCheck();
32507
34782
  }
32508
34783
  }
@@ -32551,6 +34826,38 @@ class RadioWidgetComponent {
32551
34826
  this.control.disable({ emitEvent: false });
32552
34827
  }
32553
34828
  }
34829
+ areApiCallsSuppressed() {
34830
+ return areEngineApiCallsSuppressed(this.engine);
34831
+ }
34832
+ hasSelectedValue() {
34833
+ return this.control.value !== undefined && this.control.value !== null && this.control.value !== '';
34834
+ }
34835
+ withSelectedValueFallbackOptions(options) {
34836
+ const fallback = buildFallbackOptions(this.control.value, this.getStoredFieldLabel());
34837
+ if (fallback.length === 0) {
34838
+ return options;
34839
+ }
34840
+ const merged = [...options];
34841
+ for (const option of fallback) {
34842
+ if (merged.some(existing => Object.is(existing.value, option.value) || String(existing.value) === String(option.value))) {
34843
+ continue;
34844
+ }
34845
+ merged.unshift(option);
34846
+ }
34847
+ return merged;
34848
+ }
34849
+ getStoredFieldLabel() {
34850
+ if (!this.engine || typeof this.engine.getFieldLabel !== 'function') {
34851
+ return undefined;
34852
+ }
34853
+ return this.engine.getFieldLabel(this.config.name);
34854
+ }
34855
+ setStoredFieldLabel(label) {
34856
+ if (!this.engine || typeof this.engine.setFieldLabel !== 'function') {
34857
+ return;
34858
+ }
34859
+ this.engine.setFieldLabel(this.config.name, label);
34860
+ }
32554
34861
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: RadioWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
32555
34862
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: RadioWidgetComponent, isStandalone: true, selector: "app-radio-widget", inputs: { config: "config", engine: "engine", control: "control" }, usesOnChanges: true, ngImport: i0, template: `
32556
34863
  <div [class]="fieldContainerClass"
@@ -32679,6 +34986,7 @@ class CheckboxGroupWidgetComponent {
32679
34986
  .pipe(takeUntilDestroyed(this.destroyRef))
32680
34987
  .subscribe(val => {
32681
34988
  if (this.engine) {
34989
+ this.setStoredFieldLabel(resolveOptionFieldLabel(val, this.options, this.getStoredFieldLabel()));
32682
34990
  this.engine.setValue(this.config.name, val);
32683
34991
  this.engine.emitUiEvent({ fieldId: this.config.id, fieldName: this.config.name, type: 'change', value: val });
32684
34992
  if (this.control.dirty) {
@@ -32687,7 +34995,12 @@ class CheckboxGroupWidgetComponent {
32687
34995
  }
32688
34996
  });
32689
34997
  // this.syncEnabledState(); // Checkboxes individually disabled via [disabled] binding
32690
- await this.loadOptions();
34998
+ if (this.areApiCallsSuppressed() && this.hasSelectedValue()) {
34999
+ this.options = this.withSelectedValueFallbackOptions([]);
35000
+ }
35001
+ else {
35002
+ await this.loadOptions();
35003
+ }
32691
35004
  // Dependencies
32692
35005
  const dependencyIds = this.getDependencyFieldIds();
32693
35006
  if (this.engine) {
@@ -32742,7 +35055,10 @@ class CheckboxGroupWidgetComponent {
32742
35055
  let opts = [];
32743
35056
  opts = await this.dataProvider.getOptions(this.config, this.engine);
32744
35057
  if (this.requestId === reqId) {
32745
- this.options = opts;
35058
+ this.options = this.withSelectedValueFallbackOptions(opts);
35059
+ if (this.engine) {
35060
+ this.setStoredFieldLabel(resolveOptionFieldLabel(this.control.value, this.options, this.getStoredFieldLabel()));
35061
+ }
32746
35062
  this.loading = false;
32747
35063
  this.loadError = null;
32748
35064
  this.cdr.markForCheck();
@@ -32752,7 +35068,7 @@ class CheckboxGroupWidgetComponent {
32752
35068
  if (this.requestId === reqId) {
32753
35069
  this.loading = false;
32754
35070
  this.loadError = 'Failed to load options.';
32755
- this.options = this.config.staticOptions || [];
35071
+ this.options = this.withSelectedValueFallbackOptions(this.config.staticOptions || []);
32756
35072
  this.cdr.markForCheck();
32757
35073
  }
32758
35074
  }
@@ -32820,6 +35136,38 @@ class CheckboxGroupWidgetComponent {
32820
35136
  }
32821
35137
  }
32822
35138
  }
35139
+ areApiCallsSuppressed() {
35140
+ return areEngineApiCallsSuppressed(this.engine);
35141
+ }
35142
+ hasSelectedValue() {
35143
+ return Array.isArray(this.control.value) && this.control.value.length > 0;
35144
+ }
35145
+ withSelectedValueFallbackOptions(options) {
35146
+ const fallback = buildFallbackOptions(this.control.value, this.getStoredFieldLabel());
35147
+ if (fallback.length === 0) {
35148
+ return options;
35149
+ }
35150
+ const merged = [...options];
35151
+ for (const option of fallback) {
35152
+ if (merged.some(existing => Object.is(existing.value, option.value) || String(existing.value) === String(option.value))) {
35153
+ continue;
35154
+ }
35155
+ merged.unshift(option);
35156
+ }
35157
+ return merged;
35158
+ }
35159
+ getStoredFieldLabel() {
35160
+ if (!this.engine || typeof this.engine.getFieldLabel !== 'function') {
35161
+ return undefined;
35162
+ }
35163
+ return this.engine.getFieldLabel(this.config.name);
35164
+ }
35165
+ setStoredFieldLabel(label) {
35166
+ if (!this.engine || typeof this.engine.setFieldLabel !== 'function') {
35167
+ return;
35168
+ }
35169
+ this.engine.setFieldLabel(this.config.name, label);
35170
+ }
32823
35171
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: CheckboxGroupWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
32824
35172
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: CheckboxGroupWidgetComponent, isStandalone: true, selector: "app-checkbox-group-widget", inputs: { config: "config", engine: "engine", control: "control" }, usesOnChanges: true, ngImport: i0, template: `
32825
35173
  <div [class]="fieldContainerClass"
@@ -33485,7 +35833,7 @@ class RepeatableGroupWidgetComponent {
33485
35833
  </div>
33486
35834
  </ng-template>
33487
35835
  </div>
33488
- `, 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.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: LayoutNodeComponent, selector: "app-layout-node", inputs: ["node", "engine", "fields", "designMode", "readOnlyMode", "scopePath", "device", "breakpoint", "connectedDropLists"], outputs: ["nodeDrop", "nodeSelect"] }] });
35836
+ `, 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.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: LayoutNodeComponent, selector: "app-layout-node", inputs: ["node", "engine", "fields", "designMode", "showLayoutGuides", "readOnlyMode", "scopePath", "device", "breakpoint", "connectedDropLists"], outputs: ["nodeDrop", "nodeSelect"] }] });
33489
35837
  }
33490
35838
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: RepeatableGroupWidgetComponent, decorators: [{
33491
35839
  type: Component,
@@ -37799,6 +40147,116 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
37799
40147
  args: [DOCUMENT]
37800
40148
  }] }] });
37801
40149
 
40150
+ class AiToolRegistryService {
40151
+ state;
40152
+ widgetDefs;
40153
+ eventApis;
40154
+ plugins;
40155
+ toolByName;
40156
+ constructor() {
40157
+ this.state = inject(DesignerStateService);
40158
+ const injectedWidgetDefs = inject(WIDGET_DEFINITIONS, { optional: true }) ?? [];
40159
+ this.widgetDefs = injectedWidgetDefs.flat();
40160
+ this.eventApis = [];
40161
+ this.plugins = [FORM_DESIGNER_AI_TOOL_PLUGIN];
40162
+ this.toolByName = this.buildToolIndex(this.plugins);
40163
+ }
40164
+ /**
40165
+ * Create an instance directly for testing without Angular DI.
40166
+ */
40167
+ static forTest(state, eventApis, plugins, widgetDefs) {
40168
+ const instance = Object.create(AiToolRegistryService.prototype);
40169
+ instance.state = state;
40170
+ instance.eventApis = eventApis ? [...eventApis] : [];
40171
+ instance.widgetDefs = widgetDefs ? [...widgetDefs] : [];
40172
+ instance.plugins = plugins?.length ? [...plugins] : [FORM_DESIGNER_AI_TOOL_PLUGIN];
40173
+ instance.toolByName = instance.buildToolIndex(instance.plugins);
40174
+ return instance;
40175
+ }
40176
+ describeTools() {
40177
+ return this.plugins.flatMap((plugin) => plugin.tools.map((tool) => ({
40178
+ name: tool.name,
40179
+ description: tool.summary,
40180
+ summary: tool.summary,
40181
+ domain: tool.domain,
40182
+ readOnly: tool.readOnly,
40183
+ tags: tool.tags,
40184
+ whenToUse: tool.whenToUse,
40185
+ whenNotToUse: tool.whenNotToUse,
40186
+ exampleIntents: tool.exampleIntents,
40187
+ requiresSelection: tool.requiresSelection,
40188
+ requiresCapabilities: tool.requiresCapabilities,
40189
+ requiredAuthScopes: tool.requiredAuthScopes,
40190
+ parametersSchema: tool.parametersSchema,
40191
+ })));
40192
+ }
40193
+ async execute(name, args) {
40194
+ const tool = this.toolByName.get(name);
40195
+ if (!tool) {
40196
+ return {
40197
+ ok: false,
40198
+ result: null,
40199
+ error: `Unknown tool: "${name}"`,
40200
+ };
40201
+ }
40202
+ const context = this.createContext();
40203
+ if (tool.isApplicable && !tool.isApplicable(context)) {
40204
+ return {
40205
+ ok: false,
40206
+ result: null,
40207
+ error: `Tool "${name}" is not applicable in current context.`,
40208
+ };
40209
+ }
40210
+ const result = await tool.execute(context, args);
40211
+ return result;
40212
+ }
40213
+ getDiscoveryContext() {
40214
+ return {
40215
+ workspace: 'designer',
40216
+ readOnly: this.state.isReadOnly(),
40217
+ hasSelection: !!this.state.selectedNodeId(),
40218
+ capabilities: {
40219
+ eventApis: this.eventApis.length > 0,
40220
+ widgetCatalog: this.widgetDefs.length > 0,
40221
+ },
40222
+ };
40223
+ }
40224
+ /** Allow host/shell to update available event APIs at runtime. */
40225
+ setEventApis(apis) {
40226
+ this.eventApis = [...apis];
40227
+ }
40228
+ /** Allow host/shell to update widget catalog at runtime. */
40229
+ setWidgetDefinitions(widgetDefs) {
40230
+ this.widgetDefs = [...widgetDefs];
40231
+ }
40232
+ /** Register additional plain TS plugins without requiring Angular DI contracts. */
40233
+ registerPlugin(plugin) {
40234
+ this.plugins = [...this.plugins, plugin];
40235
+ this.toolByName = this.buildToolIndex(this.plugins);
40236
+ }
40237
+ createContext() {
40238
+ return {
40239
+ state: this.state,
40240
+ eventApis: this.eventApis,
40241
+ widgetDefs: this.widgetDefs,
40242
+ };
40243
+ }
40244
+ buildToolIndex(plugins) {
40245
+ const index = new Map();
40246
+ for (const plugin of plugins) {
40247
+ for (const tool of plugin.tools) {
40248
+ index.set(tool.name, tool);
40249
+ }
40250
+ }
40251
+ return index;
40252
+ }
40253
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: AiToolRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
40254
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: AiToolRegistryService });
40255
+ }
40256
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: AiToolRegistryService, decorators: [{
40257
+ type: Injectable
40258
+ }], ctorParameters: () => [] });
40259
+
37802
40260
  class InMemoryDataCatalogService extends DataCatalog {
37803
40261
  sources = [];
37804
40262
  listSources() {
@@ -37971,5 +40429,5 @@ function provideHttpDataSourceClient(config) {
37971
40429
  * Generated bundle index. Do not edit.
37972
40430
  */
37973
40431
 
37974
- export { APPEARANCE_FIELDS, AiToolRegistryService, CONTENT_TEXT_CLASS, CONTENT_TEXT_MUTED_CLASS, CORE_DESIGNER_PLUGINS, CURRENT_SCHEMA_VERSION, DATA_PROVIDER, DATA_SOURCE_CLIENT, DEFAULT_TEMPLATE_LIBRARY, DEFAULT_WEBSITE_SECTIONS, DEFAULT_WIDGET_PACKS, DESIGNER_PLUGINS, DESIGNER_SECTIONS, DataCatalog, DataPanelComponent, DataProvider, DefaultDataProvider, DefaultDataSourceClient, DefaultFileUploadClient, DesignerContext, DesignerStateService, DynamicPropertiesComponent, EFFECTS_FIELDS, EMAIL_SAFE_STYLE_SECTIONS, EMAIL_WIDGETS, EmailRendererComponent, EventsPanelComponent, EventsWorkspaceComponent, FIELD_CHOICE_LABEL_CLASS, FIELD_CHOICE_SURFACE_CLASS, FIELD_CONTAINER_CLASS, FIELD_ERROR_CLASS, FIELD_HELP_CLASS, FIELD_LABEL_CLASS, FIELD_OPTION_CLASS, FIELD_REQUIRED_CLASS, FIELD_RESULTS_PANEL_CLASS, FIELD_WIDGETS, FILE_UPLOAD_CARD_CLASS, FILE_UPLOAD_CLIENT, FORM_DESIGNER_AI_TOOL_PLUGIN, FULL_WEB_STYLE_SECTIONS, FieldPaletteComponent, FormDesignerShellComponent, FormEngine, FormViewerComponent, GlobalDataManagerComponent, HTTP_DATA_SOURCE_CLIENT_CONFIG, HttpDataSourceClient, InMemoryDataCatalogService, InspectorAdvancedSectionComponent, InspectorBackgroundsSectionComponent, InspectorBordersSectionComponent, InspectorEffectsSectionComponent, InspectorLayoutSectionComponent, InspectorPositionSectionComponent, InspectorSizeSectionComponent, InspectorSpacingSectionComponent, InspectorSpinInputComponent, InspectorTypographySectionComponent, JsonFormDesignerComponent, JsonFormRendererComponent, LAYOUT_FIELDS, LayoutCanvasComponent, LayoutNodeComponent, OUTLINED_FIELD_BORDER_WIDTH, OUTLINED_FIELD_IDLE_BORDER_COLOR, OUTLINED_FIELD_INTERACTIVE_BORDER_COLOR, OUTLINED_FIELD_INVALID_BORDER_COLOR, PAGE_WIDGETS, PropertiesPanelComponent, RulesPanelComponent, RuntimeFieldDataAccessRegistryService, SELECT_THEME_HOOKS, SELECT_THEME_TOKENS, SPACING_BOX_MODEL_FIELD, SPACING_MARGIN_FIELDS, SPACING_PADDING_FIELDS, STANDARD_FORM_STYLE_SECTIONS, STYLE_APPEARANCE_SECTION, STYLE_EFFECTS_SECTION, STYLE_LAYOUT_SECTION, STYLE_SECTIONS, STYLE_SPACING_SECTION, STYLE_TRANSFORM_SECTION, STYLE_TYPOGRAPHY_SECTION, TABLE_CELL_CLASS, TABLE_EMPTY_CLASS, TABLE_GRID_CLASS, TABLE_HEAD_CELL_CLASS, TABLE_HEAD_CLASS, TABLE_ICON_BUTTON_CLASS, TABLE_ROOT_CLASS, TABLE_ROW_CLASS, TABLE_TOOLBAR_CLASS, TRANSFORM_FIELDS, TYPOGRAPHY_FIELDS, ThemeService, UiAccordionComponent, UiBoxModelComponent, UiColorSwatchComponent, UiDimensionComponent, UiEdgeBoxComponent, UiFieldWrapperComponent, UiInputComponent, UiRangeNumberComponent, UiSelectIconComponent, UiTabComponent, UiTabsComponent, WIDGET_DEFINITIONS, WIDGET_ID_SEPARATOR, WebsiteBrickStudioComponent, WebsiteDesignerShellComponent, WebsitePreviewShellComponent, WebsiteProjectService, WidgetDefinitionResolverService, WidgetInspectorComponent, appendSectionToSchema, buildWidgetId, checkSchemaForApiOnlySources, createDefaultWebsiteBrick, createDefaultWebsiteBricks, createDefaultWebsiteTheme, createEmptySchema, createFormEngine, createPluginContext, createWebsiteBrick, createWebsitePage, createWebsiteProject, defineWidget, flattenPluginWidgets, getButtonClass, getChoiceControlClass, getEffectiveDataConfig, getFileInputClass, getFileUploadShellClass, getHeadingClass, getOutlinedFieldInlineStyle, getTextControlClass, getWidgetsForFlavor, hasWrapperSurfaceStyles, inferSchema, isWidgetVisibleInPalette, mergeAndNormalize, normalizeRuntimeOptions, normalizeStyle$1 as normalizeStyle, parseCsv, parseJsonArray, parseSchema, provideDesignerPlugins, provideHttpDataSourceClient, serializeSchema, slugifyId, splitControlSurfaceStyles, stripEmbeddedSourceData };
37975
- //# sourceMappingURL=notdefined-ngx-form-designer.mjs.map
40432
+ export { APPEARANCE_FIELDS, AiToolRegistryService, CONTENT_TEXT_CLASS, CONTENT_TEXT_MUTED_CLASS, CORE_DESIGNER_PLUGINS, CURRENT_SCHEMA_VERSION, DATA_PROVIDER, DATA_SOURCE_CLIENT, DEFAULT_TEMPLATE_LIBRARY, DEFAULT_WEBSITE_SECTIONS, DEFAULT_WIDGET_PACKS, DESIGNER_PLUGINS, DESIGNER_SECTIONS, DataCatalog, DataPanelComponent, DataProvider, DefaultDataProvider, DefaultDataSourceClient, DefaultFileUploadClient, DesignerContext, DesignerStateService, DynamicPropertiesComponent, EFFECTS_FIELDS, EMAIL_SAFE_STYLE_SECTIONS, EMAIL_WIDGETS, EmailRendererComponent, EventsPanelComponent, EventsWorkspaceComponent, FIELD_CHOICE_LABEL_CLASS, FIELD_CHOICE_SURFACE_CLASS, FIELD_CONTAINER_CLASS, FIELD_ERROR_CLASS, FIELD_HELP_CLASS, FIELD_LABEL_CLASS, FIELD_OPTION_CLASS, FIELD_REQUIRED_CLASS, FIELD_RESULTS_PANEL_CLASS, FIELD_WIDGETS, FILE_UPLOAD_CARD_CLASS, FILE_UPLOAD_CLIENT, FORM_DESIGNER_AI_TOOL_PLUGIN, FULL_WEB_STYLE_SECTIONS, FieldPaletteComponent, FormDesignerAiFeatureStateService, FormDesignerShellComponent, FormEngine, FormJourneyStateService, FormJourneyViewerComponent, FormViewerComponent, GlobalDataManagerComponent, HTTP_DATA_SOURCE_CLIENT_CONFIG, HttpDataSourceClient, InMemoryDataCatalogService, InspectorAdvancedSectionComponent, InspectorBackgroundsSectionComponent, InspectorBordersSectionComponent, InspectorEffectsSectionComponent, InspectorLayoutSectionComponent, InspectorPositionSectionComponent, InspectorSizeSectionComponent, InspectorSpacingSectionComponent, InspectorSpinInputComponent, InspectorTypographySectionComponent, JsonFormDesignerComponent, JsonFormRendererComponent, LAYOUT_FIELDS, LayoutCanvasComponent, LayoutNodeComponent, OUTLINED_FIELD_BORDER_WIDTH, OUTLINED_FIELD_IDLE_BORDER_COLOR, OUTLINED_FIELD_INTERACTIVE_BORDER_COLOR, OUTLINED_FIELD_INVALID_BORDER_COLOR, PAGE_WIDGETS, PropertiesPanelComponent, RulesPanelComponent, RuntimeFieldDataAccessRegistryService, SELECT_THEME_HOOKS, SELECT_THEME_TOKENS, SPACING_BOX_MODEL_FIELD, SPACING_MARGIN_FIELDS, SPACING_PADDING_FIELDS, STANDARD_FORM_STYLE_SECTIONS, STYLE_APPEARANCE_SECTION, STYLE_EFFECTS_SECTION, STYLE_LAYOUT_SECTION, STYLE_SECTIONS, STYLE_SPACING_SECTION, STYLE_TRANSFORM_SECTION, STYLE_TYPOGRAPHY_SECTION, TABLE_CELL_CLASS, TABLE_EMPTY_CLASS, TABLE_GRID_CLASS, TABLE_HEAD_CELL_CLASS, TABLE_HEAD_CLASS, TABLE_ICON_BUTTON_CLASS, TABLE_ROOT_CLASS, TABLE_ROW_CLASS, TABLE_TOOLBAR_CLASS, TRANSFORM_FIELDS, TYPOGRAPHY_FIELDS, ThemeService, UiAccordionComponent, UiBoxModelComponent, UiColorSwatchComponent, UiDimensionComponent, UiEdgeBoxComponent, UiFieldWrapperComponent, UiInputComponent, UiRangeNumberComponent, UiSelectIconComponent, UiTabComponent, UiTabsComponent, WIDGET_DEFINITIONS, WIDGET_ID_SEPARATOR, WebsiteBrickStudioComponent, WebsiteDesignerShellComponent, WebsitePreviewShellComponent, WebsiteProjectService, WidgetDefinitionResolverService, WidgetInspectorComponent, appendSectionToSchema, buildWidgetId, checkSchemaForApiOnlySources, createDefaultWebsiteBrick, createDefaultWebsiteBricks, createDefaultWebsiteTheme, createEmptySchema, createFormEngine, createFormJourneyPage, createFormJourneyProject, createPluginContext, createWebsiteBrick, createWebsitePage, createWebsiteProject, defineWidget, flattenPluginWidgets, getButtonClass, getChoiceControlClass, getEffectiveDataConfig, getFileInputClass, getFileUploadShellClass, getHeadingClass, getOutlinedFieldInlineStyle, getTextControlClass, getWidgetsForFlavor, hasWrapperSurfaceStyles, inferSchema, isFormJourneyProject, isWidgetVisibleInPalette, mergeAndNormalize, normalizeRuntimeOptions, normalizeStyle$1 as normalizeStyle, normalizeToJourney, parseCsv, parseJsonArray, parseSchema, provideDesignerPlugins, provideFormDesignerAngaiFeature, provideHttpDataSourceClient, serializeSchema, slugifyId, splitControlSurfaceStyles, stripEmbeddedSourceData, unwrapSinglePageJourney };
40433
+ //# sourceMappingURL=uch-web-ngx-form-designer.mjs.map