@formio/js 5.1.0-dev.6152.95f2394 → 5.1.0-dev.6155.76252ef

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 (43) hide show
  1. package/dist/formio.builder.css +1 -0
  2. package/dist/formio.builder.min.css +1 -1
  3. package/dist/formio.form.css +1 -0
  4. package/dist/formio.form.js +9 -9
  5. package/dist/formio.form.min.css +1 -1
  6. package/dist/formio.form.min.js +1 -1
  7. package/dist/formio.full.css +1 -0
  8. package/dist/formio.full.js +11 -11
  9. package/dist/formio.full.min.css +1 -1
  10. package/dist/formio.full.min.js +1 -1
  11. package/dist/formio.js +4 -4
  12. package/dist/formio.min.js +1 -1
  13. package/dist/formio.utils.js +5 -5
  14. package/dist/formio.utils.min.js +1 -1
  15. package/lib/cjs/PDFBuilder.d.ts +1 -0
  16. package/lib/cjs/PDFBuilder.js +8 -8
  17. package/lib/cjs/WebformBuilder.d.ts +1 -1
  18. package/lib/cjs/WebformBuilder.js +36 -11
  19. package/lib/cjs/components/button/Button.d.ts +1 -1
  20. package/lib/cjs/components/button/Button.js +4 -4
  21. package/lib/cjs/components/radio/Radio.d.ts +8 -0
  22. package/lib/cjs/components/radio/Radio.js +15 -5
  23. package/lib/cjs/components/select/Select.d.ts +1 -0
  24. package/lib/cjs/components/select/Select.js +18 -2
  25. package/lib/cjs/components/selectboxes/SelectBoxes.d.ts +6 -0
  26. package/lib/cjs/translations/en.d.ts +1 -0
  27. package/lib/cjs/translations/en.js +1 -1
  28. package/lib/cjs/utils/utils.js +14 -3
  29. package/lib/mjs/PDFBuilder.d.ts +1 -0
  30. package/lib/mjs/PDFBuilder.js +8 -8
  31. package/lib/mjs/WebformBuilder.d.ts +1 -1
  32. package/lib/mjs/WebformBuilder.js +36 -12
  33. package/lib/mjs/components/button/Button.d.ts +1 -1
  34. package/lib/mjs/components/button/Button.js +1 -1
  35. package/lib/mjs/components/radio/Radio.d.ts +8 -0
  36. package/lib/mjs/components/radio/Radio.js +15 -5
  37. package/lib/mjs/components/select/Select.d.ts +1 -0
  38. package/lib/mjs/components/select/Select.js +18 -2
  39. package/lib/mjs/components/selectboxes/SelectBoxes.d.ts +6 -0
  40. package/lib/mjs/translations/en.d.ts +1 -0
  41. package/lib/mjs/translations/en.js +1 -0
  42. package/lib/mjs/utils/utils.js +14 -2
  43. package/package.json +2 -2
@@ -51,5 +51,6 @@ export default class PDFBuilder extends WebformBuilder {
51
51
  onDropzoneDrop(e: any): boolean;
52
52
  dropEvent: any;
53
53
  onDragEnd(e: any): void;
54
+ repeatablePathsComps: any[] | undefined;
54
55
  }
55
56
  import WebformBuilder from './WebformBuilder';
@@ -451,21 +451,21 @@ class PDFBuilder extends WebformBuilder_1.default {
451
451
  e.target.style.cursor = 'default';
452
452
  }
