@yourself.create/ngx-form-designer 0.0.8 → 0.0.10

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.
@@ -23,6 +23,15 @@ import Quill from 'quill';
23
23
  import { DomSanitizer } from '@angular/platform-browser';
24
24
  import { HttpClient, provideHttpClient } from '@angular/common/http';
25
25
 
26
+ function usesFieldThousandSeparator(field) {
27
+ return field?.useThousandSeparator === true;
28
+ }
29
+ function isFieldReadonly(field) {
30
+ return field?.html5?.readonly === true;
31
+ }
32
+ function isFieldDisabled(field) {
33
+ return field?.html5?.disabled === true;
34
+ }
26
35
  const CURRENT_SCHEMA_VERSION = '1.0.0';
27
36
 
28
37
  /**
@@ -446,8 +455,8 @@ class FormEngine {
446
455
  setValue(fieldName, value) {
447
456
  this.values[fieldName] = value;
448
457
  this.valueSubject.next({ ...this.values }); // Emit new state
449
- // Re-run validation for this field.
450
- this.validateField(fieldName);
458
+ // Rules and dependencies can affect other fields, so revalidate the full form.
459
+ this.validate();
451
460
  }
452
461
  getValues() {
453
462
  return { ...this.values };
@@ -502,7 +511,7 @@ class FormEngine {
502
511
  if (!field)
503
512
  return false;
504
513
  // 1. Dependencies (Legacy)
505
- let enabled = this.evaluateDependencyRules(field, 'enable', 'disable', true);
514
+ let enabled = this.evaluateDependencyRules(field, 'enable', 'disable', !isFieldDisabled(field));
506
515
  // 2. Enterprise Rules
507
516
  enabled = this.evaluateEnterpriseRules(field, 'enable', 'disable', enabled);
508
517
  return enabled;
@@ -568,30 +577,6 @@ class FormEngine {
568
577
  }
569
578
  }
570
579
  }
571
- // Custom Validation Rules
572
- if (field.validation) {
573
- field.validation.forEach(rule => {
574
- let isValid = true;
575
- if (rule.type === 'builtin') {
576
- if (rule.name === 'email') {
577
- isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(value));
578
- }
579
- }
580
- else if (rule.type === 'expression' && rule.expression) {
581
- try {
582
- const checkFn = new Function('value', 'form', `return ${rule.expression}`);
583
- isValid = checkFn(value, this.values);
584
- }
585
- catch (e) {
586
- console.error('Validation expression error', e);
587
- isValid = false;
588
- }
589
- }
590
- if (!isValid) {
591
- fieldErrors.push(rule.message || 'Validation failed.');
592
- }
593
- });
594
- }
595
580
  }
596
581
  if (fieldErrors.length > 0) {
597
582
  this.errors[fieldName] = fieldErrors;
@@ -638,13 +623,31 @@ class FormEngine {
638
623
  if (hasPositiveRules && startValue === true) {
639
624
  currentState = false; // Implicitly hide/disable
640
625
  }
641
- field.rules.forEach((rule) => {
642
- const conditionMet = this.ruleEvaluator.evaluateRule(rule, this.values, this.schema.fields);
643
- const nextAction = conditionMet ? rule.action : rule.elseAction;
626
+ this.getEnterpriseRuleOutcomes(field).forEach(({ nextAction }) => {
644
627
  currentState = this.applyRuleAction(currentState, nextAction, positiveAction, negativeAction);
645
628
  });
646
629
  return currentState;
647
630
  }
631
+ getEnterpriseRuleOutcomes(field) {
632
+ if (!field.rules || field.rules.length === 0) {
633
+ return [];
634
+ }
635
+ return field.rules.map((rule) => {
636
+ const conditionMet = this.ruleEvaluator.evaluateRule(rule, this.values, this.schema.fields);
637
+ return {
638
+ rule,
639
+ conditionMet,
640
+ nextAction: conditionMet ? rule.action : rule.elseAction
641
+ };
642
+ });
643
+ }
644
+ getRuleValidationMessage(rule) {
645
+ const name = rule.name?.trim();
646
+ if (name) {
647
+ return name;
648
+ }
649
+ return `Rule "${rule.action}" failed.`;
650
+ }
648
651
  applyRuleAction(currentState, action, positiveAction, negativeAction) {
649
652
  if (action === positiveAction) {
650
653
  return true;
@@ -4496,6 +4499,7 @@ class FormEventRunner {
4496
4499
  const target = fields.find(f => f.id === action.targetFieldId);
4497
4500
  if (!target)
4498
4501
  return;
4502
+ const previousValue = this.engine.getValue(target.name);
4499
4503
  let val;
4500
4504
  if (action.valueFrom === 'literal') {
4501
4505
  val = action.valueLiteral;
@@ -4510,6 +4514,14 @@ class FormEventRunner {
4510
4514
  }
4511
4515
  }
4512
4516
  this.engine.setValue(target.name, val);
4517
+ if (!Object.is(previousValue, val)) {
4518
+ this.engine.emitUiEvent({
4519
+ fieldId: target.id,
4520
+ fieldName: target.name,
4521
+ type: 'change',
4522
+ value: val
4523
+ });
4524
+ }
4513
4525
  }
4514
4526
  async handleApiAction(action, eventId, evt, fields) {
4515
4527
  if (!this.apiExecutor) {
@@ -5181,6 +5193,7 @@ class JsonFormRendererComponent {
5181
5193
  uploadClient = inject(FILE_UPLOAD_CLIENT, { optional: true }) ?? inject(DefaultFileUploadClient);
5182
5194
  runtimeFieldDataAccessRegistry = inject(RuntimeFieldDataAccessRegistryService);
5183
5195
  dataCatalog = inject(DataCatalog, { optional: true });
5196
+ ruleEvaluationService = inject(RuleEvaluationService);
5184
5197
  constructor(designerState) {
5185
5198
  this.designerState = designerState;
5186
5199
  }
@@ -5435,10 +5448,12 @@ class JsonFormRendererComponent {
5435
5448
  const errors = revalidate
5436
5449
  ? (this.engine?.validate() ?? {})
5437
5450
  : (this.engine?.getErrors() ?? {});
5451
+ const fields = this.buildFieldValidationState(errors);
5438
5452
  return {
5439
5453
  errors: { ...errors },
5440
5454
  isValid: Object.keys(errors).length === 0,
5441
- fields: this.buildFieldValidationState(errors)
5455
+ isSeverityError: Object.values(fields).some(field => field.isSeverityError),
5456
+ fields
5442
5457
  };
5443
5458
  }
5444
5459
  buildFieldValidationState(errors) {
@@ -5459,71 +5474,99 @@ class JsonFormRendererComponent {
5459
5474
  visible,
5460
5475
  required,
5461
5476
  valid: fieldErrors.length === 0,
5477
+ isSeverityError: false,
5462
5478
  errors: fieldErrors,
5463
- validators: this.describeFieldValidators(field, visible, required)
5479
+ validators: this.describeFieldValidators(field, visible, required, fieldErrors)
5464
5480
  };
5481
+ states[field.name].isSeverityError = states[field.name].validators.some(validator => validator.severity === 'error' && validator.valid === false);
5465
5482
  }
5466
5483
  return states;
5467
5484
  }
5468
- describeFieldValidators(field, visible, required) {
5485
+ describeFieldValidators(field, visible, required, fieldErrors) {
5469
5486
  const validators = [];
5470
5487
  const value = this.engine?.getValue(field.name);
5471
5488
  const hasValue = !this.isValidationEmpty(value);
5489
+ const ruleOutcomes = this.getRuleOutcomes(field);
5490
+ const requiredSeverity = ruleOutcomes.some(outcome => outcome.rule.severity === 'error' && outcome.nextAction === 'required');
5472
5491
  if (this.hasRequiredValidation(field) || required) {
5492
+ const message = 'This field is required.';
5473
5493
  validators.push({
5474
5494
  name: 'required',
5475
5495
  source: 'required',
5476
5496
  active: visible && required,
5477
- message: 'This field is required.'
5497
+ valid: !fieldErrors.includes(message),
5498
+ message,
5499
+ ...(requiredSeverity ? { severity: 'error' } : {})
5478
5500
  });
5479
5501
  }
5480
5502
  if (field.html5?.minLength !== undefined) {
5503
+ const message = `Minimum length is ${field.html5.minLength}.`;
5481
5504
  validators.push({
5482
5505
  name: 'minLength',
5483
5506
  source: 'html5',
5484
5507
  active: visible && hasValue,
5485
- value: field.html5.minLength
5508
+ valid: !fieldErrors.includes(message),
5509
+ value: field.html5.minLength,
5510
+ message
5486
5511
  });
5487
5512
  }
5488
5513
  if (field.html5?.maxLength !== undefined) {
5514
+ const message = `Maximum length is ${field.html5.maxLength}.`;
5489
5515
  validators.push({
5490
5516
  name: 'maxLength',
5491
5517
  source: 'html5',
5492
5518
  active: visible && hasValue,
5493
- value: field.html5.maxLength
5519
+ valid: !fieldErrors.includes(message),
5520
+ value: field.html5.maxLength,
5521
+ message
5494
5522
  });
5495
5523
  }
5496
5524
  if (field.html5?.min !== undefined) {
5525
+ const message = `Minimum value is ${field.html5.min}.`;
5497
5526
  validators.push({
5498
5527
  name: 'min',
5499
5528
  source: 'html5',
5500
5529
  active: visible && hasValue,
5501
- value: field.html5.min
5530
+ valid: !fieldErrors.includes(message),
5531
+ value: field.html5.min,
5532
+ message
5502
5533
  });
5503
5534
  }
5504
5535
  if (field.html5?.max !== undefined) {
5536
+ const message = `Maximum value is ${field.html5.max}.`;
5505
5537
  validators.push({
5506
5538
  name: 'max',
5507
5539
  source: 'html5',
5508
5540
  active: visible && hasValue,
5509
- value: field.html5.max
5541
+ valid: !fieldErrors.includes(message),
5542
+ value: field.html5.max,
5543
+ message
5510
5544
  });
5511
5545
  }
5512
5546
  if (field.html5?.pattern) {
5547
+ const message = 'Invalid format.';
5513
5548
  validators.push({
5514
5549
  name: 'pattern',
5515
5550
  source: 'html5',
5516
5551
  active: visible && hasValue,
5517
- value: field.html5.pattern
5552
+ valid: !fieldErrors.includes(message),
5553
+ value: field.html5.pattern,
5554
+ message
5518
5555
  });
5519
5556
  }
5520
- for (const rule of field.validation ?? []) {
5557
+ for (const outcome of ruleOutcomes) {
5558
+ const message = this.getRuleValidationMessage(outcome.rule);
5521
5559
  validators.push({
5522
- name: rule.type === 'builtin' ? (rule.name ?? 'builtin') : 'expression',
5523
- source: 'custom',
5524
- active: visible && hasValue && this.isValidationRuleActive(rule),
5525
- value: rule.type === 'expression' ? rule.expression : rule.name,
5526
- message: rule.message
5560
+ name: outcome.rule.name?.trim() || this.describeRuleName(outcome.rule),
5561
+ source: 'rule',
5562
+ active: true,
5563
+ valid: outcome.conditionMet || outcome.nextAction !== undefined,
5564
+ value: {
5565
+ action: outcome.rule.action,
5566
+ elseAction: outcome.rule.elseAction
5567
+ },
5568
+ message,
5569
+ ...(outcome.rule.severity ? { severity: outcome.rule.severity } : {})
5527
5570
  });
5528
5571
  }
5529
5572
  return validators;
@@ -5542,16 +5585,30 @@ class JsonFormRendererComponent {
5542
5585
  }
5543
5586
  return false;
5544
5587
  }
5545
- isValidationRuleActive(rule) {
5546
- if (!rule.when)
5547
- return true;
5548
- try {
5549
- const checkFn = new Function('form', `return ${rule.when}`);
5550
- return checkFn(this.engine?.getValues() ?? {});
5588
+ getRuleOutcomes(field) {
5589
+ if (!field.rules?.length) {
5590
+ return [];
5551
5591
  }
5552
- catch {
5553
- return false;
5592
+ const schema = this.engine?.getSchema() ?? this.schema;
5593
+ const formValues = this.engine?.getValues() ?? {};
5594
+ return field.rules.map(rule => {
5595
+ const conditionMet = this.ruleEvaluationService.evaluateRule(rule, formValues, schema?.fields ?? []);
5596
+ return {
5597
+ rule,
5598
+ conditionMet,
5599
+ nextAction: conditionMet ? rule.action : rule.elseAction
5600
+ };
5601
+ });
5602
+ }
5603
+ describeRuleName(rule) {
5604
+ return rule.action.charAt(0).toUpperCase() + rule.action.slice(1);
5605
+ }
5606
+ getRuleValidationMessage(rule) {
5607
+ const name = rule.name?.trim();
5608
+ if (name) {
5609
+ return name;
5554
5610
  }
5611
+ return `Rule "${rule.action}" failed.`;
5555
5612
  }
5556
5613
  isValidationEmpty(value) {
5557
5614
  return value === null
@@ -10271,13 +10328,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
10271
10328
  class DynamicPropertiesComponent {
10272
10329
  onPropertyChange;
10273
10330
  designerCtx = inject(DesignerContext);
10274
- validatorTypeOptions = [
10275
- { label: 'Built-in', value: 'builtin' },
10276
- { label: 'Expression', value: 'expression' }
10277
- ];
10278
- builtinValidatorOptions = [
10279
- { label: 'Email', value: 'email' }
10280
- ];
10281
10331
  get properties() {
10282
10332
  if (!this.config)
10283
10333
  return [];
@@ -10609,68 +10659,6 @@ class DynamicPropertiesComponent {
10609
10659
  this.setValue(path, options);
10610
10660
  }
10611
10661
  }
10612
- // Validators editor methods
10613
- addValidator(path) {
10614
- if (this.readOnly)
10615
- return;
10616
- const validators = [...(this.getValue(path) || [])];
10617
- validators.push(this.createDefaultValidationRule());
10618
- this.setValue(path, validators);
10619
- this.handleFieldChange();
10620
- }
10621
- removeValidator(path, index) {
10622
- if (this.readOnly)
10623
- return;
10624
- const validators = [...(this.getValue(path) || [])];
10625
- validators.splice(index, 1);
10626
- this.setValue(path, validators);
10627
- this.handleFieldChange();
10628
- }
10629
- updateValidator(path, index, field, value) {
10630
- if (this.readOnly)
10631
- return;
10632
- const validators = [...(this.getValue(path) || [])];
10633
- const currentRule = validators[index];
10634
- if (!currentRule) {
10635
- return;
10636
- }
10637
- let nextRule = { ...currentRule };
10638
- if (field === 'type') {
10639
- nextRule = value === 'expression'
10640
- ? {
10641
- type: 'expression',
10642
- expression: 'return true;',
10643
- message: 'Validation failed.'
10644
- }
10645
- : this.createDefaultValidationRule();
10646
- }
10647
- else if (field === 'name') {
10648
- nextRule.name = String(value);
10649
- nextRule.message = this.defaultValidationMessage(nextRule);
10650
- }
10651
- else if (field === 'expression') {
10652
- nextRule.expression = String(value);
10653
- }
10654
- else if (field === 'message') {
10655
- nextRule.message = String(value);
10656
- }
10657
- validators[index] = nextRule;
10658
- this.setValue(path, validators);
10659
- this.handleFieldChange();
10660
- }
10661
- createDefaultValidationRule() {
10662
- return {
10663
- type: 'builtin',
10664
- name: 'email',
10665
- message: 'Enter a valid email address.'
10666
- };
10667
- }
10668
- defaultValidationMessage(rule) {
10669
- if (rule.type === 'builtin' && rule.name === 'email') {
10670
- return 'Enter a valid email address.';
10671
- }
10672
- return 'Validation failed.';
10673
- }
10674
10662
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: DynamicPropertiesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10675
10663
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: DynamicPropertiesComponent, isStandalone: true, selector: "app-dynamic-properties", inputs: { onPropertyChange: "onPropertyChange", config: "config", readOnly: "readOnly", includeSections: "includeSections", excludeSections: "excludeSections", allFields: "allFields" }, outputs: { configChange: "configChange" }, usesOnChanges: true, ngImport: i0, template: `
10676
10664
  <div class="dynamic-properties flex flex-col font-sans text-sm">
@@ -10813,51 +10801,6 @@ class DynamicPropertiesComponent {
10813
10801
  </div>
10814
10802
  </ui-field-wrapper>
10815
10803
 
10816
- <!-- Validators Editor -->
10817
- <ui-field-wrapper *ngIf="field.type === 'validators-editor'" [label]="field.label || ''" [helpText]="field.helpText || ''">
10818
- <div class="w-full border border-gray-200 rounded-lg p-3 bg-gray-50">
10819
- <div class="flex items-center justify-between mb-3">
10820
- <span class="text-xs font-semibold text-gray-600 uppercase">{{field.label}}</span>
10821
- <button type="button"
10822
- (click)="addValidator(field.key)"
10823
- class="text-xs px-2 py-1 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors">
10824
- +
10825
- </button>
10826
- </div>
10827
- <div class="space-y-2">
10828
- <div *ngFor="let val of getValue(field.key) || []; let i = index" class="flex items-center gap-2 p-2 bg-white rounded border border-gray-200">
10829
- <select [ngModel]="val.type || 'builtin'"
10830
- (ngModelChange)="updateValidator(field.key, i, 'type', $event)"
10831
- class="flex-1 h-8 px-2 text-sm rounded border border-gray-300 bg-white focus:border-blue-500 focus:ring-1 focus:ring-blue-500 outline-none">
10832
- <option *ngFor="let option of validatorTypeOptions" [value]="option.value">{{ option.label }}</option>
10833
- </select>
10834
- <select *ngIf="(val.type || 'builtin') === 'builtin'"
10835
- [ngModel]="val.name || 'email'"
10836
- (ngModelChange)="updateValidator(field.key, i, 'name', $event)"
10837
- class="flex-1 h-8 px-2 text-sm rounded border border-gray-300 bg-white focus:border-blue-500 focus:ring-1 focus:ring-blue-500 outline-none">
10838
- <option *ngFor="let option of builtinValidatorOptions" [value]="option.value">{{ option.label }}</option>
10839
- </select>
10840
- <input *ngIf="val.type === 'expression'"
10841
- type="text"
10842
- [ngModel]="val.expression || ''"
10843
- (ngModelChange)="updateValidator(field.key, i, 'expression', $event)"
10844
- placeholder="return true;"
10845
- class="flex-1 h-8 px-2 text-sm rounded border border-gray-300 bg-white focus:border-blue-500 focus:ring-1 focus:ring-blue-500 outline-none">
10846
- <input type="text"
10847
- [ngModel]="val.message || ''"
10848
- (ngModelChange)="updateValidator(field.key, i, 'message', $event)"
10849
- placeholder="Validation message"
10850
- class="flex-1 h-8 px-2 text-sm rounded border border-gray-300 bg-white focus:border-blue-500 focus:ring-1 focus:ring-blue-500 outline-none">
10851
- <button type="button"
10852
- (click)="removeValidator(field.key, i)"
10853
- class="w-8 h-8 flex items-center justify-center text-red-600 hover:bg-red-50 rounded transition-colors">
10854
- <lucide-icon name="x" class="w-4 h-4"></lucide-icon>
10855
- </button>
10856
- </div>
10857
- </div>
10858
- </div>
10859
- </ui-field-wrapper>
10860
-
10861
10804
  <!-- Field Reference -->
10862
10805
  <ui-field-wrapper *ngIf="field.type === 'field-reference'" [label]="field.label || ''" [helpText]="field.helpText || ''">
10863
10806
  <select [ngModel]="getValue(field.key) || ''"
@@ -11106,51 +11049,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
11106
11049
  </div>
11107
11050
  </ui-field-wrapper>
11108
11051
 
11109
- <!-- Validators Editor -->
11110
- <ui-field-wrapper *ngIf="field.type === 'validators-editor'" [label]="field.label || ''" [helpText]="field.helpText || ''">
11111
- <div class="w-full border border-gray-200 rounded-lg p-3 bg-gray-50">
11112
- <div class="flex items-center justify-between mb-3">
11113
- <span class="text-xs font-semibold text-gray-600 uppercase">{{field.label}}</span>
11114
- <button type="button"
11115
- (click)="addValidator(field.key)"
11116
- class="text-xs px-2 py-1 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors">
11117
- +
11118
- </button>
11119
- </div>
11120
- <div class="space-y-2">
11121
- <div *ngFor="let val of getValue(field.key) || []; let i = index" class="flex items-center gap-2 p-2 bg-white rounded border border-gray-200">
11122
- <select [ngModel]="val.type || 'builtin'"
11123
- (ngModelChange)="updateValidator(field.key, i, 'type', $event)"
11124
- class="flex-1 h-8 px-2 text-sm rounded border border-gray-300 bg-white focus:border-blue-500 focus:ring-1 focus:ring-blue-500 outline-none">
11125
- <option *ngFor="let option of validatorTypeOptions" [value]="option.value">{{ option.label }}</option>
11126
- </select>
11127
- <select *ngIf="(val.type || 'builtin') === 'builtin'"
11128
- [ngModel]="val.name || 'email'"
11129
- (ngModelChange)="updateValidator(field.key, i, 'name', $event)"
11130
- class="flex-1 h-8 px-2 text-sm rounded border border-gray-300 bg-white focus:border-blue-500 focus:ring-1 focus:ring-blue-500 outline-none">
11131
- <option *ngFor="let option of builtinValidatorOptions" [value]="option.value">{{ option.label }}</option>
11132
- </select>
11133
- <input *ngIf="val.type === 'expression'"
11134
- type="text"
11135
- [ngModel]="val.expression || ''"
11136
- (ngModelChange)="updateValidator(field.key, i, 'expression', $event)"
11137
- placeholder="return true;"
11138
- class="flex-1 h-8 px-2 text-sm rounded border border-gray-300 bg-white focus:border-blue-500 focus:ring-1 focus:ring-blue-500 outline-none">
11139
- <input type="text"
11140
- [ngModel]="val.message || ''"
11141
- (ngModelChange)="updateValidator(field.key, i, 'message', $event)"
11142
- placeholder="Validation message"
11143
- class="flex-1 h-8 px-2 text-sm rounded border border-gray-300 bg-white focus:border-blue-500 focus:ring-1 focus:ring-blue-500 outline-none">
11144
- <button type="button"
11145
- (click)="removeValidator(field.key, i)"
11146
- class="w-8 h-8 flex items-center justify-center text-red-600 hover:bg-red-50 rounded transition-colors">
11147
- <lucide-icon name="x" class="w-4 h-4"></lucide-icon>
11148
- </button>
11149
- </div>
11150
- </div>
11151
- </div>
11152
- </ui-field-wrapper>
11153
-
11154
11052
  <!-- Field Reference -->
11155
11053
  <ui-field-wrapper *ngIf="field.type === 'field-reference'" [label]="field.label || ''" [helpText]="field.helpText || ''">
11156
11054
  <select [ngModel]="getValue(field.key) || ''"
@@ -15261,7 +15159,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
15261
15159
  type: Output
15262
15160
  }] } });
15263
15161
 
15264
- function parsePathSegments$2(path) {
15162
+ function parsePathSegments$3(path) {
15265
15163
  const segments = [];
15266
15164
  const matcher = /([^[.\]]+)|\[(\d+)\]/g;
15267
15165
  let match;
@@ -15277,14 +15175,14 @@ function parsePathSegments$2(path) {
15277
15175
  }
15278
15176
  return segments;
15279
15177
  }
15280
- function resolvePathValue$1(value, path) {
15178
+ function resolvePathValue$2(value, path) {
15281
15179
  if (!path)
15282
15180
  return value;
15283
15181
  const normalized = path.trim();
15284
15182
  if (!normalized)
15285
15183
  return value;
15286
15184
  let current = value;
15287
- for (const segment of parsePathSegments$2(normalized)) {
15185
+ for (const segment of parsePathSegments$3(normalized)) {
15288
15186
  if (current === null || current === undefined)
15289
15187
  return undefined;
15290
15188
  if (Array.isArray(current)) {
@@ -15301,7 +15199,7 @@ function resolvePathValue$1(value, path) {
15301
15199
  return current;
15302
15200
  }
15303
15201
  function assignPathValue(target, path, value) {
15304
- const segments = parsePathSegments$2(path);
15202
+ const segments = parsePathSegments$3(path);
15305
15203
  if (!segments.length) {
15306
15204
  return;
15307
15205
  }
@@ -15437,7 +15335,6 @@ class DataPanelComponent {
15437
15335
  selectionFieldId;
15438
15336
  selectionMatchPath;
15439
15337
  childRowsPath;
15440
- formatNumericOptionLabels = false;
15441
15338
  optionLabelPrefixPath;
15442
15339
  rootPathOptions = [];
15443
15340
  rowPathOptions = [];
@@ -15679,7 +15576,6 @@ class DataPanelComponent {
15679
15576
  this.selectionFieldId = undefined;
15680
15577
  this.selectionMatchPath = undefined;
15681
15578
  this.childRowsPath = undefined;
15682
- this.formatNumericOptionLabels = false;
15683
15579
  this.optionLabelPrefixPath = undefined;
15684
15580
  this.rootPathOptions = [];
15685
15581
  this.rowPathOptions = [];
@@ -15721,7 +15617,6 @@ class DataPanelComponent {
15721
15617
  this.selectionFieldId = d.selectionFieldId;
15722
15618
  this.selectionMatchPath = d.selectionMatchPath;
15723
15619
  this.childRowsPath = d.childRowsPath;
15724
- this.formatNumericOptionLabels = d.formatNumericOptionLabels === true;
15725
15620
  this.optionLabelPrefixPath = d.optionLabelPrefixPath;
15726
15621
  // Search
15727
15622
  this.searchEnabled = !!d.searchEnabled;
@@ -15774,10 +15669,7 @@ class DataPanelComponent {
15774
15669
  labelKey: this.sourceType === 'source' ? this.labelKey : undefined,
15775
15670
  valueKey: this.sourceType === 'source' ? this.valueKey : undefined,
15776
15671
  rowsPath: this.sourceType === 'source' ? this.normalizedRowsPath() : undefined,
15777
- formatNumericOptionLabels: this.shouldPersistOptionLabelFormatting()
15778
- ? this.formatNumericOptionLabels
15779
- : undefined,
15780
- optionLabelPrefixPath: this.shouldPersistOptionLabelFormatting()
15672
+ optionLabelPrefixPath: this.shouldPersistOptionLabelPrefix()
15781
15673
  ? this.optionLabelPrefixPath
15782
15674
  : undefined,
15783
15675
  rowSelectionMode: this.sourceType === 'source' && this.bindingShape === 'scalar' && this.rowSelectionMode === 'selected'
@@ -15896,9 +15788,6 @@ class DataPanelComponent {
15896
15788
  && this.bindingShape === 'scalar'
15897
15789
  && (this.widgetType === 'text' || this.widgetType === 'number');
15898
15790
  }
15899
- supportsNumericDisplayFormatting() {
15900
- return this.widgetType === 'select' || this.widgetType === 'number';
15901
- }
15902
15791
  displayFormattingTitle() {
15903
15792
  return this.widgetType === 'select' ? 'Amount Display' : 'Field Display';
15904
15793
  }
@@ -15907,15 +15796,10 @@ class DataPanelComponent {
15907
15796
  return 'Format the visible dropdown label without changing the stored option value.';
15908
15797
  }
15909
15798
  if (this.widgetType === 'number') {
15910
- return 'Show a fixed prefix and grouped digits in the visible field value without changing the stored number.';
15799
+ return 'Show a fixed prefix in the visible field value without changing the stored number.';
15911
15800
  }
15912
15801
  return 'Show a fixed prefix beside the editable value without changing the stored field value.';
15913
15802
  }
15914
- numericDisplayFormattingLabel() {
15915
- return this.widgetType === 'select'
15916
- ? 'Show thousand separators for numeric labels'
15917
- : 'Show thousand separators for numeric values';
15918
- }
15919
15803
  showStaticOptionsEditor() {
15920
15804
  return this.sourceType === 'static' && this.widgetType !== 'table' && this.usesOptionMapping();
15921
15805
  }
@@ -15944,12 +15828,12 @@ class DataPanelComponent {
15944
15828
  }
15945
15829
  getPreviewLabel(row) {
15946
15830
  const key = this.labelKey || 'label';
15947
- const value = resolvePathValue$1(row, key);
15831
+ const value = resolvePathValue$2(row, key);
15948
15832
  return value === undefined || value === null || value === '' ? '(no label)' : String(value);
15949
15833
  }
15950
15834
  getPreviewValue(row) {
15951
15835
  const key = this.valueKey || 'value';
15952
- const value = resolvePathValue$1(row, key);
15836
+ const value = resolvePathValue$2(row, key);
15953
15837
  return value === undefined || value === null ? '' : String(value);
15954
15838
  }
15955
15839
  availableRootPaths() {
@@ -15966,7 +15850,7 @@ class DataPanelComponent {
15966
15850
  const sample = this.extractPreviewRows(this.previewRows, this.effectiveRowsPath())[0];
15967
15851
  return sample ? collectArrayPaths(sample) : [];
15968
15852
  }
15969
- shouldPersistOptionLabelFormatting() {
15853
+ shouldPersistOptionLabelPrefix() {
15970
15854
  if (this.widgetType === 'select' && this.usesOptionMapping()) {
15971
15855
  return true;
15972
15856
  }
@@ -15982,7 +15866,7 @@ class DataPanelComponent {
15982
15866
  return normalized ? normalized : 'defaultValue';
15983
15867
  }
15984
15868
  readScalarTargetValue() {
15985
- const resolved = resolvePathValue$1(this.config, this.scalarTargetPath());
15869
+ const resolved = resolvePathValue$2(this.config, this.scalarTargetPath());
15986
15870
  if (resolved !== undefined) {
15987
15871
  return resolved;
15988
15872
  }
@@ -16005,7 +15889,7 @@ class DataPanelComponent {
16005
15889
  }
16006
15890
  const flattened = [];
16007
15891
  for (const row of rows) {
16008
- const resolved = resolvePathValue$1(row, normalizedPath);
15892
+ const resolved = resolvePathValue$2(row, normalizedPath);
16009
15893
  if (Array.isArray(resolved)) {
16010
15894
  flattened.push(...resolved);
16011
15895
  continue;
@@ -16297,15 +16181,6 @@ class DataPanelComponent {
16297
16181
  <div class="text-xs font-semibold uppercase tracking-wide text-gray-500">{{ displayFormattingTitle() }}</div>
16298
16182
  <div class="mt-1 text-[11px] text-gray-500">{{ displayFormattingDescription() }}</div>
16299
16183
 
16300
- <label class="mt-3 flex items-center gap-2 text-sm text-gray-700" *ngIf="supportsNumericDisplayFormatting()">
16301
- <input
16302
- type="checkbox"
16303
- [checked]="formatNumericOptionLabels"
16304
- (change)="formatNumericOptionLabels = $any($event.target).checked; emitChange()"
16305
- class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
16306
- <span>{{ numericDisplayFormattingLabel() }}</span>
16307
- </label>
16308
-
16309
16184
  <div class="mt-3 flex flex-col gap-1">
16310
16185
  <label class="text-xs font-medium text-gray-500">Prefix Key</label>
16311
16186
  <input
@@ -16916,15 +16791,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
16916
16791
  <div class="text-xs font-semibold uppercase tracking-wide text-gray-500">{{ displayFormattingTitle() }}</div>
16917
16792
  <div class="mt-1 text-[11px] text-gray-500">{{ displayFormattingDescription() }}</div>
16918
16793
 
16919
- <label class="mt-3 flex items-center gap-2 text-sm text-gray-700" *ngIf="supportsNumericDisplayFormatting()">
16920
- <input
16921
- type="checkbox"
16922
- [checked]="formatNumericOptionLabels"
16923
- (change)="formatNumericOptionLabels = $any($event.target).checked; emitChange()"
16924
- class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
16925
- <span>{{ numericDisplayFormattingLabel() }}</span>
16926
- </label>
16927
-
16928
16794
  <div class="mt-3 flex flex-col gap-1">
16929
16795
  <label class="text-xs font-medium text-gray-500">Prefix Key</label>
16930
16796
  <input
@@ -17866,7 +17732,7 @@ class RulesPanelComponent {
17866
17732
  <div class="flex flex-col gap-2">
17867
17733
  <div class="flex items-center gap-2 bg-amber-50 p-2 rounded-md border border-amber-100">
17868
17734
  <span class="text-xs font-semibold text-amber-700 uppercase">Severity</span>
17869
- <span class="text-xs text-amber-800 italic">Saved for backend validation only.</span>
17735
+ <span class="text-xs text-amber-800 italic">Error severity is emitted in runtime validation; warn stays informational.</span>
17870
17736
  </div>
17871
17737
 
17872
17738
  <div class="flex items-center gap-2">
@@ -17988,7 +17854,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
17988
17854
  <div class="flex flex-col gap-2">
17989
17855
  <div class="flex items-center gap-2 bg-amber-50 p-2 rounded-md border border-amber-100">
17990
17856
  <span class="text-xs font-semibold text-amber-700 uppercase">Severity</span>
17991
- <span class="text-xs text-amber-800 italic">Saved for backend validation only.</span>
17857
+ <span class="text-xs text-amber-800 italic">Error severity is emitted in runtime validation; warn stays informational.</span>
17992
17858
  </div>
17993
17859
 
17994
17860
  <div class="flex items-center gap-2">
@@ -20255,13 +20121,13 @@ async function updateFieldSettings(state, args, widgetDefs = []) {
20255
20121
  updates[key] = value;
20256
20122
  }
20257
20123
  }
20124
+ const html5Patch = {};
20258
20125
  if (typeof a['disabled'] === 'boolean') {
20259
- updates.disabled = a['disabled'];
20126
+ html5Patch['disabled'] = a['disabled'];
20260
20127
  }
20261
20128
  if (typeof a['readonly'] === 'boolean') {
20262
- updates.readonly = a['readonly'];
20129
+ html5Patch['readonly'] = a['readonly'];
20263
20130
  }
20264
- const html5Patch = {};
20265
20131
  const html5Keys = ['required', 'min', 'max', 'step', 'minLength', 'maxLength', 'pattern'];
20266
20132
  for (const key of html5Keys) {
20267
20133
  if (typeof a[key] !== 'undefined') {
@@ -22071,7 +21937,7 @@ const DATA_SOURCES_TEMPLATE = {
22071
21937
  "type": "text",
22072
21938
  "label": "Selected country code",
22073
21939
  "placeholder": "Auto-filled",
22074
- "readonly": true,
21940
+ "html5": { "readonly": true },
22075
21941
  "helpText": "Updated by an event binding on the Country field."
22076
21942
  },
22077
21943
  {
@@ -22477,8 +22343,8 @@ const TRANSACTION_KYC_REVIEW_TEMPLATE = {
22477
22343
  "name": "clientDisplay",
22478
22344
  "type": "text",
22479
22345
  "label": "Client",
22480
- "readonly": true,
22481
22346
  "html5": {
22347
+ "readonly": true,
22482
22348
  "required": false
22483
22349
  },
22484
22350
  "rules": [
@@ -22524,7 +22390,7 @@ const TRANSACTION_KYC_REVIEW_TEMPLATE = {
22524
22390
  "type": "text",
22525
22391
  "label": "Balance",
22526
22392
  "defaultValue": "USD 24,580.00",
22527
- "readonly": true,
22393
+ "html5": { "readonly": true },
22528
22394
  "rules": [
22529
22395
  {
22530
22396
  "id": "rule_balance_visible",
@@ -23292,7 +23158,7 @@ const JOURNEY_BRANCHING_TEMPLATE_STEP_TWO = {
23292
23158
  "name": "summaryCountryCode",
23293
23159
  "type": "text",
23294
23160
  "label": "Country Code",
23295
- "readonly": true,
23161
+ "html5": { "readonly": true },
23296
23162
  "dataConfig": {
23297
23163
  "type": "source",
23298
23164
  "datasourceId": "journey_branch_ds_country_profile",
@@ -23497,7 +23363,7 @@ const JOURNEY_BRANCHING_TEMPLATE_STEP_THREE = {
23497
23363
  "name": "summaryCurrency",
23498
23364
  "type": "text",
23499
23365
  "label": "Currency",
23500
- "readonly": true,
23366
+ "html5": { "readonly": true },
23501
23367
  "dataConfig": {
23502
23368
  "type": "source",
23503
23369
  "datasourceId": "journey_branch_ds_country_profile",
@@ -23510,7 +23376,7 @@ const JOURNEY_BRANCHING_TEMPLATE_STEP_THREE = {
23510
23376
  "name": "summaryTimezone",
23511
23377
  "type": "text",
23512
23378
  "label": "Timezone",
23513
- "readonly": true,
23379
+ "html5": { "readonly": true },
23514
23380
  "dataConfig": {
23515
23381
  "type": "source",
23516
23382
  "datasourceId": "journey_branch_ds_country_profile",
@@ -31742,7 +31608,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
31742
31608
  }]
31743
31609
  }] });
31744
31610
 
31745
- function parsePathSegments$1(path) {
31611
+ function parsePathSegments$2(path) {
31746
31612
  const segments = [];
31747
31613
  const matcher = /([^[.\]]+)|\[(\d+)\]/g;
31748
31614
  let match;
@@ -31758,14 +31624,14 @@ function parsePathSegments$1(path) {
31758
31624
  }
31759
31625
  return segments;
31760
31626
  }
31761
- function resolvePathValue(value, path) {
31627
+ function resolvePathValue$1(value, path) {
31762
31628
  if (!path)
31763
31629
  return value;
31764
31630
  const normalizedPath = path.trim();
31765
31631
  if (!normalizedPath)
31766
31632
  return value;
31767
31633
  let current = value;
31768
- for (const segment of parsePathSegments$1(normalizedPath)) {
31634
+ for (const segment of parsePathSegments$2(normalizedPath)) {
31769
31635
  if (current === null || current === undefined) {
31770
31636
  return undefined;
31771
31637
  }
@@ -31789,7 +31655,7 @@ function hasPathSyntax(path) {
31789
31655
  return false;
31790
31656
  return path.includes('.') || path.includes('[');
31791
31657
  }
31792
- function valuesMatch(left, right) {
31658
+ function valuesMatch$1(left, right) {
31793
31659
  if (Object.is(left, right))
31794
31660
  return true;
31795
31661
  if (left === undefined || left === null || right === undefined || right === null)
@@ -31845,8 +31711,8 @@ class DataProvider {
31845
31711
  if (query.sort && query.sort.length > 0) {
31846
31712
  rows = [...rows].sort((a, b) => {
31847
31713
  for (const s of query.sort) {
31848
- const valA = resolvePathValue(a, s.column);
31849
- const valB = resolvePathValue(b, s.column);
31714
+ const valA = resolvePathValue$1(a, s.column);
31715
+ const valB = resolvePathValue$1(b, s.column);
31850
31716
  if (valA === valB)
31851
31717
  continue;
31852
31718
  const result = compareSortableValues(valA, valB);
@@ -32006,12 +31872,12 @@ class DefaultDataProvider extends DataProvider {
32006
31872
  }
32007
31873
  const row = context.row;
32008
31874
  const resolvedPath = cfg.valueKey;
32009
- const resolvedValue = resolvePathValue(row, resolvedPath);
31875
+ const resolvedValue = resolvePathValue$1(row, resolvedPath);
32010
31876
  if (resolvedPath && resolvedValue !== undefined) {
32011
31877
  return resolvedValue;
32012
31878
  }
32013
- if (resolvePathValue(row, 'value') !== undefined) {
32014
- return resolvePathValue(row, 'value');
31879
+ if (resolvePathValue$1(row, 'value') !== undefined) {
31880
+ return resolvePathValue$1(row, 'value');
32015
31881
  }
32016
31882
  return row;
32017
31883
  }
@@ -32028,7 +31894,7 @@ class DefaultDataProvider extends DataProvider {
32028
31894
  for (const candidate of [context.row, context.parentRow, context.sourceRow]) {
32029
31895
  if (!candidate)
32030
31896
  continue;
32031
- const resolved = resolvePathValue(candidate, prefixPath);
31897
+ const resolved = resolvePathValue$1(candidate, prefixPath);
32032
31898
  const value = toDisplayText(resolved);
32033
31899
  if (value) {
32034
31900
  return value;
@@ -32131,7 +31997,7 @@ class DefaultDataProvider extends DataProvider {
32131
31997
  }
32132
31998
  const flattened = [];
32133
31999
  for (const row of rows) {
32134
- const resolved = resolvePathValue(row, normalizedPath);
32000
+ const resolved = resolvePathValue$1(row, normalizedPath);
32135
32001
  if (Array.isArray(resolved)) {
32136
32002
  for (const entry of resolved) {
32137
32003
  flattened.push({
@@ -32159,7 +32025,7 @@ class DefaultDataProvider extends DataProvider {
32159
32025
  }
32160
32026
  const flattened = [];
32161
32027
  for (const row of rows) {
32162
- const resolved = resolvePathValue(row, normalizedPath);
32028
+ const resolved = resolvePathValue$1(row, normalizedPath);
32163
32029
  if (Array.isArray(resolved)) {
32164
32030
  for (const entry of resolved) {
32165
32031
  flattened.push(this.toRowRecord(entry));
@@ -32187,7 +32053,7 @@ class DefaultDataProvider extends DataProvider {
32187
32053
  if (selectorValue === undefined || selectorValue === null || selectorValue === '') {
32188
32054
  return undefined;
32189
32055
  }
32190
- return rows.find(row => valuesMatch(resolvePathValue(row, cfg.selectionMatchPath), selectorValue));
32056
+ return rows.find(row => valuesMatch$1(resolvePathValue$1(row, cfg.selectionMatchPath), selectorValue));
32191
32057
  }
32192
32058
  selectOptionContext(contexts, cfg, engine) {
32193
32059
  if (contexts.length === 0)
@@ -32204,7 +32070,7 @@ class DefaultDataProvider extends DataProvider {
32204
32070
  if (selectorValue === undefined || selectorValue === null || selectorValue === '') {
32205
32071
  return undefined;
32206
32072
  }
32207
- return contexts.find(context => valuesMatch(resolvePathValue(context.row, cfg.selectionMatchPath), selectorValue));
32073
+ return contexts.find(context => valuesMatch$1(resolvePathValue$1(context.row, cfg.selectionMatchPath), selectorValue));
32208
32074
  }
32209
32075
  applyRowFilters(rows, filters, engine) {
32210
32076
  if (!filters || filters.length === 0)
@@ -32221,7 +32087,7 @@ class DefaultDataProvider extends DataProvider {
32221
32087
  const expected = this.resolveFilterValue(filter, engine);
32222
32088
  if (expected === undefined)
32223
32089
  return true;
32224
- const actual = resolvePathValue(row, filter.column);
32090
+ const actual = resolvePathValue$1(row, filter.column);
32225
32091
  const val = expected;
32226
32092
  switch (filter.op) {
32227
32093
  case 'eq': return actual === val;
@@ -32293,17 +32159,16 @@ class DefaultDataProvider extends DataProvider {
32293
32159
  }
32294
32160
  mapRowToOption(row, labelKey, valueKey) {
32295
32161
  return {
32296
- label: String(resolvePathValue(row, labelKey) ?? ''),
32297
- value: this.toOptionValue(resolvePathValue(row, valueKey))
32162
+ label: String(resolvePathValue$1(row, labelKey) ?? ''),
32163
+ value: this.toOptionValue(resolvePathValue$1(row, valueKey))
32298
32164
  };
32299
32165
  }
32300
32166
  mapContextToOption(context, labelKey, valueKey, cfg) {
32301
- const rawLabel = String(resolvePathValue(context.row, labelKey) ?? '');
32302
32167
  const prefix = this.resolveOptionLabelPrefix(context, cfg);
32303
- const label = this.formatOptionLabel(rawLabel, cfg);
32304
32168
  return {
32305
- label: prefix ? `${prefix} ${label}` : label,
32306
- value: this.toOptionValue(resolvePathValue(context.row, valueKey))
32169
+ label: String(resolvePathValue$1(context.row, labelKey) ?? ''),
32170
+ displayPrefix: prefix || undefined,
32171
+ value: this.toOptionValue(resolvePathValue$1(context.row, valueKey))
32307
32172
  };
32308
32173
  }
32309
32174
  resolveOptionLabelPrefix(context, cfg) {
@@ -32314,7 +32179,7 @@ class DefaultDataProvider extends DataProvider {
32314
32179
  for (const candidate of [context.row, context.parentRow, context.sourceRow]) {
32315
32180
  if (!candidate)
32316
32181
  continue;
32317
- const resolved = resolvePathValue(candidate, prefixPath);
32182
+ const resolved = resolvePathValue$1(candidate, prefixPath);
32318
32183
  const value = toDisplayText(resolved);
32319
32184
  if (value) {
32320
32185
  return value;
@@ -32322,29 +32187,6 @@ class DefaultDataProvider extends DataProvider {
32322
32187
  }
32323
32188
  return '';
32324
32189
  }
32325
- formatOptionLabel(label, cfg) {
32326
- if (!cfg.formatNumericOptionLabels) {
32327
- return label;
32328
- }
32329
- const trimmed = label.trim();
32330
- if (!trimmed) {
32331
- return label;
32332
- }
32333
- const normalized = trimmed.replace(/,/g, '');
32334
- if (!/^-?\d+(\.\d+)?$/.test(normalized)) {
32335
- return label;
32336
- }
32337
- const numericValue = Number(normalized);
32338
- if (!Number.isFinite(numericValue)) {
32339
- return label;
32340
- }
32341
- const fractionPart = normalized.split('.')[1];
32342
- return new Intl.NumberFormat(undefined, {
32343
- useGrouping: true,
32344
- minimumFractionDigits: fractionPart?.length ?? 0,
32345
- maximumFractionDigits: fractionPart?.length ?? 0
32346
- }).format(numericValue);
32347
- }
32348
32190
  async getRuntimeOptions(field, engine) {
32349
32191
  if (!engine)
32350
32192
  return undefined;
@@ -32503,6 +32345,60 @@ function getHeadingClass(level) {
32503
32345
  }
32504
32346
  }
32505
32347
 
32348
+ function parsePathSegments$1(path) {
32349
+ const segments = [];
32350
+ const matcher = /([^[.\]]+)|\[(\d+)\]/g;
32351
+ let match;
32352
+ while ((match = matcher.exec(path)) !== null) {
32353
+ const [, property, index] = match;
32354
+ if (property) {
32355
+ segments.push(property);
32356
+ continue;
32357
+ }
32358
+ if (index !== undefined) {
32359
+ segments.push(index);
32360
+ }
32361
+ }
32362
+ return segments;
32363
+ }
32364
+ function resolvePathValue(value, path) {
32365
+ if (!path)
32366
+ return value;
32367
+ const normalizedPath = path.trim();
32368
+ if (!normalizedPath)
32369
+ return value;
32370
+ let current = value;
32371
+ for (const segment of parsePathSegments$1(normalizedPath)) {
32372
+ if (current === null || current === undefined) {
32373
+ return undefined;
32374
+ }
32375
+ if (Array.isArray(current)) {
32376
+ const index = Number(segment);
32377
+ if (!Number.isInteger(index)) {
32378
+ return undefined;
32379
+ }
32380
+ current = current[index];
32381
+ continue;
32382
+ }
32383
+ if (typeof current !== 'object') {
32384
+ return undefined;
32385
+ }
32386
+ current = current[segment];
32387
+ }
32388
+ return current;
32389
+ }
32390
+ function valuesMatch(left, right) {
32391
+ if (Object.is(left, right))
32392
+ return true;
32393
+ if (left === undefined || left === null || right === undefined || right === null)
32394
+ return false;
32395
+ const leftIsPrimitive = typeof left === 'string' || typeof left === 'number' || typeof left === 'boolean';
32396
+ const rightIsPrimitive = typeof right === 'string' || typeof right === 'number' || typeof right === 'boolean';
32397
+ if (leftIsPrimitive && rightIsPrimitive) {
32398
+ return String(left) === String(right);
32399
+ }
32400
+ return false;
32401
+ }
32506
32402
  class TextFieldWidgetComponent {
32507
32403
  _config;
32508
32404
  hasReceivedConfig = false;
@@ -32552,7 +32448,7 @@ class TextFieldWidgetComponent {
32552
32448
  return this.config?.type === 'color';
32553
32449
  }
32554
32450
  usesFormattedNumberInput() {
32555
- return this.config?.type === 'number' && this.config?.dataConfig?.formatNumericOptionLabels === true;
32451
+ return this.config?.type === 'number' && usesFieldThousandSeparator(this.config);
32556
32452
  }
32557
32453
  hasDisplayPrefix() {
32558
32454
  return !this.isTextarea() && !this.isColorField() && this.displayPrefix.trim().length > 0;
@@ -32609,7 +32505,7 @@ class TextFieldWidgetComponent {
32609
32505
  const datasourceId = this.config?.dataConfig?.datasourceId;
32610
32506
  if (!datasourceId || update.datasourceId !== datasourceId)
32611
32507
  return;
32612
- void this.refreshValueFromDataSource();
32508
+ void this.refreshValueFromDataSource(true);
32613
32509
  });
32614
32510
  }
32615
32511
  this.engine.valueChanges$
@@ -32617,7 +32513,7 @@ class TextFieldWidgetComponent {
32617
32513
  .subscribe(values => {
32618
32514
  this.syncEnabledState();
32619
32515
  if (this.haveDependencyValuesChanged(values)) {
32620
- void this.refreshValueFromDataSource();
32516
+ void this.refreshValueFromDataSource(true);
32621
32517
  }
32622
32518
  });
32623
32519
  // Listen for submit attempts to show validation errors
@@ -32711,7 +32607,7 @@ class TextFieldWidgetComponent {
32711
32607
  return styles;
32712
32608
  }
32713
32609
  return mergeAndNormalize(styles, {
32714
- paddingLeft: `calc(20px + ${Math.max(this.displayPrefix.length, 1)}ch)`
32610
+ paddingLeft: `calc(25px + ${Math.max(this.displayPrefix.length, 1)}ch) !important`
32715
32611
  });
32716
32612
  }
32717
32613
  hasWrapperFrame() {
@@ -32721,7 +32617,7 @@ class TextFieldWidgetComponent {
32721
32617
  return this.engine ? this.engine.isFieldVisible(this.config.id) : true;
32722
32618
  }
32723
32619
  get enabled() {
32724
- if (this.config?.disabled)
32620
+ if (isFieldDisabled(this.config))
32725
32621
  return false;
32726
32622
  return this.engine ? this.engine.isFieldEnabled(this.config.id) : true;
32727
32623
  }
@@ -32747,14 +32643,23 @@ class TextFieldWidgetComponent {
32747
32643
  this.control.disable({ emitEvent: false });
32748
32644
  }
32749
32645
  }
32750
- async refreshValueFromDataSource() {
32646
+ async refreshValueFromDataSource(clearWhenMissing = false) {
32751
32647
  if (!this.dataProvider || !this.config.dataConfig)
32752
32648
  return;
32753
32649
  try {
32650
+ if (clearWhenMissing && !(await this.hasSelectedRowMatch())) {
32651
+ this.clearResolvedValue();
32652
+ return;
32653
+ }
32754
32654
  const val = await this.dataProvider.getValue(this.config, this.engine);
32755
32655
  this.displayPrefix = await this.dataProvider.getValueDisplayPrefix(this.config, this.engine);
32756
- if (val === undefined || val === null)
32656
+ if (val === undefined || val === null) {
32657
+ if (!clearWhenMissing) {
32658
+ return;
32659
+ }
32660
+ this.clearResolvedValue();
32757
32661
  return;
32662
+ }
32758
32663
  this.control.setValue(val, { emitEvent: false });
32759
32664
  this.syncFormattedNumberValue();
32760
32665
  if (this.engine) {
@@ -32765,12 +32670,48 @@ class TextFieldWidgetComponent {
32765
32670
  // Ignore failed datasource refreshes; field remains editable.
32766
32671
  }
32767
32672
  }
32673
+ clearResolvedValue() {
32674
+ this.displayPrefix = '';
32675
+ this.control.setValue(null, { emitEvent: false });
32676
+ this.syncFormattedNumberValue();
32677
+ if (this.engine) {
32678
+ this.engine.setValue(this.config.name, null);
32679
+ }
32680
+ }
32681
+ async hasSelectedRowMatch() {
32682
+ const cfg = this.config.dataConfig;
32683
+ if (!cfg
32684
+ || cfg.rowSelectionMode !== 'selected'
32685
+ || !cfg.selectionFieldId
32686
+ || !cfg.selectionMatchPath
32687
+ || !this.engine) {
32688
+ return true;
32689
+ }
32690
+ const selectorField = this.engine.getSchema().fields.find(field => field.id === cfg.selectionFieldId);
32691
+ if (!selectorField) {
32692
+ return false;
32693
+ }
32694
+ const selectorValue = this.engine.getValue(selectorField.name);
32695
+ if (selectorValue === undefined || selectorValue === null || selectorValue === '') {
32696
+ return false;
32697
+ }
32698
+ const rows = await this.dataProvider.getList(this.config, this.engine);
32699
+ return rows.some(row => valuesMatch(resolvePathValue(row, cfg.selectionMatchPath), selectorValue));
32700
+ }
32768
32701
  getDataConfigSignature(config) {
32769
32702
  if (!config)
32770
32703
  return '';
32771
32704
  const dataConfig = config.dataConfig;
32772
32705
  if (!dataConfig) {
32773
- return `${config.id}::no-data-config`;
32706
+ return [
32707
+ config.id,
32708
+ config.name,
32709
+ config.type,
32710
+ String(config.html5?.readonly ?? ''),
32711
+ String(config.html5?.disabled ?? ''),
32712
+ String(config.useThousandSeparator ?? ''),
32713
+ 'no-data-config'
32714
+ ].join('::');
32774
32715
  }
32775
32716
  const sourceKey = dataConfig.type === 'source' || dataConfig.type === 'global' || dataConfig.type === 'api'
32776
32717
  ? String(dataConfig.datasourceId ?? '')
@@ -32778,16 +32719,18 @@ class TextFieldWidgetComponent {
32778
32719
  return [
32779
32720
  config.id,
32780
32721
  config.name,
32722
+ String(config.html5?.readonly ?? ''),
32723
+ String(config.html5?.disabled ?? ''),
32781
32724
  dataConfig.type ?? '',
32782
32725
  sourceKey,
32783
32726
  String(dataConfig.valueKey ?? ''),
32784
32727
  String(dataConfig.rowsPath ?? ''),
32785
- String(dataConfig.formatNumericOptionLabels ?? ''),
32786
32728
  String(dataConfig.optionLabelPrefixPath ?? ''),
32787
32729
  String(dataConfig.optionLabelPrefixFieldId ?? ''),
32788
32730
  String(dataConfig.rowSelectionMode ?? ''),
32789
32731
  String(dataConfig.selectionFieldId ?? ''),
32790
32732
  String(dataConfig.selectionMatchPath ?? ''),
32733
+ String(config.useThousandSeparator ?? ''),
32791
32734
  (dataConfig.filters ?? []).map(filter => `${filter.column}:${filter.valueFrom ?? ''}:${filter.fieldId ?? ''}`).join('|')
32792
32735
  ].join('::');
32793
32736
  }
@@ -32922,7 +32865,7 @@ class TextFieldWidgetComponent {
32922
32865
  (blur)="onBlur()"
32923
32866
  (mouseenter)="onMouseEnter()"
32924
32867
  (mouseleave)="onMouseLeave()"
32925
- [readonly]="config.readonly"
32868
+ [readonly]="config.html5?.readonly"
32926
32869
  [attr.aria-required]="required"
32927
32870
  [attr.aria-invalid]="!!error"
32928
32871
  [attr.aria-describedby]="getAriaDescribedBy()"
@@ -32943,7 +32886,7 @@ class TextFieldWidgetComponent {
32943
32886
  (blur)="onBlur()"
32944
32887
  (mouseenter)="onMouseEnter()"
32945
32888
  (mouseleave)="onMouseLeave()"
32946
- [readonly]="config.readonly"
32889
+ [readonly]="config.html5?.readonly"
32947
32890
  [attr.aria-required]="required"
32948
32891
  [attr.aria-invalid]="!!error"
32949
32892
  [attr.aria-describedby]="getAriaDescribedBy()"
@@ -32955,7 +32898,7 @@ class TextFieldWidgetComponent {
32955
32898
  [cpAlphaChannel]="'disabled'"
32956
32899
  [cpOutputFormat]="'hex'"
32957
32900
  [cpFallbackColor]="'#000000'"
32958
- [cpDisabled]="config.readonly || !enabled"
32901
+ [cpDisabled]="config.html5?.readonly || !enabled"
32959
32902
  (colorPickerChange)="onColorPickerChange($event)">
32960
32903
  } @else if (usesFormattedNumberInput()) {
32961
32904
  @if (hasDisplayPrefix()) {
@@ -32977,7 +32920,7 @@ class TextFieldWidgetComponent {
32977
32920
  (blur)="onBlur()"
32978
32921
  (mouseenter)="onMouseEnter()"
32979
32922
  (mouseleave)="onMouseLeave()"
32980
- [readonly]="config.readonly"
32923
+ [readonly]="config.html5?.readonly"
32981
32924
  [disabled]="control.disabled"
32982
32925
  [attr.aria-required]="required"
32983
32926
  [attr.aria-invalid]="!!error"
@@ -33007,7 +32950,7 @@ class TextFieldWidgetComponent {
33007
32950
  (blur)="onBlur()"
33008
32951
  (mouseenter)="onMouseEnter()"
33009
32952
  (mouseleave)="onMouseLeave()"
33010
- [readonly]="config.readonly"
32953
+ [readonly]="config.html5?.readonly"
33011
32954
  [attr.aria-required]="required"
33012
32955
  [attr.aria-invalid]="!!error"
33013
32956
  [attr.aria-describedby]="getAriaDescribedBy()"
@@ -33065,7 +33008,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
33065
33008
  (blur)="onBlur()"
33066
33009
  (mouseenter)="onMouseEnter()"
33067
33010
  (mouseleave)="onMouseLeave()"
33068
- [readonly]="config.readonly"
33011
+ [readonly]="config.html5?.readonly"
33069
33012
  [attr.aria-required]="required"
33070
33013
  [attr.aria-invalid]="!!error"
33071
33014
  [attr.aria-describedby]="getAriaDescribedBy()"
@@ -33086,7 +33029,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
33086
33029
  (blur)="onBlur()"
33087
33030
  (mouseenter)="onMouseEnter()"
33088
33031
  (mouseleave)="onMouseLeave()"
33089
- [readonly]="config.readonly"
33032
+ [readonly]="config.html5?.readonly"
33090
33033
  [attr.aria-required]="required"
33091
33034
  [attr.aria-invalid]="!!error"
33092
33035
  [attr.aria-describedby]="getAriaDescribedBy()"
@@ -33098,7 +33041,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
33098
33041
  [cpAlphaChannel]="'disabled'"
33099
33042
  [cpOutputFormat]="'hex'"
33100
33043
  [cpFallbackColor]="'#000000'"
33101
- [cpDisabled]="config.readonly || !enabled"
33044
+ [cpDisabled]="config.html5?.readonly || !enabled"
33102
33045
  (colorPickerChange)="onColorPickerChange($event)">
33103
33046
  } @else if (usesFormattedNumberInput()) {
33104
33047
  @if (hasDisplayPrefix()) {
@@ -33120,7 +33063,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
33120
33063
  (blur)="onBlur()"
33121
33064
  (mouseenter)="onMouseEnter()"
33122
33065
  (mouseleave)="onMouseLeave()"
33123
- [readonly]="config.readonly"
33066
+ [readonly]="config.html5?.readonly"
33124
33067
  [disabled]="control.disabled"
33125
33068
  [attr.aria-required]="required"
33126
33069
  [attr.aria-invalid]="!!error"
@@ -33150,7 +33093,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
33150
33093
  (blur)="onBlur()"
33151
33094
  (mouseenter)="onMouseEnter()"
33152
33095
  (mouseleave)="onMouseLeave()"
33153
- [readonly]="config.readonly"
33096
+ [readonly]="config.html5?.readonly"
33154
33097
  [attr.aria-required]="required"
33155
33098
  [attr.aria-invalid]="!!error"
33156
33099
  [attr.aria-describedby]="getAriaDescribedBy()"
@@ -33263,12 +33206,12 @@ class FileUploadWidgetComponent {
33263
33206
  return this.engine ? this.engine.isFieldVisible(this.config.id) : true;
33264
33207
  }
33265
33208
  get enabled() {
33266
- if (this.config?.disabled)
33209
+ if (isFieldDisabled(this.config))
33267
33210
  return false;
33268
33211
  return this.engine ? this.engine.isFieldEnabled(this.config.id) : true;
33269
33212
  }
33270
33213
  get isDisabled() {
33271
- return !this.enabled || !!this.config?.readonly;
33214
+ return !this.enabled || isFieldReadonly(this.config);
33272
33215
  }
33273
33216
  get required() {
33274
33217
  return this.engine ? this.engine.isFieldRequired(this.config.id) : !!this.config?.html5?.required;
@@ -33759,7 +33702,7 @@ class SelectWidgetComponent {
33759
33702
  this.runtimeOptionsLoaded = false;
33760
33703
  this.currentSearchTerm = '';
33761
33704
  this.setOptionsFromRaw([]);
33762
- void this.loadOptions(this.currentSearchTerm);
33705
+ void this.loadOptions(this.currentSearchTerm, false, true);
33763
33706
  });
33764
33707
  }
33765
33708
  }
@@ -33902,7 +33845,7 @@ class SelectWidgetComponent {
33902
33845
  return this.engine ? this.engine.isFieldVisible(this.config.id) : true;
33903
33846
  }
33904
33847
  get enabled() {
33905
- if (this.config?.disabled)
33848
+ if (isFieldDisabled(this.config))
33906
33849
  return false;
33907
33850
  return this.engine ? this.engine.isFieldEnabled(this.config.id) : true;
33908
33851
  }
@@ -33959,7 +33902,7 @@ class SelectWidgetComponent {
33959
33902
  event.preventDefault();
33960
33903
  }
33961
33904
  onPillClick() {
33962
- if (this.config.readonly) {
33905
+ if (isFieldReadonly(this.config)) {
33963
33906
  return;
33964
33907
  }
33965
33908
  this.onClick();
@@ -34026,7 +33969,7 @@ class SelectWidgetComponent {
34026
33969
  assign('--fd-select-icon-color', controlStyles['iconColor'] ?? controlStyles['color']);
34027
33970
  return vars;
34028
33971
  }
34029
- async loadOptions(term = '', isSearch = false) {
33972
+ async loadOptions(term = '', isSearch = false, clearStaleSelection = false) {
34030
33973
  if (!this.config)
34031
33974
  return;
34032
33975
  const reqId = ++this.requestId;
@@ -34044,8 +33987,17 @@ class SelectWidgetComponent {
34044
33987
  opts = await this.dataProvider.getOptions(this.config, this.engine);
34045
33988
  }
34046
33989
  if (this.requestId === reqId) {
34047
- this.setOptionsFromRaw(this.withSelectedValueFallbackOptions(opts));
34048
- this.syncStoredFieldLabel(this.control.value);
33990
+ const selectedValue = this.resolveSelectedValueForFallback();
33991
+ const shouldClearStaleSelection = clearStaleSelection
33992
+ && this.hasMeaningfulValue(selectedValue)
33993
+ && !this.hasOptionValue(opts, this.toComparableOptionValue(selectedValue));
33994
+ this.setOptionsFromRaw(shouldClearStaleSelection ? opts : this.withSelectedValueFallbackOptions(opts));
33995
+ if (shouldClearStaleSelection) {
33996
+ this.control.setValue(null);
33997
+ }
33998
+ else {
33999
+ this.syncStoredFieldLabel(this.control.value);
34000
+ }
34049
34001
  this.loading = false;
34050
34002
  this.loadError = null;
34051
34003
  this.cdr.markForCheck();
@@ -34152,6 +34104,9 @@ class SelectWidgetComponent {
34152
34104
  config.id,
34153
34105
  config.name,
34154
34106
  config.type,
34107
+ String(config.html5?.readonly ?? ''),
34108
+ String(config.html5?.disabled ?? ''),
34109
+ String(config.useThousandSeparator ?? ''),
34155
34110
  'no-data-config',
34156
34111
  fieldStaticOptions
34157
34112
  ].join('::');
@@ -34164,11 +34119,13 @@ class SelectWidgetComponent {
34164
34119
  config.id,
34165
34120
  config.name,
34166
34121
  config.type,
34122
+ String(config.html5?.readonly ?? ''),
34123
+ String(config.html5?.disabled ?? ''),
34167
34124
  dataConfig.type ?? '',
34168
34125
  String(dataConfig.datasourceId ?? ''),
34169
34126
  String(dataConfig.labelKey ?? ''),
34170
34127
  String(dataConfig.valueKey ?? ''),
34171
- String(dataConfig.formatNumericOptionLabels ?? ''),
34128
+ String(config.useThousandSeparator ?? ''),
34172
34129
  String(dataConfig.optionLabelPrefixFieldId ?? ''),
34173
34130
  String(dataConfig.searchEnabled ?? ''),
34174
34131
  String(dataConfig.optionsLimit ?? ''),
@@ -34221,15 +34178,18 @@ class SelectWidgetComponent {
34221
34178
  applyDisplayFormatting() {
34222
34179
  this.options = this.rawOptions.map(option => ({
34223
34180
  ...option,
34224
- label: this.formatOptionLabel(option.label)
34181
+ label: this.formatOptionLabel(option)
34225
34182
  }));
34226
34183
  }
34227
- formatOptionLabel(label) {
34228
- const formattedLabel = this.config.dataConfig?.formatNumericOptionLabels
34184
+ formatOptionLabel(option) {
34185
+ const label = option.label;
34186
+ const formattedLabel = usesFieldThousandSeparator(this.config)
34229
34187
  ? this.formatNumericLabel(label)
34230
34188
  : label;
34231
- const prefix = this.resolveOptionLabelPrefix();
34232
- return prefix ? `${prefix} ${formattedLabel}` : formattedLabel;
34189
+ const prefixParts = [option.displayPrefix, this.resolveOptionLabelPrefix()]
34190
+ .map(value => value?.trim())
34191
+ .filter((value) => !!value);
34192
+ return prefixParts.length > 0 ? `${prefixParts.join(' ')} ${formattedLabel}` : formattedLabel;
34233
34193
  }
34234
34194
  resolveOptionLabelPrefix() {
34235
34195
  const prefixFieldId = this.config.dataConfig?.optionLabelPrefixFieldId;
@@ -34312,6 +34272,12 @@ class SelectWidgetComponent {
34312
34272
  hasOptionValue(options, value) {
34313
34273
  return options.some(option => Object.is(option.value, value) || String(option.value) === String(value));
34314
34274
  }
34275
+ toComparableOptionValue(value) {
34276
+ if (typeof value === 'number' && Number.isFinite(value)) {
34277
+ return value;
34278
+ }
34279
+ return String(value ?? '');
34280
+ }
34315
34281
  resolveSelectedValueForFallback() {
34316
34282
  const controlValue = this.control.value;
34317
34283
  if (this.hasMeaningfulValue(controlValue)) {
@@ -34356,7 +34322,7 @@ class SelectWidgetComponent {
34356
34322
  if (this.config.dataConfig?.optionLabelPrefixFieldId) {
34357
34323
  return this.resolveOptionLabelPrefix().length === 0;
34358
34324
  }
34359
- return this.config.dataConfig?.formatNumericOptionLabels !== true;
34325
+ return !usesFieldThousandSeparator(this.config);
34360
34326
  }
34361
34327
  setStoredFieldLabel(label) {
34362
34328
  if (!this.engine || typeof this.engine.setFieldLabel !== 'function') {
@@ -34394,8 +34360,8 @@ class SelectWidgetComponent {
34394
34360
  [loadingText]="'Loading options...'"
34395
34361
  [notFoundText]="loadError || 'No options found'"
34396
34362
  [searchable]="isSearchable()"
34397
- [clearable]="!required && !config.readonly && !hasPillLabel()"
34398
- [readonly]="config.readonly"
34363
+ [clearable]="!required && !config.html5?.readonly && !hasPillLabel()"
34364
+ [readonly]="config.html5?.readonly"
34399
34365
  [labelForId]="fieldId"
34400
34366
  [inputAttrs]="getInputAttributes()"
34401
34367
  [typeahead]="searchTerms$"
@@ -34421,7 +34387,7 @@ class SelectWidgetComponent {
34421
34387
  class="fd-select-pill"
34422
34388
  [ngStyle]="getPillStyles()"
34423
34389
  data-fd-select-pill
34424
- [disabled]="config.readonly"
34390
+ [disabled]="config.html5?.readonly"
34425
34391
  (mousedown)="onPillMouseDown($event)"
34426
34392
  (click)="onPillClick()">
34427
34393
  <span class="fd-select-pill__label">{{ pillLabel() }}</span>
@@ -34474,8 +34440,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
34474
34440
  [loadingText]="'Loading options...'"
34475
34441
  [notFoundText]="loadError || 'No options found'"
34476
34442
  [searchable]="isSearchable()"
34477
- [clearable]="!required && !config.readonly && !hasPillLabel()"
34478
- [readonly]="config.readonly"
34443
+ [clearable]="!required && !config.html5?.readonly && !hasPillLabel()"
34444
+ [readonly]="config.html5?.readonly"
34479
34445
  [labelForId]="fieldId"
34480
34446
  [inputAttrs]="getInputAttributes()"
34481
34447
  [typeahead]="searchTerms$"
@@ -34501,7 +34467,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
34501
34467
  class="fd-select-pill"
34502
34468
  [ngStyle]="getPillStyles()"
34503
34469
  data-fd-select-pill
34504
- [disabled]="config.readonly"
34470
+ [disabled]="config.html5?.readonly"
34505
34471
  (mousedown)="onPillMouseDown($event)"
34506
34472
  (click)="onPillClick()">
34507
34473
  <span class="fd-select-pill__label">{{ pillLabel() }}</span>
@@ -34596,7 +34562,7 @@ class SearchWidgetComponent {
34596
34562
  return this.engine ? this.engine.isFieldVisible(this.config.id) : true;
34597
34563
  }
34598
34564
  get enabled() {
34599
- if (this.config?.disabled)
34565
+ if (isFieldDisabled(this.config))
34600
34566
  return false;
34601
34567
  return this.engine ? this.engine.isFieldEnabled(this.config.id) : true;
34602
34568
  }
@@ -34659,7 +34625,7 @@ class SearchWidgetComponent {
34659
34625
  const datasourceId = this.config?.dataConfig?.datasourceId;
34660
34626
  if (!datasourceId || update.datasourceId !== datasourceId)
34661
34627
  return;
34662
- void this.refreshForCurrentQuery();
34628
+ void this.refreshForCurrentQuery(true);
34663
34629
  });
34664
34630
  }
34665
34631
  this.engine.valueChanges$
@@ -34803,7 +34769,7 @@ class SearchWidgetComponent {
34803
34769
  this.queryControl.disable({ emitEvent: false });
34804
34770
  }
34805
34771
  }
34806
- async refreshForCurrentQuery() {
34772
+ async refreshForCurrentQuery(clearStaleSelection = false) {
34807
34773
  const query = this.queryControl.value ?? '';
34808
34774
  if (!this.shouldQuery(query)) {
34809
34775
  this.options = [];
@@ -34813,7 +34779,7 @@ class SearchWidgetComponent {
34813
34779
  this.cdr.markForCheck();
34814
34780
  return;
34815
34781
  }
34816
- await this.loadOptions(query);
34782
+ await this.loadOptions(query, clearStaleSelection);
34817
34783
  }
34818
34784
  async handleQueryChange(query) {
34819
34785
  if (this.selectingOption) {
@@ -34833,7 +34799,7 @@ class SearchWidgetComponent {
34833
34799
  }
34834
34800
  await this.loadOptions(normalizedQuery);
34835
34801
  }
34836
- async loadOptions(term) {
34802
+ async loadOptions(term, clearStaleSelection = false) {
34837
34803
  const requestId = ++this.requestId;
34838
34804
  this.loading = true;
34839
34805
  this.loadError = null;
@@ -34847,6 +34813,15 @@ class SearchWidgetComponent {
34847
34813
  return;
34848
34814
  }
34849
34815
  this.options = this.normalizeOptions(options);
34816
+ if (clearStaleSelection && this.isSelectedOptionState() && !this.hasOptionValue(this.options, this.control.value)) {
34817
+ this.queryControl.setValue('', { emitEvent: false });
34818
+ this.control.setValue(null);
34819
+ this.loading = false;
34820
+ this.loadError = null;
34821
+ this.activeOptionIndex = -1;
34822
+ this.cdr.markForCheck();
34823
+ return;
34824
+ }
34850
34825
  this.activeOptionIndex = this.options.length > 0 ? 0 : -1;
34851
34826
  this.loading = false;
34852
34827
  this.loadError = null;
@@ -34878,6 +34853,31 @@ class SearchWidgetComponent {
34878
34853
  const minChars = this.getMinChars();
34879
34854
  return (query ?? '').trim().length >= minChars;
34880
34855
  }
34856
+ isSelectedOptionState() {
34857
+ if (!this.hasMeaningfulValue(this.control.value)) {
34858
+ return false;
34859
+ }
34860
+ const storedLabel = this.getStoredFieldLabel();
34861
+ if (typeof storedLabel === 'string' && storedLabel.length > 0) {
34862
+ return true;
34863
+ }
34864
+ return String(this.control.value ?? '') !== String(this.queryControl.value ?? '');
34865
+ }
34866
+ hasOptionValue(options, value) {
34867
+ if (!this.hasMeaningfulValue(value)) {
34868
+ return false;
34869
+ }
34870
+ return options.some(option => Object.is(option.value, value) || String(option.value) === String(value));
34871
+ }
34872
+ hasMeaningfulValue(value) {
34873
+ if (value === undefined || value === null)
34874
+ return false;
34875
+ if (typeof value === 'string')
34876
+ return value.length > 0;
34877
+ if (Array.isArray(value))
34878
+ return value.length > 0;
34879
+ return true;
34880
+ }
34881
34881
  getMinChars() {
34882
34882
  return this.toSafeNonNegativeInt(this.config?.['searchMinChars'], 1);
34883
34883
  }
@@ -34945,7 +34945,7 @@ class SearchWidgetComponent {
34945
34945
  type="search"
34946
34946
  [placeholder]="config.placeholder || 'Search...'"
34947
34947
  [formControl]="queryControl"
34948
- [readonly]="config.readonly"
34948
+ [readonly]="config.html5?.readonly"
34949
34949
  [attr.aria-required]="required"
34950
34950
  [attr.aria-invalid]="!!error"
34951
34951
  [attr.aria-describedby]="getAriaDescribedBy()"
@@ -35024,7 +35024,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
35024
35024
  type="search"
35025
35025
  [placeholder]="config.placeholder || 'Search...'"
35026
35026
  [formControl]="queryControl"
35027
- [readonly]="config.readonly"
35027
+ [readonly]="config.html5?.readonly"
35028
35028
  [attr.aria-required]="required"
35029
35029
  [attr.aria-invalid]="!!error"
35030
35030
  [attr.aria-describedby]="getAriaDescribedBy()"
@@ -35311,7 +35311,7 @@ class TreeSelectWidgetComponent {
35311
35311
  expandRecursive(filtered);
35312
35312
  }
35313
35313
  toggleDropdown(e) {
35314
- if (this.config.disabled || this.config.readonly)
35314
+ if (isFieldDisabled(this.config) || isFieldReadonly(this.config))
35315
35315
  return;
35316
35316
  this.engine?.emitUiEvent({ fieldId: this.config.id, fieldName: this.config.name, type: 'click' });
35317
35317
  if (this.isOpen) {
@@ -35423,8 +35423,8 @@ class TreeSelectWidgetComponent {
35423
35423
  <!-- Dropdown Trigger -->
35424
35424
  <div class="relative group" (click)="toggleDropdown($event)">
35425
35425
  <div class="flex items-center justify-between w-full rounded-md border text-sm px-3 py-2 cursor-pointer transition-colors"
35426
- [class.bg-gray-50]="config.disabled"
35427
- [class.cursor-not-allowed]="config.disabled"
35426
+ [class.bg-gray-50]="config.html5?.disabled"
35427
+ [class.cursor-not-allowed]="config.html5?.disabled"
35428
35428
  [class.border-gray-300]="!isInvalid && !isOpen"
35429
35429
  [class.border-blue-500]="isOpen"
35430
35430
  [class.ring-2]="isOpen"
@@ -35527,8 +35527,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
35527
35527
  <!-- Dropdown Trigger -->
35528
35528
  <div class="relative group" (click)="toggleDropdown($event)">
35529
35529
  <div class="flex items-center justify-between w-full rounded-md border text-sm px-3 py-2 cursor-pointer transition-colors"
35530
- [class.bg-gray-50]="config.disabled"
35531
- [class.cursor-not-allowed]="config.disabled"
35530
+ [class.bg-gray-50]="config.html5?.disabled"
35531
+ [class.cursor-not-allowed]="config.html5?.disabled"
35532
35532
  [class.border-gray-300]="!isInvalid && !isOpen"
35533
35533
  [class.border-blue-500]="isOpen"
35534
35534
  [class.ring-2]="isOpen"
@@ -35778,7 +35778,7 @@ class RadioWidgetComponent {
35778
35778
  return this.engine ? this.engine.isFieldVisible(this.config.id) : true;
35779
35779
  }
35780
35780
  get enabled() {
35781
- if (this.config?.disabled)
35781
+ if (isFieldDisabled(this.config))
35782
35782
  return false;
35783
35783
  return this.engine ? this.engine.isFieldEnabled(this.config.id) : true;
35784
35784
  }
@@ -36094,7 +36094,7 @@ class CheckboxGroupWidgetComponent {
36094
36094
  return this.engine ? this.engine.isFieldVisible(this.config.id) : true;
36095
36095
  }
36096
36096
  get enabled() {
36097
- if (this.config?.disabled)
36097
+ if (isFieldDisabled(this.config))
36098
36098
  return false;
36099
36099
  return this.engine ? this.engine.isFieldEnabled(this.config.id) : true;
36100
36100
  }
@@ -36330,7 +36330,7 @@ class CheckboxWidgetComponent {
36330
36330
  return this.engine ? this.engine.isFieldVisible(this.config.id) : true;
36331
36331
  }
36332
36332
  get enabled() {
36333
- if (this.config?.disabled)
36333
+ if (isFieldDisabled(this.config))
36334
36334
  return false;
36335
36335
  return this.engine ? this.engine.isFieldEnabled(this.config.id) : true;
36336
36336
  }
@@ -36564,7 +36564,7 @@ class RepeatableGroupWidgetComponent {
36564
36564
  return this.engine ? this.engine.isFieldVisible(this.config.id) : true;
36565
36565
  }
36566
36566
  get enabled() {
36567
- if (this.config?.disabled)
36567
+ if (isFieldDisabled(this.config))
36568
36568
  return false;
36569
36569
  return this.engine ? this.engine.isFieldEnabled(this.config.id) : true;
36570
36570
  }
@@ -37023,7 +37023,7 @@ class InlineQuillEditorComponent {
37023
37023
  setHtml(nextHtml) {
37024
37024
  if (!this.quill)
37025
37025
  return;
37026
- if (this.quill.hasFocus())
37026
+ if (this.hasFocus || this.quill.hasFocus())
37027
37027
  return;
37028
37028
  const normalized = nextHtml ?? '';
37029
37029
  if (normalized === this.lastHtml)
@@ -37899,8 +37899,8 @@ const COMMON_GROUP_FIELDS = [
37899
37899
  { key: 'groupKey', type: 'text', label: 'Group Key', placeholder: 'documents', helpText: 'Group fields into one object on submit/preview (uses the field key inside the group).' }
37900
37900
  ];
37901
37901
  const COMMON_INTERACTIVITY_FIELDS = [
37902
- { key: 'readonly', type: 'checkbox', label: 'Read Only' },
37903
- { key: 'disabled', type: 'checkbox', label: 'Disabled' }
37902
+ { key: 'html5.readonly', type: 'checkbox', label: 'Read Only' },
37903
+ { key: 'html5.disabled', type: 'checkbox', label: 'Disabled' }
37904
37904
  ];
37905
37905
  const COMMON_BASIC_FIELDS = [
37906
37906
  { key: 'name', type: 'text', label: 'Field Key (Name)' },
@@ -37960,16 +37960,10 @@ const BASE_REQUIRED_FIELD = {
37960
37960
  label: 'Required',
37961
37961
  helpText: 'Base required state; Rules can override this dynamically.'
37962
37962
  };
37963
- const CUSTOM_VALIDATION_RULES_FIELD = {
37964
- key: 'validation',
37965
- type: 'validators-editor',
37966
- label: 'Custom Validation Rules',
37967
- helpText: 'Use these for field-level validation checks. Use the Rules tab for conditional behaviour.'
37968
- };
37969
37963
  function createValidationSection(...fields) {
37970
37964
  return {
37971
37965
  label: 'Validation',
37972
- fields: [...fields, CUSTOM_VALIDATION_RULES_FIELD]
37966
+ fields
37973
37967
  };
37974
37968
  }
37975
37969
  const TEXT_VALIDATION_SECTION = createValidationSection(BASE_REQUIRED_FIELD, { key: 'html5.minLength', type: 'number', label: 'Min Length' }, { key: 'html5.maxLength', type: 'number', label: 'Max Length' }, { key: 'html5.pattern', type: 'text', label: 'Pattern (regex)' });
@@ -37979,6 +37973,28 @@ const TIME_VALIDATION_SECTION = createValidationSection(BASE_REQUIRED_FIELD, { k
37979
37973
  const DATETIME_VALIDATION_SECTION = createValidationSection(BASE_REQUIRED_FIELD, { key: 'html5.min', type: 'text', label: 'Earliest (YYYY-MM-DDTHH:MM)' }, { key: 'html5.max', type: 'text', label: 'Latest (YYYY-MM-DDTHH:MM)' });
37980
37974
  const SELECT_VALIDATION_SECTION = createValidationSection(BASE_REQUIRED_FIELD);
37981
37975
  const FILE_VALIDATION_SECTION = createValidationSection(BASE_REQUIRED_FIELD);
37976
+ const NUMBER_DISPLAY_SECTION = {
37977
+ label: 'Display',
37978
+ fields: [
37979
+ {
37980
+ key: 'useThousandSeparator',
37981
+ type: 'checkbox',
37982
+ label: 'Show thousand separators',
37983
+ helpText: 'Formats grouped digits in the visible field value without changing the stored number.'
37984
+ }
37985
+ ]
37986
+ };
37987
+ const SELECT_DISPLAY_SECTION = {
37988
+ label: 'Display',
37989
+ fields: [
37990
+ {
37991
+ key: 'useThousandSeparator',
37992
+ type: 'checkbox',
37993
+ label: 'Show thousand separators for numeric labels',
37994
+ helpText: 'Formats numeric option labels without changing the stored option value.'
37995
+ }
37996
+ ]
37997
+ };
37982
37998
  const BUTTON_VARIANT_OPTIONS = [
37983
37999
  { label: 'Primary (Blue)', value: 'primary' },
37984
38000
  { label: 'Secondary (Outline)', value: 'secondary' }
@@ -38187,6 +38203,7 @@ const FIELD_WIDGETS = [
38187
38203
  ]
38188
38204
  },
38189
38205
  NUMBER_VALIDATION_SECTION,
38206
+ NUMBER_DISPLAY_SECTION,
38190
38207
  { label: 'Appearance', fields: COMMON_APPEARANCE_FIELDS },
38191
38208
  ...COMMON_STYLE_SECTIONS
38192
38209
  ]
@@ -38648,6 +38665,7 @@ const FIELD_WIDGETS = [
38648
38665
  },
38649
38666
  // Options are now handled in Data Tab
38650
38667
  SELECT_VALIDATION_SECTION,
38668
+ SELECT_DISPLAY_SECTION,
38651
38669
  { label: 'Appearance', fields: COMMON_APPEARANCE_FIELDS },
38652
38670
  ...COMMON_STYLE_SECTIONS
38653
38671
  ]
@@ -41463,5 +41481,5 @@ function provideHttpDataSourceClient(config) {
41463
41481
  * Generated bundle index. Do not edit.
41464
41482
  */
41465
41483
 
41466
- 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 };
41484
+ 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, isFieldDisabled, isFieldReadonly, isFormJourneyProject, isWidgetVisibleInPalette, mergeAndNormalize, normalizeRuntimeOptions, normalizeStyle$1 as normalizeStyle, normalizeToJourney, parseCsv, parseJsonArray, parseSchema, provideDesignerPlugins, provideFormDesignerAngaiFeature, provideHttpDataSourceClient, serializeSchema, slugifyId, splitControlSurfaceStyles, stripEmbeddedSourceData, unwrapSinglePageJourney, usesFieldThousandSeparator };
41467
41485
  //# sourceMappingURL=uch-web-ngx-form-designer.mjs.map