@formio/js 5.0.0-rc.37 → 5.0.0-rc.38

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. package/dist/fonts/bootstrap-icons.woff +0 -0
  2. package/dist/fonts/bootstrap-icons.woff2 +0 -0
  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.js +853 -132
  7. package/dist/formio.form.min.js +1 -1
  8. package/dist/formio.form.min.js.LICENSE.txt +5 -3
  9. package/dist/formio.full.css +4 -4
  10. package/dist/formio.full.js +827 -136
  11. package/dist/formio.full.min.css +3 -3
  12. package/dist/formio.full.min.js +1 -1
  13. package/dist/formio.full.min.js.LICENSE.txt +5 -3
  14. package/dist/formio.js +4 -4
  15. package/dist/formio.min.js +1 -1
  16. package/dist/formio.min.js.LICENSE.txt +1 -1
  17. package/dist/formio.utils.js +25 -14
  18. package/dist/formio.utils.min.js +1 -1
  19. package/dist/formio.utils.min.js.LICENSE.txt +5 -3
  20. package/lib/cjs/Element.js +2 -2
  21. package/lib/cjs/components/_classes/component/Component.js +21 -7
  22. package/lib/cjs/components/_classes/component/fixtures/comp5.js +2 -2
  23. package/lib/cjs/components/_classes/list/ListComponent.js +5 -12
  24. package/lib/cjs/components/_classes/nested/NestedComponent.js +17 -9
  25. package/lib/cjs/components/columns/editForm/Columns.edit.display.js +1 -1
  26. package/lib/cjs/components/editgrid/EditGrid.js +11 -4
  27. package/lib/cjs/components/form/Form.js +3 -1
  28. package/lib/cjs/components/html/HTML.js +2 -2
  29. package/lib/cjs/components/html/fixtures/comp3.js +31 -0
  30. package/lib/cjs/components/html/fixtures/index.js +3 -1
  31. package/lib/cjs/components/radio/Radio.js +2 -1
  32. package/lib/cjs/components/radio/fixtures/comp10.js +23 -0
  33. package/lib/cjs/components/radio/fixtures/index.js +3 -1
  34. package/lib/cjs/components/recaptcha/ReCaptcha.js +3 -0
  35. package/lib/cjs/components/select/Select.js +84 -9
  36. package/lib/cjs/components/survey/Survey.js +10 -0
  37. package/lib/cjs/utils/conditionOperators/IsEqualTo.js +19 -1
  38. package/lib/cjs/utils/conditionOperators/IsNotEqualTo.js +4 -5
  39. package/lib/cjs/utils/utils.js +40 -2
  40. package/lib/mjs/Element.js +2 -2
  41. package/lib/mjs/components/_classes/component/Component.js +24 -8
  42. package/lib/mjs/components/_classes/component/fixtures/comp5.js +2 -2
  43. package/lib/mjs/components/_classes/list/ListComponent.js +5 -12
  44. package/lib/mjs/components/_classes/nested/NestedComponent.js +17 -9
  45. package/lib/mjs/components/columns/editForm/Columns.edit.display.js +1 -1
  46. package/lib/mjs/components/editgrid/EditGrid.js +11 -4
  47. package/lib/mjs/components/form/Form.js +3 -1
  48. package/lib/mjs/components/html/HTML.js +2 -2
  49. package/lib/mjs/components/html/fixtures/comp3.js +29 -0
  50. package/lib/mjs/components/html/fixtures/index.js +2 -1
  51. package/lib/mjs/components/radio/Radio.js +2 -1
  52. package/lib/mjs/components/radio/fixtures/comp10.js +21 -0
  53. package/lib/mjs/components/radio/fixtures/index.js +2 -1
  54. package/lib/mjs/components/recaptcha/ReCaptcha.js +3 -0
  55. package/lib/mjs/components/select/Select.js +83 -10
  56. package/lib/mjs/components/survey/Survey.js +10 -0
  57. package/lib/mjs/utils/conditionOperators/IsEqualTo.js +18 -1
  58. package/lib/mjs/utils/conditionOperators/IsNotEqualTo.js +4 -5
  59. package/lib/mjs/utils/utils.js +35 -0
  60. package/package.json +2 -2
  61. package/types/formio.d.ts +4 -0
@@ -30,8 +30,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
30
30
  return (mod && mod.__esModule) ? mod : { "default": mod };
31
31
  };
32
32
  Object.defineProperty(exports, "__esModule", { value: true });
