@formio/js 5.4.0-api98.1 → 5.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/dist/formio.builder.css +26 -5
  2. package/dist/formio.builder.min.css +1 -1
  3. package/dist/formio.embed.js +1 -1
  4. package/dist/formio.embed.min.js +1 -1
  5. package/dist/formio.embed.min.js.LICENSE.txt +1 -1
  6. package/dist/formio.form.css +26 -5
  7. package/dist/formio.form.js +3462 -3448
  8. package/dist/formio.form.min.css +1 -1
  9. package/dist/formio.form.min.js +1 -1
  10. package/dist/formio.form.min.js.LICENSE.txt +2 -2
  11. package/dist/formio.full.css +26 -5
  12. package/dist/formio.full.js +4277 -4263
  13. package/dist/formio.full.min.css +1 -1
  14. package/dist/formio.full.min.js +1 -1
  15. package/dist/formio.full.min.js.LICENSE.txt +2 -2
  16. package/dist/formio.js +1738 -1724
  17. package/dist/formio.min.js +1 -1
  18. package/dist/formio.min.js.LICENSE.txt +2 -2
  19. package/dist/formio.utils.js +1631 -1617
  20. package/dist/formio.utils.min.js +1 -1
  21. package/dist/formio.utils.min.js.LICENSE.txt +2 -2
  22. package/lib/cjs/Element.d.ts +11 -0
  23. package/lib/cjs/Element.js +24 -0
  24. package/lib/cjs/Embed.js +23 -2
  25. package/lib/cjs/Form.d.ts +1 -1
  26. package/lib/cjs/Formio.js +1 -1
  27. package/lib/cjs/PDFBuilder.js +6 -1
  28. package/lib/cjs/Webform.d.ts +1 -1
  29. package/lib/cjs/Webform.js +9 -6
  30. package/lib/cjs/WebformBuilder.js +14 -1
  31. package/lib/cjs/Wizard.js +15 -11
  32. package/lib/cjs/components/Components.d.ts +3 -0
  33. package/lib/cjs/components/Components.js +3 -1
  34. package/lib/cjs/components/_classes/component/Component.d.ts +13 -0
  35. package/lib/cjs/components/_classes/component/Component.js +137 -44
  36. package/lib/cjs/components/_classes/component/editForm/Component.edit.conditional.js +1 -1
  37. package/lib/cjs/components/_classes/component/editForm/Component.edit.data.js +1 -1
  38. package/lib/cjs/components/_classes/component/editForm/utils.d.ts +1 -0
  39. package/lib/cjs/components/_classes/component/editForm/utils.js +3 -0
  40. package/lib/cjs/components/_classes/nested/NestedComponent.js +5 -7
  41. package/lib/cjs/components/address/Address.js +2 -0
  42. package/lib/cjs/components/button/Button.d.ts +1 -0
  43. package/lib/cjs/components/button/Button.js +22 -1
  44. package/lib/cjs/components/datagrid/DataGrid.js +35 -10
  45. package/lib/cjs/components/datamap/DataMap.d.ts +1 -4
  46. package/lib/cjs/components/datamap/DataMap.js +42 -10
  47. package/lib/cjs/components/datetime/DateTime.js +11 -1
  48. package/lib/cjs/components/datetime/editForm/DateTime.edit.date.d.ts +18 -1
  49. package/lib/cjs/components/datetime/editForm/DateTime.edit.date.js +3 -0
  50. package/lib/cjs/components/datetime/editForm/DateTime.edit.time.d.ts +13 -2
  51. package/lib/cjs/components/datetime/editForm/DateTime.edit.time.js +3 -0
  52. package/lib/cjs/components/day/Day.d.ts +0 -15
  53. package/lib/cjs/components/day/Day.js +8 -17
  54. package/lib/cjs/components/editgrid/EditGrid.js +13 -3
  55. package/lib/cjs/components/file/File.js +7 -6
  56. package/lib/cjs/components/form/Form.d.ts +1 -0
  57. package/lib/cjs/components/form/Form.js +20 -8
  58. package/lib/cjs/components/number/Number.d.ts +1 -0
  59. package/lib/cjs/components/number/Number.js +18 -0
  60. package/lib/cjs/components/select/Select.js +6 -3
  61. package/lib/cjs/components/signature/Signature.js +5 -5
  62. package/lib/cjs/components/signature/editForm/Signature.edit.display.d.ts +0 -6
  63. package/lib/cjs/components/signature/editForm/Signature.edit.display.js +0 -1
  64. package/lib/cjs/components/textfield/editForm/TextField.edit.display.d.ts +0 -10
  65. package/lib/cjs/components/textfield/editForm/TextField.edit.display.js +9 -23
  66. package/lib/cjs/formio.form.js +2 -5
  67. package/lib/cjs/package.json +1 -1
  68. package/lib/cjs/providers/address/GoogleAddressProvider.js +1 -1
  69. package/lib/cjs/providers/storage/azure.js +9 -3
  70. package/lib/cjs/translations/en.d.ts +1 -0
  71. package/lib/cjs/translations/en.js +3 -1
  72. package/lib/cjs/utils/formUtils.d.ts +2 -2
  73. package/lib/cjs/utils/index.d.ts +3 -3
  74. package/lib/cjs/utils/utils.d.ts +1 -1
  75. package/lib/cjs/utils/utils.js +28 -11
  76. package/lib/cjs/widgets/CalendarWidget.js +1 -1
  77. package/lib/mjs/Element.d.ts +11 -0
  78. package/lib/mjs/Element.js +23 -0
  79. package/lib/mjs/Embed.js +21 -2
  80. package/lib/mjs/Form.d.ts +1 -1
  81. package/lib/mjs/Formio.js +1 -1
  82. package/lib/mjs/PDFBuilder.js +6 -1
  83. package/lib/mjs/Webform.d.ts +1 -1
  84. package/lib/mjs/Webform.js +6 -3
  85. package/lib/mjs/WebformBuilder.js +13 -1
  86. package/lib/mjs/Wizard.js +9 -10
  87. package/lib/mjs/components/Components.d.ts +3 -0
  88. package/lib/mjs/components/Components.js +3 -1
  89. package/lib/mjs/components/_classes/component/Component.d.ts +13 -0
  90. package/lib/mjs/components/_classes/component/Component.js +135 -43
  91. package/lib/mjs/components/_classes/component/editForm/Component.edit.conditional.js +1 -1
  92. package/lib/mjs/components/_classes/component/editForm/Component.edit.data.js +1 -1
  93. package/lib/mjs/components/_classes/component/editForm/utils.d.ts +1 -0
  94. package/lib/mjs/components/_classes/component/editForm/utils.js +3 -0
  95. package/lib/mjs/components/_classes/nested/NestedComponent.js +5 -6
  96. package/lib/mjs/components/address/Address.js +2 -0
  97. package/lib/mjs/components/button/Button.d.ts +1 -0
  98. package/lib/mjs/components/button/Button.js +21 -1
  99. package/lib/mjs/components/datagrid/DataGrid.js +39 -11
  100. package/lib/mjs/components/datamap/DataMap.d.ts +1 -4
  101. package/lib/mjs/components/datamap/DataMap.js +41 -10
  102. package/lib/mjs/components/datetime/DateTime.js +11 -1
  103. package/lib/mjs/components/datetime/editForm/DateTime.edit.date.d.ts +18 -1
  104. package/lib/mjs/components/datetime/editForm/DateTime.edit.date.js +3 -0
  105. package/lib/mjs/components/datetime/editForm/DateTime.edit.time.d.ts +13 -2
  106. package/lib/mjs/components/datetime/editForm/DateTime.edit.time.js +3 -0
  107. package/lib/mjs/components/day/Day.d.ts +0 -15
  108. package/lib/mjs/components/day/Day.js +8 -17
  109. package/lib/mjs/components/editgrid/EditGrid.js +12 -2
  110. package/lib/mjs/components/file/File.js +7 -6
  111. package/lib/mjs/components/form/Form.d.ts +1 -0
  112. package/lib/mjs/components/form/Form.js +18 -6
  113. package/lib/mjs/components/number/Number.d.ts +1 -0
  114. package/lib/mjs/components/number/Number.js +17 -0
  115. package/lib/mjs/components/select/Select.js +6 -3
  116. package/lib/mjs/components/signature/Signature.js +1 -1
  117. package/lib/mjs/components/signature/editForm/Signature.edit.display.d.ts +0 -6
  118. package/lib/mjs/components/signature/editForm/Signature.edit.display.js +0 -1
  119. package/lib/mjs/components/textfield/editForm/TextField.edit.display.d.ts +0 -10
  120. package/lib/mjs/components/textfield/editForm/TextField.edit.display.js +9 -23
  121. package/lib/mjs/formio.form.js +4 -7
  122. package/lib/mjs/package.json +1 -1
  123. package/lib/mjs/providers/address/GoogleAddressProvider.js +1 -1
  124. package/lib/mjs/providers/storage/azure.js +9 -3
  125. package/lib/mjs/translations/en.d.ts +1 -0
  126. package/lib/mjs/translations/en.js +3 -1
  127. package/lib/mjs/utils/formUtils.d.ts +2 -2
  128. package/lib/mjs/utils/index.d.ts +3 -3
  129. package/lib/mjs/utils/utils.d.ts +1 -1
  130. package/lib/mjs/utils/utils.js +27 -11
  131. package/lib/mjs/widgets/CalendarWidget.js +2 -2
  132. package/package.json +8 -7
