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

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.
@@ -5257,7 +5257,7 @@ class JsonFormRendererComponent {
5257
5257
  this.runtimeFieldDataAccessRegistry.unregister(this.engine);
5258
5258
  }
5259
5259
  async emitValuePayload(values) {
5260
- const fieldValueMap = await this.buildFieldValueMap(values);
5260
+ const fieldValueMap = await this.buildFieldValueMap(values, { normalizeFileFieldsForSubmit: true });
5261
5261
  const groupedValues = this.buildGroupedValues(fieldValueMap);
5262
5262
  const combinedValues = this.buildCombinedValues(fieldValueMap);
5263
5263
  this.valueChange.emit(fieldValueMap);
@@ -5419,16 +5419,10 @@ class JsonFormRendererComponent {
5419
5419
  this.validationChange.emit(validation);
5420
5420
  // Notify all widgets to show errors if any
5421
5421
  this.engine?.submit();
5422
- const preUploadFieldValueMap = this.uploadOnSubmit
5423
- ? await this.buildFieldValueMap(this.engine?.getValues() ?? {}, { normalizeFileFieldsForSubmit: true })
5424
- : undefined;
5425
5422
  const uploadedFiles = this.uploadOnSubmit ? await this.uploadPendingFiles() : {};
5426
5423
  this.uploadedFilesChange.emit(uploadedFiles);
5427
5424
  const values = this.engine?.getValues() ?? {};
5428
- const fieldValueMap = await this.buildFieldValueMap(values, { normalizeFileFieldsForSubmit: true });
5429
- const submitValues = preUploadFieldValueMap
5430
- ? this.mergeFileMetadata(fieldValueMap, preUploadFieldValueMap)
5431
- : fieldValueMap;
5425
+ const submitValues = await this.buildFieldValueMap(values, { normalizeFileFieldsForSubmit: true });
5432
5426
  this.formSubmit.emit({
5433
5427
  values: submitValues,
5434
5428
  groupedValues: this.buildGroupedValues(submitValues),
@@ -5689,13 +5683,14 @@ class JsonFormRendererComponent {
5689
5683
  continue;
5690
5684
  }
5691
5685
  if (field.type === 'file') {
5686
+ const normalizeFileFieldsForSubmit = options.normalizeFileFieldsForSubmit === true;
5692
5687
  const fileValue = {
5693
5688
  fieldName: field.name,
5694
- fieldValue: options.normalizeFileFieldsForSubmit
5695
- ? this.normalizeFileFieldSubmitValue(rawValue)
5689
+ fieldValue: normalizeFileFieldsForSubmit
5690
+ ? await this.buildFileSubmitValueEntries(rawValue)
5696
5691
  : rawValue,
5697
- ...(options.normalizeFileFieldsForSubmit ? { fieldType: field.type } : {}),
5698
- ...(await this.buildFileFieldMetadata(rawValue))
5692
+ ...(normalizeFileFieldsForSubmit ? { fieldType: field.type } : {}),
5693
+ ...(!normalizeFileFieldsForSubmit ? await this.buildFileFieldMetadata(rawValue) : {})
5699
5694
  };
5700
5695
  mapped[field.id] = fileValue;
5701
5696
  continue;
@@ -5782,7 +5777,12 @@ class JsonFormRendererComponent {
5782
5777
  return undefined;
5783
5778
  return values.length === 1 ? values[0] : values;
5784
5779
  }
5785
- normalizeFileFieldSubmitValue(value) {
5780
+ async buildFileSubmitValueEntries(value) {
5781
+ const rawEntries = this.normalizeFileFieldEntries(value);
5782
+ const normalizedEntries = await Promise.all(rawEntries.map(entry => this.normalizeSingleFileFieldEntry(entry)));
5783
+ return normalizedEntries.filter((entry) => entry !== null);
5784
+ }
5785
+ normalizeFileFieldEntries(value) {
5786
5786
  if (this.isFileList(value)) {
5787
5787
  return Array.from(value);
5788
5788
  }
@@ -5794,6 +5794,23 @@ class JsonFormRendererComponent {
5794
5794
  }
5795
5795
  return [value];
5796
5796
  }
5797
+ async normalizeSingleFileFieldEntry(value) {
5798
+ if (this.isFile(value)) {
5799
+ return this.buildPendingSubmitFileValue(value);
5800
+ }
5801
+ if (this.isUploadedFileRef(value)) {
5802
+ return { ...value };
5803
+ }
5804
+ return null;
5805
+ }
5806
+ async buildPendingSubmitFileValue(file) {
5807
+ return {
5808
+ name: file.name || undefined,
5809
+ size: Number.isFinite(file.size) ? file.size : undefined,
5810
+ type: file.type || undefined,
5811
+ data: await this.readFileBase64(file)
5812
+ };
5813
+ }
5797
5814
  getUploadedFileRefs(value) {
5798
5815
  if (this.isUploadedFileRef(value))
5799
5816
  return [value];
@@ -5808,26 +5825,6 @@ class JsonFormRendererComponent {
5808
5825
  : undefined;
5809
5826
  return fieldLabel === undefined ? {} : { fieldLabel };
5810
5827
  }
5811
- mergeFileMetadata(target, source) {
5812
- const merged = { ...target };
5813
- for (const fieldId of Object.keys(source)) {
5814
- const sourceValue = source[fieldId];
5815
- const targetValue = merged[fieldId];
5816
- if (!sourceValue || !targetValue)
5817
- continue;
5818
- if (sourceValue.fileType === undefined && sourceValue.data === undefined)
5819
- continue;
5820
- const nextValue = { ...targetValue };
5821
- if (sourceValue.fileType !== undefined) {
5822
- nextValue.fileType = sourceValue.fileType;
5823
- }
5824
- if (sourceValue.data !== undefined) {
5825
- nextValue.data = sourceValue.data;
5826
- }
5827
- merged[fieldId] = nextValue;
5828
- }
5829
- return merged;
5830
- }
5831
5828
  buildGroupedValues(values) {
5832
5829
  const grouped = {};
5833
5830
  const schema = this.engine?.getSchema();
@@ -15891,8 +15888,33 @@ class DataPanelComponent {
15891
15888
  showOptionMappingControls() {
15892
15889
  return this.sourceType === 'source' && this.widgetType !== 'table' && this.usesOptionMapping();
15893
15890
  }
15894
- showOptionLabelFormattingControls() {
15895
- return this.widgetType === 'select' && this.usesOptionMapping();
15891
+ showDisplayFormattingControls() {
15892
+ if (this.widgetType === 'select' && this.usesOptionMapping()) {
15893
+ return true;
15894
+ }
15895
+ return this.sourceType === 'source'
15896
+ && this.bindingShape === 'scalar'
15897
+ && (this.widgetType === 'text' || this.widgetType === 'number');
15898
+ }
15899
+ supportsNumericDisplayFormatting() {
15900
+ return this.widgetType === 'select' || this.widgetType === 'number';
15901
+ }
15902
+ displayFormattingTitle() {
15903
+ return this.widgetType === 'select' ? 'Amount Display' : 'Field Display';
15904
+ }
15905
+ displayFormattingDescription() {
15906
+ if (this.widgetType === 'select') {
15907
+ return 'Format the visible dropdown label without changing the stored option value.';
15908
+ }
15909
+ if (this.widgetType === 'number') {
15910
+ return 'Show a fixed prefix and grouped digits in the visible field value without changing the stored number.';
15911
+ }
15912
+ return 'Show a fixed prefix beside the editable value without changing the stored field value.';
15913
+ }
15914
+ numericDisplayFormattingLabel() {
15915
+ return this.widgetType === 'select'
15916
+ ? 'Show thousand separators for numeric labels'
15917
+ : 'Show thousand separators for numeric values';
15896
15918
  }
15897
15919
  showStaticOptionsEditor() {
15898
15920
  return this.sourceType === 'static' && this.widgetType !== 'table' && this.usesOptionMapping();
@@ -15945,7 +15967,12 @@ class DataPanelComponent {
15945
15967
  return sample ? collectArrayPaths(sample) : [];
15946
15968
  }
15947
15969
  shouldPersistOptionLabelFormatting() {
15948
- return this.widgetType === 'select' && this.usesOptionMapping();
15970
+ if (this.widgetType === 'select' && this.usesOptionMapping()) {
15971
+ return true;
15972
+ }
15973
+ return this.sourceType === 'source'
15974
+ && this.bindingShape === 'scalar'
15975
+ && (this.widgetType === 'text' || this.widgetType === 'number');
15949
15976
  }
15950
15977
  usesOptionMapping() {
15951
15978
  return this.bindingShape === 'list' || this.widgetType === 'search';
@@ -16266,17 +16293,17 @@ class DataPanelComponent {
16266
16293
  </div>
16267
16294
  </div>
16268
16295
 
16269
- <div class="rounded-lg border border-gray-200 bg-white p-3" *ngIf="showOptionLabelFormattingControls()">
16270
- <div class="text-xs font-semibold uppercase tracking-wide text-gray-500">Amount Display</div>
16271
- <div class="mt-1 text-[11px] text-gray-500">Format the visible dropdown label without changing the stored option value.</div>
16296
+ <div class="rounded-lg border border-gray-200 bg-white p-3" *ngIf="showDisplayFormattingControls()">
16297
+ <div class="text-xs font-semibold uppercase tracking-wide text-gray-500">{{ displayFormattingTitle() }}</div>
16298
+ <div class="mt-1 text-[11px] text-gray-500">{{ displayFormattingDescription() }}</div>
16272
16299
 
16273
- <label class="mt-3 flex items-center gap-2 text-sm text-gray-700">
16300
+ <label class="mt-3 flex items-center gap-2 text-sm text-gray-700" *ngIf="supportsNumericDisplayFormatting()">
16274
16301
  <input
16275
16302
  type="checkbox"
16276
16303
  [checked]="formatNumericOptionLabels"
16277
16304
  (change)="formatNumericOptionLabels = $any($event.target).checked; emitChange()"
16278
16305
  class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
16279
- <span>Show thousand separators for numeric labels</span>
16306
+ <span>{{ numericDisplayFormattingLabel() }}</span>
16280
16307
  </label>
16281
16308
 
16282
16309
  <div class="mt-3 flex flex-col gap-1">
@@ -16885,17 +16912,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
16885
16912
  </div>
16886
16913
  </div>
16887
16914
 
16888
- <div class="rounded-lg border border-gray-200 bg-white p-3" *ngIf="showOptionLabelFormattingControls()">
16889
- <div class="text-xs font-semibold uppercase tracking-wide text-gray-500">Amount Display</div>
16890
- <div class="mt-1 text-[11px] text-gray-500">Format the visible dropdown label without changing the stored option value.</div>
16915
+ <div class="rounded-lg border border-gray-200 bg-white p-3" *ngIf="showDisplayFormattingControls()">
16916
+ <div class="text-xs font-semibold uppercase tracking-wide text-gray-500">{{ displayFormattingTitle() }}</div>
16917
+ <div class="mt-1 text-[11px] text-gray-500">{{ displayFormattingDescription() }}</div>
16891
16918
 
16892
- <label class="mt-3 flex items-center gap-2 text-sm text-gray-700">
16919
+ <label class="mt-3 flex items-center gap-2 text-sm text-gray-700" *ngIf="supportsNumericDisplayFormatting()">
16893
16920
  <input
16894
16921
  type="checkbox"
16895
16922
  [checked]="formatNumericOptionLabels"
16896
16923
  (change)="formatNumericOptionLabels = $any($event.target).checked; emitChange()"
16897
16924
  class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
16898
- <span>Show thousand separators for numeric labels</span>
16925
+ <span>{{ numericDisplayFormattingLabel() }}</span>
16899
16926
  </label>
16900
16927
 
16901
16928
  <div class="mt-3 flex flex-col gap-1">
@@ -17674,11 +17701,18 @@ class RulesPanelComponent {
17674
17701
  ruleActionOptions = [
17675
17702
  { label: 'Visible', value: 'visible' },
17676
17703
  { label: 'Hidden', value: 'hidden' },
17677
- { label: 'Enabled', value: 'enable' },
17678
17704
  { label: 'Disabled', value: 'disable' },
17679
17705
  { label: 'Required', value: 'required' },
17680
17706
  { label: 'Optional', value: 'optional' }
17681
17707
  ];
17708
+ actionLabels = {
17709
+ visible: 'Visible',
17710
+ hidden: 'Hidden',
17711
+ enable: 'Enabled',
17712
+ disable: 'Disabled',
17713
+ required: 'Required',
17714
+ optional: 'Optional'
17715
+ };
17682
17716
  severityOptions = [
17683
17717
  { label: 'Warn', value: 'warn' },
17684
17718
  { label: 'Error', value: 'error' }
@@ -17730,7 +17764,7 @@ class RulesPanelComponent {
17730
17764
  return `${primary} / else ${this.getActionLabel(rule.elseAction)}`;
17731
17765
  }
17732
17766
  getActionLabel(action) {
17733
- return this.ruleActionOptions.find((option) => option.value === action)?.label ?? action;
17767
+ return this.actionLabels[action] ?? action;
17734
17768
  }
17735
17769
  getActionColor(action) {
17736
17770
  switch (action) {
@@ -17749,7 +17783,7 @@ class RulesPanelComponent {
17749
17783
  <div class="flex items-center justify-between mb-2">
17750
17784
  <div class="flex flex-col">
17751
17785
  <span class="font-semibold text-gray-700">Rules</span>
17752
- <span class="text-xs text-gray-500">Rules can override visibility, enabled, and required set elsewhere.</span>
17786
+ <span class="text-xs text-gray-500">Rules can override visibility, disabled, and required state set elsewhere.</span>
17753
17787
  </div>
17754
17788
  <button (click)="addRule()"
17755
17789
  [disabled]="readOnly"
@@ -17871,7 +17905,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
17871
17905
  <div class="flex items-center justify-between mb-2">
17872
17906
  <div class="flex flex-col">
17873
17907
  <span class="font-semibold text-gray-700">Rules</span>
17874
- <span class="text-xs text-gray-500">Rules can override visibility, enabled, and required set elsewhere.</span>
17908
+ <span class="text-xs text-gray-500">Rules can override visibility, disabled, and required state set elsewhere.</span>
17875
17909
  </div>
17876
17910
  <button (click)="addRule()"
17877
17911
  [disabled]="readOnly"
@@ -19546,34 +19580,35 @@ class FormPreviewComponent {
19546
19580
  copyData() {
19547
19581
  navigator.clipboard.writeText(JSON.stringify(this.previewData(), null, 2));
19548
19582
  }
19549
- sanitizePreviewData(value) {
19583
+ sanitizePreviewData(value, withinFileField = false) {
19550
19584
  if (Array.isArray(value)) {
19551
- return value.map(item => this.sanitizePreviewData(item));
19585
+ return value.map(item => this.sanitizePreviewData(item, withinFileField));
19552
19586
  }
19553
19587
  if (!this.isObjectRecord(value)) {
19554
19588
  return value;
19555
19589
  }
19590
+ const nextWithinFileField = withinFileField || this.isFileFieldRecord(value);
19556
19591
  const sanitized = {};
19557
19592
  for (const [key, entryValue] of Object.entries(value)) {
19558
- if (key === 'data' && this.shouldHideFileData(value, entryValue)) {
19593
+ if (key === 'data' && this.shouldHideFileData(nextWithinFileField, entryValue)) {
19559
19594
  continue;
19560
19595
  }
19561
- sanitized[key] = this.sanitizePreviewData(entryValue);
19596
+ sanitized[key] = this.sanitizePreviewData(entryValue, nextWithinFileField);
19562
19597
  }
19563
19598
  return sanitized;
19564
19599
  }
19565
19600
  isObjectRecord(value) {
19566
19601
  return !!value && typeof value === 'object';
19567
19602
  }
19568
- shouldHideFileData(record, value) {
19569
- if (!this.isFileFieldRecord(record))
19603
+ shouldHideFileData(withinFileField, value) {
19604
+ if (!withinFileField)
19570
19605
  return false;
19571
19606
  return this.isBytePayload(value) || this.isBase64Payload(value);
19572
19607
  }
19573
19608
  isFileFieldRecord(value) {
19574
19609
  return typeof value['fieldName'] === 'string'
19575
19610
  && Object.hasOwn(value, 'fieldValue')
19576
- && Object.hasOwn(value, 'fileType');
19611
+ && (value['fieldType'] === 'file' || Object.hasOwn(value, 'fileType'));
19577
19612
  }
19578
19613
  isBytePayload(value) {
19579
19614
  if (value instanceof Uint8Array)
@@ -31774,7 +31809,20 @@ function compareSortableValues(left, right) {
31774
31809
  }
31775
31810
  return String(left ?? '').localeCompare(String(right ?? ''));
31776
31811
  }
31812
+ function toDisplayText(value) {
31813
+ if (typeof value === 'string') {
31814
+ const trimmed = value.trim();
31815
+ return trimmed.length > 0 ? trimmed : undefined;
31816
+ }
31817
+ if (typeof value === 'number' || typeof value === 'boolean') {
31818
+ return String(value);
31819
+ }
31820
+ return undefined;
31821
+ }
31777
31822
  class DataProvider {
31823
+ async getValueDisplayPrefix(_field, _engine) {
31824
+ return '';
31825
+ }
31778
31826
  // Non-abstract with default implementation for backward compatibility
31779
31827
  async queryOptions(field, query, engine) {
31780
31828
  const options = await this.getOptions(field, engine);
@@ -31946,23 +31994,17 @@ class DefaultDataProvider extends DataProvider {
31946
31994
  }
31947
31995
  async getValue(field, engine) {
31948
31996
  const cfg = getEffectiveDataConfig(field);
31997
+ const currentValue = this.getCurrentFieldValue(field, engine);
31949
31998
  if (cfg.type === 'static') {
31950
31999
  if (cfg.staticValue !== undefined)
31951
32000
  return cfg.staticValue;
31952
- return field.defaultValue;
31953
- }
31954
- let rows = await this.getRawRows(cfg, field, engine);
31955
- rows = this.resolveCollectionRows(rows, cfg, engine);
31956
- rows = this.applyRowFilters(rows, cfg.filters, engine);
31957
- if (!rows || rows.length === 0) {
31958
- return field.defaultValue;
32001
+ return currentValue !== undefined ? currentValue : field.defaultValue;
31959
32002
  }
31960
- const selectedRow = this.selectScalarRow(rows, cfg, engine);
31961
- if (cfg.rowSelectionMode === 'selected' && !selectedRow) {
31962
- const currentValue = field.name && engine ? engine.getValue(field.name) : undefined;
32003
+ const context = await this.getScalarValueContext(cfg, field, engine);
32004
+ if (!context) {
31963
32005
  return currentValue !== undefined ? currentValue : field.defaultValue;
31964
32006
  }
31965
- const row = selectedRow ?? rows[0];
32007
+ const row = context.row;
31966
32008
  const resolvedPath = cfg.valueKey;
31967
32009
  const resolvedValue = resolvePathValue(row, resolvedPath);
31968
32010
  if (resolvedPath && resolvedValue !== undefined) {
@@ -31973,6 +32015,27 @@ class DefaultDataProvider extends DataProvider {
31973
32015
  }
31974
32016
  return row;
31975
32017
  }
32018
+ async getValueDisplayPrefix(field, engine) {
32019
+ const cfg = getEffectiveDataConfig(field);
32020
+ const prefixPath = cfg.optionLabelPrefixPath?.trim();
32021
+ if (!prefixPath) {
32022
+ return '';
32023
+ }
32024
+ const context = await this.getScalarValueContext(cfg, field, engine);
32025
+ if (!context) {
32026
+ return '';
32027
+ }
32028
+ for (const candidate of [context.row, context.parentRow, context.sourceRow]) {
32029
+ if (!candidate)
32030
+ continue;
32031
+ const resolved = resolvePathValue(candidate, prefixPath);
32032
+ const value = toDisplayText(resolved);
32033
+ if (value) {
32034
+ return value;
32035
+ }
32036
+ }
32037
+ return '';
32038
+ }
31976
32039
  async resolveValue(field, value) {
31977
32040
  return String(value);
31978
32041
  }
@@ -32007,6 +32070,9 @@ class DefaultDataProvider extends DataProvider {
32007
32070
  return [];
32008
32071
  }
32009
32072
  }
32073
+ getCurrentFieldValue(field, engine) {
32074
+ return field.name && engine ? engine.getValue(field.name) : undefined;
32075
+ }
32010
32076
  async getGlobalRows(cfg) {
32011
32077
  if (!cfg.datasourceId)
32012
32078
  return [];
@@ -32017,6 +32083,18 @@ class DefaultDataProvider extends DataProvider {
32017
32083
  const rows = await this.getRawRows(cfg, field, engine);
32018
32084
  return this.resolveCollectionRowContexts(rows, cfg, engine);
32019
32085
  }
32086
+ async getScalarValueContext(cfg, field, engine) {
32087
+ const contexts = await this.getOptionRowContexts(cfg, field, engine);
32088
+ const filteredContexts = this.applyOptionRowFilters(contexts, cfg.filters, engine);
32089
+ if (filteredContexts.length === 0) {
32090
+ return undefined;
32091
+ }
32092
+ const selectedContext = this.selectOptionContext(filteredContexts, cfg, engine);
32093
+ if (cfg.rowSelectionMode === 'selected' && !selectedContext) {
32094
+ return undefined;
32095
+ }
32096
+ return selectedContext ?? filteredContexts[0];
32097
+ }
32020
32098
  resolveCollectionRowContexts(rows, cfg, engine) {
32021
32099
  const parentContexts = this.extractRowContexts(rows, cfg.rowsPath);
32022
32100
  if (cfg.rowSelectionMode === 'selected' && cfg.childRowsPath) {
@@ -32237,10 +32315,8 @@ class DefaultDataProvider extends DataProvider {
32237
32315
  if (!candidate)
32238
32316
  continue;
32239
32317
  const resolved = resolvePathValue(candidate, prefixPath);
32240
- if (resolved === undefined || resolved === null)
32241
- continue;
32242
- const value = String(resolved).trim();
32243
- if (value.length > 0) {
32318
+ const value = toDisplayText(resolved);
32319
+ if (value) {
32244
32320
  return value;
32245
32321
  }
32246
32322
  }
@@ -32433,6 +32509,8 @@ class TextFieldWidgetComponent {
32433
32509
  dependencyValueSnapshot = new Map();
32434
32510
  isControlFocused = false;
32435
32511
  isControlHovered = false;
32512
+ displayPrefix = '';
32513
+ formattedNumberValue = '';
32436
32514
  set config(value) {
32437
32515
  const previousSignature = this.getDataConfigSignature(this._config);
32438
32516
  this._config = value;
@@ -32447,7 +32525,10 @@ class TextFieldWidgetComponent {
32447
32525
  if (this.dataProvider && this._config?.dataConfig) {
32448
32526
  this.seedDependencySnapshotFromEngine();
32449
32527
  void this.refreshValueFromDataSource();
32528
+ return;
32450
32529
  }
32530
+ this.displayPrefix = '';
32531
+ this.syncFormattedNumberValue();
32451
32532
  }
32452
32533
  get config() {
32453
32534
  return this._config;
@@ -32470,6 +32551,12 @@ class TextFieldWidgetComponent {
32470
32551
  isColorField() {
32471
32552
  return this.config?.type === 'color';
32472
32553
  }
32554
+ usesFormattedNumberInput() {
32555
+ return this.config?.type === 'number' && this.config?.dataConfig?.formatNumericOptionLabels === true;
32556
+ }
32557
+ hasDisplayPrefix() {
32558
+ return !this.isTextarea() && !this.isColorField() && this.displayPrefix.trim().length > 0;
32559
+ }
32473
32560
  getControlClass(multiline = false) {
32474
32561
  return getTextControlClass({
32475
32562
  invalid: !!this.error,
@@ -32507,6 +32594,9 @@ class TextFieldWidgetComponent {
32507
32594
  if (this.dataProvider && this.config.dataConfig) {
32508
32595
  void this.refreshValueFromDataSource();
32509
32596
  }
32597
+ else {
32598
+ this.syncFormattedNumberValue();
32599
+ }
32510
32600
  // Sync enabled state based on config + rules
32511
32601
  this.syncEnabledState();
32512
32602
  if (this.engine) {
@@ -32542,6 +32632,9 @@ class TextFieldWidgetComponent {
32542
32632
  this.control.valueChanges
32543
32633
  .pipe(takeUntilDestroyed(this.destroyRef))
32544
32634
  .subscribe(val => {
32635
+ if (this.usesFormattedNumberInput() && !this.isControlFocused) {
32636
+ this.syncFormattedNumberValue();
32637
+ }
32545
32638
  if (this.engine) {
32546
32639
  this.engine.setValue(this.config.name, val);
32547
32640
  this.engine.emitUiEvent({ fieldId: this.config.id, fieldName: this.config.name, type: 'change', value: val });
@@ -32553,6 +32646,7 @@ class TextFieldWidgetComponent {
32553
32646
  }
32554
32647
  onFocus() {
32555
32648
  this.isControlFocused = true;
32649
+ this.syncFormattedNumberValue();
32556
32650
  if (this.engine)
32557
32651
  this.engine.emitUiEvent({ fieldId: this.config.id, fieldName: this.config.name, type: 'focus' });
32558
32652
  }
@@ -32562,6 +32656,8 @@ class TextFieldWidgetComponent {
32562
32656
  }
32563
32657
  onBlur() {
32564
32658
  this.isControlFocused = false;
32659
+ this.control.markAsTouched();
32660
+ this.syncFormattedNumberValue();
32565
32661
  if (this.engine) {
32566
32662
  this.engine.emitUiEvent({ fieldId: this.config.id, fieldName: this.config.name, type: 'blur' });
32567
32663
  if (this.control.touched) {
@@ -32609,6 +32705,15 @@ class TextFieldWidgetComponent {
32609
32705
  invalid: !!this.error
32610
32706
  }), splitControlSurfaceStyles(this.config.style).controlStyles);
32611
32707
  }
32708
+ getSingleLineControlStyles() {
32709
+ const styles = this.getControlStyles();
32710
+ if (!this.hasDisplayPrefix()) {
32711
+ return styles;
32712
+ }
32713
+ return mergeAndNormalize(styles, {
32714
+ paddingLeft: `calc(20px + ${Math.max(this.displayPrefix.length, 1)}ch)`
32715
+ });
32716
+ }
32612
32717
  hasWrapperFrame() {
32613
32718
  return hasWrapperSurfaceStyles(this.config.style);
32614
32719
  }
@@ -32647,9 +32752,11 @@ class TextFieldWidgetComponent {
32647
32752
  return;
32648
32753
  try {
32649
32754
  const val = await this.dataProvider.getValue(this.config, this.engine);
32755
+ this.displayPrefix = await this.dataProvider.getValueDisplayPrefix(this.config, this.engine);
32650
32756
  if (val === undefined || val === null)
32651
32757
  return;
32652
32758
  this.control.setValue(val, { emitEvent: false });
32759
+ this.syncFormattedNumberValue();
32653
32760
  if (this.engine) {
32654
32761
  this.engine.setValue(this.config.name, val);
32655
32762
  }
@@ -32675,6 +32782,9 @@ class TextFieldWidgetComponent {
32675
32782
  sourceKey,
32676
32783
  String(dataConfig.valueKey ?? ''),
32677
32784
  String(dataConfig.rowsPath ?? ''),
32785
+ String(dataConfig.formatNumericOptionLabels ?? ''),
32786
+ String(dataConfig.optionLabelPrefixPath ?? ''),
32787
+ String(dataConfig.optionLabelPrefixFieldId ?? ''),
32678
32788
  String(dataConfig.rowSelectionMode ?? ''),
32679
32789
  String(dataConfig.selectionFieldId ?? ''),
32680
32790
  String(dataConfig.selectionMatchPath ?? ''),
@@ -32730,6 +32840,59 @@ class TextFieldWidgetComponent {
32730
32840
  .filter(field => dependencyIds.has(field.id))
32731
32841
  .map(field => field.name);
32732
32842
  }
32843
+ onFormattedNumberInput(event) {
32844
+ const input = event.target;
32845
+ this.formattedNumberValue = input.value;
32846
+ if (this.control.pristine) {
32847
+ this.control.markAsDirty();
32848
+ }
32849
+ const normalized = input.value.replace(/,/g, '').trim();
32850
+ if (!normalized) {
32851
+ this.control.setValue(null);
32852
+ return;
32853
+ }
32854
+ const parsed = Number(normalized);
32855
+ this.control.setValue(Number.isFinite(parsed) ? parsed : null);
32856
+ }
32857
+ syncFormattedNumberValue() {
32858
+ if (!this.usesFormattedNumberInput()) {
32859
+ return;
32860
+ }
32861
+ const rawValue = this.control.value;
32862
+ if (rawValue === undefined || rawValue === null || rawValue === '') {
32863
+ this.formattedNumberValue = '';
32864
+ return;
32865
+ }
32866
+ if (typeof rawValue !== 'string' && typeof rawValue !== 'number') {
32867
+ this.formattedNumberValue = '';
32868
+ return;
32869
+ }
32870
+ const normalized = String(rawValue).replace(/,/g, '').trim();
32871
+ if (!normalized) {
32872
+ this.formattedNumberValue = '';
32873
+ return;
32874
+ }
32875
+ if (this.isControlFocused) {
32876
+ this.formattedNumberValue = normalized;
32877
+ return;
32878
+ }
32879
+ this.formattedNumberValue = this.formatNumericValue(normalized);
32880
+ }
32881
+ formatNumericValue(value) {
32882
+ if (!/^-?\d+(\.\d+)?$/.test(value)) {
32883
+ return value;
32884
+ }
32885
+ const numericValue = Number(value);
32886
+ if (!Number.isFinite(numericValue)) {
32887
+ return value;
32888
+ }
32889
+ const fractionPart = value.split('.')[1];
32890
+ return new Intl.NumberFormat(undefined, {
32891
+ useGrouping: true,
32892
+ minimumFractionDigits: fractionPart?.length ?? 0,
32893
+ maximumFractionDigits: fractionPart?.length ?? 0
32894
+ }).format(numericValue);
32895
+ }
32733
32896
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: TextFieldWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
32734
32897
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: TextFieldWidgetComponent, isStandalone: true, selector: "app-text-field-widget", inputs: { config: "config", engine: "engine", control: "control" }, ngImport: i0, template: `
32735
32898
  <div [class]="fieldContainerClass"
@@ -32794,7 +32957,46 @@ class TextFieldWidgetComponent {
32794
32957
  [cpFallbackColor]="'#000000'"
32795
32958
  [cpDisabled]="config.readonly || !enabled"
32796
32959
  (colorPickerChange)="onColorPickerChange($event)">
32960
+ } @else if (usesFormattedNumberInput()) {
32961
+ @if (hasDisplayPrefix()) {
32962
+ <span
32963
+ class="pointer-events-none absolute left-3 top-1/2 z-[1] -translate-y-1/2 text-sm text-slate-500"
32964
+ data-fd-field-prefix>
32965
+ {{ displayPrefix }}
32966
+ </span>
32967
+ }
32968
+ <input
32969
+ [id]="fieldId"
32970
+ type="text"
32971
+ inputmode="decimal"
32972
+ [placeholder]="config.placeholder || ''"
32973
+ [value]="formattedNumberValue"
32974
+ (input)="onFormattedNumberInput($event)"
32975
+ (click)="onClick()"
32976
+ (focus)="onFocus()"
32977
+ (blur)="onBlur()"
32978
+ (mouseenter)="onMouseEnter()"
32979
+ (mouseleave)="onMouseLeave()"
32980
+ [readonly]="config.readonly"
32981
+ [disabled]="control.disabled"
32982
+ [attr.aria-required]="required"
32983
+ [attr.aria-invalid]="!!error"
32984
+ [attr.aria-describedby]="getAriaDescribedBy()"
32985
+ [attr.aria-label]="getAccessibleLabel()"
32986
+ [class]="getControlClass()"
32987
+ [ngStyle]="getSingleLineControlStyles()"
32988
+ data-fd="field-control"
32989
+ [attr.min]="config.html5?.min"
32990
+ [attr.max]="config.html5?.max"
32991
+ [attr.step]="config.html5?.step">
32797
32992
  } @else {
32993
+ @if (hasDisplayPrefix()) {
32994
+ <span
32995
+ class="pointer-events-none absolute left-3 top-1/2 z-[1] -translate-y-1/2 text-sm text-slate-500"
32996
+ data-fd-field-prefix>
32997
+ {{ displayPrefix }}
32998
+ </span>
32999
+ }
32798
33000
  <input
32799
33001
  [id]="fieldId"
32800
33002
  [type]="config.type"
@@ -32811,7 +33013,7 @@ class TextFieldWidgetComponent {
32811
33013
  [attr.aria-describedby]="getAriaDescribedBy()"
32812
33014
  [attr.aria-label]="getAccessibleLabel()"
32813
33015
  [class]="getControlClass()"
32814
- [ngStyle]="getControlStyles()"
33016
+ [ngStyle]="getSingleLineControlStyles()"
32815
33017
  data-fd="field-control"
32816
33018
  [attr.min]="config.html5?.min"
32817
33019
  [attr.max]="config.html5?.max"
@@ -32898,7 +33100,46 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
32898
33100
  [cpFallbackColor]="'#000000'"
32899
33101
  [cpDisabled]="config.readonly || !enabled"
32900
33102
  (colorPickerChange)="onColorPickerChange($event)">
33103
+ } @else if (usesFormattedNumberInput()) {
33104
+ @if (hasDisplayPrefix()) {
33105
+ <span
33106
+ class="pointer-events-none absolute left-3 top-1/2 z-[1] -translate-y-1/2 text-sm text-slate-500"
33107
+ data-fd-field-prefix>
33108
+ {{ displayPrefix }}
33109
+ </span>
33110
+ }
33111
+ <input
33112
+ [id]="fieldId"
33113
+ type="text"
33114
+ inputmode="decimal"
33115
+ [placeholder]="config.placeholder || ''"
33116
+ [value]="formattedNumberValue"
33117
+ (input)="onFormattedNumberInput($event)"
33118
+ (click)="onClick()"
33119
+ (focus)="onFocus()"
33120
+ (blur)="onBlur()"
33121
+ (mouseenter)="onMouseEnter()"
33122
+ (mouseleave)="onMouseLeave()"
33123
+ [readonly]="config.readonly"
33124
+ [disabled]="control.disabled"
33125
+ [attr.aria-required]="required"
33126
+ [attr.aria-invalid]="!!error"
33127
+ [attr.aria-describedby]="getAriaDescribedBy()"
33128
+ [attr.aria-label]="getAccessibleLabel()"
33129
+ [class]="getControlClass()"
33130
+ [ngStyle]="getSingleLineControlStyles()"
33131
+ data-fd="field-control"
33132
+ [attr.min]="config.html5?.min"
33133
+ [attr.max]="config.html5?.max"
33134
+ [attr.step]="config.html5?.step">
32901
33135
  } @else {
33136
+ @if (hasDisplayPrefix()) {
33137
+ <span
33138
+ class="pointer-events-none absolute left-3 top-1/2 z-[1] -translate-y-1/2 text-sm text-slate-500"
33139
+ data-fd-field-prefix>
33140
+ {{ displayPrefix }}
33141
+ </span>
33142
+ }
32902
33143
  <input
32903
33144
  [id]="fieldId"
32904
33145
  [type]="config.type"
@@ -32915,7 +33156,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
32915
33156
  [attr.aria-describedby]="getAriaDescribedBy()"
32916
33157
  [attr.aria-label]="getAccessibleLabel()"
32917
33158
  [class]="getControlClass()"
32918
- [ngStyle]="getControlStyles()"
33159
+ [ngStyle]="getSingleLineControlStyles()"
32919
33160
  data-fd="field-control"
32920
33161
  [attr.min]="config.html5?.min"
32921
33162
  [attr.max]="config.html5?.max"
@@ -37657,6 +37898,10 @@ function generateId$2() {
37657
37898
  const COMMON_GROUP_FIELDS = [
37658
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).' }
37659
37900
  ];
37901
+ const COMMON_INTERACTIVITY_FIELDS = [
37902
+ { key: 'readonly', type: 'checkbox', label: 'Read Only' },
37903
+ { key: 'disabled', type: 'checkbox', label: 'Disabled' }
37904
+ ];
37660
37905
  const COMMON_BASIC_FIELDS = [
37661
37906
  { key: 'name', type: 'text', label: 'Field Key (Name)' },
37662
37907
  { key: 'label', type: 'text', label: 'Label' },
@@ -37664,6 +37909,7 @@ const COMMON_BASIC_FIELDS = [
37664
37909
  { key: 'placeholder', type: 'text', label: 'Placeholder' },
37665
37910
  { key: 'helpText', type: 'text', label: 'Help Text' }, // Hint
37666
37911
  { key: 'tooltip', type: 'text', label: 'Tooltip' },
37912
+ ...COMMON_INTERACTIVITY_FIELDS,
37667
37913
  ...COMMON_GROUP_FIELDS
37668
37914
  ];
37669
37915
  const FILE_BASIC_FIELDS = [
@@ -37672,6 +37918,7 @@ const FILE_BASIC_FIELDS = [
37672
37918
  { key: 'showLabel', type: 'checkbox', label: 'Show Native Label' },
37673
37919
  { key: 'helpText', type: 'text', label: 'Help Text' },
37674
37920
  { key: 'tooltip', type: 'text', label: 'Tooltip' },
37921
+ ...COMMON_INTERACTIVITY_FIELDS,
37675
37922
  ...COMMON_GROUP_FIELDS
37676
37923
  ];
37677
37924
  const COMMON_APPEARANCE_FIELDS = [
@@ -37696,9 +37943,7 @@ const COMMON_APPEARANCE_FIELDS = [
37696
37943
  { label: 'Third Width (33%)', value: '33.33%' },
37697
37944
  { label: 'Quarter Width (25%)', value: '25%' }
37698
37945
  ]
37699
- },
37700
- { key: 'readonly', type: 'checkbox', label: 'Read Only' },
37701
- { key: 'disabled', type: 'checkbox', label: 'Disabled' }
37946
+ }
37702
37947
  ];
37703
37948
  const COMMON_STYLE_SECTIONS = [
37704
37949
  STYLE_LAYOUT_SECTION,
@@ -37749,6 +37994,7 @@ const BUTTON_PROPERTIES = [
37749
37994
  label: 'Style',
37750
37995
  fields: [
37751
37996
  { key: 'variant', type: 'select', label: 'Variant', options: BUTTON_VARIANT_OPTIONS },
37997
+ ...COMMON_INTERACTIVITY_FIELDS,
37752
37998
  ...COMMON_APPEARANCE_FIELDS
37753
37999
  ]
37754
38000
  },
@@ -37767,6 +38013,7 @@ const IMAGE_BUTTON_PROPERTIES = [
37767
38013
  label: 'Style',
37768
38014
  fields: [
37769
38015
  { key: 'variant', type: 'select', label: 'Variant', options: BUTTON_VARIANT_OPTIONS },
38016
+ ...COMMON_INTERACTIVITY_FIELDS,
37770
38017
  ...COMMON_APPEARANCE_FIELDS
37771
38018
  ]
37772
38019
  },
@@ -37816,6 +38063,7 @@ const REPEATABLE_WIDGET_PROPERTIES = [
37816
38063
  { key: 'showLabel', type: 'checkbox', label: 'Show Native Label' },
37817
38064
  { key: 'helpText', type: 'text', label: 'Help Text' },
37818
38065
  { key: 'tooltip', type: 'text', label: 'Tooltip' },
38066
+ ...COMMON_INTERACTIVITY_FIELDS,
37819
38067
  ...COMMON_GROUP_FIELDS
37820
38068
  ]
37821
38069
  },