@formio/js 5.6.1 → 5.7.0

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 (104) 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 +5195 -5189
  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 +4 -2
  11. package/dist/formio.full.css +5 -5
  12. package/dist/formio.full.js +6046 -6040
  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 +4 -2
  16. package/dist/formio.js +1193 -1187
  17. package/dist/formio.min.js +1 -1
  18. package/dist/formio.min.js.LICENSE.txt +4 -2
  19. package/dist/formio.utils.js +1084 -1078
  20. package/dist/formio.utils.min.js +1 -1
  21. package/dist/formio.utils.min.js.LICENSE.txt +4 -2
  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/_classes/component/Component.js +0 -65
  35. package/lib/cjs/components/_classes/nested/NestedComponent.form.js +13 -0
  36. package/lib/cjs/components/_classes/nested/NestedComponent.js +4 -0
  37. package/lib/cjs/components/content/editForm/Content.edit.display.js +8 -0
  38. package/lib/cjs/components/currency/editForm/Currency.edit.display.js +20 -0
  39. package/lib/cjs/components/day/editForm/Day.edit.display.js +8 -0
  40. package/lib/cjs/components/editgrid/EditGrid.d.ts +1 -0
  41. package/lib/cjs/components/editgrid/EditGrid.js +22 -2
  42. package/lib/cjs/components/email/editForm/Email.edit.display.js +12 -0
  43. package/lib/cjs/components/fieldset/editForm/Fieldset.edit.display.js +8 -0
  44. package/lib/cjs/components/file/editForm/File.edit.file.js +1 -1
  45. package/lib/cjs/components/form/Form.js +21 -1
  46. package/lib/cjs/components/hidden/Hidden.d.ts +0 -1
  47. package/lib/cjs/components/hidden/Hidden.js +1 -1
  48. package/lib/cjs/components/hidden/editForm/Hidden.edit.display.js +8 -0
  49. package/lib/cjs/components/html/editForm/HTML.edit.display.js +8 -0
  50. package/lib/cjs/components/number/Number.d.ts +11 -0
  51. package/lib/cjs/components/number/Number.js +35 -0
  52. package/lib/cjs/components/number/editForm/Number.edit.display.js +12 -0
  53. package/lib/cjs/components/password/editForm/Password.edit.display.js +12 -0
  54. package/lib/cjs/components/phonenumber/PhoneNumber.form.js +8 -0
  55. package/lib/cjs/components/select/editForm/Select.edit.data.d.ts +1 -1
  56. package/lib/cjs/components/select/editForm/Select.edit.data.js +1 -0
  57. package/lib/cjs/components/tabs/editForm/Tabs.edit.display.js +8 -0
  58. package/lib/cjs/components/textarea/editForm/TextArea.edit.display.js +12 -0
  59. package/lib/cjs/components/url/editForm/Url.edit.display.js +12 -0
  60. package/lib/cjs/components/well/editForm/Well.edit.display.js +8 -0
  61. package/lib/cjs/package.json +1 -1
  62. package/lib/cjs/utils/utils.js +6 -18
  63. package/lib/mjs/Embed.js +37 -24
  64. package/lib/mjs/Form.d.ts +8 -2
  65. package/lib/mjs/Form.js +1 -1
  66. package/lib/mjs/Formio.js +1 -1
  67. package/lib/mjs/PDF.d.ts +1 -0
  68. package/lib/mjs/PDF.js +21 -5
  69. package/lib/mjs/PDFBuilder.d.ts +1 -0
  70. package/lib/mjs/PDFBuilder.js +1 -0
  71. package/lib/mjs/Webform.d.ts +13 -1
  72. package/lib/mjs/Webform.js +75 -24
  73. package/lib/mjs/Wizard.d.ts +1 -1
  74. package/lib/mjs/Wizard.js +12 -6
  75. package/lib/mjs/components/_classes/component/Component.js +0 -65
  76. package/lib/mjs/components/_classes/nested/NestedComponent.form.js +13 -0
  77. package/lib/mjs/components/_classes/nested/NestedComponent.js +4 -0
  78. package/lib/mjs/components/content/editForm/Content.edit.display.js +8 -0
  79. package/lib/mjs/components/currency/editForm/Currency.edit.display.js +20 -0
  80. package/lib/mjs/components/day/editForm/Day.edit.display.js +8 -0
  81. package/lib/mjs/components/editgrid/EditGrid.d.ts +1 -0
  82. package/lib/mjs/components/editgrid/EditGrid.js +22 -2
  83. package/lib/mjs/components/email/editForm/Email.edit.display.js +12 -0
  84. package/lib/mjs/components/fieldset/editForm/Fieldset.edit.display.js +8 -0
  85. package/lib/mjs/components/file/editForm/File.edit.file.js +1 -1
  86. package/lib/mjs/components/form/Form.js +21 -1
  87. package/lib/mjs/components/hidden/Hidden.d.ts +0 -1
  88. package/lib/mjs/components/hidden/Hidden.js +1 -1
  89. package/lib/mjs/components/hidden/editForm/Hidden.edit.display.js +8 -0
  90. package/lib/mjs/components/html/editForm/HTML.edit.display.js +8 -0
  91. package/lib/mjs/components/number/Number.d.ts +11 -0
  92. package/lib/mjs/components/number/Number.js +34 -0
  93. package/lib/mjs/components/number/editForm/Number.edit.display.js +12 -0
  94. package/lib/mjs/components/password/editForm/Password.edit.display.js +12 -0
  95. package/lib/mjs/components/phonenumber/PhoneNumber.form.js +8 -0
  96. package/lib/mjs/components/select/editForm/Select.edit.data.d.ts +1 -1
  97. package/lib/mjs/components/select/editForm/Select.edit.data.js +1 -0
  98. package/lib/mjs/components/tabs/editForm/Tabs.edit.display.js +8 -0
  99. package/lib/mjs/components/textarea/editForm/TextArea.edit.display.js +12 -0
  100. package/lib/mjs/components/url/editForm/Url.edit.display.js +12 -0
  101. package/lib/mjs/components/well/editForm/Well.edit.display.js +8 -0
  102. package/lib/mjs/package.json +1 -1
  103. package/lib/mjs/utils/utils.js +6 -18
  104. package/package.json +1 -1
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) {
@@ -2595,19 +2595,6 @@ export default class Component extends Element {
2595
2595
  return Promise.resolve(editor);
2596
2596
  }
2597
2597
  else {
2598
- // Due to an issue with ckeditor not loading styles in the shadowdom (https://github.com/ckeditor/ckeditor5/issues/15824), we need to copy cke-styles to the shadowdom
2599
- let current = element;
2600
- while (current) {
2601
- if (current instanceof ShadowRoot) {
2602
- const ckeStyles = document.querySelector('style[data-cke="true"]');
2603
- const clone = document.createElement('style');
2604
- clone.setAttribute('data-cke', 'true');
2605
- clone.textContent = ckeStyles.textContent;
2606
- current.prepend(clone);
2607
- break;
2608
- }
2609
- current = current.parentNode || current.host;
2610
- }
2611
2598
  return ClassicEditor.create(element, settings).then((editor) => {
2612
2599
  editor.model.document.on('change', () => onChange(editor.data.get()));
2613
2600
  return editor;
@@ -2634,58 +2621,6 @@ export default class Component extends Element {
2634
2621
  return Promise.reject();
2635
2622
  }
2636
2623
  this.quill = new Quill(element, isIEBrowser ? { ...settings, modules: {} } : settings);
2637
- const root = element.getRootNode();
2638
- if (root instanceof ShadowRoot && root.getSelection) {
2639
- const sel = this.quill.selection;
2640
- // 1. getNativeRange: read selection from shadowRoot instead of document
2641
- sel.getNativeRange = () => {
2642
- const shadowSelection = root.getSelection();
2643
- if (shadowSelection == null || shadowSelection.rangeCount <= 0)
2644
- return null;
2645
- const nativeRange = shadowSelection.getRangeAt(0);
2646
- if (nativeRange == null)
2647
- return null;
2648
- return sel.normalizeNative(nativeRange);
2649
- };
2650
- // 2. hasFocus: check shadowRoot.activeElement instead of document.activeElement
2651
- sel.hasFocus = () => {
2652
- return (root.activeElement === sel.root ||
2653
- (root.activeElement != null && sel.root.contains(root.activeElement)));
2654
- };
2655
- // 3. setNativeRange: use shadowRoot's selection to add/remove ranges
2656
- const origSetNativeRange = sel.setNativeRange.bind(sel);
2657
- sel.setNativeRange = (startNode, startOffset, endNode, endOffset, force) => {
2658
- // Delegate to original logic for the null case (blur)
2659
- if (startNode == null) {
2660
- origSetNativeRange(null);
2661
- return;
2662
- }
2663
- if (!sel.hasFocus()) {
2664
- sel.root.focus({ preventScroll: true });
2665
- }
2666
- const shadowSelection = root.getSelection();
2667
- if (shadowSelection == null)
2668
- return;
2669
- const { native } = sel.getNativeRange() || {};
2670
- endNode = endNode ?? startNode;
2671
- endOffset = endOffset ?? startOffset;
2672
- if (native == null ||
2673
- force ||
2674
- startNode !== native.startContainer ||
2675
- startOffset !== native.startOffset ||
2676
- endNode !== native.endContainer ||
2677
- endOffset !== native.endOffset) {
2678
- const range = document.createRange();
2679
- range.setStart(startNode, startOffset);
2680
- range.setEnd(endNode, endOffset);
2681
- shadowSelection.removeAllRanges();
2682
- shadowSelection.addRange(range);
2683
- }
2684
- };
2685
- document.addEventListener('selectionchange', () => {
2686
- sel.update();
2687
- });
2688
- }
2689
2624
  /** This block of code adds the [source] capabilities. See https://codepen.io/anon/pen/ZyEjrQ */
2690
2625
  const txtArea = document.createElement('textarea');
2691
2626
  txtArea.setAttribute('class', 'quill-source-code');
@@ -6,6 +6,19 @@ import Components from '../../Components';
6
6
  */
7
7
  export default function (...extend) {
8
8
  return Components.baseEditForm([
9
+ {
10
+ key: 'display',
11
+ components: [
12
+ {
13
+ key: 'labelWidth',
14
+ ignore: true,
15
+ },
16
+ {
17
+ key: 'labelMargin',
18
+ ignore: true,
19
+ },
20
+ ],
21
+ },
9
22
  {
10
23
  key: 'data',
11
24
  ignore: true,
@@ -826,6 +826,10 @@ export default class NestedComponent extends Field {
826
826
  // if no value is provided and noDefault flag, set empty value
827
827
  else if (flags.noDefault && component.allowData) {
828
828
  flags.resetValue = true;
829
+ // Preserve the live value instead of wiping it, so client-side calculations that read it don't blank out.
830
+ if (component.component.persistent === 'client-only') {
831
+ return false;
832
+ }
829
833
  return component.setValue(component.emptyValue, flags);
830
834
  }
831
835
  else if ((!this.rootPristine || component.visible) &&
@@ -3,6 +3,14 @@ export default [
3
3
  key: 'labelPosition',
4
4
  ignore: true,
5
5
  },
6
+ {
7
+ key: 'labelWidth',
8
+ ignore: true,
9
+ },
10
+ {
11
+ key: 'labelMargin',
12
+ ignore: true,
13
+ },
6
14
  {
7
15
  key: 'placeholder',
8
16
  ignore: true,
@@ -3,10 +3,30 @@ export default [
3
3
  key: 'inputMask',
4
4
  ignore: true,
5
5
  },
6
+ {
7
+ key: 'widget.type',
8
+ ignore: true,
9
+ },
10
+ {
11
+ key: 'widget',
12
+ ignore: true,
13
+ },
14
+ {
15
+ key: 'inputMasks',
16
+ ignore: true,
17
+ },
6
18
  {
7
19
  key: 'allowMultipleMasks',
8
20
  ignore: true,
9
21
  },
22
+ {
23
+ key: 'labelWidth',
24
+ ignore: true,
25
+ },
26
+ {
27
+ key: 'labelMargin',
28
+ ignore: true,
29
+ },
10
30
  {
11
31
  key: 'showWordCount',
12
32
  ignore: true,
@@ -3,6 +3,14 @@ export default [
3
3
  key: 'labelPosition',
4
4
  ignore: true,
5
5
  },
6
+ {
7
+ key: 'labelWidth',
8
+ ignore: true,
9
+ },
10
+ {
11
+ key: 'labelMargin',
12
+ ignore: true,
13
+ },
6
14
  {
7
15
  weight: 15,
8
16
  type: 'checkbox',
@@ -48,6 +48,7 @@ export default class EditGridComponent extends NestedArrayComponent {
48
48
  setVariableTypeComponents(): void;
49
49
  variableTypeComponentsIndexes: any[] | undefined;
50
50
  isOpen(editRow: any): boolean;
51
+ shouldBlockUnsavedOpenRows(): boolean;
51
52
  isComponentVisibleInSomeRow(component: any): any;
52
53
  visibleInHeader: any;
53
54
  flattenRowDataValue(dataValue: any): {};
@@ -310,6 +310,24 @@ export default class EditGridComponent extends NestedArrayComponent {
310
310
  isOpen(editRow) {
311
311
  return [EditRowState.New, EditRowState.Editing, EditRowState.Viewing].includes(editRow.state);
312
312
  }
313
+ shouldBlockUnsavedOpenRows() {
314
+ const openRows = this.editRows.filter((editRow) => this.isOpen(editRow));
315
+ if (!openRows.length) {
316
+ return false;
317
+ }
318
+ const isOnlyPristineOpenWhenEmptyRow = this.component.openWhenEmpty &&
319
+ openRows.length === 1 &&
320
+ openRows.every((row) => row.state === EditRowState.New) &&
321
+ openRows.every((row) => {
322
+ let isPristine = true;
323
+ eachComponent(row.components, (comp) => {
324
+ isPristine =
325
+ comp.type === 'form' ? _.isEqual(comp.emptyValue, comp.dataValue) : comp.pristine;
326
+ });
327
+ return isPristine;
328
+ });
329
+ return !isOnlyPristineOpenWhenEmptyRow;
330
+ }
313
331
  isComponentVisibleInSomeRow(component) {
314
332
  const rows = this.editRows;
315
333
  const savedStates = [EditRowState.Saved, EditRowState.Editing, EditRowState.Draft];
@@ -1070,7 +1088,7 @@ export default class EditGridComponent extends NestedArrayComponent {
1070
1088
  if (valid === null) {
1071
1089
  editRow.errors.push({
1072
1090
  type: 'error',
1073
- message: this.t('componentInvalidRowValidation', { componentKey: this.key })
1091
+ message: this.t('componentInvalidRowValidation', { componentKey: this.key }),
1074
1092
  });
1075
1093
  }
1076
1094
  }
@@ -1142,7 +1160,9 @@ export default class EditGridComponent extends NestedArrayComponent {
1142
1160
  }
1143
1161
  return false;
1144
1162
  }
1145
- else if (rowsEditing && this.saveEditMode && !this.component.openWhenEmpty) {
1163
+ else if (rowsEditing &&
1164
+ this.saveEditMode &&
1165
+ (this.shouldBlockUnsavedOpenRows() || this.errors.length)) {
1146
1166
  this._errors = this.setCustomValidity(this.t(this.errorMessage('unsavedRowsError')), dirty);
1147
1167
  errors.push(...this._errors);
1148
1168
  return false;
@@ -7,6 +7,18 @@ export default [
7
7
  key: 'allowMultipleMasks',
8
8
  ignore: true,
9
9
  },
10
+ {
11
+ key: 'inputMasks',
12
+ ignore: true,
13
+ },
14
+ {
15
+ key: 'widget.type',
16
+ ignore: true,
17
+ },
18
+ {
19
+ key: 'widget',
20
+ ignore: true,
21
+ },
10
22
  {
11
23
  key: 'showWordCount',
12
24
  ignore: true,
@@ -3,6 +3,14 @@ export default [
3
3
  key: 'labelPosition',
4
4
  ignore: true,
5
5
  },
6
+ {
7
+ key: 'labelWidth',
8
+ ignore: true,
9
+ },
10
+ {
11
+ key: 'labelMargin',
12
+ ignore: true,
13
+ },
6
14
  {
7
15
  key: 'placeholder',
8
16
  ignore: true,
@@ -290,7 +290,7 @@ export default [
290
290
  key: 'imageSize',
291
291
  label: 'Image Size',
292
292
  placeholder: '100',
293
- tooltip: 'The image size, in square pixels, for previewing images.',
293
+ tooltip: 'The image size, in square pixels, for previewing images. If explicitly set, the frame of the component must be large enough to allow for the specified size.',
294
294
  weight: 40,
295
295
  clearOnHide: false,
296
296
  conditional: {
@@ -269,7 +269,7 @@ export default class FormComponent extends Component {
269
269
  return super.attach(element);
270
270
  }
271
271
  return super.attach(element).then(() => {
272
- if (this.isSubFormLazyLoad() && !this.hasLoadedForm && !this.subFormLoading) {
272
+ if (this.isSubFormLazyLoad() && !this.subForm && !this.subFormLoading) {
273
273
  this.createSubForm(true);
274
274
  }
275
275
  if (!this.subFormReady) {
@@ -366,6 +366,26 @@ export default class FormComponent extends Component {
366
366
  }
367
367
  return super.redraw();
368
368
  }
369
+ /**
370
+ * Get Row Index. Declared so the setter below does not shadow the inherited getter.
371
+ * @returns {number} - The row index.
372
+ */
373
+ get rowIndex() {
374
+ return super.rowIndex;
375
+ }
376
+ /**
377
+ * Set Row Index to row and pass it down to the subform so that its components
378
+ * regenerate their paths. Unlike NestedComponent, the subform is not part of
379
+ * this.components, so the row index has to be forwarded explicitly.
380
+ * @param {number} value - The row index.
381
+ * @returns {void}
382
+ */
383
+ set rowIndex(value) {
384
+ super.rowIndex = value;
385
+ if (this.subForm) {
386
+ this.subForm.rowIndex = value;
387
+ }
388
+ }
369
389
  /**
370
390
  * Pass everyComponent to subform.
371
391
  * @param {any[]} args - Arguments to pass through to the subform's everyComponent method.
@@ -10,7 +10,6 @@ export default class HiddenComponent extends Input {
10
10
  };
11
11
  get inputInfo(): any;
12
12
  labelIsHidden(): boolean;
13
- get emptyValue(): string;
14
13
  setValue(value: any, flags?: {}): boolean;
15
14
  }
16
15
  import Input from '../_classes/input/Input';
@@ -43,7 +43,7 @@ export default class HiddenComponent extends Input {
43
43
  return true;
44
44
  }
45
45
  get emptyValue() {
46
- return '';
46
+ return null;
47
47
  }
48
48
  setValue(value, flags = {}) {
49
49
  return this.updateValue(value, flags);