453
453
  highlightInvalidComponents() {
454
- const repeatablePaths = this.findRepeatablePaths();
454
+ const repeatablePathsComps = this.findComponentsWithRepeatablePaths();
455
455
  // update elements which path was duplicated if any pathes have been changed
456
- if (!lodash_1.default.isEqual(this.repeatablePaths, repeatablePaths)) {
457
- (0, utils_1.eachComponent)(this.webform.getComponents(), (comp, path) => {
458
- if (this.repeatablePaths.includes(path)) {
456
+ if (!lodash_1.default.isEqual(this.repeatablePathsComps, repeatablePathsComps)) {
457
+ (0, utils_1.eachComponent)(this.webform.getComponents(), (comp) => {
458
+ if (this.repeatablePathsComps.includes(comp.component)) {
459
459
  this.webform.postMessage({ name: 'updateElement', data: comp.component });
460
460
  }
461
461
  });
462
- this.repeatablePaths = repeatablePaths;
462
+ this.repeatablePathsComps = repeatablePathsComps;
463
463
  }
464
- if (!repeatablePaths.length) {
464
+ if (!repeatablePathsComps.length) {
465
465
  return;
466
466
  }
467
- (0, utils_1.eachComponent)(this.webform.getComponents(), (comp, path) => {
468
- if (this.repeatablePaths.includes(path)) {
467
+ (0, utils_1.eachComponent)(this.webform.getComponents(), (comp) => {
468
+ if (this.repeatablePathsComps.includes(comp)) {
469
469
  this.webform.postMessage({
470
470
  name: 'showBuilderErrors',
471
471
  data: {
@@ -78,7 +78,7 @@ export default class WebformBuilder extends Component {
78
78
  replaceDoubleQuotes(data: any, fieldsToRemoveDoubleQuotes?: any[]): any;
79
79
  updateComponent(component: any, changed: any): void;
80
80
  originalDefaultValue: any;
81
- findRepeatablePaths(): any[];
81
+ findComponentsWithRepeatablePaths(): any[];
82
82
  highlightInvalidComponents(): void;
83
83
  /**
84
84
  * Called when a new component is saved.
@@ -1125,28 +1125,53 @@ class WebformBuilder extends Component_1.default {
1125
1125
  // Called when we update a component.
1126
1126
  this.emit('updateComponent', component);
1127
1127
  }
1128
- findRepeatablePaths() {
1129
- const repeatablePaths = [];
1128
+ findComponentsWithRepeatablePaths() {
1129
+ const repeatablePaths = {};
1130
1130
  const keys = new Map();
1131
1131
  (0, utils_1.eachComponent)(this.form.components, (comp, path, components, parent, paths) => {
1132
- if (keys.has(paths.dataPath)) {
1133
- repeatablePaths.push(paths.dataPath);
1134
- }
1135
- else {
1136
- keys.set(paths.dataPath, true);
1132
+ var _a, _b;
1133
+ const isRadioCheckbox = comp.type === 'checkbox' && comp.inputType === 'radio';
1134
+ const isLayout = (0, utils_1.componentInfo)(comp).layout;
1135
+ if (!isLayout) {
1136
+ if (keys.has(paths.dataPath)) {
1137
+ const onlyRadioCheckboxes = ((_a = repeatablePaths[paths.dataPath]) === null || _a === void 0 ? void 0 : _a.onlyRadioCheckboxes) === false ? false : isRadioCheckbox;
1138
+ repeatablePaths[paths.dataPath] = {
1139
+ comps: [...(((_b = repeatablePaths[paths.dataPath]) === null || _b === void 0 ? void 0 : _b.comps) || []), keys.get(paths.dataPath), comp],
1140
+ onlyRadioCheckboxes,
1141
+ };
1142
+ }
1143
+ else {
1144
+ keys.set(paths.dataPath, comp);
1145
+ }
1137
1146
  }
1138
1147
  }, true);
1139
- return repeatablePaths;
1148
+ const componentsWithRepeatablePaths = [];
1149
+ Object.keys(repeatablePaths).forEach((path) => {
1150
+ const { comps, onlyRadioCheckboxes } = repeatablePaths[path];
1151
+ if (!onlyRadioCheckboxes) {
1152
+ componentsWithRepeatablePaths.push(...comps);
1153
+ }
1154
+ });
1155
+ return componentsWithRepeatablePaths;
1140
1156
  }
1141
1157
  highlightInvalidComponents() {
1142
- const repeatablePaths = this.findRepeatablePaths();
1158
+ const repeatablePathsComps = this.findComponentsWithRepeatablePaths();
1143
1159
  let hasInvalidComponents = false;
1160
+ // Matches anything expect letters and '_' at the beginning of the key and anything except of letters, numbers,
1161
+ // '-', '.' and '_' in the rest of the key
1162
+ const badCharacters = /^[^A-Za-z_]+|[^A-Za-z0-9\-._]+/g;
1144
1163
  this.webform.everyComponent((comp) => {
1145
- const path = comp.path;
1146
- if (repeatablePaths.includes(path)) {
1164
+ if (repeatablePathsComps.includes(comp.component)) {
1147
1165
  comp.setCustomValidity(this.t('apiKey', { key: comp.key }));
1148
1166
  hasInvalidComponents = true;
1149
1167
  }
1168
+ else if (comp.key.replace(badCharacters, '') === '') {
1169
+ comp.setCustomValidity(this.t('apiKeyNotValid', { key: comp.key }));
1170
+ hasInvalidComponents = true;
1171
+ }
1172
+ else {
1173
+ comp.setCustomValidity();
1174
+ }
1150
1175
  });
1151
1176
  this.emit('builderFormValidityChange', hasInvalidComponents);
1152
1177
  }
@@ -7,7 +7,7 @@ export default class ButtonComponent extends Field {
7
7
  weight: number;
8
8
  schema: any;
9
9
  };
10
- static savedValueTypes(schema: any): any;
10
+ static savedValueTypes(schema: any): string[];
11
11
  constructor(component: any, options: any, data: any);
12
12
  filesUploading: number;
13
13
  get inputInfo(): any;
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const lodash_1 = __importDefault(require("lodash"));
7
7
  const Field_1 = __importDefault(require("../_classes/field/Field"));
8
8
  const Input_1 = __importDefault(require("../_classes/input/Input"));
9
- const formUtils_1 = require("../../utils/formUtils");
9
+ const utils_1 = require("../../utils");
10
10
  class ButtonComponent extends Field_1.default {
11
11
  static schema(...extend) {
12
12
  return Input_1.default.schema({
@@ -35,7 +35,7 @@ class ButtonComponent extends Field_1.default {
35
35
  };
36
36
  }
37
37
  static savedValueTypes(schema) {
38
- return (0, formUtils_1.getComponentSavedTypes)(schema) || [formUtils_1.componentValueTypes.boolean];
38
+ return (0, utils_1.getComponentSavedTypes)(schema) || [utils_1.componentValueTypes.boolean];
39
39
  }
40
40
  constructor(component, options, data) {
41
41
  super(component, options, data);
@@ -306,7 +306,7 @@ class ButtonComponent extends Field_1.default {
306
306
  const form = this.getRoot();
307
307
  const flattened = {};
308
308
  const components = {};
309
- (0, formUtils_1.eachComponent)(form.components, (componentWrapper, path) => {
309
+ (0, utils_1.eachComponent)(form.components, (componentWrapper, path) => {
310
310
  const component = componentWrapper.component || componentWrapper;
311
311
  flattened[path] = component;
312
312
  components[component.key] = component;
@@ -447,7 +447,7 @@ class ButtonComponent extends Field_1.default {
447
447
  }, 100);
448
448
  }
449
449
  get oauthComponentPath() {
450
- const pathArray = (0, formUtils_1.getArrayFromComponentPath)(this.path);
450
+ const pathArray = (0, utils_1.getArrayFromComponentPath)(this.path);
451
451
  return lodash_1.default.chain(pathArray).filter(pathPart => !lodash_1.default.isNumber(pathPart)).join('.').value();
452
452
  }
453
453
  focus() {
@@ -28,6 +28,7 @@ export default class RadioComponent extends ListComponent {
28
28
  optionsLoaded: boolean | undefined;
29
29
  loadedOptions: any[] | undefined;
30
30
  beforeSubmit(): Promise<any>;
31
+ convertValues(values: any): any;
31
32
  render(): string;
32
33
  attach(element: any): Promise<void>;
33
34
  detach(element: any): void;
@@ -41,5 +42,12 @@ export default class RadioComponent extends ListComponent {
41
42
  setSelectedClasses(): void;
42
43
  updateValue(value: any, flags: any): boolean;
43
44
  currentValue: any;
45
+ /**
46
+ * Normalize values coming into updateValue. For example, depending on the configuration, string value `"true"` will be normalized to boolean `true`.
47
+ * @param {*} value - The value to normalize
48
+ * @returns {*} - Returns the normalized value
49
+ */
50
+ convertByDataType(value: any): any;
51
+ normalizeValue(value: any): any;
44
52
  }
45
53
  import ListComponent from '../_classes/list/ListComponent';
@@ -153,6 +153,12 @@ class RadioComponent extends ListComponent_1.default {
153
153
  this.dataReady.then(() => res(true));
154
154
  });
155
155
  }
156
+ convertValues(values) {
157
+ if (this.options.renderMode === 'html' && this.type === 'radio') {
158
+ return values.map(x => (Object.assign(Object.assign({}, x), { value: this.convertByDataType(x.value) })));
159
+ }
160
+ return values;
161
+ }
156
162
  render() {
157
163
  if (!this.optionsLoaded) {
158
164
  return super.render(this.renderTemplate('loader'));
@@ -160,7 +166,7 @@ class RadioComponent extends ListComponent_1.default {
160
166
  return super.render(this.renderTemplate('radio', {
161
167
  input: this.inputInfo,
162
168
  inline: this.component.inline,
163
- values: this.component.dataSrc === 'values' ? this.component.values : this.loadedOptions,
169
+ values: this.component.dataSrc === 'values' ? this.convertValues(this.component.values) : this.loadedOptions,
164
170
  value: this.dataValue,
165
171
  row: this.row,
166
172
  }));
@@ -406,7 +412,7 @@ class RadioComponent extends ListComponent_1.default {
406
412
  * @param {*} value - The value to normalize
407
413
  * @returns {*} - Returns the normalized value
408
414
  */
409
- normalizeValue(value) {
415
+ convertByDataType(value) {
410
416
  const dataType = this.component.dataType || 'auto';
411
417
  if (value === this.emptyValue) {
412
418
  return value;
@@ -438,14 +444,18 @@ class RadioComponent extends ListComponent_1.default {
438
444
  value = !(!value || value.toString() === 'false');
439
445
  break;
440
446
  }
441
- if (this.isSelectURL && this.templateData && this.templateData[value]) {
447
+ return value;
448
+ }
449
+ normalizeValue(value) {
450
+ const valueConverted = this.convertByDataType(value);
451
+ if (this.isSelectURL && this.templateData && this.templateData[valueConverted]) {
442
452
  const submission = this.root.submission;
443
453
  if (!submission.metadata.selectData) {
444
454
  submission.metadata.selectData = {};
445
455
  }
446
- lodash_1.default.set(submission.metadata.selectData, this.path, this.templateData[value]);
456
+ lodash_1.default.set(submission.metadata.selectData, this.path, this.templateData[valueConverted]);
447
457
  }
448
- return super.normalizeValue(value);
458
+ return super.normalizeValue(valueConverted);
449
459
  }
450
460
  }
451
461
  exports.default = RadioComponent;
@@ -89,6 +89,7 @@ export default class SelectComponent extends ListComponent {
89
89
  disableInfiniteScroll(): void;
90
90
  set serverCount(value: any);
91
91
  get serverCount(): any;
92
+ shouldResetChoicesItems(items: any): boolean;
92
93
  setItems(items: any, fromSearch: any): void;
93
94
  selectItems: any;
94
95
  set downloadedResources(value: any);
@@ -355,6 +355,18 @@ class SelectComponent extends ListComponent_1.default {
355
355
  this.downloadedResources.serverCount = this.downloadedResources.length;
356
356
  this.serverCount = this.downloadedResources.length;
357
357
  }
358
+ shouldResetChoicesItems(items) {
359
+ if (this.choices._store.choices.length !== items.length) {
360
+ return true;
361
+ }
362
+ for (let item of items) {
363
+ const choicesItem = this.choices._store.choices.find((i) => i.label === item.label);
364
+ if (!choicesItem) {
365
+ return true;
366
+ }
367
+ }
368
+ return false;
369
+ }
358
370
  /* eslint-disable max-statements */
359
371
  setItems(items, fromSearch) {
360
372
  var _a, _b;
@@ -441,7 +453,7 @@ class SelectComponent extends ListComponent_1.default {
441
453
  this.addOption(itemValueAndLabel.value, itemValueAndLabel.label, {}, lodash_1.default.get(item, this.component.idPath, String(index)));
442
454
  });
443
455
  if (this.choices) {
444
- this.choices.setChoices(this.selectOptions, 'value', 'label', true);
456
+ this.choices.setChoices(this.selectOptions, 'value', 'label', true, true, !fromSearch && this.shouldResetChoicesItems(this.selectOptions));
445
457
  }
446
458
  else if (this.loading) {
447
459
  // Re-attach select input.
@@ -903,8 +915,9 @@ class SelectComponent extends ListComponent_1.default {
903
915
  });
904
916
  }
905
917
  // Add value options.
918
+ const value = this.undoValueTyping(this.dataValue);
906
919
  this.addValueOptions();
907
- this.setChoicesValue(this.dataValue);
920
+ this.setChoicesValue(value);
908
921
  if (this.isSelectResource && this.refs.addResource) {
909
922
  this.addEventListener(this.refs.addResource, 'click', (event) => {
910
923
  event.preventDefault();
@@ -1283,6 +1296,9 @@ class SelectComponent extends ListComponent_1.default {
1283
1296
  this.lazyLoadInit = true;
1284
1297
  const searchProperty = this.component.searchField || this.component.valueProperty;
1285
1298
  this.triggerUpdate(lodash_1.default.get(value.data || value, searchProperty, value), true);
1299
+ this.itemsLoaded.then(() => {
1300
+ this.setChoicesValue(value, hasPreviousValue, flags);
1301
+ });
1286
1302
  return changed;
1287
1303
  }
1288
1304
  // Add the value options.
@@ -8,6 +8,12 @@ export default class SelectBoxesComponent extends RadioComponent {
8
8
  * @returns {boolean} - If the value is empty.
9
9
  */
10
10
  isEmpty(value?: any): boolean;
11
+ /**
12
+ * Normalize values coming into updateValue.
13
+ * @param {any} value - The value to normalize.
14
+ * @returns {*} - The normalized value
15
+ */
16
+ normalizeValue(value: any): any;
11
17
  setInputsDisabled(value: any, onlyUnchecked: any): void;
12
18
  checkComponentValidity(data: any, dirty: any, rowData: any, options: any, errors?: any[]): boolean;
13
19
  }
@@ -70,6 +70,7 @@ declare const _default: {
70
70
  reCaptchaTokenValidationError: string;
71
71
  reCaptchaTokenNotSpecifiedError: string;
72
72
  apiKey: string;
73
+ apiKeyNotValid: string;
73
74
  typeRemaining: string;
74
75
  typeCount: string;
75
76
  requiredDayField: string;
@@ -5,6 +5,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  var _a;
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  const bootstrap_1 = __importDefault(require("@formio/bootstrap"));
8
- exports.default = Object.assign(Object.assign({}, (((_a = bootstrap_1.default === null || bootstrap_1.default === void 0 ? void 0 : bootstrap_1.default.translations) === null || _a === void 0 ? void 0 : _a.en) || {})), { unsavedRowsError: 'Please save all rows before proceeding.', invalidRowsError: 'Please correct invalid rows before proceeding.', invalidRowError: 'Invalid row. Please correct it or delete.', invalidOption: '{{field}} is an invalid value.', invalidDay: '{{field}} is not a valid day.', complete: 'Submission Complete', error: 'Please fix the following errors before submitting.', errorListHotkey: 'Press Ctrl + Alt + X to go back to the error list.', errorsListNavigationMessage: 'Click to navigate to the field with following error.', submitError: 'Please check the form and correct all errors before submitting.', required: '{{field}} is required', unique: '{{field}} must be unique', array: '{{field}} must be an array', array_nonempty: '{{field}} must be a non-empty array', nonarray: '{{field}} must not be an array', select: '{{field}} contains an invalid selection', pattern: '{{field}} does not match the pattern {{pattern}}', minLength: '{{field}} must have at least {{length}} characters.', maxLength: '{{field}} must have no more than {{length}} characters.', minWords: '{{field}} must have at least {{length}} words.', maxWords: '{{field}} must have no more than {{length}} words.', min: '{{field}} cannot be less than {{min}}.', max: '{{field}} cannot be greater than {{max}}.', maxDate: '{{field}} should not contain date after {{maxDate}}', minDate: '{{field}} should not contain date before {{minDate}}', maxYear: '{{field}} should not contain year greater than {{maxYear}}', minYear: '{{field}} should not contain year less than {{minYear}}', minSelectedCount: 'You must select at least {{minCount}} items', maxSelectedCount: 'You may only select up to {{maxCount}} items', invalid_email: '{{field}} must be a valid email.', invalid_url: '{{field}} must be a valid url.', invalid_regex: '{{field}} does not match the pattern {{regex}}.', invalid_date: '{{field}} is not a valid date.', invalid_day: '{{field}} is not a valid day.', invalidValueProperty: 'Invalid Value Property', mask: '{{field}} does not match the mask.', valueIsNotAvailable: '{{ field }} is an invalid value.', stripe: '{{stripe}}', month: 'Month', day: 'Day', year: 'Year', january: 'January', february: 'February', march: 'March', april: 'April', may: 'May', june: 'June', july: 'July', august: 'August', september: 'September', october: 'October', november: 'November', december: 'December', next: 'Next', previous: 'Previous', cancel: 'Cancel', submit: 'Submit Form', confirmCancel: 'Are you sure you want to cancel?', saveDraftInstanceError: 'Cannot save draft because there is no formio instance.', saveDraftAuthError: 'Cannot save draft unless a user is authenticated.', restoreDraftInstanceError: 'Cannot restore draft because there is no formio instance.', saveDraftError: 'Unable to save draft.', restoreDraftError: 'Unable to restore draft.', time: 'Invalid time', cancelButtonAriaLabel: 'Cancel button. Click to reset the form', previousButtonAriaLabel: 'Previous button. Click to go back to the previous tab', nextButtonAriaLabel: 'Next button. Click to go to the next tab', submitButtonAriaLabel: 'Submit Form button. Click to submit the form', reCaptchaTokenValidationError: 'ReCAPTCHA: Token validation error', reCaptchaTokenNotSpecifiedError: 'ReCAPTCHA: Token is not specified in submission', apiKey: 'API Key is not unique: {{key}}', typeRemaining: '{{ remaining }} {{ type }} remaining.', typeCount: '{{ count }} {{ type }}', requiredDayField: '{{ field }} is required', requiredDayEmpty: '{{ field }} is required', requiredMonthField: '{{ field }} is required', requiredYearField: '{{ field }} is required', formNotReady: 'Form not ready. Use form.ready promise', noFormElement: 'No DOM element for form.', notUniqueKey: 'API Key is not unique', newFormSchema: 'Form schema is for a newer version, please upgrade your renderer. Some functionality may not work.', missingUrl: 'Missing URL argument', urlNotAttachedToBtn: 'You should add a URL to this button.', loadingProjectSettingsError: 'Could not load project settings', sessionStorageSupportError: 'Session storage is not supported in this browser.', builderUniqueError: `You cannot add more than one {{componentKeyOrTitle}} component to one page.`, pageNotFound: 'Page not found', noDragInfoError: 'There is no Drag Info available for either dragged or sibling element', addonSupportTypeError: 'Addon {{label}} does not support component of type {{type}}', setPathError: 'Should not be setting the path of a component.', calculatedPathDeprecation: 'component.calculatedPath was deprecated, use component.path instead.', unknownTemplate: 'Unknown template: {{name}}', unknownComponent: 'Unknown component: {{type}}', renderTemplateFunctionDeprecation: `Form.io 'render' template function is deprecated.
8
+ exports.default = Object.assign(Object.assign({}, (((_a = bootstrap_1.default === null || bootstrap_1.default === void 0 ? void 0 : bootstrap_1.default.translations) === null || _a === void 0 ? void 0 : _a.en) || {})), { unsavedRowsError: 'Please save all rows before proceeding.', invalidRowsError: 'Please correct invalid rows before proceeding.', invalidRowError: 'Invalid row. Please correct it or delete.', invalidOption: '{{field}} is an invalid value.', invalidDay: '{{field}} is not a valid day.', complete: 'Submission Complete', error: 'Please fix the following errors before submitting.', errorListHotkey: 'Press Ctrl + Alt + X to go back to the error list.', errorsListNavigationMessage: 'Click to navigate to the field with following error.', submitError: 'Please check the form and correct all errors before submitting.', required: '{{field}} is required', unique: '{{field}} must be unique', array: '{{field}} must be an array', array_nonempty: '{{field}} must be a non-empty array', nonarray: '{{field}} must not be an array', select: '{{field}} contains an invalid selection', pattern: '{{field}} does not match the pattern {{pattern}}', minLength: '{{field}} must have at least {{length}} characters.', maxLength: '{{field}} must have no more than {{length}} characters.', minWords: '{{field}} must have at least {{length}} words.', maxWords: '{{field}} must have no more than {{length}} words.', min: '{{field}} cannot be less than {{min}}.', max: '{{field}} cannot be greater than {{max}}.', maxDate: '{{field}} should not contain date after {{maxDate}}', minDate: '{{field}} should not contain date before {{minDate}}', maxYear: '{{field}} should not contain year greater than {{maxYear}}', minYear: '{{field}} should not contain year less than {{minYear}}', minSelectedCount: 'You must select at least {{minCount}} items', maxSelectedCount: 'You may only select up to {{maxCount}} items', invalid_email: '{{field}} must be a valid email.', invalid_url: '{{field}} must be a valid url.', invalid_regex: '{{field}} does not match the pattern {{regex}}.', invalid_date: '{{field}} is not a valid date.', invalid_day: '{{field}} is not a valid day.', invalidValueProperty: 'Invalid Value Property', mask: '{{field}} does not match the mask.', valueIsNotAvailable: '{{ field }} is an invalid value.', stripe: '{{stripe}}', month: 'Month', day: 'Day', year: 'Year', january: 'January', february: 'February', march: 'March', april: 'April', may: 'May', june: 'June', july: 'July', august: 'August', september: 'September', october: 'October', november: 'November', december: 'December', next: 'Next', previous: 'Previous', cancel: 'Cancel', submit: 'Submit Form', confirmCancel: 'Are you sure you want to cancel?', saveDraftInstanceError: 'Cannot save draft because there is no formio instance.', saveDraftAuthError: 'Cannot save draft unless a user is authenticated.', restoreDraftInstanceError: 'Cannot restore draft because there is no formio instance.', saveDraftError: 'Unable to save draft.', restoreDraftError: 'Unable to restore draft.', time: 'Invalid time', cancelButtonAriaLabel: 'Cancel button. Click to reset the form', previousButtonAriaLabel: 'Previous button. Click to go back to the previous tab', nextButtonAriaLabel: 'Next button. Click to go to the next tab', submitButtonAriaLabel: 'Submit Form button. Click to submit the form', reCaptchaTokenValidationError: 'ReCAPTCHA: Token validation error', reCaptchaTokenNotSpecifiedError: 'ReCAPTCHA: Token is not specified in submission', apiKey: 'API Key is not unique: {{key}}', apiKeyNotValid: 'API Key is not valid: {{key}}', typeRemaining: '{{ remaining }} {{ type }} remaining.', typeCount: '{{ count }} {{ type }}', requiredDayField: '{{ field }} is required', requiredDayEmpty: '{{ field }} is required', requiredMonthField: '{{ field }} is required', requiredYearField: '{{ field }} is required', formNotReady: 'Form not ready. Use form.ready promise', noFormElement: 'No DOM element for form.', notUniqueKey: 'API Key is not unique', newFormSchema: 'Form schema is for a newer version, please upgrade your renderer. Some functionality may not work.', missingUrl: 'Missing URL argument', urlNotAttachedToBtn: 'You should add a URL to this button.', loadingProjectSettingsError: 'Could not load project settings', sessionStorageSupportError: 'Session storage is not supported in this browser.', builderUniqueError: `You cannot add more than one {{componentKeyOrTitle}} component to one page.`, pageNotFound: 'Page not found', noDragInfoError: 'There is no Drag Info available for either dragged or sibling element', addonSupportTypeError: 'Addon {{label}} does not support component of type {{type}}', setPathError: 'Should not be setting the path of a component.', calculatedPathDeprecation: 'component.calculatedPath was deprecated, use component.path instead.', unknownTemplate: 'Unknown template: {{name}}', unknownComponent: 'Unknown component: {{type}}', renderTemplateFunctionDeprecation: `Form.io 'render' template function is deprecated.
9
9
  If you need to render template (template A) inside of another template (template B),
10
10
  pass pre-compiled template A (use this.renderTemplate('template_A_name') as template context variable for template B`, whenReadyDeprecation: 'The whenReady() method has been deprecated. Please use the dataReady property instead.', loadResourcesError: 'Unable to load resources for {{componentKey}}', noSelectDataConfiguration: 'Select component {{componentKey}} does not have data configuration.', indexedDBSupportError: "Your browser doesn't support current version of indexedDB", caretPositionSavingError: 'An error occurred while trying to save caret position', iteratableRowsError: 'Getter #iteratableRows() is not implemented', checkRowDeprecation: 'Deprecation Warning: checkRow method has been replaced with processRow', noOAuthBtn: 'You must add the OAuth button to a form for it to function properly', noOAuthConfiguration: 'OAuth not configured. You must configure oauth for your project before it will work.', oAuthErrorsTitle: 'The Following Error Has Occured', noOAuthFormUrl: 'You must attach a Form API url to your form in order to use OAuth buttons.', oAuthStateError: 'OAuth state does not match. Please try logging in again.', componentInvalidRowValidation: 'Invalid row validation for {{componentKey}}', videoPlayerNotFound: 'Video player not found in template.', synchronizationFailed: 'Synchronization is failed', fileWithDuplicatedNameInProgress: 'File with the same name is already being uploaded', fileWithDuplicatedNameLoaded: 'File with the same name is already uploaded', nestedForm: 'Nested form', noDataProvided: 'No data provided', subformSubmissionLoadingError: 'Unable to load subform submission {{submissionId}}:', noDelimiterSet: 'In order for thousands separator to work properly, you must set the delimiter to true in the component json', noSiteKey: 'There is no Site Key specified in settings in form JSON', failedToNormalize: 'Failed to normalize value', failedToCompareItems: 'Error while comparing items', editorFocusError: 'An editor did not initialize properly when trying to focus:', quillImageUploadFailed: 'Quill image upload failed', noFilesSelected: 'No files selected', needConfigurationForQuill: 'The WYSIWYG settings are configured for CKEditor. For this renderer, you will need to use configurations for the Quill Editor. See https://quilljs.com/docs/configuration for more information.', waitPdfConverting: 'Converting PDF. Please wait.', uploading: 'Uploading', pasteBelow: 'Paste below', copy: 'Copy', move: 'Move', edit: 'Edit', editJson: 'Edit JSON', remove: 'Remove', clickToSetValue: 'Click to set value', words: 'words', characters: 'characters', addAnother: 'Add Another', yes: 'Yes', no: 'No', wantToClearData: 'Do you want to clear data?', yesDelete: 'Yes, delete it', waitFileProcessing: 'Processing file. Please wait...', wrongFileType: 'File is the wrong type; it must be {{ pattern }}', fileTooSmall: 'File is too small; it must be at least {{ size }}', fileTooBig: 'File is too big; it must be at most {{ size }}', noFileService: 'File Service not provided.', fileProcessingFailed: 'File processing has been failed.', readyForUpload: 'Ready to be uploaded into storage', readyForRemovingFromStorage: 'Ready to be removed from storage', preparingFileToRemove: 'Preparing file to remove', succefullyRemoved: 'Succefully removed', succefullyUploaded: 'Succefully uploaded', maxSelectItems: 'You may only select up to {{maxCount}} items', minSelectItems: 'You must select at least {{minCount}} items', clickToSign: 'Click to Sign', surveyQuestion: 'Question', surveyQuestionValue: 'Value', success: 'Success', noResultsFound: 'No results found', noChoices: 'No choices to choose from', typeToSearch: 'Type to search', loading: 'Loading' });
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.translateHTMLTemplate = exports.getContextButtons = exports.getContextComponents = exports.observeOverload = exports.withSwitch = exports.firstNonNil = exports.unfold = exports.bootstrapVersion = exports.uniqueKey = exports.iterateKey = exports.delay = exports.fieldData = exports.getCurrencyAffixes = exports.getNumberDecimalLimit = exports.getNumberSeparators = exports.matchInputMask = exports.unmaskValue = exports.getInputMask = exports.convertFormatToMask = exports.convertFormatToDayjs = exports.convertFormatToFlatpickr = exports.getLocaleDateFormatInfo = exports.formatOffset = exports.formatDate = exports.dayjsDate = exports.shouldHandleTimezone = exports.offsetDate = exports.currentTimezone = exports.isValidDate = exports.getDateSetting = exports.guid = exports.uniqueName = exports.convertStringToHTMLElement = exports.unescapeHTML = exports.removeHTML = exports.setActionProperty = exports.checkTrigger = exports.checkCondition = exports.checkJsonConditional = exports.checkCustomConditional = exports.getComponentActualValue = exports.checkSimpleConditional = exports.checkCalculated = exports.isMongoId = exports.boolValue = exports.getScriptPlugin = exports.getElementRect = exports.getPropertyValue = exports.getRandomComponentId = exports.evaluate = void 0;
7
7
  exports.hasEncodedTimezone = exports.interpolateErrors = exports.getComponentSavedTypes = exports.componentValueTypes = exports.getFocusableElements = exports.isPromise = exports.getDataParentComponent = exports.getComponentPath = exports.getComponentPathWithoutIndicies = exports.getBrowserInfo = exports.getIEBrowserVersion = exports.round = exports.getStringFromComponentPath = exports.isChildOf = exports.getArrayFromComponentPath = exports.isInputComponent = exports.fastCloneDeep = exports.sanitize = void 0;
8
+ /* global jQuery */
8
9
  const lodash_1 = __importDefault(require("lodash"));
9
10
  const moment_timezone_1 = __importDefault(require("moment-timezone/moment-timezone"));
10
11
  const jstimezonedetect_1 = __importDefault(require("jstimezonedetect"));
@@ -208,13 +209,20 @@ function checkSimpleConditional(component, condition, row, data, instance) {
208
209
  return true;
209
210
  }
210
211
  const conditionsResult = lodash_1.default.map(conditions, (cond) => {
211
- var _a, _b;
212
212
  const { value: comparedValue, operator, component: conditionComponentPath } = cond;
213
213
  if (!conditionComponentPath) {
214
214
  return true;
215
215
  }
216
216
  const splittedConditionPath = conditionComponentPath.split('.');
217
- const conditionalPaths = ((_a = instance === null || instance === void 0 ? void 0 : instance.parent) === null || _a === void 0 ? void 0 : _a.type) === 'datagrid' || ((_b = instance === null || instance === void 0 ? void 0 : instance.parent) === null || _b === void 0 ? void 0 : _b.type) === 'editgrid' ? [] : getConditionalPathsRecursive(splittedConditionPath, data);
217
+ const checkParentTypeInTree = (instance, componentType) => {
218
+ if (!(instance === null || instance === void 0 ? void 0 : instance.parent)) {
219
+ return false;
220
+ }
221
+ return (instance === null || instance === void 0 ? void 0 : instance.parent.type) === componentType || checkParentTypeInTree(instance.parent, componentType);
222
+ };
223
+ const conditionalPaths = checkParentTypeInTree(instance, 'datagrid') || checkParentTypeInTree(instance, 'editgrid')
224
+ ? []
225
+ : getConditionalPathsRecursive(splittedConditionPath, data);
218
226
  if (conditionalPaths.length > 0) {
219
227
  return conditionalPaths.map((path) => {
220
228
  const value = getComponentActualValue(path, data, row);
@@ -333,7 +341,7 @@ function getRow(component, row, instance, conditional) {
333
341
  }
334
342
  const dataParent = getDataParentComponent(instance);
335
343
  if (dataParent) {
336
- const parentPath = (_a = dataParent.paths) === null || _a === void 0 ? void 0 : _a.localDataPath;
344
+ const parentPath = (_a = dataParent.paths) === null || _a === void 0 ? void 0 : _a.localPath;
337
345
  const isTriggerCondtionComponentPath = condition.when || !condition.conditions
338
346
  ? (_b = condition.when) === null || _b === void 0 ? void 0 : _b.startsWith((_c = dataParent.paths) === null || _c === void 0 ? void 0 : _c.localPath)
339
347
  : lodash_1.default.some(condition.conditions, cond => { var _a; return cond.component.startsWith((_a = dataParent.paths) === null || _a === void 0 ? void 0 : _a.localPath); });
@@ -1056,6 +1064,9 @@ function bootstrapVersion(options) {
1056
1064
  if (options.bootstrap) {
1057
1065
  return options.bootstrap;
1058
1066
  }
1067
+ if ((typeof jQuery === 'function') && (typeof jQuery().collapse === 'function')) {
1068
+ return parseInt(jQuery.fn.collapse.Constructor.VERSION.split('.')[0], 10);
1069
+ }
1059
1070
  if (window.bootstrap && window.bootstrap.Collapse) {
1060
1071
  return parseInt(window.bootstrap.Collapse.VERSION.split('.')[0], 10);
1061
1072
  }
@@ -51,5 +51,6 @@ export default class PDFBuilder extends WebformBuilder {
51
51
  onDropzoneDrop(e: any): boolean;
52
52
  dropEvent: any;
53
53
  onDragEnd(e: any): void;
54
+ repeatablePathsComps: any[] | undefined;
54
55
  }
55
56
  import WebformBuilder from './WebformBuilder';
@@ -444,21 +444,21 @@ export default class PDFBuilder extends WebformBuilder {
444
444
  e.target.style.cursor = 'default';
445
445
  }
446
446
  highlightInvalidComponents() {
447
- const repeatablePaths = this.findRepeatablePaths();
447
+ const repeatablePathsComps = this.findComponentsWithRepeatablePaths();
448
448
  // update elements which path was duplicated if any pathes have been changed
449
- if (!_.isEqual(this.repeatablePaths, repeatablePaths)) {
450
- eachComponent(this.webform.getComponents(), (comp, path) => {
451
- if (this.repeatablePaths.includes(path)) {
449
+ if (!_.isEqual(this.repeatablePathsComps, repeatablePathsComps)) {
450
+ eachComponent(this.webform.getComponents(), (comp) => {
451
+ if (this.repeatablePathsComps.includes(comp.component)) {
452
452
  this.webform.postMessage({ name: 'updateElement', data: comp.component });
453
453
  }
454
454
  });
455
- this.repeatablePaths = repeatablePaths;
455
+ this.repeatablePathsComps = repeatablePathsComps;
456
456
  }
457
- if (!repeatablePaths.length) {
457
+ if (!repeatablePathsComps.length) {
458
458
  return;
459
459
  }
460
- eachComponent(this.webform.getComponents(), (comp, path) => {
461
- if (this.repeatablePaths.includes(path)) {
460
+ eachComponent(this.webform.getComponents(), (comp) => {
461
+ if (this.repeatablePathsComps.includes(comp)) {
462
462
  this.webform.postMessage({
463
463
  name: 'showBuilderErrors',
464
464
  data: {
@@ -78,7 +78,7 @@ export default class WebformBuilder extends Component {
78
78
  replaceDoubleQuotes(data: any, fieldsToRemoveDoubleQuotes?: any[]): any;
79
79
  updateComponent(component: any, changed: any): void;
80
80
  originalDefaultValue: any;
81
- findRepeatablePaths(): any[];
81
+ findComponentsWithRepeatablePaths(): any[];
82
82
  highlightInvalidComponents(): void;
83
83
  /**
84
84
  * Called when a new component is saved.
@@ -3,7 +3,7 @@ import Component from './components/_classes/component/Component';
3
3
  import tippy from 'tippy.js';
4
4
  import Components from './components/Components';
5
5
  import { Formio } from './Formio';
6
- import { fastCloneDeep, bootstrapVersion, getArrayFromComponentPath, getStringFromComponentPath, eachComponent, getComponent, } from './utils';
6
+ import { fastCloneDeep, bootstrapVersion, getArrayFromComponentPath, getStringFromComponentPath, eachComponent, getComponent, componentInfo } from './utils';
7
7
  import BuilderUtils from './utils/builder';
8
8
  import _ from 'lodash';
9
9
  import autoScroll from 'dom-autoscroller';
@@ -1108,28 +1108,52 @@ export default class WebformBuilder extends Component {
1108
1108
  // Called when we update a component.
1109
1109
  this.emit('updateComponent', component);
1110
1110
  }
1111
- findRepeatablePaths() {
1112
- const repeatablePaths = [];
1111
+ findComponentsWithRepeatablePaths() {
1112
+ const repeatablePaths = {};
1113
1113
  const keys = new Map();
1114
1114
  eachComponent(this.form.components, (comp, path, components, parent, paths) => {
1115
- if (keys.has(paths.dataPath)) {
1116
- repeatablePaths.push(paths.dataPath);
1117
- }
1118
- else {
1119
- keys.set(paths.dataPath, true);
1115
+ const isRadioCheckbox = comp.type === 'checkbox' && comp.inputType === 'radio';
1116
+ const isLayout = componentInfo(comp).layout;
1117
+ if (!isLayout) {
1118
+ if (keys.has(paths.dataPath)) {
1119
+ const onlyRadioCheckboxes = repeatablePaths[paths.dataPath]?.onlyRadioCheckboxes === false ? false : isRadioCheckbox;
1120
+ repeatablePaths[paths.dataPath] = {
1121
+ comps: [...(repeatablePaths[paths.dataPath]?.comps || []), keys.get(paths.dataPath), comp],
1122
+ onlyRadioCheckboxes,
1123
+ };
1124
+ }
1125
+ else {
1126
+ keys.set(paths.dataPath, comp);
1127
+ }
1120
1128
  }
1121
1129
  }, true);
1122
- return repeatablePaths;
1130
+ const componentsWithRepeatablePaths = [];
1131
+ Object.keys(repeatablePaths).forEach((path) => {
1132
+ const { comps, onlyRadioCheckboxes } = repeatablePaths[path];
1133
+ if (!onlyRadioCheckboxes) {
1134
+ componentsWithRepeatablePaths.push(...comps);
1135
+ }
1136
+ });
1137
+ return componentsWithRepeatablePaths;
1123
1138
  }
1124
1139
  highlightInvalidComponents() {
1125
- const repeatablePaths = this.findRepeatablePaths();
1140
+ const repeatablePathsComps = this.findComponentsWithRepeatablePaths();
1126
1141
  let hasInvalidComponents = false;
1142
+ // Matches anything expect letters and '_' at the beginning of the key and anything except of letters, numbers,
1143
+ // '-', '.' and '_' in the rest of the key
1144
+ const badCharacters = /^[^A-Za-z_]+|[^A-Za-z0-9\-._]+/g;
1127
1145
  this.webform.everyComponent((comp) => {
1128
- const path = comp.path;
1129
- if (repeatablePaths.includes(path)) {
1146
+ if (repeatablePathsComps.includes(comp.component)) {
1130
1147
  comp.setCustomValidity(this.t('apiKey', { key: comp.key }));
1131
1148
  hasInvalidComponents = true;
1132
1149
  }
1150
+ else if (comp.key.replace(badCharacters, '') === '') {
1151
+ comp.setCustomValidity(this.t('apiKeyNotValid', { key: comp.key }));
1152
+ hasInvalidComponents = true;
1153
+ }
1154
+ else {
1155
+ comp.setCustomValidity();
1156
+ }
1133
1157
  });
1134
1158
  this.emit('builderFormValidityChange', hasInvalidComponents);
1135
1159
  }
@@ -7,7 +7,7 @@ export default class ButtonComponent extends Field {
7
7
  weight: number;
8
8
  schema: any;
9
9
  };
10
- static savedValueTypes(schema: any): any;
10
+ static savedValueTypes(schema: any): string[];
11
11
  constructor(component: any, options: any, data: any);
12
12
  filesUploading: number;
13
13
  get inputInfo(): any;
@@ -1,7 +1,7 @@
1
1
  import _ from 'lodash';
2
2
  import Field from '../_classes/field/Field';
3
3
  import Input from '../_classes/input/Input';
4
- import { componentValueTypes, eachComponent, getArrayFromComponentPath, getComponentSavedTypes } from '../../utils/formUtils';
4
+ import { componentValueTypes, eachComponent, getArrayFromComponentPath, getComponentSavedTypes } from '../../utils';
5
5
  export default class ButtonComponent extends Field {
6
6
  static schema(...extend) {
7
7
  return Input.schema({
@@ -28,6 +28,7 @@ export default class RadioComponent extends ListComponent {
28
28
  optionsLoaded: boolean | undefined;
29
29
  loadedOptions: any[] | undefined;
30
30
  beforeSubmit(): Promise<any>;
31
+ convertValues(values: any): any;
31
32
  render(): string;
32
33
  attach(element: any): Promise<void>;
33
34
  detach(element: any): void;
@@ -41,5 +42,12 @@ export default class RadioComponent extends ListComponent {
41
42
  setSelectedClasses(): void;
42
43
  updateValue(value: any, flags: any): boolean;
43
44
  currentValue: any;
45
+ /**
46
+ * Normalize values coming into updateValue. For example, depending on the configuration, string value `"true"` will be normalized to boolean `true`.
47
+ * @param {*} value - The value to normalize
48
+ * @returns {*} - Returns the normalized value
49
+ */
50
+ convertByDataType(value: any): any;
51
+ normalizeValue(value: any): any;
44
52
  }
45
53
  import ListComponent from '../_classes/list/ListComponent';
@@ -153,6 +153,12 @@ export default class RadioComponent extends ListComponent {
153
153
  this.dataReady.then(() => res(true));
154
154
  });
155
155
  }
156
+ convertValues(values) {
157
+ if (this.options.renderMode === 'html' && this.type === 'radio') {
158
+ return values.map(x => ({ ...x, value: this.convertByDataType(x.value) }));
159
+ }
160
+ return values;
161
+ }
156
162
  render() {
157
163
  if (!this.optionsLoaded) {
158
164
  return super.render(this.renderTemplate('loader'));
@@ -160,7 +166,7 @@ export default class RadioComponent extends ListComponent {
160
166
  return super.render(this.renderTemplate('radio', {
161
167
  input: this.inputInfo,
162
168
  inline: this.component.inline,
163
- values: this.component.dataSrc === 'values' ? this.component.values : this.loadedOptions,
169
+ values: this.component.dataSrc === 'values' ? this.convertValues(this.component.values) : this.loadedOptions,
164
170
  value: this.dataValue,
165
171
  row: this.row,
166
172
  }));
@@ -406,7 +412,7 @@ export default class RadioComponent extends ListComponent {
406
412
  * @param {*} value - The value to normalize
407
413
  * @returns {*} - Returns the normalized value
408
414
  */
409
- normalizeValue(value) {
415
+ convertByDataType(value) {
410
416
  const dataType = this.component.dataType || 'auto';
411
417
  if (value === this.emptyValue) {
412
418
  return value;
@@ -438,13 +444,17 @@ export default class RadioComponent extends ListComponent {
438
444
  value = !(!value || value.toString() === 'false');
439
445
  break;
440
446
  }
441
- if (this.isSelectURL && this.templateData && this.templateData[value]) {
447
+ return value;
448
+ }
449
+ normalizeValue(value) {
450
+ const valueConverted = this.convertByDataType(value);
451
+ if (this.isSelectURL && this.templateData && this.templateData[valueConverted]) {
442
452
  const submission = this.root.submission;
443
453
  if (!submission.metadata.selectData) {
444
454
  submission.metadata.selectData = {};
445
455
  }
446
- _.set(submission.metadata.selectData, this.path, this.templateData[value]);
456
+ _.set(submission.metadata.selectData, this.path, this.templateData[valueConverted]);
447
457
  }
448
- return super.normalizeValue(value);
458
+ return super.normalizeValue(valueConverted);
449
459
  }
450
460
  }
@@ -89,6 +89,7 @@ export default class SelectComponent extends ListComponent {
89
89
  disableInfiniteScroll(): void;
90
90
  set serverCount(value: any);
91
91
  get serverCount(): any;
92
+ shouldResetChoicesItems(items: any): boolean;
92
93
  setItems(items: any, fromSearch: any): void;
93
94
  selectItems: any;
94
95
  set downloadedResources(value: any);