@formio/js 5.6.0 → 5.7.0-api912.2

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 (116) hide show
  1. package/dist/formio.builder.css +5 -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 +5 -5
  7. package/dist/formio.form.js +5218 -5191
  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 +3 -1
  11. package/dist/formio.full.css +5 -5
  12. package/dist/formio.full.js +6073 -6046
  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 +3 -1
  16. package/dist/formio.js +1215 -1188
  17. package/dist/formio.min.js +1 -1
  18. package/dist/formio.min.js.LICENSE.txt +3 -1
  19. package/dist/formio.utils.js +1106 -1079
  20. package/dist/formio.utils.min.js +1 -1
  21. package/dist/formio.utils.min.js.LICENSE.txt +3 -1
  22. package/lib/cjs/Embed.js +38 -25
  23. package/lib/cjs/Form.d.ts +8 -2
  24. package/lib/cjs/Form.js +1 -1
  25. package/lib/cjs/Formio.js +1 -1
  26. package/lib/cjs/PDF.d.ts +1 -0
  27. package/lib/cjs/PDF.js +21 -5
  28. package/lib/cjs/PDFBuilder.d.ts +1 -0
  29. package/lib/cjs/PDFBuilder.js +1 -0
  30. package/lib/cjs/Webform.d.ts +13 -1
  31. package/lib/cjs/Webform.js +79 -26
  32. package/lib/cjs/Wizard.d.ts +1 -1
  33. package/lib/cjs/Wizard.js +12 -6
  34. package/lib/cjs/components/Components.js +1 -5
  35. package/lib/cjs/components/_classes/component/Component.d.ts +14 -0
  36. package/lib/cjs/components/_classes/component/Component.js +33 -65
  37. package/lib/cjs/components/_classes/nested/NestedComponent.form.js +13 -0
  38. package/lib/cjs/components/_classes/nested/NestedComponent.js +10 -8
  39. package/lib/cjs/components/content/editForm/Content.edit.display.js +8 -0
  40. package/lib/cjs/components/currency/editForm/Currency.edit.display.js +20 -0
  41. package/lib/cjs/components/datagrid/DataGrid.js +2 -6
  42. package/lib/cjs/components/day/editForm/Day.edit.display.js +8 -0
  43. package/lib/cjs/components/editgrid/EditGrid.d.ts +1 -0
  44. package/lib/cjs/components/editgrid/EditGrid.js +22 -2
  45. package/lib/cjs/components/email/editForm/Email.edit.display.js +12 -0
  46. package/lib/cjs/components/fieldset/editForm/Fieldset.edit.display.js +8 -0
  47. package/lib/cjs/components/file/editForm/File.edit.file.js +1 -1
  48. package/lib/cjs/components/form/Form.js +21 -1
  49. package/lib/cjs/components/hidden/Hidden.d.ts +0 -1
  50. package/lib/cjs/components/hidden/Hidden.js +1 -1
  51. package/lib/cjs/components/hidden/editForm/Hidden.edit.display.js +8 -0
  52. package/lib/cjs/components/html/editForm/HTML.edit.display.js +8 -0
  53. package/lib/cjs/components/number/Number.d.ts +11 -0
  54. package/lib/cjs/components/number/Number.js +35 -0
  55. package/lib/cjs/components/number/editForm/Number.edit.display.js +12 -0
  56. package/lib/cjs/components/password/editForm/Password.edit.display.js +12 -0
  57. package/lib/cjs/components/phonenumber/PhoneNumber.form.js +8 -0
  58. package/lib/cjs/components/select/editForm/Select.edit.data.d.ts +1 -1
  59. package/lib/cjs/components/select/editForm/Select.edit.data.js +1 -0
  60. package/lib/cjs/components/tabs/editForm/Tabs.edit.display.js +8 -0
  61. package/lib/cjs/components/textarea/editForm/TextArea.edit.display.js +12 -0
  62. package/lib/cjs/components/url/editForm/Url.edit.display.js +12 -0
  63. package/lib/cjs/components/well/editForm/Well.edit.display.js +8 -0
  64. package/lib/cjs/package.json +1 -1
  65. package/lib/cjs/utils/formUtils.d.ts +3 -0
  66. package/lib/cjs/utils/formUtils.js +5 -2
  67. package/lib/cjs/utils/index.d.ts +3 -0
  68. package/lib/cjs/utils/utils.js +6 -18
  69. package/lib/mjs/Embed.js +37 -24
  70. package/lib/mjs/Form.d.ts +8 -2
  71. package/lib/mjs/Form.js +1 -1
  72. package/lib/mjs/Formio.js +1 -1
  73. package/lib/mjs/PDF.d.ts +1 -0
  74. package/lib/mjs/PDF.js +21 -5
  75. package/lib/mjs/PDFBuilder.d.ts +1 -0
  76. package/lib/mjs/PDFBuilder.js +1 -0
  77. package/lib/mjs/Webform.d.ts +13 -1
  78. package/lib/mjs/Webform.js +75 -24
  79. package/lib/mjs/Wizard.d.ts +1 -1
  80. package/lib/mjs/Wizard.js +12 -6
  81. package/lib/mjs/components/Components.js +1 -5
  82. package/lib/mjs/components/_classes/component/Component.d.ts +14 -0
  83. package/lib/mjs/components/_classes/component/Component.js +33 -65
  84. package/lib/mjs/components/_classes/nested/NestedComponent.form.js +13 -0
  85. package/lib/mjs/components/_classes/nested/NestedComponent.js +10 -8
  86. package/lib/mjs/components/content/editForm/Content.edit.display.js +8 -0
  87. package/lib/mjs/components/currency/editForm/Currency.edit.display.js +20 -0
  88. package/lib/mjs/components/datagrid/DataGrid.js +2 -6
  89. package/lib/mjs/components/day/editForm/Day.edit.display.js +8 -0
  90. package/lib/mjs/components/editgrid/EditGrid.d.ts +1 -0
  91. package/lib/mjs/components/editgrid/EditGrid.js +22 -2
  92. package/lib/mjs/components/email/editForm/Email.edit.display.js +12 -0
  93. package/lib/mjs/components/fieldset/editForm/Fieldset.edit.display.js +8 -0
  94. package/lib/mjs/components/file/editForm/File.edit.file.js +1 -1
  95. package/lib/mjs/components/form/Form.js +21 -1
  96. package/lib/mjs/components/hidden/Hidden.d.ts +0 -1
  97. package/lib/mjs/components/hidden/Hidden.js +1 -1
  98. package/lib/mjs/components/hidden/editForm/Hidden.edit.display.js +8 -0
  99. package/lib/mjs/components/html/editForm/HTML.edit.display.js +8 -0
  100. package/lib/mjs/components/number/Number.d.ts +11 -0
  101. package/lib/mjs/components/number/Number.js +34 -0
  102. package/lib/mjs/components/number/editForm/Number.edit.display.js +12 -0
  103. package/lib/mjs/components/password/editForm/Password.edit.display.js +12 -0
  104. package/lib/mjs/components/phonenumber/PhoneNumber.form.js +8 -0
  105. package/lib/mjs/components/select/editForm/Select.edit.data.d.ts +1 -1
  106. package/lib/mjs/components/select/editForm/Select.edit.data.js +1 -0
  107. package/lib/mjs/components/tabs/editForm/Tabs.edit.display.js +8 -0
  108. package/lib/mjs/components/textarea/editForm/TextArea.edit.display.js +12 -0
  109. package/lib/mjs/components/url/editForm/Url.edit.display.js +12 -0
  110. package/lib/mjs/components/well/editForm/Well.edit.display.js +8 -0
  111. package/lib/mjs/package.json +1 -1
  112. package/lib/mjs/utils/formUtils.d.ts +3 -0
  113. package/lib/mjs/utils/formUtils.js +2 -2
  114. package/lib/mjs/utils/index.d.ts +3 -0
  115. package/lib/mjs/utils/utils.js +6 -18
  116. package/package.json +2 -2
