@unvired/turboforms-embed-sdk 2.0.60 → 2.0.62

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.
@@ -7168,12 +7168,37 @@
7168
7168
  min-width: 50px !important;
7169
7169
  }
7170
7170
 
7171
- /* 7b. Truncate long labels in DataGrid to prevent horizontal bloat */
7172
- .tf-embed-sdk .formio-component-datagrid .col-form-label {
7173
- max-width: 100% !important;
7174
- overflow: hidden !important;
7175
- text-overflow: ellipsis !important;
7176
- white-space: nowrap !important;
7171
+ /* 7b. Hide component labels ONLY for direct children of DataGrid table body cells (where thead header displays the column title) */
7172
+ .tf-embed-sdk .formio-component-datagrid table.datagrid-table > tbody > tr > td > .formio-component > label:not(.form-check-label):not(.custom-control-label),
7173
+ .tf-embed-sdk .formio-component-datagrid table.datagrid-table > tbody > tr > td > .formio-component > .col-form-label,
7174
+ .tf-embed-sdk .formio-component-datagrid table.datagrid-table > tbody > tr > td > .formio-component > [ref="label"],
7175
+ .tf-embed-sdk .formio-component-datagrid table.datagrid-table > tbody > tr > td > .formio-component > .control-label--hidden,
7176
+ .tf-embed-sdk .formio-component-datagrid table.datagrid-table > tbody > tr > td > .formio-form-group > label:not(.form-check-label):not(.custom-control-label),
7177
+ .tf-embed-sdk .formio-component-datagrid table.datagrid-table > tbody > tr > td > .formio-form-group > .col-form-label,
7178
+ .tf-embed-sdk .formio-component-datagrid table.datagrid-table > tbody > tr > td > .formio-form-group > [ref="label"],
7179
+ .tf-embed-sdk .formio-component-datagrid table.datagrid-table > tbody > tr > td > .formio-form-group > .control-label--hidden {
7180
+ display: none !important;
7181
+ margin: 0 !important;
7182
+ padding: 0 !important;
7183
+ height: 0 !important;
7184
+ min-height: 0 !important;
7185
+ line-height: 0 !important;
7186
+ font-size: 0 !important;
7187
+ border: none !important;
7188
+ visibility: hidden !important;
7189
+ }
7190
+
7191
+ /* For explicitly hidden labels (hideLabel: true), hide the label element */
7192
+ .tf-embed-sdk .formio-component-label-hidden > label.control-label--hidden,
7193
+ .tf-embed-sdk .formio-component-label-hidden > .control-label--hidden {
7194
+ display: none !important;
7195
+ }
7196
+
7197
+ /* Reset direct component vertical margins inside DataGrid cells for tight layout */
7198
+ .tf-embed-sdk .formio-component-datagrid table.datagrid-table > tbody > tr > td > .formio-form-group,
7199
+ .tf-embed-sdk .formio-component-datagrid table.datagrid-table > tbody > tr > td > .formio-component {
7200
+ margin-top: 0 !important;
7201
+ margin-bottom: 0 !important;
7177
7202
  }
7178
7203
 
7179
7204
  /* 8. Responsive Table Equality and Rescue */
@@ -7193,17 +7218,6 @@
7193
7218
  .tf-embed-sdk .table td .formio-component {
7194
7219
  width: 100% !important;
7195
7220
  max-width: 100% !important;
7196
- }
7197
-
7198
- /* Ensure labels inside table cells don't break the responsive horizontal layout */
7199
- .tf-embed-sdk .table td label:not(.custom-control-label) {
7200
- max-width: 100% !important;
7201
- word-break: break-word !important;
7202
- white-space: normal !important;
7203
- line-height: 1.1 !important;
7204
- font-size: 0.75rem !important;
7205
- margin-bottom: 2px !important;
7206
- display: block !important;
7207
7221
  }</style>
7208
7222
  <style>/* TABS RENDERER - UI NORMALIZATION */
7209
7223
 
@@ -32372,7 +32386,38 @@ class SmartSelectComponent extends SmartSelectField {
32372
32386
  isEntireObjectDisplay() {
32373
32387
  return this.component.dataSrc === 'resource' && this.valueProperty === 'data';
32374
32388
  }
32389
+ findMasterdataItem(val) {
32390
+ if (!this.component.masterdata || val == null) return null;
32391
+ const valStr = String(val);
32392
+ const valProp = this.component.valueProperty || this.valueProperty;
32393
+ return this.component.masterdata.find(item => {
32394
+ if (!item || typeof item !== 'object') return false;
32395
+ if (valProp) {
32396
+ let pVal = _.get(item, valProp);
32397
+ if (pVal !== undefined && pVal !== null && String(pVal) === valStr) return true;
32398
+ if (valProp.startsWith('data.')) {
32399
+ pVal = _.get(item, valProp.replace(/^data\./, ''));
32400
+ if (pVal !== undefined && pVal !== null && String(pVal) === valStr) return true;
32401
+ }
32402
+ if (item[valProp] !== undefined && item[valProp] !== null && String(item[valProp]) === valStr) return true;
32403
+ }
32404
+ const raw = item.data || item;
32405
+ for (const k of Object.keys(raw)) {
32406
+ if (/id$/i.test(k) || k === 'value' || k === '_id' || k === 'id') {
32407
+ if (raw[k] !== undefined && raw[k] !== null && String(raw[k]) === valStr) {
32408
+ return true;
32409
+ }
32410
+ }
32411
+ }
32412
+ return false;
32413
+ });
32414
+ }
32415
+
32375
32416
  selectValueAndLabel(data) {
32417
+ if ((typeof data === 'string' || typeof data === 'number') && this.component.dataSrc === 'masterdata' && this.component.masterdata) {
32418
+ const matched = this.findMasterdataItem(data);
32419
+ if (matched) data = matched;
32420
+ }
32376
32421
  const value = this.getOptionValue((this.isEntireObjectDisplay() && !this.itemValue(data)) ? data : this.itemValue(data));
32377
32422
  return {
32378
32423
  value,
@@ -32385,8 +32430,13 @@ class SmartSelectComponent extends SmartSelectField {
32385
32430
  return '';
32386
32431
  }
32387
32432
 
32433
+ if ((typeof data === 'string' || typeof data === 'number') && this.component.dataSrc === 'masterdata' && this.component.masterdata) {
32434
+ const matched = this.findMasterdataItem(data);
32435
+ if (matched) data = matched;
32436
+ }
32437
+
32388
32438
  // If they wish to show the value in read only mode, then just return the itemValue here.
32389
- if (this.options.readOnly && this.component.readOnlyValue) {
32439
+ if (this.options.readOnly && this.component.readOnlyValue && !this.component.template && !this.component.labelProperty) {
32390
32440
  return this.itemValue(data);
32391
32441
  }
32392
32442
  // Perform a fast interpretation if we should not use the template.
@@ -32435,7 +32485,7 @@ class SmartSelectComponent extends SmartSelectField {
32435
32485
  }
32436
32486
 
32437
32487
  let templateData = data;
32438
- if (typeof data === 'object' && !data.data && this.component.template && this.component.template.indexOf('item.data.') !== -1) {
32488
+ if (typeof data === 'object' && data !== null && !data.data) {
32439
32489
  templateData = Object.assign({ data: data }, data);
32440
32490
  }
32441
32491
 
@@ -33085,11 +33135,8 @@ class SmartSelectComponent extends SmartSelectField {
33085
33135
  case 'masterdata':
33086
33136
  logger.info(`[TF_SDK:181] ✅ masterdata case for '${this.key}'. masterdata length:`, this.filteredMasterdata ? this.filteredMasterdata.length : 0);
33087
33137
  let safeMasterData = this.filteredMasterdata || [];
33088
- if (safeMasterData.length > 0 && !safeMasterData[0].data) {
33089
- const expectsData = (this.component.template && this.component.template.includes('item.data.')) || (this.component.valueProperty && this.component.valueProperty.startsWith('data.'));
33090
- if (expectsData) {
33091
- safeMasterData = safeMasterData.map(item => Object.assign({ data: item }, item));
33092
- }
33138
+ if (safeMasterData.length > 0) {
33139
+ safeMasterData = safeMasterData.map(item => (item && typeof item === 'object' && !item.data) ? Object.assign({ data: item }, item) : item);
33093
33140
  }
33094
33141
 
33095
33142
  if (!this.isFromSearch && !this.isScrollLoading && !this.component.dropdownOpened) {
@@ -34546,6 +34593,21 @@ class SmartSelectComponent extends SmartSelectField {
34546
34593
 
34547
34594
  value = convertToString(value);
34548
34595
 
34596
+ if (this.component.dataSrc === 'masterdata' && this.component.masterdata) {
34597
+ const convertMasterdata = (v) => {
34598
+ if (typeof v === 'string' || typeof v === 'number') {
34599
+ const matched = this.findMasterdataItem(v);
34600
+ return matched || v;
34601
+ }
34602
+ return v;
34603
+ };
34604
+ if (Array.isArray(value)) {
34605
+ value = value.map(convertMasterdata);
34606
+ } else if (value != null) {
34607
+ value = convertMasterdata(value);
34608
+ }
34609
+ }
34610
+
34549
34611
  if (['values', 'custom'].includes(this.component.dataSrc) && !this.asyncCustomValues()) {
34550
34612
  const {
34551
34613
  items,
@@ -34569,7 +34631,7 @@ class SmartSelectComponent extends SmartSelectField {
34569
34631
  : value;
34570
34632
  }
34571
34633
 
34572
- if (_.isString(value)) {
34634
+ if (_.isString(value) && this.component.dataSrc !== 'masterdata') {
34573
34635
  return value;
34574
34636
  }
34575
34637
  const getTemplateValue = (v) => {
@@ -39371,15 +39433,78 @@ function getValueFromData(data, path) {
39371
39433
  return current;
39372
39434
  }
39373
39435
 
39436
+ /**
39437
+ * Helper to check if a component instance has an empty value.
39438
+ */
39374
39439
  /**
39375
39440
  * Helper to check if a component instance has an empty value.
39376
39441
  */
39377
39442
  function isComponentEmpty(inst) {
39378
39443
  if (!inst) return true;
39379
- const val = inst.getValue ? inst.getValue() : inst.dataValue;
39380
- return inst.isEmpty
39381
- ? inst.isEmpty(val)
39382
- : (val === undefined || val === null || val === "" || (Array.isArray(val) && val.length === 0));
39444
+ let val = inst.getValue ? inst.getValue() : inst.dataValue;
39445
+
39446
+ if (val === undefined || val === null || val === "") return true;
39447
+
39448
+ if (Array.isArray(val)) {
39449
+ if (val.length === 0) return true;
39450
+ const hasValidItem = val.some(item => {
39451
+ if (item === undefined || item === null || item === "") return false;
39452
+ if (typeof item === "object" && Object.keys(item).length === 0) return false;
39453
+ return true;
39454
+ });
39455
+ if (!hasValidItem) return true;
39456
+ }
39457
+
39458
+ if (typeof val === "object" && !Array.isArray(val)) {
39459
+ if (Object.keys(val).length === 0) return true;
39460
+ const hasValidVal = Object.values(val).some(v => v !== undefined && v !== null && v !== "" && v !== false);
39461
+ if (!hasValidVal) return true;
39462
+ }
39463
+
39464
+ if (inst.isEmpty) {
39465
+ return inst.isEmpty(val);
39466
+ }
39467
+
39468
+ return false;
39469
+ }
39470
+
39471
+ /**
39472
+ * Helper to retrieve the exact component instance associated with an error,
39473
+ * including specific row instances inside DataGrids/EditGrids.
39474
+ */
39475
+ function getComponentByError(form, err) {
39476
+ if (!form || !err) return null;
39477
+
39478
+ // 1. Direct component instance reference on error object
39479
+ if (err.component && err.component.element) {
39480
+ return err.component;
39481
+ }
39482
+
39483
+ // 2. Try exact path (e.g., "dataGrid[0].crewLead", "dataGrid.0.crewLead")
39484
+ const path = err.path || err.context?.path || err.context?.hasPath;
39485
+ if (path) {
39486
+ let inst = typeof form.getComponent === "function" ? form.getComponent(path) : null;
39487
+ if (inst && inst.element) return inst;
39488
+
39489
+ // Convert array notation dataGrid[0].crewLead -> dataGrid.0.crewLead
39490
+ const dotPath = String(path).replace(/\[(\d+)\]/g, ".$1");
39491
+ inst = typeof form.getComponent === "function" ? form.getComponent(dotPath) : null;
39492
+ if (inst && inst.element) return inst;
39493
+
39494
+ // Convert dot notation dataGrid.0.crewLead -> dataGrid[0].crewLead
39495
+ const bracketPath = String(path).replace(/\.(\d+)\./g, "[$1].");
39496
+ inst = typeof form.getComponent === "function" ? form.getComponent(bracketPath) : null;
39497
+ if (inst && inst.element) return inst;
39498
+ }
39499
+
39500
+ // 3. Fallback to component key
39501
+ const key = err.context?.component?.key || err.component?.key || (typeof err.context?.component === "string" ? err.context.component : null);
39502
+ if (key && typeof form.getComponent === "function") {
39503
+ const inst = form.getComponent(key);
39504
+ if (inst && inst.element) return inst;
39505
+ }
39506
+
39507
+ return null;
39383
39508
  }
39384
39509
 
39385
39510
  /**
@@ -39392,10 +39517,44 @@ function clearComponentErrorUI(inst) {
39392
39517
  inst.setCustomValidity("");
39393
39518
  }
39394
39519
  inst.element.classList.remove("has-error", "formio-error-wrapper", "is-invalid");
39395
- inst.element.querySelectorAll(".is-invalid").forEach((el) => el.classList.remove("is-invalid"));
39396
- inst.element.querySelectorAll(".formio-errors, .invalid-feedback").forEach((el) => {
39397
- el.innerHTML = "";
39398
- el.style.display = "none";
39520
+
39521
+ // Only remove .is-invalid from direct choice or input elements belonging to this component
39522
+ const choices = inst.element.querySelectorAll(".choices__inner, .ui.selection.dropdown, .is-invalid");
39523
+ choices.forEach((el) => {
39524
+ if (el.closest(".formio-component") === inst.element) {
39525
+ el.classList.remove("is-invalid");
39526
+ }
39527
+ });
39528
+
39529
+ // Clear error message container ONLY if it belongs directly to inst.element (not a child component)
39530
+ const errorLabels = inst.element.querySelectorAll(".formio-errors, .invalid-feedback, [ref='messageContainer']");
39531
+ errorLabels.forEach((errorLabel) => {
39532
+ if (errorLabel.closest(".formio-component") === inst.element) {
39533
+ errorLabel.innerHTML = "";
39534
+ errorLabel.style.display = "none";
39535
+ }
39536
+ });
39537
+ }
39538
+
39539
+ /**
39540
+ * Scans all component instances in the form and clears error UI for any component that has been filled.
39541
+ */
39542
+ function clearResolvedErrors(form) {
39543
+ if (!form || typeof form.everyComponent !== "function") return;
39544
+ form.everyComponent((inst) => {
39545
+ if (!inst || !inst.element) return;
39546
+ const compType = inst.component?.type || inst.type;
39547
+ const isContainerType = ["datagrid", "editgrid", "container", "panel", "columns", "fieldset", "well", "tabs", "form"].includes(compType);
39548
+ if (isContainerType) return;
39549
+
39550
+ // Check if this component currently displays error styling
39551
+ const hasErrorClass = inst.element.classList.contains("has-error") || inst.element.classList.contains("formio-error-wrapper");
39552
+ const hasVisibleError = Array.from(inst.element.querySelectorAll(".formio-errors, .invalid-feedback"))
39553
+ .some(el => el.style.display !== "none" && el.innerHTML.trim() !== "" && el.closest(".formio-component") === inst.element);
39554
+
39555
+ if ((hasErrorClass || hasVisibleError) && !isComponentEmpty(inst)) {
39556
+ clearComponentErrorUI(inst);
39557
+ }
39399
39558
  });
39400
39559
  }
39401
39560
 
@@ -39406,13 +39565,17 @@ function showComponentErrorUI(inst, err) {
39406
39565
  if (!inst || !inst.element) return;
39407
39566
  inst.element.classList.add("has-error", "formio-error-wrapper");
39408
39567
  const choices = inst.element.querySelector(".choices__inner, .ui.selection.dropdown");
39409
- if (choices) choices.classList.add("is-invalid");
39568
+ if (choices && choices.closest(".formio-component") === inst.element) {
39569
+ choices.classList.add("is-invalid");
39570
+ }
39410
39571
 
39411
39572
  const msg = err.message || (err.context?.component?.label ? `${err.context.component.label} is required` : `${inst.component?.label || inst.key} is required`);
39412
39573
  const compId = inst.id || inst.component?.id || inst.key;
39413
39574
  const innerErrorId = `e-${compId}-${inst.key}`;
39414
39575
 
39415
- let errorLabel = inst.element.querySelector(".formio-errors, [ref='messageContainer']");
39576
+ let errorLabel = Array.from(inst.element.querySelectorAll(".formio-errors, [ref='messageContainer']"))
39577
+ .find(el => el.closest(".formio-component") === inst.element);
39578
+
39416
39579
  if (!errorLabel) {
39417
39580
  errorLabel = document.createElement("div");
39418
39581
  errorLabel.setAttribute("ref", "messageContainer");
@@ -39438,13 +39601,8 @@ function setupOnChange(privateExternal, permission) {
39438
39601
  document.activeElement.blur();
39439
39602
  }
39440
39603
 
39441
- // Immediately clear error UI for any component that has been filled
39442
- if (event?.changed?.instance) {
39443
- const inst = event.changed.instance;
39444
- if (!isComponentEmpty(inst)) {
39445
- clearComponentErrorUI(inst);
39446
- }
39447
- }
39604
+ // Immediately clear error UI for any filled component across the form (including DataGrid rows)
39605
+ clearResolvedErrors(formObj);
39448
39606
 
39449
39607
  // Clear existing timeout
39450
39608
  if (changeTimeout) {
@@ -39597,21 +39755,31 @@ function FormOnComplete() {
39597
39755
  let isTrulyInvalid = false;
39598
39756
  let trueErrors = [];
39599
39757
 
39600
- if (formObj && formObj.errors && formObj.errors.length > 0) {
39601
- formObj.errors.forEach((err) => {
39602
- if (err.context && err.context.component) {
39603
- const inst = formObj.getComponent(err.context.component.key);
39604
- if (inst && inst.element) {
39605
- if (!isComponentEmpty(inst)) {
39606
- clearComponentErrorUI(inst);
39607
- } else {
39608
- isTrulyInvalid = true;
39609
- trueErrors.push(err);
39610
- showComponentErrorUI(inst, err);
39611
- }
39758
+ // Combine errors from formObj.errors, error (promise reject), error.errors, error.details
39759
+ let rawErrors = [];
39760
+ if (formObj && Array.isArray(formObj.errors) && formObj.errors.length > 0) {
39761
+ rawErrors = rawErrors.concat(formObj.errors);
39762
+ }
39763
+ if (Array.isArray(error)) {
39764
+ rawErrors = rawErrors.concat(error);
39765
+ } else if (error && Array.isArray(error.errors)) {
39766
+ rawErrors = rawErrors.concat(error.errors);
39767
+ } else if (error && Array.isArray(error.details)) {
39768
+ rawErrors = rawErrors.concat(error.details);
39769
+ } else if (error && typeof error === "object" && (error.message || error.context)) {
39770
+ rawErrors.push(error);
39771
+ }
39772
+
39773
+ if (rawErrors.length > 0) {
39774
+ rawErrors.forEach((err) => {
39775
+ const inst = getComponentByError(formObj, err);
39776
+ if (inst && inst.element) {
39777
+ if (!isComponentEmpty(inst)) {
39778
+ clearComponentErrorUI(inst);
39612
39779
  } else {
39613
39780
  isTrulyInvalid = true;
39614
39781
  trueErrors.push(err);
39782
+ showComponentErrorUI(inst, err);
39615
39783
  }
39616
39784
  } else {
39617
39785
  isTrulyInvalid = true;
@@ -41474,7 +41642,7 @@ initThemeHook();
41474
41642
  </div>
41475
41643
 
41476
41644
  <div id="sticky-footer">
41477
- <div class="build-version">SDK v2.0.60</div>
41645
+ <div class="build-version">SDK v2.0.62</div>
41478
41646
 
41479
41647
  <button class="footer-btn footer-btn-secondary" id="prevBtn" style="display:none" onclick="FormOnPrevious()">
41480
41648
  <i class="bi bi-chevron-left"></i> Previous