@@ -140,7 +140,7 @@ export default class Component extends Element {
140
140
  unique: false,
141
141
  },
142
142
  /**
143
- * The simple conditional settings for a component.
143
+ * the simple conditional settings for a component.
144
144
  */
145
145
  conditional: {
146
146
  show: null,
@@ -426,6 +426,24 @@ export default class Component extends Element {
426
426
  get componentsMap() {
427
427
  return this.root?.childComponentsMap || {};
428
428
  }
429
+ /**
430
+ * Walks this component's root chain, invoking `fn` with each ancestor root's
431
+ * `childComponentsMap`. Component registration is propagated up the wizard /
432
+ * nested-form chain at create time, so any code that mutates a registration
433
+ * (creation, removal, path-driven re-key) must update every map in the chain.
434
+ * @param {(map: object) => void} fn - Called once per root that exposes a `childComponentsMap`.
435
+ */
436
+ eachRootChildComponentsMap(fn) {
437
+ let currentRoot = this.root;
438
+ let prevRootId = null;
439
+ while (currentRoot && currentRoot.id !== prevRootId) {
440
+ if (currentRoot.childComponentsMap) {
441
+ fn(currentRoot.childComponentsMap);
442
+ }
443
+ prevRootId = currentRoot.id;
444
+ currentRoot = currentRoot.root;
445
+ }
446
+ }
429
447
  /**
430
448
  * Returns if the parent should conditionally clear.
431
449
  *
@@ -1127,9 +1145,12 @@ export default class Component extends Element {
1127
1145
  * @returns {string} - The submission timezone.
1128
1146
  */
1129
1147
  get submissionTimezone() {
1130
- this.options.submissionTimezone =
1131
- this.options.submissionTimezone || _.get(this.root, 'options.submissionTimezone');
1132
- return this.options.submissionTimezone;
1148
+ if (!this.options.submissionTimezone) {
1149
+ this.options.submissionTimezone = _.get(this.root, 'options.submissionTimezone');
1150
+ }
1151
+ return (this.options.submissionTimezone ||
1152
+ _.get(this.root, '_submission.metadata.timezone') ||
1153
+ _.get(this.component, 'widget.submissionTimezone'));
1133
1154
  }
1134
1155
  /**
1135
1156
  * Return the current timezone.
@@ -1144,6 +1165,7 @@ export default class Component extends Element {
1144
1165
  * @returns {string} - The current timezone.
1145
1166
  */
1146
1167
  getTimezone(settings) {
1168
+ settings = settings || {};
1147
1169
  if (settings.timezone) {
1148
1170
  return settings.timezone;
1149
1171
  }
@@ -1151,12 +1173,19 @@ export default class Component extends Element {
1151
1173
  return 'UTC';
1152
1174
  }
1153
1175
  const submissionTimezone = this.submissionTimezone;
1176
+ const mode = settings.displayInTimezone === '' || settings.displayInTimezone == null
1177
+ ? 'viewer'
1178
+ : settings.displayInTimezone;
1179
+ if (this.options.pdf && submissionTimezone) {
1180
+ return submissionTimezone;
1181
+ }
1182
+ const staticSnapshot = this.options.server ||
1183
+ this.options.renderMode === 'html' ||
1184
+ !!this.options.viewAsHtml;
1154
1185
  if (submissionTimezone &&
1155
- (settings.displayInTimezone === 'submission' ||
1156
- ((this.options.pdf || this.options.server) && settings.displayInTimezone === 'viewer'))) {
1186
+ (mode === 'submission' || (staticSnapshot && (mode === 'viewer' || mode === 'location')))) {
1157
1187
  return submissionTimezone;
1158
1188
  }
1159
- // Return current timezone if none are provided.
1160
1189
  return currentTimezone();
1161
1190
  }
1162
1191
  /**
@@ -1183,6 +1212,19 @@ export default class Component extends Element {
1183
1212
  }
1184
1213
  }
1185
1214
  }
1215
+ /**
1216
+ * Announces a message to screen readers via the component's live region.
1217
+ * @param {string} message - The message to announce.
1218
+ */
1219
+ announce(message) {
1220
+ const liveRegion = this.refs.liveRegion;
1221
+ if (liveRegion) {
1222
+ liveRegion.textContent = '';
1223
+ setTimeout(() => {
1224
+ liveRegion.textContent = message;
1225
+ }, 50);
1226
+ }
1227
+ }
1186
1228
  /**
1187
1229
  * Opens the modal element.
1188
1230
  * @param {string} template - The template to use for the modal dialog.
@@ -1323,6 +1365,7 @@ export default class Component extends Element {
1323
1365
  this.loadRefs(element, {
1324
1366
  messageContainer: 'single',
1325
1367
  tooltip: 'multiple',
1368
+ liveRegion: 'single',
1326
1369
  });
1327
1370
  this.attachTooltips(this.refs.tooltip);
1328
1371
  // Attach logic.
@@ -1562,13 +1605,13 @@ export default class Component extends Element {
1562
1605
  value.forEach((val, index) => {
1563
1606
  const widget = this.refs.input[index] && this.refs.input[index].widget;
1564
1607
  if (widget) {
1565
- values.push(widget.getValueAsString(val, options));
1608
+ values.push(widget.getValueAsString(val));
1566
1609
  }
1567
1610
  });
1568
1611
  return values;
1569
1612
  }
1570
1613
  const widget = this.refs.input[0].widget;
1571
- return widget.getValueAsString(value, options);
1614
+ return widget.getValueAsString(value);
1572
1615
  }
1573
1616
  /**
1574
1617
  * Returns the value of the component as a string.
@@ -2304,29 +2347,23 @@ export default class Component extends Element {
2304
2347
  * @returns {void}
2305
2348
  */
2306
2349
  setErrorClasses(elements, dirty, hasErrors, hasMessages, element = this.element) {
2307
- this.clearErrorClasses();
2308
- elements.forEach((element) => {
2309
- this.setElementInvalid(this.performInputMapping(element), hasErrors);
2350
+ elements.forEach((el) => {
2351
+ this.setElementInvalid(this.performInputMapping(el), hasErrors);
2310
2352
  });
2311
2353
  this.setInputWidgetErrorClasses(elements, hasErrors);
2312
2354
  // do not set error classes for hidden components
2313
2355
  if (!this.visible) {
2356
+ this.clearErrorClasses(element);
2314
2357
  return;
2315
2358
  }
2316
- if (hasErrors) {
2317
- // Add error classes
2318
- elements.forEach((input) => {
2319
- this.setElementInvalid(this.performInputMapping(input), true);
2320
- });
2321
- if (dirty && this.options.highlightErrors) {
2322
- this.addClass(element, this.options.componentErrorClass);
2323
- }
2324
- else {
2325
- this.addClass(element, 'has-error');
2326
- }
2327
- }
2328
- if (hasMessages) {
2329
- this.addClass(element, 'has-message');
2359
+ const wantHighlight = hasErrors && !!dirty && !!this.options.highlightErrors;
2360
+ const wantHasError = hasErrors && !wantHighlight;
2361
+ this.toggleClass(element, this.options.componentErrorClass, wantHighlight);
2362
+ this.toggleClass(element, 'has-error', wantHasError);
2363
+ this.toggleClass(element, 'has-message', hasMessages);
2364
+ // Preserve previous clearErrorClasses() behavior: drop the 'alert alert-danger' pair if left over.
2365
+ if (element?.classList?.contains('alert-danger')) {
2366
+ this.removeClass(element, 'alert alert-danger');
2330
2367
  }
2331
2368
  }
2332
2369
  /**
@@ -2582,12 +2619,61 @@ export default class Component extends Element {
2582
2619
  { type: 'styles', src: `${Formio.cdn.quill}/quill.${settings.theme}.css` },
2583
2620
  ], true);
2584
2621
  // Lazy load the quill library.
2585
- return Formio.requireLibrary('quill', 'Quill', _.get(this.options, 'editors.quill.src', `${Formio.cdn.quill}/quill.min.js`), true).then(() => {
2622
+ return Formio.requireLibrary('quill', 'Quill', _.get(this.options, 'editors.quill.src', `${Formio.cdn.quill}/quill.js`), true).then(() => {
2586
2623
  return Formio.requireLibrary('quill-table', 'Quill', `${Formio.cdn.baseUrl}/quill/quill-table.js`, true).then(() => {
2587
2624
  if (!element.parentNode) {
2588
2625
  return Promise.reject();
2589
2626
  }
2590
2627
  this.quill = new Quill(element, isIEBrowser ? { ...settings, modules: {} } : settings);
2628
+ const root = element.getRootNode();
2629
+ if (root instanceof ShadowRoot && root.getSelection) {
2630
+ const sel = this.quill.selection;
2631
+ // 1. getNativeRange: read selection from shadowRoot instead of document
2632
+ sel.getNativeRange = () => {
2633
+ const shadowSelection = root.getSelection();
2634
+ if (shadowSelection == null || shadowSelection.rangeCount <= 0)
2635
+ return null;
2636
+ const nativeRange = shadowSelection.getRangeAt(0);
2637
+ if (nativeRange == null)
2638
+ return null;
2639
+ return sel.normalizeNative(nativeRange);
2640
+ };
2641
+ // 2. hasFocus: check shadowRoot.activeElement instead of document.activeElement
2642
+ sel.hasFocus = () => {
2643
+ return root.activeElement === sel.root ||
2644
+ (root.activeElement != null && sel.root.contains(root.activeElement));
2645
+ };
2646
+ // 3. setNativeRange: use shadowRoot's selection to add/remove ranges
2647
+ const origSetNativeRange = sel.setNativeRange.bind(sel);
2648
+ sel.setNativeRange = (startNode, startOffset, endNode, endOffset, force) => {
2649
+ // Delegate to original logic for the null case (blur)
2650
+ if (startNode == null) {
2651
+ origSetNativeRange(null);
2652
+ return;
2653
+ }
2654
+ if (!sel.hasFocus()) {
2655
+ sel.root.focus({ preventScroll: true });
2656
+ }
2657
+ const shadowSelection = root.getSelection();
2658
+ if (shadowSelection == null)
2659
+ return;
2660
+ const { native } = sel.getNativeRange() || {};
2661
+ endNode = endNode ?? startNode;
2662
+ endOffset = endOffset ?? startOffset;
2663
+ if (native == null || force ||
2664
+ startNode !== native.startContainer || startOffset !== native.startOffset ||
2665
+ endNode !== native.endContainer || endOffset !== native.endOffset) {
2666
+ const range = document.createRange();
2667
+ range.setStart(startNode, startOffset);
2668
+ range.setEnd(endNode, endOffset);
2669
+ shadowSelection.removeAllRanges();
2670
+ shadowSelection.addRange(range);
2671
+ }
2672
+ };
2673
+ document.addEventListener('selectionchange', () => {
2674
+ sel.update();
2675
+ });
2676
+ }
2591
2677
  /** This block of code adds the [source] capabilities. See https://codepen.io/anon/pen/ZyEjrQ */
2592
2678
  const txtArea = document.createElement('textarea');
2593
2679
  txtArea.setAttribute('class', 'quill-source-code');
@@ -2606,7 +2692,8 @@ export default class Component extends Element {
2606
2692
  // Make sure to select cursor when they click on the element.
2607
2693
  this.addEventListener(element, 'click', () => this.quill.focus());
2608
2694
  // Allows users to skip toolbar items when tabbing though form
2609
- const elm = document.querySelectorAll('.ql-formats > button');
2695
+ const queryRoot = element.getRootNode() || document;
2696
+ const elm = queryRoot.querySelectorAll('.ql-formats > button');
2610
2697
  for (let i = 0; i < elm.length; i++) {
2611
2698
  elm[i].setAttribute('tabindex', '-1');
2612
2699
  }
@@ -3234,7 +3321,7 @@ export default class Component extends Element {
3234
3321
  if (flags.silentCheck) {
3235
3322
  return [];
3236
3323
  }
3237
- let isDirty = flags.dirty === false ? false : this.dirty || flags.dirty;
3324
+ let isDirty = flags.dirty || this.dirty;
3238
3325
  if (this.options.alwaysDirty) {
3239
3326
  isDirty = true;
3240
3327
  }
@@ -3440,21 +3527,26 @@ export default class Component extends Element {
3440
3527
  }
3441
3528
  });
3442
3529
  this.addEventListener(element, 'blur', () => {
3443
- if (this.root) {
3444
- this.root.pendingBlur = FormioUtils.delay(() => {
3445
- this.emit('blur', this);
3446
- if (this.component.validateOn === 'blur') {
3447
- this.root.triggerChange?.({ fromBlur: true }, {
3448
- instance: this,
3449
- component: this.component,
3450
- value: this.dataValue,
3451
- flags: { fromBlur: true },
3452
- });
3453
- }
3454
- this.root.focusedComponent = null;
3455
- this.root.pendingBlur = null;
3456
- });
3530
+ const root = this.root;
3531
+ if (!root) {
3532
+ return;
3457
3533
  }
3534
+ root.pendingBlur = FormioUtils.delay(() => {
3535
+ if (!root) {
3536
+ return;
3537
+ }
3538
+ this.emit('blur', this);
3539
+ if (this.component.validateOn === 'blur') {
3540
+ root.triggerChange?.({ fromBlur: true }, {
3541
+ instance: this,
3542
+ component: this.component,
3543
+ value: this.dataValue,
3544
+ flags: { fromBlur: true },
3545
+ });
3546
+ }
3547
+ root.focusedComponent = null;
3548
+ root.pendingBlur = null;
3549
+ });
3458
3550
  });
3459
3551
  }
3460
3552
  setCustomValidity(messages, dirty, external) {
@@ -44,5 +44,5 @@ export default [
44
44
  },
45
45
  EditFormUtils.javaScriptValue('Advanced Conditions', 'customConditional', 'conditional.json', 110, '<p>You must assign the <strong>show</strong> variable a boolean result.</p>' +
46
46
  '<p><strong>Note: Advanced Conditional logic will override the results of the Simple Conditional logic.</strong></p>' +
47
- '<h5>Example</h5><pre>show = !!data.showMe;</pre>', '<p><a href="https://help.form.io/userguide/form-building/logic-and-conditions" target="_blank" rel="noopener noreferrer">Click here for an example</a></p>'),
47
+ '<h5>Example</h5><pre>show = !!data.showMe;</pre>', '<p><a href="https://help.form.io/userguide/form-building/logic-and-conditions" target="_blank" rel="noopener noreferrer">Click here for an example</a></p>', EditFormUtils.tokenVariableDescription()),
48
48
  ];
@@ -139,7 +139,7 @@ export default [
139
139
  input: true,
140
140
  },
141
141
  EditFormUtils.javaScriptValue('Custom Default Value', 'customDefaultValue', 'customDefaultValue', 1000, '<p><h4>Example:</h4><pre>value = data.firstName + " " + data.lastName;</pre></p>', '<p><h4>Example:</h4><pre>{"cat": [{"var": "data.firstName"}, " ", {"var": "data.lastName"}]}</pre>'),
142
- EditFormUtils.javaScriptValue('Calculated Value', 'calculateValue', 'calculateValue', 1100, '<p><h4>Example:</h4><pre>value = data.a + data.b + data.c;</pre></p>', '<p><h4>Example:</h4><pre>{"+": [{"var": "data.a"}, {"var": "data.b"}, {"var": "data.c"}]}</pre><p><a href="https://help.form.io/userguide/form-building/logic-and-conditions#calculated-values" target="_blank" rel="noopener noreferrer">Click here for an example</a></p>', '<tr><th>token</th><td>The decoded JWT token for the authenticated user.</td></tr>'),
142
+ EditFormUtils.javaScriptValue('Calculated Value', 'calculateValue', 'calculateValue', 1100, '<p><h4>Example:</h4><pre>value = data.a + data.b + data.c;</pre></p>', '<p><h4>Example:</h4><pre>{"+": [{"var": "data.a"}, {"var": "data.b"}, {"var": "data.c"}]}</pre><p><a href="https://help.form.io/userguide/form-building/logic-and-conditions#calculated-values" target="_blank" rel="noopener noreferrer">Click here for an example</a></p>', EditFormUtils.tokenVariableDescription()),
143
143
  {
144
144
  type: 'checkbox',
145
145
  input: true,
@@ -2,6 +2,7 @@ export default EditFormUtils;
2
2
  declare namespace EditFormUtils {
3
3
  function sortAndFilterComponents(components: any): any;
4
4
  function unifyComponents(objValue: any, srcValue: any): any;
5
+ function tokenVariableDescription(): string;
5
6
  function logicVariablesTable(additional: any): {
6
7
  type: string;
7
8
  tag: string;
@@ -32,6 +32,9 @@ const EditFormUtils = {
32
32
  }
33
33
  return _.isEqual(objValue, srcValue);
34
34
  },
35
+ tokenVariableDescription() {
36
+ return '<tr><th>token</th><td>The decoded JWT token for the authenticated user.</td></tr>';
37
+ },
35
38
  logicVariablesTable(additional) {
36
39
  additional = additional || '';
37
40
  return {
@@ -556,12 +556,11 @@ export default class NestedComponent extends Field {
556
556
  components = components || this.components;
557
557
  component.destroy(all);
558
558
  _.remove(components, { id: component.id });
559
- if (this.componentsMap[component.path]) {
560
- delete this.componentsMap[component.path];
561
- }
562
- if (this.root?.componentsMap[component.path]) {
563
- delete this.root?.componentsMap[component.path];
564
- }
559
+ component.eachRootChildComponentsMap((map) => {
560
+ if (map[component.path]) {
561
+ delete map[component.path];
562
+ }
563
+ });
565
564
  }
566
565
  /**
567
566
  * Removes a component provided the API key of that component.
@@ -261,6 +261,8 @@ export default class AddressComponent extends ContainerComponent {
261
261
  this.restoreComponentsContext();
262
262
  }
263
263
  if (changed || (!_.isEmpty(value) && flags.fromSubmission)) {
264
+ // Recheck conditions on child components before redraw so their visibility is updated
265
+ this.getComponents().forEach((comp) => comp.checkConditions(this.root?.data));
264
266
  this.redraw();
265
267
  }
266
268
  return changed;
@@ -29,6 +29,7 @@ export default class ButtonComponent extends Field {
29
29
  detach(element: any): void;
30
30
  onClick(event: any): void;
31
31
  openOauth(settings: any): void;
32
+ _handleOauthSessionExpired(): void;
32
33
  get oauthComponentPath(): any;
33
34
  focus(): void;
34
35
  triggerCaptcha(): void;
@@ -363,6 +363,12 @@ export default class ButtonComponent extends Field {
363
363
  }
364
364
  }
365
365
  openOauth(settings) {
366
+ // this is if the temp session (storing the state and code verifiers) expires in the db
367
+ // and we need to fetch new oauth state
368
+ if (settings.sessionExpireAt && Date.now() >= settings.sessionExpireAt) {
369
+ this._handleOauthSessionExpired();
370
+ return;
371
+ }
366
372
  if (!this.root?.formio) {
367
373
  console.warn('You must attach a Form API url to your form in order to use OAuth buttons.');
368
374
  return;
@@ -378,7 +384,8 @@ export default class ButtonComponent extends Field {
378
384
  if (settings.state) {
379
385
  params.state = settings.state;
380
386
  }
381
- else if (settings.code_challenge) {
387
+ // okta requires both a state and a code challenge for PKCE
388
+ if (settings.code_challenge) {
382
389
  params.code_challenge = settings.code_challenge;
383
390
  params.code_challenge_method = 'S256';
384
391
  }
@@ -420,6 +427,9 @@ export default class ButtonComponent extends Field {
420
427
  this.root?.setAlert('danger', 'OAuth state does not match. Please try logging in again.');
421
428
  return;
422
429
  }
430
+ if (settings.sessionId) {
431
+ params.sessionId = settings.sessionId;
432
+ }
423
433
  // Depending on where the settings came from, submit to either the submission endpoint (old) or oauth endpoint (new).
424
434
  let requestPromise = Promise.resolve();
425
435
  if (_.has(this, 'root.form.config.oauth') &&
@@ -446,6 +456,11 @@ export default class ButtonComponent extends Field {
446
456
  this.root?.onSubmit(result, true);
447
457
  })
448
458
  .catch((err) => {
459
+ console.log(err);
460
+ if (settings.sessionExpireAt && Date.now() >= settings.sessionExpireAt) {
461
+ this._handleOauthSessionExpired();
462
+ return;
463
+ }
449
464
  this.root?.onSubmissionError(err);
450
465
  });
451
466
  }
@@ -461,6 +476,11 @@ export default class ButtonComponent extends Field {
461
476
  }
462
477
  }, 100);
463
478
  }
479
+ _handleOauthSessionExpired() {
480
+ this.root?.setAlert('warning', this.t('oauthSessionExpired'));
481
+ this.loading = true;
482
+ setTimeout(() => window.location.reload(), 2000);
483
+ }
464
484
  get oauthComponentPath() {
465
485
  const pathArray = getArrayFromComponentPath(this.path);
466
486
  return _.chain(pathArray)
@@ -1,6 +1,6 @@
1
1
  import _ from 'lodash';
2
2
  import NestedArrayComponent from '../_classes/nestedarray/NestedArrayComponent';
3
- import { fastCloneDeep, getFocusableElements, getComponent, eachComponent, screenReaderSpeech } from '../../utils';
3
+ import { fastCloneDeep, getFocusableElements, getComponent, eachComponent } from '../../utils';
4
4
  import dragula from 'dragula';
5
5
  export default class DataGridComponent extends NestedArrayComponent {
6
6
  static schema(...extend) {
@@ -441,17 +441,25 @@ export default class DataGridComponent extends NestedArrayComponent {
441
441
  component: this.component,
442
442
  row,
443
443
  });
444
- screenReaderSpeech('Row has been added');
445
444
  this.checkConditions();
446
445
  this.triggerChange?.({ modified: true, noPristineChangeOnModified: true });
447
446
  this.redraw().then(() => {
448
447
  this.focusOnNewRowElement(this.rows[index]);
448
+ this.announce(this.t('Row has been added'));
449
449
  });
450
450
  }
451
451
  updateComponentsRowIndex(components, rowIndex) {
452
452
  components.forEach((component, colIndex) => {
453
- if (this.componentsMap[component.paths.dataPath]) {
454
- delete this.componentsMap[component.paths.dataPath];
453
+ // The rowIndex setter cascades into descendants and regenerates their
454
+ // paths, but does not re-key them in componentsMap. Collect the slot
455
+ // and every descendant up front so we can re-key them after paths
456
+ // regenerate. Required for nested-form / sub-wizard scenarios where
457
+ // the outer wizard validates against its own componentsMap copy.
458
+ const entries = [{ instance: component, oldPath: component.paths.dataPath }];
459
+ if (typeof component.everyComponent === 'function') {
460
+ component.everyComponent((descendant) => {
461
+ entries.push({ instance: descendant, oldPath: descendant.paths.dataPath });
462
+ });
455
463
  }
456
464
  if (component.options?.name) {
457
465
  const newName = `[${this.key}][${rowIndex}]`;
@@ -459,7 +467,14 @@ export default class DataGridComponent extends NestedArrayComponent {
459
467
  }
460
468
  component.rowIndex = rowIndex;
461
469
  component.row = `${rowIndex}-${colIndex}`;
462
- this.componentsMap[component.paths.dataPath] = component;
470
+ entries.forEach(({ instance, oldPath }) => {
471
+ instance.eachRootChildComponentsMap((map) => {
472
+ if (map[oldPath] === instance) {
473
+ delete map[oldPath];
474
+ }
475
+ map[instance.paths.dataPath] = instance;
476
+ });
477
+ });
463
478
  });
464
479
  }
465
480
  updateRowsComponents(rowIndex) {
@@ -472,15 +487,14 @@ export default class DataGridComponent extends NestedArrayComponent {
472
487
  const flags = { isReordered: !makeEmpty, resetValue: makeEmpty };
473
488
  this.splice(index, flags);
474
489
  this.emit('dataGridDeleteRow', { index });
475
- if (this.rows.length > 1) {
476
- screenReaderSpeech('Row has been deleted');
477
- }
478
490
  const [row,] = this.rows.splice(index, 1);
479
491
  this.removeSubmissionMetadataRow(index);
480
492
  this.removeRowComponents(row);
481
493
  this.updateRowsComponents(index);
482
494
  this.setValue(this.dataValue, flags);
483
- this.redraw();
495
+ this.redraw().then(() => {
496
+ this.announce(this.t('Row has been deleted'));
497
+ });
484
498
  }
485
499
  removeRowComponents(row) {
486
500
  _.each(row, (component) => this.removeComponent(component));
@@ -530,8 +544,9 @@ export default class DataGridComponent extends NestedArrayComponent {
530
544
  options.row = `${rowIndex}-${colIndex}`;
531
545
  options.rowIndex = rowIndex;
532
546
  options.onChange = (flags, changed, modified) => {
533
- if (changed.component.type === 'form') {
534
- const formComp = getComponent(this.component.components, changed.component.key);
547
+ const changedComponent = changed.component;
548
+ if (changedComponent?.type === 'form' && changedComponent?.key) {
549
+ const formComp = getComponent(this.component.components, changedComponent.key);
535
550
  _.set(formComp, 'components', changed.component.components);
536
551
  }
537
552
  // If we're in a nested form we need to ensure our changes are triggered upstream
@@ -539,8 +554,21 @@ export default class DataGridComponent extends NestedArrayComponent {
539
554
  changed.instance.root.triggerChange?.(flags, changed, modified);
540
555
  }
541
556
  else {
557
+ if (modified && !flags.noPristineChangeOnModified) {
558
+ this.pristine = false;
559
+ }
560
+ this.triggerRootChange(flags, {
561
+ instance: this,
562
+ component: this.component,
563
+ value: this.dataValue,
564
+ flags,
565
+ }, modified);
542
566
  this.triggerRootChange(flags, changed, modified);
543
567
  }
568
+ this.processRow('checkData', null, {
569
+ ...flags,
570
+ changed,
571
+ }, row, _.toArray(this.rows[rowIndex]));
544
572
  };
545
573
  let columnComponent;
546
574
  if (this.builderMode) {
@@ -15,14 +15,11 @@ export default class DataMapComponent extends DataGridComponent {
15
15
  disableBuilderActions: boolean;
16
16
  };
17
17
  get valueKey(): any;
18
- get iteratableRows(): {
19
- components: any;
20
- data: any;
21
- }[][];
22
18
  hasHeader(): boolean;
23
19
  getRowKey(rowIndex: any): string;
24
20
  get defaultRowKey(): string;
25
21
  getValueAsString(value: any, options: any): any;
22
+ findComponentInstance(key: any): any;
26
23
  createRowComponents(row: any, rowIndex: any): {
27
24
  __key: any;
28
25
  };
@@ -118,15 +118,14 @@ export default class DataMapComponent extends DataGridComponent {
118
118
  }
119
119
  get iteratableRows() {
120
120
  return this.rows.map((row) => {
121
- return Object.keys(row).map((key) => ({
122
- components: row[key],
123
- data: row[key].dataValue,
124
- }));
121
+ return {
122
+ components: row,
123
+ data: _.mapValues(row, (comp) => comp.dataValue)
124
+ };
125
125
  });
126
126
  }
127
127
  componentContext(component) {
128
- return this.iteratableRows[component.row].find((comp) => comp.components.key === component.key)
129
- .data;
128
+ return this.iteratableRows[component.row].data[component.key];
130
129
  }
131
130
  hasHeader() {
132
131
  return true;
@@ -181,10 +180,14 @@ export default class DataMapComponent extends DataGridComponent {
181
180
  <tbody>
182
181
  `;
183
182
  result = Object.keys(value).reduce((result, key) => {
183
+ const componentInstance = this.findComponentInstance(key);
184
+ const viewValue = componentInstance
185
+ ? componentInstance.getView(value[key], options)
186
+ : this.getView(value[key], options);
184
187
  result += `
185
188
  <tr>
186
189
  <th style="padding: 5px 10px;">${key}</th>
187
- <td style="width:100%;padding:5px 10px;">${this.getView(value[key], options)}</td>
190
+ <td style="width:100%;padding:5px 10px;">${viewValue}</td>
188
191
  </tr>
189
192
  `;
190
193
  return result;
@@ -204,6 +207,18 @@ export default class DataMapComponent extends DataGridComponent {
204
207
  }
205
208
  return typeof value === 'object' ? '[Complex Data]' : value;
206
209
  }
210
+ findComponentInstance(key) {
211
+ if (!this.rows || !this.rows.length) {
212
+ return null;
213
+ }
214
+ // Find component instance with matching key
215
+ const foundRow = _.find(this.rows, (row) => row?.[this.valueKey]?.key === key);
216
+ if (foundRow?.[this.valueKey]) {
217
+ return foundRow[this.valueKey];
218
+ }
219
+ // If not found by key, return the first row's value component as fallback
220
+ return this.rows[0]?.[this.valueKey] || null;
221
+ }
207
222
  getDataValueAsTable(value, options) {
208
223
  let result = `
209
224
  <table border="1" style="width:100%">
@@ -211,10 +226,14 @@ export default class DataMapComponent extends DataGridComponent {
211
226
  `;
212
227
  if (this.visible && _.isObject(value)) {
213
228
  Object.keys(value).forEach((key) => {
229
+ const componentInstance = this.findComponentInstance(key);
230
+ const viewValue = componentInstance
231
+ ? componentInstance.getView(value[key], options)
232
+ : this.getView(value[key], options);
214
233
  result += `
215
234
  <tr>
216
235
  <th style="padding: 5px 10px;">${key}</th>
217
- <td style="width:100%;padding:5px 10px;">${this.getView(value[key], options)}</td>
236
+ <td style="width:100%;padding:5px 10px;">${viewValue}</td>
218
237
  </tr>
219
238
  `;
220
239
  });
@@ -249,9 +268,21 @@ export default class DataMapComponent extends DataGridComponent {
249
268
  });
250
269
  const valueComponent = _.clone(this.component.valueComponent);
251
270
  valueComponent.key = key;
252
- const componentOptions = this.options;
271
+ const componentOptions = _.clone(this.options);
253
272
  componentOptions.row = options.row;
254
- components[this.valueKey] = this.createComponent(valueComponent, componentOptions, this.dataValue);
273
+ if (this.submissionTimezone) {
274
+ componentOptions.submissionTimezone = this.submissionTimezone;
275
+ if (valueComponent.type === 'datetime') {
276
+ valueComponent.widget = { ...valueComponent.widget, submissionTimezone: this.submissionTimezone };
277
+ }
278
+ }
279
+ const createdComponent = this.createComponent(valueComponent, componentOptions, this.dataValue);
280
+ // Ensure submissionTimezone is set on datetime component instance's widget and options
281
+ if (createdComponent?.type === 'datetime' && this.submissionTimezone) {
282
+ createdComponent.component.widget = { ...createdComponent.component.widget, submissionTimezone: this.submissionTimezone };
283
+ createdComponent.options.submissionTimezone = this.submissionTimezone;
284
+ }
285
+ components[this.valueKey] = createdComponent;
255
286
  return components;
256
287
  }
257
288
  get canAddColumn() {