@@ -29,6 +29,9 @@ export const findComponents: typeof Utils.findComponents;
29
29
  export const eachComponentDataAsync: (components: import("@formio/core").Component[], data: import("@formio/core").DataObject, fn: import("@formio/core").EachComponentDataAsyncCallback, includeAll?: boolean | undefined, local?: boolean | undefined, parent?: import("@formio/core").Component | undefined, parentPaths?: import("@formio/core").ComponentPaths | undefined, noScopeReset?: boolean | undefined, afterFn?: import("@formio/core").EachComponentDataAsyncCallback | undefined, localRoot?: import("@formio/core").LocalRoot | undefined) => Promise<void>;
30
30
  export const eachComponentData: (components: import("@formio/core").Component[], data: import("@formio/core").DataObject, fn: import("@formio/core").EachComponentDataCallback, includeAll?: boolean | undefined, local?: boolean | undefined, parent?: import("@formio/core").Component | undefined, parentPaths?: import("@formio/core").ComponentPaths | undefined, noScopeReset?: boolean | undefined, afterFn?: import("@formio/core").EachComponentDataCallback | undefined, localRoot?: import("@formio/core").LocalRoot | undefined) => void;
31
31
  export const getComponentKey: typeof Utils.getComponentKey;
32
+ export const getSharedPathKey: typeof Utils.getSharedPathKey;
33
+ export const registerInstanceAtPath: typeof Utils.registerInstanceAtPath;
34
+ export const resolveInstanceAtPath: typeof Utils.resolveInstanceAtPath;
32
35
  export const getContextualRowPath: any;
33
36
  export const getContextualRowData: typeof Utils.getContextualRowData;
34
37
  export const componentInfo: typeof Utils.componentInfo;
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getItemTemplateKeys = exports.compareSelectResourceWithObjectTypeValues = exports.isSelectResourceWithObjectValue = exports.isComponentDataEmpty = exports.getEmptyValue = exports.findComponent = exports.applyFormChanges = exports.generateFormChange = exports.getStrings = exports.getValue = exports.escapeRegExCharacters = exports.formatAsCurrency = exports.parseFloatExt = exports.hasCondition = exports.removeComponent = exports.searchComponents = exports.getComponent = exports.matchComponent = exports.isLayoutComponent = exports.getComponentActualValue = exports.getComponentData = exports.eachComponentAsync = exports.eachComponent = exports.componentInfo = exports.getContextualRowData = exports.getContextualRowPath = exports.getComponentKey = exports.eachComponentData = exports.eachComponentDataAsync = exports.findComponents = exports.getComponentValue = exports.getComponentFromPath = exports.getBestMatch = exports.componentMatches = exports.getComponentPaths = exports.componentPath = exports.isComponentNestedDataType = exports.resetComponentScope = exports.setComponentScope = exports.getComponentPath = exports.getModelType = exports.MODEL_TYPES_OF_KNOWN_COMPONENTS = exports.uniqueName = exports.guid = exports.flattenComponents = void 0;
3
+ exports.getItemTemplateKeys = exports.compareSelectResourceWithObjectTypeValues = exports.isSelectResourceWithObjectValue = exports.isComponentDataEmpty = exports.getEmptyValue = exports.findComponent = exports.applyFormChanges = exports.generateFormChange = exports.getStrings = exports.getValue = exports.escapeRegExCharacters = exports.formatAsCurrency = exports.parseFloatExt = exports.hasCondition = exports.removeComponent = exports.searchComponents = exports.getComponent = exports.matchComponent = exports.isLayoutComponent = exports.getComponentActualValue = exports.getComponentData = exports.eachComponentAsync = exports.eachComponent = exports.componentInfo = exports.getContextualRowData = exports.getContextualRowPath = exports.resolveInstanceAtPath = exports.registerInstanceAtPath = exports.getSharedPathKey = exports.getComponentKey = exports.eachComponentData = exports.eachComponentDataAsync = exports.findComponents = exports.getComponentValue = exports.getComponentFromPath = exports.getBestMatch = exports.componentMatches = exports.getComponentPaths = exports.componentPath = exports.isComponentNestedDataType = exports.resetComponentScope = exports.setComponentScope = exports.getComponentPath = exports.getModelType = exports.MODEL_TYPES_OF_KNOWN_COMPONENTS = exports.uniqueName = exports.guid = exports.flattenComponents = void 0;
4
4
  const core_1 = require("@formio/core");