33
- 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.convertFormatToMoment = exports.convertFormatToFlatpickr = exports.getLocaleDateFormatInfo = exports.formatOffset = exports.formatDate = exports.momentDate = exports.loadZones = exports.shouldLoadZones = exports.zonesLoaded = exports.offsetDate = exports.currentTimezone = exports.isValidDate = exports.getDateSetting = exports.guid = exports.uniqueName = exports.convertStringToHTMLElement = exports.unescapeHTML = exports.setActionProperty = exports.checkTrigger = exports.checkCondition = exports.checkJsonConditional = exports.checkCustomConditional = exports.getComponentActualValue = exports.checkSimpleConditional = exports.checkCalculated = exports.isMongoId = exports.boolValue = exports.getElementRect = exports.getPropertyValue = exports.getRandomComponentId = exports.evaluate = exports.moment = exports.ConditionOperators = exports.jsonLogic = void 0;
34
- exports.interpolateErrors = exports.getComponentSavedTypes = exports.componentValueTypes = exports._ = exports.getFocusableElements = exports.isInsideScopingComponent = exports.isPromise = exports.getDataParentComponent = exports.getComponentPath = exports.getComponentPathWithoutIndicies = exports.getBrowserInfo = exports.getIEBrowserVersion = exports.round = exports.getStringFromComponentPath = exports.isChildOf = exports.getArrayFromComponentPath = exports.isInputComponent = exports.interpolate = exports.Evaluator = exports.fastCloneDeep = exports.sanitize = exports.translateHTMLTemplate = exports.getContextButtons = exports.getContextComponents = void 0;
33
+ 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.convertFormatToMoment = exports.convertFormatToFlatpickr = exports.getLocaleDateFormatInfo = exports.formatOffset = exports.formatDate = exports.momentDate = exports.loadZones = exports.shouldLoadZones = exports.zonesLoaded = exports.offsetDate = exports.currentTimezone = exports.isValidDate = exports.getDateSetting = exports.guid = exports.uniqueName = exports.convertStringToHTMLElement = exports.escapeHTML = exports.unescapeHTML = exports.setActionProperty = exports.checkTrigger = exports.checkCondition = exports.checkJsonConditional = exports.checkCustomConditional = exports.getComponentActualValue = exports.checkSimpleConditional = exports.checkCalculated = exports.isMongoId = exports.boolValue = exports.getElementRect = exports.getPropertyValue = exports.getRandomComponentId = exports.evaluate = exports.moment = exports.ConditionOperators = exports.jsonLogic = void 0;
34
+ exports.isSelectResourceWithObjectValue = exports.getItemTemplateKeys = exports.interpolateErrors = exports.getComponentSavedTypes = exports.componentValueTypes = exports._ = exports.getFocusableElements = exports.isInsideScopingComponent = exports.isPromise = exports.getDataParentComponent = exports.getComponentPath = exports.getComponentPathWithoutIndicies = exports.getBrowserInfo = exports.getIEBrowserVersion = exports.round = exports.getStringFromComponentPath = exports.isChildOf = exports.getArrayFromComponentPath = exports.isInputComponent = exports.interpolate = exports.Evaluator = exports.fastCloneDeep = exports.sanitize = exports.translateHTMLTemplate = exports.getContextButtons = exports.getContextComponents = exports.observeOverload = void 0;
35
35
  const lodash_1 = __importDefault(require("lodash"));
36
36
  exports._ = lodash_1.default;
37
37
  const fetch_ponyfill_1 = __importDefault(require("fetch-ponyfill"));
@@ -447,6 +447,22 @@ function unescapeHTML(str) {
447
447
  return doc.documentElement.textContent;
448
448
  }
449
449
  exports.unescapeHTML = unescapeHTML;
