@formio/js 5.6.1 → 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.
- package/dist/formio.builder.css +5 -5
- package/dist/formio.builder.min.css +1 -1
- package/dist/formio.embed.js +1 -1
- package/dist/formio.embed.min.js +1 -1
- package/dist/formio.embed.min.js.LICENSE.txt +1 -1
- package/dist/formio.form.css +5 -5
- package/dist/formio.form.js +5212 -5185
- package/dist/formio.form.min.css +1 -1
- package/dist/formio.form.min.js +1 -1
- package/dist/formio.form.min.js.LICENSE.txt +3 -1
- package/dist/formio.full.css +5 -5
- package/dist/formio.full.js +6063 -6036
- package/dist/formio.full.min.css +1 -1
- package/dist/formio.full.min.js +1 -1
- package/dist/formio.full.min.js.LICENSE.txt +3 -1
- package/dist/formio.js +1211 -1184
- package/dist/formio.min.js +1 -1
- package/dist/formio.min.js.LICENSE.txt +3 -1
- package/dist/formio.utils.js +1102 -1075
- package/dist/formio.utils.min.js +1 -1
- package/dist/formio.utils.min.js.LICENSE.txt +3 -1
- package/lib/cjs/Embed.js +38 -25
- package/lib/cjs/Form.d.ts +8 -2
- package/lib/cjs/Form.js +1 -1
- package/lib/cjs/Formio.js +1 -1
- package/lib/cjs/PDF.d.ts +1 -0
- package/lib/cjs/PDF.js +21 -5
- package/lib/cjs/PDFBuilder.d.ts +1 -0
- package/lib/cjs/PDFBuilder.js +1 -0
- package/lib/cjs/Webform.d.ts +13 -1
- package/lib/cjs/Webform.js +79 -26
- package/lib/cjs/Wizard.d.ts +1 -1
- package/lib/cjs/Wizard.js +12 -6
- package/lib/cjs/components/_classes/component/Component.js +0 -65
- package/lib/cjs/components/_classes/nested/NestedComponent.form.js +13 -0
- package/lib/cjs/components/_classes/nested/NestedComponent.js +4 -0
- package/lib/cjs/components/content/editForm/Content.edit.display.js +8 -0
- package/lib/cjs/components/currency/editForm/Currency.edit.display.js +20 -0
- package/lib/cjs/components/day/editForm/Day.edit.display.js +8 -0
- package/lib/cjs/components/editgrid/EditGrid.d.ts +1 -0
- package/lib/cjs/components/editgrid/EditGrid.js +22 -2
- package/lib/cjs/components/email/editForm/Email.edit.display.js +12 -0
- package/lib/cjs/components/fieldset/editForm/Fieldset.edit.display.js +8 -0
- package/lib/cjs/components/file/editForm/File.edit.file.js +1 -1
- package/lib/cjs/components/form/Form.js +21 -1
- package/lib/cjs/components/hidden/Hidden.d.ts +0 -1
- package/lib/cjs/components/hidden/Hidden.js +1 -1
- package/lib/cjs/components/hidden/editForm/Hidden.edit.display.js +8 -0
- package/lib/cjs/components/html/editForm/HTML.edit.display.js +8 -0
- package/lib/cjs/components/number/Number.d.ts +11 -0
- package/lib/cjs/components/number/Number.js +35 -0
- package/lib/cjs/components/number/editForm/Number.edit.display.js +12 -0
- package/lib/cjs/components/password/editForm/Password.edit.display.js +12 -0
- package/lib/cjs/components/phonenumber/PhoneNumber.form.js +8 -0
- package/lib/cjs/components/select/editForm/Select.edit.data.d.ts +1 -1
- package/lib/cjs/components/select/editForm/Select.edit.data.js +1 -0
- package/lib/cjs/components/tabs/editForm/Tabs.edit.display.js +8 -0
- package/lib/cjs/components/textarea/editForm/TextArea.edit.display.js +12 -0
- package/lib/cjs/components/url/editForm/Url.edit.display.js +12 -0
- package/lib/cjs/components/well/editForm/Well.edit.display.js +8 -0
- package/lib/cjs/package.json +1 -1
- package/lib/cjs/utils/utils.js +6 -18
- package/lib/mjs/Embed.js +37 -24
- package/lib/mjs/Form.d.ts +8 -2
- package/lib/mjs/Form.js +1 -1
- package/lib/mjs/Formio.js +1 -1
- package/lib/mjs/PDF.d.ts +1 -0
- package/lib/mjs/PDF.js +21 -5
- package/lib/mjs/PDFBuilder.d.ts +1 -0
- package/lib/mjs/PDFBuilder.js +1 -0
- package/lib/mjs/Webform.d.ts +13 -1
- package/lib/mjs/Webform.js +75 -24
- package/lib/mjs/Wizard.d.ts +1 -1
- package/lib/mjs/Wizard.js +12 -6
- package/lib/mjs/components/_classes/component/Component.js +0 -65
- package/lib/mjs/components/_classes/nested/NestedComponent.form.js +13 -0
- package/lib/mjs/components/_classes/nested/NestedComponent.js +4 -0
- package/lib/mjs/components/content/editForm/Content.edit.display.js +8 -0
- package/lib/mjs/components/currency/editForm/Currency.edit.display.js +20 -0
- package/lib/mjs/components/day/editForm/Day.edit.display.js +8 -0
- package/lib/mjs/components/editgrid/EditGrid.d.ts +1 -0
- package/lib/mjs/components/editgrid/EditGrid.js +22 -2
- package/lib/mjs/components/email/editForm/Email.edit.display.js +12 -0
- package/lib/mjs/components/fieldset/editForm/Fieldset.edit.display.js +8 -0
- package/lib/mjs/components/file/editForm/File.edit.file.js +1 -1
- package/lib/mjs/components/form/Form.js +21 -1
- package/lib/mjs/components/hidden/Hidden.d.ts +0 -1
- package/lib/mjs/components/hidden/Hidden.js +1 -1
- package/lib/mjs/components/hidden/editForm/Hidden.edit.display.js +8 -0
- package/lib/mjs/components/html/editForm/HTML.edit.display.js +8 -0
- package/lib/mjs/components/number/Number.d.ts +11 -0
- package/lib/mjs/components/number/Number.js +34 -0
- package/lib/mjs/components/number/editForm/Number.edit.display.js +12 -0
- package/lib/mjs/components/password/editForm/Password.edit.display.js +12 -0
- package/lib/mjs/components/phonenumber/PhoneNumber.form.js +8 -0
- package/lib/mjs/components/select/editForm/Select.edit.data.d.ts +1 -1
- package/lib/mjs/components/select/editForm/Select.edit.data.js +1 -0
- package/lib/mjs/components/tabs/editForm/Tabs.edit.display.js +8 -0
- package/lib/mjs/components/textarea/editForm/TextArea.edit.display.js +12 -0
- package/lib/mjs/components/url/editForm/Url.edit.display.js +12 -0
- package/lib/mjs/components/well/editForm/Well.edit.display.js +8 -0
- package/lib/mjs/package.json +1 -1
- package/lib/mjs/utils/utils.js +6 -18
- 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
|
-
|
|
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 {
|
package/lib/mjs/PDFBuilder.d.ts
CHANGED
package/lib/mjs/PDFBuilder.js
CHANGED
package/lib/mjs/Webform.d.ts
CHANGED
|
@@ -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
|
*/
|
package/lib/mjs/Webform.js
CHANGED
|
@@ -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
|
-
|
|
984
|
-
|
|
985
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
1066
|
+
const errorsByComponent = new Map();
|
|
1045
1067
|
errors.forEach((err) => {
|
|
1046
1068
|
const path = getStringFromComponentPath(err.path) || err.context?.path || err.component?.key;
|
|
1047
|
-
|
|
1048
|
-
|
|
1069
|
+
const component = path && this.getErrorComponent(path, err.component?.key);
|
|
1070
|
+
if (!component) {
|
|
1071
|
+
return;
|
|
1049
1072
|
}
|
|
1050
|
-
|
|
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
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
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
|
-
|
|
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
|
}
|
package/lib/mjs/Wizard.d.ts
CHANGED
|
@@ -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
|
-
{
|
|
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.
|
|
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,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,
|
|
@@ -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 &&
|
|
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,
|
|
@@ -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.
|
|
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.
|