5
- const { flattenComponents, guid, uniqueName, MODEL_TYPES_OF_KNOWN_COMPONENTS, getModelType, getComponentPath, setComponentScope, resetComponentScope, isComponentNestedDataType, componentPath, getComponentPaths, componentMatches, getBestMatch, getComponentFromPath, getComponentValue, findComponents, eachComponentDataAsync, eachComponentData, getComponentKey, getContextualRowPath, getContextualRowData, componentInfo, eachComponent, eachComponentAsync, getComponentData, getComponentActualValue, isLayoutComponent, matchComponent, getComponent, searchComponents, removeComponent, hasCondition, parseFloatExt, formatAsCurrency, escapeRegExCharacters, getValue, getStrings, generateFormChange, applyFormChanges, findComponent, getEmptyValue, isComponentDataEmpty, isSelectResourceWithObjectValue, compareSelectResourceWithObjectTypeValues, getItemTemplateKeys, } = core_1.Utils;
5
+ const { flattenComponents, guid, uniqueName, MODEL_TYPES_OF_KNOWN_COMPONENTS, getModelType, getComponentPath, setComponentScope, resetComponentScope, isComponentNestedDataType, componentPath, getComponentPaths, componentMatches, getBestMatch, getComponentFromPath, getComponentValue, findComponents, eachComponentDataAsync, eachComponentData, getComponentKey, getSharedPathKey, registerInstanceAtPath, resolveInstanceAtPath, getContextualRowPath, getContextualRowData, componentInfo, eachComponent, eachComponentAsync, getComponentData, getComponentActualValue, isLayoutComponent, matchComponent, getComponent, searchComponents, removeComponent, hasCondition, parseFloatExt, formatAsCurrency, escapeRegExCharacters, getValue, getStrings, generateFormChange, applyFormChanges, findComponent, getEmptyValue, isComponentDataEmpty, isSelectResourceWithObjectValue, compareSelectResourceWithObjectTypeValues, getItemTemplateKeys, } = core_1.Utils;
6
6
  exports.flattenComponents = flattenComponents;
7
7
  exports.guid = guid;
8
8
  exports.uniqueName = uniqueName;
@@ -22,6 +22,9 @@ exports.findComponents = findComponents;
22
22
  exports.eachComponentDataAsync = eachComponentDataAsync;
23
23
  exports.eachComponentData = eachComponentData;
24
24
  exports.getComponentKey = getComponentKey;
25
+ exports.getSharedPathKey = getSharedPathKey;
26
+ exports.registerInstanceAtPath = registerInstanceAtPath;
27
+ exports.resolveInstanceAtPath = resolveInstanceAtPath;
25
28
  exports.getContextualRowPath = getContextualRowPath;
26
29
  exports.getContextualRowData = getContextualRowData;
27
30
  exports.componentInfo = componentInfo;