450
+ /**
451
+ * Escape HTML characters like <, >, & and etc.
452
+ * @param str
453
+ * @returns {string}
454
+ */
455
+ function escapeHTML(html) {
456
+ if (html) {
457
+ return html.replace(/&/g, '&amp;')
458
+ .replace(/</g, '&lt;')
459
+ .replace(/>/g, '&gt;')
460
+ .replace(/"/g, '&quot;')
461
+ .replace(/'/g, '&#39;');
462
+ }
463
+ return '';
464
+ }
465
+ exports.escapeHTML = escapeHTML;
450
466
  /**
451
467
  * Make HTML element from string
452
468
  * @param str
@@ -1509,3 +1525,25 @@ const interpolateErrors = (component, errors, interpolateFn) => {
1509
1525
  });
1510
1526
  };
1511
1527
  exports.interpolateErrors = interpolateErrors;
1528
+ function getItemTemplateKeys(template) {
1529
+ const templateKeys = [];
1530
+ if (!template) {
1531
+ return templateKeys;
1532
+ }
1533
+ const keys = template.match(/({{\s*(.*?)\s*}})/g);
1534
+ if (keys) {
1535
+ keys.forEach((key) => {
1536
+ const propKey = key.match(/{{\s*item\.(.*?)\s*}}/);
1537
+ if (propKey && propKey.length > 1) {
1538
+ templateKeys.push(propKey[1]);
1539
+ }
1540
+ });
1541
+ }
1542
+ return templateKeys;
1543
+ }
1544
+ exports.getItemTemplateKeys = getItemTemplateKeys;
1545
+ function isSelectResourceWithObjectValue(comp = {}) {
1546
+ const { reference, dataSrc, valueProperty } = comp;
1547
+ return reference || (dataSrc === 'resource' && (!valueProperty || valueProperty === 'data'));
1548
+ }
1549
+ exports.isSelectResourceWithObjectValue = isSelectResourceWithObjectValue;
@@ -179,7 +179,7 @@ export default class Element {
179
179
  * @param persistent
180
180
  * If this listener should persist beyond "destroy" commands.
181
181
  */
182
- addEventListener(obj, type, func, persistent) {
182
+ addEventListener(obj, type, func, persistent, capture) {
183
183
  if (!obj) {
184
184
  return;
185
185
  }
@@ -187,7 +187,7 @@ export default class Element {
187
187
  this.eventHandlers.push({ id: this.id, obj, type, func });
188
188
  }
189
189
  if ('addEventListener' in obj) {
190
- obj.addEventListener(type, func, false);
190
+ obj.addEventListener(type, func, !!capture);
191
191
  }
192
192
  else if ('attachEvent' in obj) {
193
193
  obj.attachEvent(`on${type}`, func);
@@ -3,7 +3,7 @@ import { conformToMask } from '@formio/vanilla-text-mask';
3
3
  import tippy from 'tippy.js';
4
4
  import _ from 'lodash';
5
5
  import isMobile from 'ismobilejs';
6
- import { processOne, processOneSync, validateProcess, validateProcessSync } from '@formio/core/process';
6
+ import { processOne, processOneSync, validateProcessInfo } from '@formio/core/process';
7
7
  import { Formio } from '../../../Formio';
8
8
  import * as FormioUtils from '../../../utils/utils';
9
9
  import { fastCloneDeep, boolValue, getComponentPath, isInsideScopingComponent, currentTimezone } from '../../../utils/utils';
@@ -819,7 +819,13 @@ export default class Component extends Element {
819
819
  renderTemplate(name, data = {}, modeOption) {
820
820
  // Need to make this fall back to form if renderMode is not found similar to how we search templates.
821
821
  const mode = modeOption || this.options.renderMode || 'form';
822
- data.component = this.component;
822
+ data.component = {
823
+ ...this.component,
824
+ };
825
+ // Escape HTML provided in component description and render it as a string instead
826
+ if (this.component.description) {
827
+ data.component.description = FormioUtils.escapeHTML(this.component.description);
828
+ }
823
829
  data.self = this;
824
830
  data.options = this.options;
825
831
  data.readOnly = this.options.readOnly;
@@ -1030,12 +1036,12 @@ export default class Component extends Element {
1030
1036
  const tooltipText = this.interpolate(tooltipDataTitle || tooltipAttribute)
1031
1037
  .replace(/(?:\r\n|\r|\n)/g, '<br />');
1032
1038
  this.tooltips[index] = tippy(tooltip, {
1033
- allowHTML: true,
1039
+ allowHTML: false,
1034
1040
  trigger: 'mouseenter click focus',
1035
1041
  placement: 'right',
1036
1042
  zIndex: 10000,
1037
1043
  interactive: true,
1038
- content: this.t(this.sanitize(tooltipText), { _userInput: true }),
1044
+ content: this.t(tooltipText, { _userInput: true }),
1039
1045
  });
1040
1046
  }
1041
1047
  });
@@ -2574,7 +2580,7 @@ export default class Component extends Element {
2574
2580
  scope: validationScope,
2575
2581
  instance: this,
2576
2582
  processors: [
2577
- validateProcessSync
2583
+ validateProcessInfo
2578
2584
  ]
2579
2585
  });
2580
2586
  const errors = validationScope.errors;
@@ -2655,7 +2661,7 @@ export default class Component extends Element {
2655
2661
  instance: this,
2656
2662
  scope: { errors: [] },
2657
2663
  processors: [
2658
- async ? validateProcess : validateProcessSync
2664
+ validateProcessInfo
2659
2665
  ]
2660
2666
  };
2661
2667
  if (async) {
@@ -2684,7 +2690,12 @@ export default class Component extends Element {
2684
2690
  return this.validateComponent(data, row, flags).then((errors) => {
2685
2691
  allErrors.push(...errors);
2686
2692
  if (this.parent && this.parent.childErrors) {
2687
- this.parent.childErrors.push(...errors);
2693
+ if (errors.length) {
2694
+ this.parent.childErrors.push(...errors);
2695
+ }
2696
+ else {
2697
+ _.remove(this.parent.childErrors, (err) => err.component.key === this.component.key);
2698
+ }
2688
2699
  }
2689
2700
  this.showValidationErrors(errors, data, row, flags);
2690
2701
  return errors.length === 0;
@@ -2695,7 +2706,12 @@ export default class Component extends Element {
2695
2706
  this.showValidationErrors(errors, data, row, flags);
2696
2707
  allErrors.push(...errors);
2697
2708
  if (this.parent && this.parent.childErrors) {
2698
- this.parent.childErrors.push(...errors);
2709
+ if (errors.length) {
2710
+ this.parent.childErrors.push(...errors);
2711
+ }
2712
+ else {
2713
+ _.remove(this.parent.childErrors, (err) => err.component.key === this.component.key);
2714
+ }
2699
2715
  }
2700
2716
  return errors.length === 0;
2701
2717
  }
@@ -4,8 +4,8 @@ export default {
4
4
  components: [
5
5
  {
6
6
  label: 'Text Field',
7
- description: "<img <img src='https://somesite' onerror='var _ee = 2' >",
8
- tooltip: "<img src='https://somesite' onerror='var _ee = 1 >",
7
+ description: "<img src='https://somesite' onerror='var _ee = 2' >",
8
+ tooltip: "<img src='https://somesite' onerror='var _ee = 1' >",
9
9
  applyMaskOn: 'change',
10
10
  tableView: true,
11
11
  key: 'textField',
@@ -1,6 +1,7 @@
1
1
  import Field from '../field/Field';
2
2
  import { Formio } from '../../../Formio';
3
3
  import _ from 'lodash';
4
+ import { getItemTemplateKeys } from '../../../utils/utils';
4
5
  export default class ListComponent extends Field {
5
6
  static schema(...extend) {
6
7
  return Field.schema({
@@ -43,18 +44,10 @@ export default class ListComponent extends Field {
43
44
  return true;
44
45
  }
45
46
  getTemplateKeys() {
46
- this.templateKeys = [];
47
- if (this.options.readOnly && this.component.template) {
48
- const keys = this.component.template.match(/({{\s*(.*?)\s*}})/g);
49
- if (keys) {
50
- keys.forEach((key) => {
51
- const propKey = key.match(/{{\s*item\.(.*?)\s*}}/);
52
- if (propKey && propKey.length > 1) {
53
- this.templateKeys.push(propKey[1]);
54
- }
55
- });
56
- }
57
- }
47
+ const template = this.component.template;
48
+ this.templateKeys = this.options.readOnly && template
49
+ ? getItemTemplateKeys(template)
50
+ : [];
58
51
  }
59
52
  get requestHeaders() {
60
53
  // Create the headers object.
@@ -582,6 +582,16 @@ export default class NestedComponent extends Field {
582
582
  components = components || this.component.components;
583
583
  data = data || this.rootValue;
584
584
  const { async, dirty, process } = flags;
585
+ const validationProcessorProcess = (context) => this.validationProcessor(context, flags);
586
+ const checkModalProcessorProcess = ({ instance, component, components }) => {
587
+ // If we just validated the last component, and there are errors from our parent, then we need to show a model of those errors.
588
+ if (instance &&
589
+ instance.parent &&
590
+ (component === components[components.length - 1]) &&
591
+ instance.parent.componentModal) {
592
+ instance.parent.checkModal(instance.parent.childErrors, dirty);
593
+ }
594
+ };
585
595
  const processorContext = {
586
596
  process: process || 'unknown',
587
597
  components,
@@ -589,15 +599,13 @@ export default class NestedComponent extends Field {
589
599
  data: data,
590
600
  scope: { errors: [] },
591
601
  processors: [
592
- (context) => this.validationProcessor(context, flags),
593
- ({ instance, component, components }) => {
594
- // If we just validated the last component, and there are errors from our parent, then we need to show a model of those errors.
595
- if (instance &&
596
- instance.parent &&
597
- (component === components[components.length - 1]) &&
598
- instance.parent.componentModal) {
599
- instance.parent.checkModal(instance.parent.childErrors, dirty);
600
- }
602
+ {
603
+ process: validationProcessorProcess,
604
+ processSync: validationProcessorProcess
605
+ },
606
+ {
607
+ process: checkModalProcessorProcess,
608
+ processSync: checkModalProcessorProcess
601
609
  }
602
610
  ]
603
611
  };
@@ -56,7 +56,7 @@ export default [
56
56
  key: 'columns',
57
57
  label: 'Column Properties',
58
58
  addAnother: 'Add Column',
59
- tooltip: 'The width, offset, push, and pull settings for each column.',
59
+ tooltip: 'The size and width settings for each column. One row is equal to 12. (e.g., a row with two columns spanning the entire page should be 6 and 6)',
60
60
  reorder: true,
61
61
  components: [
62
62
  {
@@ -467,7 +467,7 @@ export default class EditGridComponent extends NestedArrayComponent {
467
467
  ].forEach(({ className, event, action, }) => {
468
468
  const elements = row.getElementsByClassName(className);
469
469
  Array.prototype.forEach.call(elements, (element) => {
470
- if (this.options.readOnly && _.intersection(element.classList, ['editRow', 'removeRow']).length) {
470
+ if (this.options.pdf && _.intersection(element.classList, ['editRow', 'removeRow']).length) {
471
471
  element.style.display = 'none';
472
472
  }
473
473
  else {
@@ -599,6 +599,9 @@ export default class EditGridComponent extends NestedArrayComponent {
599
599
  const dataObj = {};
600
600
  const rowIndex = this.editRows.length;
601
601
  const editRow = this.createRow(dataObj, rowIndex);
602
+ if (editRow.state === EditRowState.New) {
603
+ this.emptyRow = fastCloneDeep(editRow.data);
604
+ }
602
605
  if (this.inlineEditMode) {
603
606
  this.triggerChange();
604
607
  }
@@ -669,7 +672,7 @@ export default class EditGridComponent extends NestedArrayComponent {
669
672
  }
670
673
  showDialog(rowIndex) {
671
674
  const editRow = this.editRows[rowIndex];
672
- if (_.isEqual(editRow.backup, editRow.data)) {
675
+ if (editRow.state === EditRowState.New ? _.isEqual(this.emptyRow, editRow.data) : _.isEqual(editRow.backup, editRow.data)) {
673
676
  return Promise.resolve();
674
677
  }
675
678
  const wrapper = this.ce('div', { ref: 'confirmationDialog' });
@@ -969,6 +972,7 @@ export default class EditGridComponent extends NestedArrayComponent {
969
972
  const editGridValue = _.get(rootValue, this.path, []);
970
973
  editGridValue[editRow.rowIndex] = editRow.data;
971
974
  _.set(rootValue, this.path, editGridValue);
975
+ const validationProcessorProcess = (context) => this.validationProcessor(context, { dirty, silentCheck });
972
976
  editRow.errors = processSync({
973
977
  components: fastCloneDeep(this.component.components).map((component) => {
974
978
  component.parentPath = `${this.path}[${editRow.rowIndex}]`;
@@ -980,7 +984,10 @@ export default class EditGridComponent extends NestedArrayComponent {
980
984
  instances: this.componentsMap,
981
985
  scope: { errors: [] },
982
986
  processors: [
983
- (context) => this.validationProcessor(context, { dirty, silentCheck })
987
+ {
988
+ process: validationProcessorProcess,
989
+ processSync: validationProcessorProcess
990
+ }
984
991
  ]
985
992
  }).errors;
986
993
  }
@@ -1070,7 +1077,7 @@ export default class EditGridComponent extends NestedArrayComponent {
1070
1077
  this.setCustomValidity(this.t(this.errorMessage('unsavedRowsError')), dirty);
1071
1078
  return false;
1072
1079
  }
1073
- const message = this.invalid || this.invalidMessage(data, dirty);
1080
+ const message = this.invalid || this.invalidMessage(data, dirty, false, row);
1074
1081
  if (allRowErrors.length && this.root?.submitted && !message) {
1075
1082
  this._errors = this.setCustomValidity(message, dirty);
1076
1083
  errors.push(...this._errors);
@@ -600,7 +600,9 @@ export default class FormComponent extends Component {
600
600
  const formId = submission.form || this.formObj.form || this.component.form;
601
601
  const submissionUrl = `${this.subForm.formio.formsUrl}/${formId}/submission/${submission._id}`;
602
602
  this.subForm.setUrl(submissionUrl, this.options);
603
- this.subForm.loadSubmission();
603
+ this.subForm.loadSubmission().catch((err) => {
604
+ console.error(`Unable to load subform submission ${submission._id}:`, err);
605
+ });
604
606
  }
605
607
  else {
606
608
  this.subForm.setValue(submission, flags);
@@ -38,13 +38,13 @@ export default class HTMLComponent extends Component {
38
38
  return ` ${this.component.content} `;
39
39
  }
40
40
  const submission = _.get(this.root, 'submission', {});
41
- const content = this.component.content ? this.interpolate(this.component.content, {
41
+ const content = this.component.content ? this.interpolate(this.sanitize(this.component.content, this.shouldSanitizeValue), {
42
42
  metadata: submission.metadata || {},
43
43
  submission: submission,
44
44
  data: this.rootValue,
45
45
  row: this.data
46
46
  }) : '';
47
- return this.sanitize(content, this.shouldSanitizeValue);
47
+ return content;
48
48
  }
49
49
  get singleTags() {
50
50
  return ['br', 'img', 'hr'];
@@ -0,0 +1,29 @@
1
+ export default {
2
+ type: 'form',
3
+ display: 'form',
4
+ components: [
5
+ {
6
+ label: 'HTML',
7
+ attrs: [
8
+ {
9
+ attr: '',
10
+ value: '',
11
+ },
12
+ ],
13
+ content: '<img src=1 onerror=alert("htmlContent")>',
14
+ refreshOnChange: false,
15
+ key: 'html',
16
+ type: 'htmlelement',
17
+ input: false,
18
+ tableView: false,
19
+ },
20
+ {
21
+ type: 'button',
22
+ label: 'Submit',
23
+ key: 'submit',
24
+ disableOnInvalid: true,
25
+ input: true,
26
+ tableView: false,
27
+ },
28
+ ],
29
+ };
@@ -1,3 +1,4 @@
1
1
  import comp1 from './comp1';
2
2
  import comp2 from './comp2';
3
- export { comp1, comp2 };
3
+ import comp3 from './comp3';
4
+ export { comp1, comp2, comp3 };
@@ -349,7 +349,8 @@ export default class RadioComponent extends ListComponent {
349
349
  if (value === this.emptyValue) {
350
350
  return value;
351
351
  }
352
- if (!isNaN(parseFloat(value)) && isFinite(value)) {
352
+ const isEquivalent = _.toString(value) === Number(value).toString();
353
+ if (!isNaN(parseFloat(value)) && isFinite(value) && isEquivalent) {
353
354
  value = +value;
354
355
  }
355
356
  if (value === 'true') {
@@ -0,0 +1,21 @@
1
+ export default {
2
+ 'label': 'Radio',
3
+ 'optionsLabelPosition': 'right',
4
+ 'inline': false,
5
+ 'tableView': false,
6
+ 'values': [
7
+ {
8
+ 'label': '01',
9
+ 'value': '01',
10
+ 'shortcut': ''
11
+ },
12
+ {
13
+ 'label': '1',
14
+ 'value': '1',
15
+ 'shortcut': ''
16
+ }
17
+ ],
18
+ 'key': 'radio',
19
+ 'type': 'radio',
20
+ 'input': true
21
+ };
@@ -7,4 +7,5 @@ import comp6 from './comp6';
7
7
  import comp7 from './comp7';
8
8
  import comp8 from './comp8';
9
9
  import comp9 from './comp9';
10
- export { comp1, comp2, comp3, comp4, comp5, comp6, comp7, comp8, comp9 };
10
+ import comp10 from './comp10';
11
+ export { comp1, comp2, comp3, comp4, comp5, comp6, comp7, comp8, comp9, comp10 };
@@ -52,6 +52,9 @@ export default class ReCaptchaComponent extends Component {
52
52
  createLabel() {
53
53
  return;
54
54
  }
55
+ get skipInEmail() {
56
+ return true;
57
+ }
55
58
  verify(actionName) {
56
59
  const siteKey = _get(this.root.form, 'settings.recaptcha.siteKey');
57
60
  if (!siteKey) {
@@ -3,7 +3,7 @@ import { Formio } from '../../Formio';
3
3
  import ListComponent from '../_classes/list/ListComponent';
4
4
  import Input from '../_classes/input/Input';
5
5
  import Form from '../../Form';
6
- import { getRandomComponentId, boolValue, isPromise, componentValueTypes, getComponentSavedTypes } from '../../utils/utils';
6
+ import { getRandomComponentId, boolValue, isPromise, componentValueTypes, getComponentSavedTypes, unescapeHTML, isSelectResourceWithObjectValue } from '../../utils/utils';
7
7
  import Choices from '../../utils/ChoicesWrapper';
8
8
  export default class SelectComponent extends ListComponent {
9
9
  static schema(...extend) {
@@ -61,7 +61,19 @@ export default class SelectComponent extends ListComponent {
61
61
  return {
62
62
  ...super.conditionOperatorsSettings,
63
63
  valueComponent(classComp) {
64
- return { ...classComp, type: 'select' };
64
+ const valueComp = { ...classComp, type: 'select' };
65
+ if (isSelectResourceWithObjectValue(classComp)) {
66
+ valueComp.reference = false;
67
+ valueComp.onSetItems = `
68
+ var templateKeys = utils.getItemTemplateKeys(component.template) || [];
69
+ items = _.map(items || [], i => {
70
+ var item = {};
71
+ _.each(templateKeys, k => _.set(item, k, _.get(i, k)));
72
+ return item;
73
+ })
74
+ `;
75
+ }
76
+ return valueComp;
65
77
  }
66
78
  };
67
79
  }
@@ -120,6 +132,7 @@ export default class SelectComponent extends ListComponent {
120
132
  this.itemsLoaded = new Promise((resolve) => {
121
133
  this.itemsLoadedResolve = resolve;
122
134
  });
135
+ this.shouldPositionDropdown = this.hasDataGridAncestor();
123
136
  if (this.isHtmlRenderMode()) {
124
137
  this.activate();
125
138
  }
@@ -851,6 +864,11 @@ export default class SelectComponent extends ListComponent {
851
864
  });
852
865
  }
853
866
  }
867
+ if (window && this.choices && this.shouldPositionDropdown) {
868
+ this.addEventListener(window.document, 'scroll', () => {
869
+ this.positionDropdown(true);
870
+ }, false, true);
871
+ }
854
872
  this.focusableElement.setAttribute('tabIndex', tabIndex);
855
873
  // If a search field is provided, then add an event listener to update items on search.
856
874
  if (this.component.searchField) {
@@ -880,7 +898,10 @@ export default class SelectComponent extends ListComponent {
880
898
  const updateComponent = (evt) => {
881
899
  this.triggerUpdate(evt.detail.value);
882
900
  };
883
- this.addEventListener(input, 'search', _.debounce(updateComponent, debounceTimeout));
901
+ this.addEventListener(input, 'search', _.debounce((e) => {
902
+ updateComponent(e);
903
+ this.positionDropdown();
904
+ }, debounceTimeout));
884
905
  this.addEventListener(input, 'stopSearch', () => this.triggerUpdate());
885
906
  this.addEventListener(input, 'hideDropdown', () => {
886
907
  if (this.choices && this.choices.input && this.choices.input.element) {
@@ -889,7 +910,15 @@ export default class SelectComponent extends ListComponent {
889
910
  this.updateItems(null, true);
890
911
  });
891
912
  }
892
- this.addEventListener(input, 'showDropdown', () => this.update());
913
+ this.addEventListener(input, 'showDropdown', () => {
914
+ this.update();
915
+ this.positionDropdown();
916
+ });
917
+ if (this.shouldPositionDropdown) {
918
+ this.addEventListener(input, 'highlightChoice', () => {
919
+ this.positionDropdown();
920
+ });
921
+ }
893
922
  if (this.choices && choicesOptions.placeholderValue && this.choices._isSelectOneElement) {
894
923
  this.addPlaceholderItem(choicesOptions.placeholderValue);
895
924
  this.addEventListener(input, 'removeItem', () => {
@@ -926,6 +955,44 @@ export default class SelectComponent extends ListComponent {
926
955
  this.triggerUpdate();
927
956
  return superAttach;
928
957
  }
958
+ setDropdownPosition() {
959
+ const dropdown = this.choices?.dropdown?.element;
960
+ const container = this.choices?.containerOuter?.element;
961
+ if (!dropdown || !container) {
962
+ return;
963
+ }
964
+ const containerPosition = container.getBoundingClientRect();
965
+ const isFlipped = container.classList.contains('is-flipped');
966
+ _.assign(dropdown.style, {
967
+ top: `${isFlipped ? containerPosition.top - dropdown.offsetHeight : containerPosition.top + containerPosition.height}px`,
968
+ left: `${containerPosition.left}px`,
969
+ width: `${containerPosition.width}px`,
970
+ position: 'fixed',
971
+ bottom: 'unset',
972
+ right: 'unset',
973
+ });
974
+ }
975
+ hasDataGridAncestor(comp) {
976
+ comp = comp || this;
977
+ if (comp.inDataGrid || comp.type === 'datagrid') {
978
+ return true;
979
+ }
980
+ else if (comp.parent) {
981
+ return this.hasDataGridAncestor(comp.parent);
982
+ }
983
+ else {
984
+ return false;
985
+ }
986
+ }
987
+ positionDropdown(scroll) {
988
+ if (!this.shouldPositionDropdown || !this.choices || (!this.choices.dropdown?.isActive && scroll)) {
989
+ return;
990
+ }
991
+ this.setDropdownPosition();
992
+ this.itemsLoaded.then(() => {
993
+ this.setDropdownPosition();
994
+ });
995
+ }
929
996
  get isLoadingAvailable() {
930
997
  return !this.isScrollLoading && this.additionalResourcesAvailable;
931
998
  }
@@ -1043,10 +1110,10 @@ export default class SelectComponent extends ListComponent {
1043
1110
  }
1044
1111
  return added;
1045
1112
  }
1046
- getValueAsString(data) {
1113
+ getValueAsString(data, options) {
1047
1114
  return (this.component.multiple && Array.isArray(data))
1048
- ? data.map(this.asString.bind(this)).join(', ')
1049
- : this.asString(data);
1115
+ ? data.map((v) => this.asString(v, options)).join(', ')
1116
+ : this.asString(data, options);
1050
1117
  }
1051
1118
  getValue() {
1052
1119
  // If the widget isn't active.
@@ -1398,7 +1465,7 @@ export default class SelectComponent extends ListComponent {
1398
1465
  }
1399
1466
  return this.component.data.values.map(value => ({ label: value.label, value: String(this.normalizeSingleValue(value.value)) }));
1400
1467
  }
1401
- asString(value) {
1468
+ asString(value, options = {}) {
1402
1469
  value = value ?? this.getValue();
1403
1470
  //need to convert values to strings to be able to compare values with available options that are strings
1404
1471
  const convertToString = (data, valueProperty) => {
@@ -1448,9 +1515,15 @@ export default class SelectComponent extends ListComponent {
1448
1515
  if (_.isString(value)) {
1449
1516
  return value;
1450
1517
  }
1518
+ const getTemplateValue = (v) => {
1519
+ const itemTemplate = this.itemTemplate(v);
1520
+ return options.csv && itemTemplate
1521
+ ? unescapeHTML(itemTemplate)
1522
+ : itemTemplate;
1523
+ };
1451
1524
  if (Array.isArray(value)) {
1452
1525
  const items = [];
1453
- value.forEach(item => items.push(this.itemTemplate(item)));
1526
+ value.forEach(item => items.push(getTemplateValue(item)));
1454
1527
  if (this.component.dataSrc === 'resource' && items.length > 0) {
1455
1528
  return items.join(', ');
1456
1529
  }
@@ -1465,7 +1538,7 @@ export default class SelectComponent extends ListComponent {
1465
1538
  return JSON.stringify(value);
1466
1539
  }
1467
1540
  return !_.isNil(value)
1468
- ? this.itemTemplate(value)
1541
+ ? getTemplateValue(value)
1469
1542
  : '-';
1470
1543
  }
1471
1544
  detach() {
@@ -148,6 +148,16 @@ export default class SurveyComponent extends Field {
148
148
  result += '</tbody></table>';
149
149
  return result;
150
150
  }
151
+ if (_.isPlainObject(value)) {
152
+ const { values = [], questions = [] } = this.component;
153
+ return _.isEmpty(value)
154
+ ? ''
155
+ : _.map(value, (v, q) => {
156
+ const valueLabel = _.get(_.find(values, val => _.isEqual(val.value, v)), 'label', v);
157
+ const questionLabel = _.get(_.find(questions, quest => _.isEqual(quest.value, q)), 'label', q);
158
+ return `${questionLabel}: ${valueLabel}`;
159
+ }).join('; ');
160
+ }
151
161
  return super.getValueAsString(value, options);
152
162
  }
153
163
  }