@@ -40,6 +40,9 @@ declare const FormioUtils: {
40
40
  eachComponentDataAsync: (components: import("@formio/core").Component[], data: import("@formio/core").DataObject, fn: import("@formio/core").EachComponentDataAsyncCallback, includeAll?: boolean | undefined, local?: boolean | undefined, parent?: import("@formio/core").Component | undefined, parentPaths?: import("@formio/core").ComponentPaths | undefined, noScopeReset?: boolean | undefined, afterFn?: import("@formio/core").EachComponentDataAsyncCallback | undefined, localRoot?: import("@formio/core").LocalRoot | undefined) => Promise<void>;
41
41
  eachComponentData: (components: import("@formio/core").Component[], data: import("@formio/core").DataObject, fn: import("@formio/core").EachComponentDataCallback, includeAll?: boolean | undefined, local?: boolean | undefined, parent?: import("@formio/core").Component | undefined, parentPaths?: import("@formio/core").ComponentPaths | undefined, noScopeReset?: boolean | undefined, afterFn?: import("@formio/core").EachComponentDataCallback | undefined, localRoot?: import("@formio/core").LocalRoot | undefined) => void;
42
42
  getComponentKey: typeof import("@formio/core/lib/utils/formUtil").getComponentKey;
43
+ getSharedPathKey: typeof import("@formio/core/lib/utils/formUtil").getSharedPathKey;
44
+ registerInstanceAtPath: typeof import("@formio/core/lib/utils/formUtil").registerInstanceAtPath;
45
+ resolveInstanceAtPath: typeof import("@formio/core/lib/utils/formUtil").resolveInstanceAtPath;
43
46
  getContextualRowPath: any;
44
47
  getContextualRowData: typeof import("@formio/core/lib/utils/formUtil").getContextualRowData;
45
48
  componentInfo: typeof import("@formio/core/lib/utils/formUtil").componentInfo;
@@ -474,9 +474,7 @@ function setActionProperty(component, action, result, row, data, instance) {
474
474
  case 'string': {
475
475
  const safeData = escapeInterpolationDataStrings(data);
476
476
  const safeRow = escapeInterpolationDataStrings(row);
477
- const safeResult = typeof result === 'string'
478
- ? lodash_1.default.escape(result)
479
- : escapeInterpolationDataStrings(result);
477
+ const safeResult = typeof result === 'string' ? lodash_1.default.escape(result) : escapeInterpolationDataStrings(result);
480
478
  const evalData = {
481
479
  data: safeData,
482
480
  row: safeRow,
@@ -1777,21 +1775,11 @@ function getScreenReaderMessage(component, type, value) {
1777
1775
  }
1778
1776
  }
1779
1777
  }
1780
- else if (typeof value === 'number' || value === null) {
1781
- if (value != null && value !== '') {
1782
- if (!isNaN(max) && value > max) {
1783
- message += `Number cannot be greater than ${max}. `;
1784
- }
1785
- if (!isNaN(min) && value < min) {
1786
- message += `Number cannot be less than ${min}.`;
1787
- }
1788
- }
1789
- else {
1790
- if (!isNaN(min))
1791
- message += `Minimum value ${min}. `;
1792
- if (!isNaN(max))
1793
- message += `Maximum value ${max}. `;
1794
- }
1778
+ else if (value === null) {
1779
+ if (!isNaN(min))
1780
+ message += `Minimum value ${min}. `;
1781
+ if (!isNaN(max))
1782
+ message += `Maximum value ${max}. `;
1795
1783
  }
1796
1784
  return message.trim();
1797
1785
  }
package/lib/mjs/Embed.js CHANGED
@@ -14,7 +14,7 @@ export class Formio {
14
14
  Formio._formioReady = ready;
15
15
  Formio._formioReadyReject = reject;
16
16
  });
17
- static version = '5.6.0';
17
+ static version = '5.7.0-api912.2';
18
18
  static setLicense(license, norecurse = false) {
19
19
  Formio.license = license;
20
20
  if (!norecurse && Formio.FormioClass) {
@@ -332,7 +332,10 @@ export class Formio {
332
332
  };
333
333
  });
334
334
  const id = Formio.config.id || `formio-${Math.random().toString(36).substring(7)}`;
335
- const hasQuillComponent = (components = []) => {
335
+ // Quill and CKEditor cannot run in the shadow DOM (https://github.com/quilljs/quill/issues/2021,
336
+ // https://github.com/ckeditor/ckeditor5/issues/15824), so fall back to the light DOM when the
337
+ // form contains one of them.
338
+ const hasShadowIncompatibleEditor = (components) => {
336
339
  if (!Array.isArray(components)) {
337
340
  return false;
338
341
  }
@@ -340,16 +343,38 @@ export class Formio {
340
343
  if (!component || typeof component !== 'object') {
341
344
  return false;
342
345
  }
343
- const isQuillTextarea = component.type === 'textarea' &&
344
- component.wysiwyg === true &&
345
- component.editor === 'quill';
346
- return (isQuillTextarea ||
347
- hasQuillComponent(component.components) ||
348
- hasQuillComponent(component.columns?.flatMap((column) => column.components || [])) ||
349
- hasQuillComponent(component.rows?.flatMap((row) => row.flatMap((cell) => cell.components || []))));
346
+ if (component.type === 'textarea') {
347
+ // A textarea with `wysiwyg` set and no explicit `editor` defaults to CKEditor at render time.
348
+ const editor = component.editor || (component.wysiwyg ? 'ckeditor' : '');
349
+ if (['quill', 'ckeditor'].includes(editor)) {
350
+ return true;
351
+ }
352
+ }
353
+ const children = (component.components || []).concat(component.columns?.flatMap((column) => column.components || []) || [], component.rows?.flatMap((row) => row.flatMap((cell) => cell.components || [])) || []);
354
+ return hasShadowIncompatibleEditor(children);
350
355
  });
351
356
  };
352
- const disableShadowForQuill = hasQuillComponent(Formio.config.form?.components);
357
+ const shouldSkipShadowDom = async () => {
358
+ if (Formio.config.form?.components) {
359
+ return hasShadowIncompatibleEditor(Formio.config.form.components);
360
+ }
361
+ const formUrl = Formio.config.src || (typeof Formio.config.form === 'string' ? Formio.config.form : '');
362
+ if (!formUrl) {
363
+ return false;
364
+ }
365
+ try {
366
+ const response = await fetch(formUrl);
367
+ if (!response.ok) {
368
+ throw new Error(`Request failed with status ${response.status}`);
369
+ }
370
+ const form = await response.json();
371
+ return hasShadowIncompatibleEditor(form?.components);
372
+ }
373
+ catch (err) {
374
+ Formio.debug('Could not load the form to check for shadow DOM support', err);
375
+ return false;
376
+ }
377
+ };
353
378
  // Create a new wrapper and add the element inside of a new wrapper.
354
379
  let wrapper = Formio.createElement('div', {
355
380
  id: `${id}-wrapper`,
@@ -358,20 +383,8 @@ export class Formio {
358
383
  // If we include the libraries, then we will attempt to run this in shadow dom.
359
384
  const useShadowDom = Formio.config.includeLibs &&
360
385
  !Formio.config.noshadow &&
361
- !disableShadowForQuill &&
362
- typeof wrapper.attachShadow === 'function';
363
- if (useShadowDom) {
364
- wrapper = wrapper.attachShadow({
365
- mode: 'open',
366
- });
367
- options.shadowRoot = wrapper;
368
- // Due to an issue with quill not loading styles in the shadowdom, we need to add quill styles and js to the shadowdom
369
- const quill = {
370
- js: `${Formio.cdn.quill}/quill.js`,
371
- css: `${Formio.cdn.quill}/quill.snow.css`,
372
- };
373
- await Formio.addLibrary(wrapper, quill, 'quill');
374
- }
386
+ typeof wrapper.attachShadow === 'function' &&
387
+ !(await shouldSkipShadowDom());
375
388
  element.parentNode.removeChild(element);
376
389
  wrapper.appendChild(element);
377
390
  // If this is inside of shadow dom, then we need to add the styles and scripts to the shadow dom.
package/lib/mjs/Form.d.ts CHANGED
@@ -45,7 +45,7 @@
45
45
  * @property {number} [saveDraftThrottle] - The throttle for the save draft feature.
46
46
  * @property {boolean} [readOnly] - Set this form to readOnly.
47
47
  * @property {boolean} [noAlerts] - Disable the alerts dialog.
48
- * @property {{[key: string]: string} | { translationsUrl: string }} [i18n] - The translation file for this rendering.
48
+ * @property {{[key: string]: string} | { translationsUrl: string } | { resource: { form: string, codeComponent: string, mapComponent: string } }} [i18n] - The translations for this rendering: inline, a hosted file, or a Form.io language resource (one submission per language).
49
49
  * @property {string} [template] - Custom logic for creation of elements.
50
50
  * @property {boolean} [noDefaults] - Exclude default values from the settings.
51
51
  * @property {any} [fileService] - The file service for this form.
@@ -198,12 +198,18 @@ export type FormOptions = {
198
198
  */
199
199
  noAlerts?: boolean | undefined;
200
200
  /**
201
- * - The translation file for this rendering.
201
+ * - The translations for this rendering: inline, a hosted file, or a Form.io language resource (one submission per language).
202
202
  */
203
203
  i18n?: {
204
204
  [key: string]: string;
205
205
  } | {
206
206
  translationsUrl: string;
207
+ } | {
208
+ resource: {
209
+ form: string;
210
+ codeComponent: string;
211
+ mapComponent: string;
212
+ };
207
213
  } | undefined;
208
214
  /**
209
215
  * - Custom logic for creation of elements.
package/lib/mjs/Form.js CHANGED
@@ -50,7 +50,7 @@ import FormioUtils from './utils';
50
50
  * @property {number} [saveDraftThrottle] - The throttle for the save draft feature.
51
51
  * @property {boolean} [readOnly] - Set this form to readOnly.
52
52
  * @property {boolean} [noAlerts] - Disable the alerts dialog.
53
- * @property {{[key: string]: string} | { translationsUrl: string }} [i18n] - The translation file for this rendering.
53
+ * @property {{[key: string]: string} | { translationsUrl: string } | { resource: { form: string, codeComponent: string, mapComponent: string } }} [i18n] - The translations for this rendering: inline, a hosted file, or a Form.io language resource (one submission per language).
54
54
  * @property {string} [template] - Custom logic for creation of elements.
55
55
  * @property {boolean} [noDefaults] - Exclude default values from the settings.
56
56
  * @property {any} [fileService] - The file service for this form.
package/lib/mjs/Formio.js CHANGED
@@ -4,7 +4,7 @@ import CDN from './CDN';
4
4
  import Providers from './providers';
5
5
  FormioCore.cdn = new CDN();
6
6
  FormioCore.Providers = Providers;
7
- FormioCore.version = '5.6.0';
7
+ FormioCore.version = '5.7.0-api912.2';
8
8
  CDN.defaultCDN = FormioCore.version.includes('rc')
9
9
  ? 'https://cdn.test-form.io'
10
10
  : 'https://cdn.form.io';
package/lib/mjs/PDF.d.ts CHANGED
@@ -2,6 +2,7 @@ export default class PDF extends Webform {
2
2
  constructor(element: any, options: any);
3
3
  components: any[];
4
4
  init(): void;
5
+ forwardRefreshEventsToIframe(): void;
5
6
  render(): any;
6
7
  submitButton: any;
7
8
  attachComponents(element: any, components: any, container: any): Promise<void>;
package/lib/mjs/PDF.js CHANGED
@@ -13,10 +13,7 @@ const getIframeOrigin = (iframeElement) => {
13
13
  const isTrustedIframeMessage = (event, form) => {
14
14
  const iframeWindow = form?.iframeElement?.contentWindow;
15
15
  const iframeOrigin = form?.iframeElement && getIframeOrigin(form.iframeElement);
16
- return Boolean(iframeWindow &&
17
- iframeOrigin &&
18
- event.source === iframeWindow &&
19
- event.origin === iframeOrigin);
16
+ return Boolean(iframeWindow && iframeOrigin && event.source === iframeWindow && event.origin === iframeOrigin);
20
17
  };
21
18
  export default class PDF extends Webform {
22
19
  constructor(element, options) {
@@ -25,7 +22,9 @@ export default class PDF extends Webform {
25
22
  this.components = [];
26
23
  }
27
24
  init() {
25
+ this.options.pdfShell = true;
28
26
  super.init();
27
+ this.forwardRefreshEventsToIframe();
29
28
  // Handle an iframe submission.
30
29
  this.on('iframe-submission', (submission) => this.setValue(submission, {
31
30
  fromIframe: true,
@@ -56,6 +55,17 @@ export default class PDF extends Webform {
56
55
  // Re-emit pageEnd without the iframe- prefix for consumers tracking PDF scroll position.
57
56
  this.on('iframe-pageEnd', (data) => this.emit('pageEnd', data), true);
58
57
  }
58
+ // The iframe renders the components in its own realm with its own emitter, so an event they
59
+ // refresh on only reaches them across the bridge.
60
+ forwardRefreshEventsToIframe() {
61
+ const refreshEvents = new Set();
62
+ eachComponent(this.component.components, (component) => {
63
+ if (component.refreshOnEvent) {
64
+ refreshEvents.add(component.refreshOnEvent);
65
+ }
66
+ });
67
+ refreshEvents.forEach((event) => this.on(event, () => this.postMessage({ name: 'emit', data: event }), true));
68
+ }
59
69
  render() {
60
70
  this.submitButton = this.addComponent({
61
71
  disabled: this.form.disableWizardSubmit,
@@ -309,7 +319,13 @@ if (typeof window !== 'undefined') {
309
319
  if (eventData.compPath) {
310
320
  const comp = form.getComponent(eventData.compPath);
311
321
  if (comp) {
312
- comp.emit(eventData.name, eventData.data);
322
+ // Emit component instance for focus/blur events to match payload of those events on other forms
323
+ if (['focus', 'blur'].includes(eventData.name)) {
324
+ comp.emit(eventData.name, comp);
325
+ }
326
+ else {
327
+ comp.emit(eventData.name, eventData.data);
328
+ }
313
329
  }
314
330
  }
315
331
  else {
@@ -19,6 +19,7 @@ export default class PDFBuilder extends WebformBuilder {
19
19
  file: boolean;
20
20
  htmlelement: boolean;
21
21
  signrequestsignature: boolean;
22
+ datasource: boolean;
22
23
  };
23
24
  };
24
25
  basic: boolean;
@@ -46,6 +46,7 @@ export default class PDFBuilder extends WebformBuilder {
46
46
  file: true,
47
47
  htmlelement: true,
48
48
  signrequestsignature: true,
49
+ datasource: true,
49
50
  },
50
51
  },
51
52
  basic: false,
@@ -293,8 +293,20 @@ declare class Webform extends NestedDataComponent {
293
293
  /**
294
294
  * Focus on selected component.
295
295
  * @param {string} key - The key of selected component.
296
+ * @param {string} [componentKey] - The schema key of the component that produced the error, if known.
296
297
  */
297
- focusOnComponent(key: string): void;
298
+ focusOnComponent(key: string, componentKey?: string | undefined): void;
299
+ /**
300
+ * Resolves the component that produced an error, to attach the error to it or to focus it from
301
+ * the error list. A checkbox with inputType 'radio' takes its `name` as its path, so several
302
+ * components can share `key`; the schema key of the component that produced the error tells them
303
+ * apart. The shared-path lookup is only consulted when `getComponent` picked another component,
304
+ * and only trusted when it finds the one with that schema key.
305
+ * @param {string} key - The path the error was reported at.
306
+ * @param {string} [componentKey] - The schema key of the component that produced the error, if known.
307
+ * @returns {any} - The component that produced the error, if found.
308
+ */
309
+ getErrorComponent(key: string, componentKey?: string | undefined): any;
298
310
  /**
299
311
  * @returns {boolean} - Whether or not the draft submission is being edited
300
312
  */
@@ -6,7 +6,7 @@ import i18nDefaults from './i18n';
6
6
  import { Formio } from './Formio';
7
7
  import Components from './components/Components';
8
8
  import NestedDataComponent from './components/_classes/nesteddata/NestedDataComponent';
9
- import { fastCloneDeep, currentTimezone, unescapeHTML, getStringFromComponentPath, convertStringToHTMLElement, getArrayFromComponentPath, eachComponent, } from './utils';
9
+ import { fastCloneDeep, currentTimezone, unescapeHTML, getStringFromComponentPath, convertStringToHTMLElement, getArrayFromComponentPath, eachComponent, resolveInstanceAtPath, } from './utils';
10
10
  // We need this here because dragula pulls in CustomEvent class that requires global to exist.
11
11
  if (typeof window !== 'undefined' && typeof window.global === 'undefined') {
12
12
  window.global = window;
@@ -979,16 +979,18 @@ export default class Webform extends NestedDataComponent {
979
979
  }
980
980
  this.loadRefs(this.alert, { errorRef: 'multiple' });
981
981
  if (this.refs.errorRef && this.refs.errorRef.length) {
982
- this.refs.errorRef.forEach((el) => {
983
- this.addEventListener(el, 'click', (e) => {
984
- const key = e.currentTarget.dataset.componentKey;
985
- this.focusOnComponent(key);
986
- });
982
+ this.refs.errorRef.forEach((el, index) => {
983
+ // The Nth link renders the Nth error; a custom template that reorders or filters the list
984
+ // fails this check, and the link then resolves by its key alone.
985
+ const listItem = options?.errors?.[index];
986
+ const isLinkForListItem = !!listItem && listItem.keyOrPath === el.dataset.componentKey;
987
+ const componentKey = isLinkForListItem ? listItem.componentKey : undefined;
988
+ const focusOnLinkedComponent = (e) => this.focusOnComponent(e.currentTarget.dataset.componentKey, componentKey);
989
+ this.addEventListener(el, 'click', focusOnLinkedComponent);
987
990
  this.addEventListener(el, 'keydown', (e) => {
988
991
  if (e.keyCode === 13) {
989
992
  e.preventDefault();
990
- const key = e.currentTarget.dataset.componentKey;
991
- this.focusOnComponent(key);
993
+ focusOnLinkedComponent(e);
992
994
  }
993
995
  });
994
996
  });
@@ -998,16 +1000,36 @@ export default class Webform extends NestedDataComponent {
998
1000
  /**
999
1001
  * Focus on selected component.
1000
1002
  * @param {string} key - The key of selected component.
1003
+ * @param {string} [componentKey] - The schema key of the component that produced the error, if known.
1001
1004
  */
1002
- focusOnComponent(key) {
1005
+ focusOnComponent(key, componentKey) {
1003
1006
  if (key) {
1004
- const component = this.getComponent(key);
1007
+ const component = this.getErrorComponent(key, componentKey);
1005
1008
  if (component) {
1006
1009
  component.focus();
1007
1010
  component.scrollIntoView();
1008
1011
  }
1009
1012
  }
1010
1013
  }
1014
+ /**
1015
+ * Resolves the component that produced an error, to attach the error to it or to focus it from
1016
+ * the error list. A checkbox with inputType 'radio' takes its `name` as its path, so several
1017
+ * components can share `key`; the schema key of the component that produced the error tells them
1018
+ * apart. The shared-path lookup is only consulted when `getComponent` picked another component,
1019
+ * and only trusted when it finds the one with that schema key.
1020
+ * @param {string} key - The path the error was reported at.
1021
+ * @param {string} [componentKey] - The schema key of the component that produced the error, if known.
1022
+ * @returns {any} - The component that produced the error, if found.
1023
+ */
1024
+ getErrorComponent(key, componentKey) {
1025
+ const component = this.getComponent(key);
1026
+ if (!componentKey || component?.component.key === componentKey) {
1027
+ return component;
1028
+ }
1029
+ // GOTCHA(G-FJS06)
1030
+ const componentWithKey = resolveInstanceAtPath(this.componentsMap, key, componentKey);
1031
+ return componentWithKey?.component.key === componentKey ? componentWithKey : component;
1032
+ }
1011
1033
  /**
1012
1034
  * @returns {boolean} - Whether or not the draft submission is being edited
1013
1035
  */
@@ -1041,23 +1063,23 @@ export default class Webform extends NestedDataComponent {
1041
1063
  return;
1042
1064
  }
1043
1065
  // Mark any components as invalid if in a custom message.
1044
- const componentErrors = {};
1066
+ const errorsByComponent = new Map();
1045
1067
  errors.forEach((err) => {
1046
1068
  const path = getStringFromComponentPath(err.path) || err.context?.path || err.component?.key;
1047
- if (!componentErrors[path]) {
1048
- componentErrors[path] = [];
1069
+ const component = path && this.getErrorComponent(path, err.component?.key);
1070
+ if (!component) {
1071
+ return;
1049
1072
  }
1050
- componentErrors[path].push(err);
1073
+ if (!errorsByComponent.has(component)) {
1074
+ errorsByComponent.set(component, []);
1075
+ }
1076
+ errorsByComponent.get(component).push(err);
1051
1077
  });
1052
1078
  // Iterate through all of our component errors and apply them to the components.
1053
- for (let path in componentErrors) {
1054
- const component = this.getComponent(path);
1055
- const errors = componentErrors[path];
1056
- if (component) {
1057
- component.serverErrors = errors.filter((err) => err.fromServer);
1058
- component.setCustomValidity(errors, true);
1059
- }
1060
- }
1079
+ errorsByComponent.forEach((componentErrors, component) => {
1080
+ component.serverErrors = componentErrors.filter((err) => err.fromServer);
1081
+ component.setCustomValidity(componentErrors, true);
1082
+ });
1061
1083
  const displayedErrors = [];
1062
1084
  if (errors.length) {
1063
1085
  errors = _.uniqBy(errors, (error) => [error.message, error.component?.id, error.context?.path].join());
@@ -1077,6 +1099,7 @@ export default class Webform extends NestedDataComponent {
1077
1099
  return {
1078
1100
  message: unescapeHTML(message),
1079
1101
  keyOrPath: formattedKeyOrPath,
1102
+ componentKey: err.component?.key,
1080
1103
  };
1081
1104
  };
1082
1105
  errors.forEach(({ message, context, fromServer, component }, index) => {
@@ -1090,7 +1113,7 @@ export default class Webform extends NestedDataComponent {
1090
1113
  // GOTCHA(G-FJS02)
1091
1114
  // Only paint the alert from a subform when the root won't double paint it to avoid a painful flicker
1092
1115
  if (this === this.root || !this.root?.submitted) {
1093
- this.root?.setAlert('danger', errorsList);
1116
+ this.root?.setAlert('danger', errorsList, { errors: displayedErrors });
1094
1117
  }
1095
1118
  if (triggerEvent) {
1096
1119
  this.emit('error', errors);
@@ -1518,9 +1541,16 @@ export default class Webform extends NestedDataComponent {
1518
1541
  const translationsUrl = typeof this.options.i18n?.translationsUrl === 'string'
1519
1542
  ? this.options.i18n.translationsUrl
1520
1543
  : null;
1544
+ // Translations can also live in a Form.io resource, one submission per language.
1545
+ const languageResource = this.options.i18n?.resource;
1546
+ const usesLanguageResource = Boolean(languageResource?.form);
1521
1547
  if (translationsUrl) {
1522
1548
  const url = this.sanitize(this.interpolate(translationsUrl, {}), this.shouldSanitizeValue);
1523
- Formio.makeStaticRequest(url, 'GET')
1549
+ // Simple CORS GET: skip Content-Type / token so static hosts need no preflight.
1550
+ Formio.makeStaticRequest(url, 'GET', null, {
1551
+ headers: { Accept: 'application/json' },
1552
+ noToken: true,
1553
+ })
1524
1554
  .then((response) => {
1525
1555
  let languages = response;
1526
1556
  try {
@@ -1547,6 +1577,27 @@ export default class Webform extends NestedDataComponent {
1547
1577
  translationsLoadResolve();
1548
1578
  });
1549
1579
  }
1580
+ else if (usesLanguageResource) {
1581
+ const { form: resourceId, codeComponent, mapComponent } = languageResource;
1582
+ const language = this.i18next?.originalLanguage || this.options.language;
1583
+ const languageQuery = `data.${codeComponent}=${encodeURIComponent(language)}`;
1584
+ // The resource is addressed by id, so it needs the /form/ route — the bare
1585
+ // `{project}/{alias}` route resolves by path only and would 404 on an id.
1586
+ const url = `${Formio.getProjectUrl()}/form/${resourceId}/submission?${languageQuery}&limit=1`;
1587
+ Formio.makeStaticRequest(url, 'GET')
1588
+ .then((submissions) => {
1589
+ const translations = submissions?.[0]?.data?.[mapComponent];
1590
+ // No submission for this language just leaves the form in its default language.
1591
+ if (translations) {
1592
+ this.addLanguage(language, translations, true);
1593
+ }
1594
+ translationsLoadResolve();
1595
+ })
1596
+ .catch((err) => {
1597
+ console.warn(err);
1598
+ translationsLoadResolve();
1599
+ });
1600
+ }
1550
1601
  else {
1551
1602
  translationsLoadResolve();
1552
1603
  }
@@ -112,7 +112,7 @@ declare class Wizard extends Webform {
112
112
  pageId(page: any): any;
113
113
  onChange(flags: any, changed: any, modified: any, changes: any): void;
114
114
  checkValidity(data: any, dirty: any, row: any, currentPageOnly: any, childErrors?: any[]): any;
115
- focusOnComponent(key: any): void | Promise<void>;
115
+ focusOnComponent(key: any, componentKey: any): void | Promise<void>;
116
116
  triggerButtonCaptcha(page: any): void;
117
117
  }
118
118
  declare namespace Wizard {
package/lib/mjs/Wizard.js CHANGED
@@ -122,9 +122,15 @@ export default class Wizard extends Webform {
122
122
  { name: 'cancel', method: 'cancel' },
123
123
  { name: 'previous', method: 'prevPage' },
124
124
  { name: 'next', method: 'nextPage' },
125
- { name: 'submit', method: 'submit', args: [false, {
125
+ {
126
+ name: 'submit',
127
+ method: 'submit',
128
+ args: [
129
+ false,
130
+ {
126
131
  state: 'submitted',
127
- }],
132
+ },
133
+ ],
128
134
  },
129
135
  ].forEach((button) => {
130
136
  if (this.hasButton(button.name)) {
@@ -1004,8 +1010,8 @@ export default class Wizard extends Webform {
1004
1010
  get errors() {
1005
1011
  return !this.isLastPage() && !this.submitted ? this.currentPage.errors : super.errors;
1006
1012
  }
1007
- focusOnComponent(key) {
1008
- const component = this.getComponent(key);
1013
+ focusOnComponent(key, componentKey) {
1014
+ const component = this.getErrorComponent(key, componentKey);
1009
1015
  if (component) {
1010
1016
  let topPanel = component.parent;
1011
1017
  while (!(topPanel.parent instanceof Wizard)) {
@@ -1015,11 +1021,11 @@ export default class Wizard extends Webform {
1015
1021
  if (pageIndex >= 0) {
1016
1022
  const page = this.pages[pageIndex];
1017
1023
  if (page && page !== this.currentPage) {
1018
- return this.setPage(pageIndex).then(() => super.focusOnComponent(key));
1024
+ return this.setPage(pageIndex).then(() => super.focusOnComponent(key, componentKey));
1019
1025
  }
1020
1026
  }
1021
1027
  }
1022
- return super.focusOnComponent(key);
1028
+ return super.focusOnComponent(key, componentKey);
1023
1029
  }
1024
1030
  triggerButtonCaptcha(page) {
1025
1031
  if (!page.components) {
@@ -65,11 +65,7 @@ export default class Components {
65
65
  else {
66
66
  comp = new Component(component, options, data);
67
67
  }
68
- if (comp.path) {
69
- comp.eachRootChildComponentsMap((map) => {
70
- map[comp.path] = comp;
71
- });
72
- }
68
+ comp.registerComponentInstance();
73
69
  // Reset the componentMatches on the root element if any new component is created.
74
70
  let parent = comp.parent;
75
71
  while (parent) {
@@ -171,6 +171,20 @@ declare class Component extends Element {
171
171
  * @param {(map: object) => void} fn - Called once per root that exposes a `childComponentsMap`.
172
172
  */
173
173
  eachRootChildComponentsMap(fn: (map: object) => void): void;
174
+ /**
175
+ * Registers this instance in every root's `childComponentsMap`. Several components can resolve
176
+ * to one data path — see `getSharedPathKey` in `@formio/core` for the rule this follows.
177
+ * @returns {void}
178
+ */
179
+ registerComponentInstance(): void;
180
+ sharedPathKey: string | undefined;
181
+ /**
182
+ * Removes this instance from every root's `childComponentsMap`.
183
+ * @param {string} [path] - The path this instance was registered under, when it has since
184
+ * been re-keyed (a DataGrid row index change regenerates paths before unregistering).
185
+ * @returns {void}
186
+ */
187
+ unregisterComponentInstance(path?: string | undefined): void;
174
188
  /**
175
189
  * Returns if the parent should conditionally clear.
176
190
  *