@formio/js 5.5.2 → 5.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +2 -2
  2. package/dist/formio.builder.css +3 -0
  3. package/dist/formio.builder.min.css +1 -1
  4. package/dist/formio.embed.js +1 -1
  5. package/dist/formio.embed.min.js +1 -1
  6. package/dist/formio.embed.min.js.LICENSE.txt +1 -1
  7. package/dist/formio.form.css +3 -0
  8. package/dist/formio.form.js +23 -23
  9. package/dist/formio.form.min.css +1 -1
  10. package/dist/formio.form.min.js +1 -1
  11. package/dist/formio.form.min.js.LICENSE.txt +1 -1
  12. package/dist/formio.full.css +3 -0
  13. package/dist/formio.full.js +25 -25
  14. package/dist/formio.full.min.css +1 -1
  15. package/dist/formio.full.min.js +1 -1
  16. package/dist/formio.full.min.js.LICENSE.txt +1 -1
  17. package/dist/formio.js +10 -10
  18. package/dist/formio.min.js +1 -1
  19. package/dist/formio.min.js.LICENSE.txt +1 -1
  20. package/dist/formio.utils.js +8 -8
  21. package/dist/formio.utils.min.js +1 -1
  22. package/dist/formio.utils.min.js.LICENSE.txt +1 -1
  23. package/lib/cjs/Embed.js +1 -1
  24. package/lib/cjs/Formio.js +1 -1
  25. package/lib/cjs/components/_classes/component/Component.js +2 -1
  26. package/lib/cjs/components/address/Address.js +5 -2
  27. package/lib/cjs/components/address/editForm/Address.edit.provider.d.ts +24 -0
  28. package/lib/cjs/components/address/editForm/Address.edit.provider.js +13 -0
  29. package/lib/cjs/components/datagrid/DataGrid.js +2 -1
  30. package/lib/cjs/components/file/File.js +17 -8
  31. package/lib/cjs/components/file/editForm/File.edit.file.d.ts +44 -0
  32. package/lib/cjs/components/file/editForm/File.edit.file.js +2 -1
  33. package/lib/cjs/components/form/Form.d.ts +7 -0
  34. package/lib/cjs/components/form/Form.js +24 -2
  35. package/lib/cjs/components/signature/Signature.js +2 -2
  36. package/lib/cjs/package.json +1 -1
  37. package/lib/cjs/providers/address/GoogleAddressProvider.d.ts +9 -1
  38. package/lib/cjs/providers/address/GoogleAddressProvider.js +116 -32
  39. package/lib/mjs/Embed.js +1 -1
  40. package/lib/mjs/Formio.js +1 -1
  41. package/lib/mjs/components/_classes/component/Component.js +1 -1
  42. package/lib/mjs/components/address/Address.js +5 -2
  43. package/lib/mjs/components/address/editForm/Address.edit.provider.d.ts +24 -0
  44. package/lib/mjs/components/address/editForm/Address.edit.provider.js +13 -0
  45. package/lib/mjs/components/datagrid/DataGrid.js +2 -1
  46. package/lib/mjs/components/file/File.js +17 -8
  47. package/lib/mjs/components/file/editForm/File.edit.file.d.ts +44 -0
  48. package/lib/mjs/components/file/editForm/File.edit.file.js +2 -1
  49. package/lib/mjs/components/form/Form.d.ts +7 -0
  50. package/lib/mjs/components/form/Form.js +24 -2
  51. package/lib/mjs/components/signature/Signature.js +2 -2
  52. package/lib/mjs/package.json +1 -1
  53. package/lib/mjs/providers/address/GoogleAddressProvider.d.ts +9 -1
  54. package/lib/mjs/providers/address/GoogleAddressProvider.js +94 -32
  55. package/package.json +7 -7
package/lib/mjs/Embed.js CHANGED
@@ -14,7 +14,7 @@ export class Formio {
14
14
  Formio._formioReady = ready;
15
15
  Formio._formioReadyReject = reject;
16
16
  });
17
- static version = '5.5.2';
17
+ static version = '5.6.0';
18
18
  static setLicense(license, norecurse = false) {
19
19
  Formio.license = license;
20
20
  if (!norecurse && Formio.FormioClass) {
package/lib/mjs/Formio.js CHANGED
@@ -4,7 +4,7 @@ import CDN from './CDN';
4
4
  import Providers from './providers';
5
5
  FormioCore.cdn = new CDN();
6
6
  FormioCore.Providers = Providers;
7
- FormioCore.version = '5.5.2';
7
+ FormioCore.version = '5.6.0';
8
8
  CDN.defaultCDN = FormioCore.version.includes('rc')
9
9
  ? 'https://cdn.test-form.io'
10
10
  : 'https://cdn.form.io';
@@ -924,7 +924,7 @@ export default class Component extends Element {
924
924
  return '';
925
925
  }
926
926
  // Use _userInput: true to ignore translations from defaults
927
- if (text in enTranslation && params._userInput) {
927
+ if (this.i18next?.language === 'en' && text in enTranslation && params._userInput) {
928
928
  return text;
929
929
  }
930
930
  params.data = params.data || this.rootValue;
@@ -414,8 +414,12 @@ export default class AddressComponent extends ContainerComponent {
414
414
  this.searchInput.forEach((element, index) => {
415
415
  if (!this.builderMode && element && this.provider) {
416
416
  if (this.component.provider === 'google') {
417
+ if (this.component.enableNewPlacesApi) {
418
+ this.provider.options.version = 'newPlacesApi';
419
+ }
417
420
  this.googlePlacesCleanups = [];
418
- this.provider.attachAutocomplete(element, index, this.onSelectAddress.bind(this))
421
+ this.provider
422
+ .attachAutocomplete(element, index, this.onSelectAddress.bind(this), this.disabled)
419
423
  .then((cleanup) => {
420
424
  if (typeof cleanup === 'function') {
421
425
  this.googlePlacesCleanups.push(cleanup);
@@ -516,7 +520,6 @@ export default class AddressComponent extends ContainerComponent {
516
520
  if (this.autocompleterWidget && typeof this.autocompleterWidget.destroy === 'function') {
517
521
  this.autocompleterWidget.destroy();
518
522
  }
519
- ;
520
523
  (this.googlePlacesCleanups || []).forEach((cleanup) => {
521
524
  cleanup();
522
525
  });
@@ -118,6 +118,30 @@ declare const _default: ({
118
118
  validate?: undefined;
119
119
  defaultValue?: undefined;
120
120
  description?: undefined;
121
+ } | {
122
+ weight: number;
123
+ type: string;
124
+ input: boolean;
125
+ key: string;
126
+ label: string;
127
+ tooltip: string;
128
+ conditional: {
129
+ json: {
130
+ '===': (string | {
131
+ var: string;
132
+ })[];
133
+ };
134
+ };
135
+ placeholder?: undefined;
136
+ valueProperty?: undefined;
137
+ dataSrc?: undefined;
138
+ data?: undefined;
139
+ validate?: undefined;
140
+ defaultValue?: undefined;
141
+ rows?: undefined;
142
+ editor?: undefined;
143
+ as?: undefined;
144
+ description?: undefined;
121
145
  } | {
122
146
  type: string;
123
147
  input: boolean;
@@ -136,6 +136,19 @@ export default [
136
136
  },
137
137
  },
138
138
  },
139
+ {
140
+ weight: 10,
141
+ type: 'checkbox',
142
+ input: true,
143
+ key: 'enableNewPlacesApi',
144
+ label: 'Places API (New)',
145
+ tooltip: 'Enable the new Google Places API instead of the legacy version.',
146
+ conditional: {
147
+ json: {
148
+ '===': [{ var: 'data.provider' }, 'google'],
149
+ },
150
+ },
151
+ },
139
152
  {
140
153
  type: 'textarea',
141
154
  input: true,
@@ -539,10 +539,11 @@ export default class DataGridComponent extends NestedArrayComponent {
539
539
  }, modified);
540
540
  this.triggerRootChange(flags, changed, modified);
541
541
  }
542
+ const rowData = this.dataValue[rowIndex];
542
543
  this.processRow('checkData', null, {
543
544
  ...flags,
544
545
  changed,
545
- }, row, _.toArray(this.rows[rowIndex]));
546
+ }, rowData, _.toArray(this.rows[rowIndex]));
546
547
  };
547
548
  let columnComponent;
548
549
  if (this.builderMode) {
@@ -92,6 +92,11 @@ export default class FileComponent extends Field {
92
92
  this.abortUploads = [];
93
93
  this.pendingfiles = [];
94
94
  this.resolvedFiles = [];
95
+ const imageSize = Number(this.component.imageSize);
96
+ const isValidImageSize = imageSize > 0;
97
+ if (!isValidImageSize) {
98
+ this.component.imageSize = '200';
99
+ }
95
100
  }
96
101
  get dataReady() {
97
102
  return this.filesReady || Promise.resolve();
@@ -635,7 +640,7 @@ export default class FileComponent extends Field {
635
640
  fileKey: this.component.fileKey || 'file',
636
641
  storage: this.component.storage,
637
642
  options: this.component.storage === 'url'
638
- ? (this.component.urlRequestOptions || this.component.options)
643
+ ? this.component.urlRequestOptions || this.component.options
639
644
  : this.component.options,
640
645
  file,
641
646
  size: file.size,
@@ -668,7 +673,9 @@ export default class FileComponent extends Field {
668
673
  return fileWithSameNameUploaded || fileWithSameNameUploading
669
674
  ? {
670
675
  status: 'error',
671
- message: this.t(fileWithSameNameUploading ? 'fileWithDuplicatedNameInProgress' : 'fileWithDuplicatedNameLoaded'),
676
+ message: this.t(fileWithSameNameUploading
677
+ ? 'fileWithDuplicatedNameInProgress'
678
+ : 'fileWithDuplicatedNameLoaded'),
672
679
  }
673
680
  : {};
674
681
  }
@@ -866,8 +873,8 @@ export default class FileComponent extends Field {
866
873
  }
867
874
  async deleteFile(fileInfo) {
868
875
  const options = this.component.storage === 'url'
869
- ? (this.component.urlRequestOptions || this.component.options || {})
870
- : (this.component.options || {});
876
+ ? this.component.urlRequestOptions || this.component.options || {}
877
+ : this.component.options || {};
871
878
  if (fileInfo &&
872
879
  ['url', 'indexeddb', 's3', 'azure', 'googledrive'].includes(this.component.storage)) {
873
880
  const { fileService } = this;
@@ -1006,7 +1013,7 @@ export default class FileComponent extends Field {
1006
1013
  delete fileToSync.progress;
1007
1014
  this.redraw();
1008
1015
  if (fileInfo) {
1009
- const fileExists = this.resolvedFiles.find(x => x.fileInfo.originalName === fileToSync.originalName);
1016
+ const fileExists = this.resolvedFiles.find((x) => x.fileInfo.originalName === fileToSync.originalName);
1010
1017
  if (!fileExists && fileToSync.status !== 'error') {
1011
1018
  this.resolvedFiles.push({ fileToSync, fileInfo });
1012
1019
  }
@@ -1060,8 +1067,8 @@ export default class FileComponent extends Field {
1060
1067
  }
1061
1068
  getFile(fileInfo) {
1062
1069
  const options = this.component.storage === 'url'
1063
- ? (this.component.urlRequestOptions || this.component.options || {})
1064
- : (this.component.options || {});
1070
+ ? this.component.urlRequestOptions || this.component.options || {}
1071
+ : this.component.options || {};
1065
1072
  const { fileService } = this;
1066
1073
  if (!fileService) {
1067
1074
  return alert(this.t('noFileService'));
@@ -1101,7 +1108,9 @@ export default class FileComponent extends Field {
1101
1108
  return Promise.resolve();
1102
1109
  }
1103
1110
  await this.syncFiles();
1104
- return this.shouldSyncFiles ? Promise.reject(this.t('synchronizationFailed')) : Promise.resolve();
1111
+ return this.shouldSyncFiles
1112
+ ? Promise.reject(this.t('synchronizationFailed'))
1113
+ : Promise.resolve();
1105
1114
  }
1106
1115
  catch (error) {
1107
1116
  return Promise.reject(error.message);
@@ -18,6 +18,7 @@ declare const _default: ({
18
18
  editor?: undefined;
19
19
  as?: undefined;
20
20
  logic?: undefined;
21
+ clearOnHide?: undefined;
21
22
  optionsLabelPosition?: undefined;
22
23
  inline?: undefined;
23
24
  defaultValue?: undefined;
@@ -48,6 +49,7 @@ declare const _default: ({
48
49
  editor?: undefined;
49
50
  as?: undefined;
50
51
  logic?: undefined;
52
+ clearOnHide?: undefined;
51
53
  optionsLabelPosition?: undefined;
52
54
  inline?: undefined;
53
55
  defaultValue?: undefined;
@@ -95,6 +97,7 @@ declare const _default: ({
95
97
  editor?: undefined;
96
98
  as?: undefined;
97
99
  logic?: undefined;
100
+ clearOnHide?: undefined;
98
101
  optionsLabelPosition?: undefined;
99
102
  inline?: undefined;
100
103
  defaultValue?: undefined;
@@ -125,6 +128,7 @@ declare const _default: ({
125
128
  editor?: undefined;
126
129
  as?: undefined;
127
130
  logic?: undefined;
131
+ clearOnHide?: undefined;
128
132
  optionsLabelPosition?: undefined;
129
133
  inline?: undefined;
130
134
  defaultValue?: undefined;
@@ -161,6 +165,7 @@ declare const _default: ({
161
165
  editor?: undefined;
162
166
  as?: undefined;
163
167
  logic?: undefined;
168
+ clearOnHide?: undefined;
164
169
  optionsLabelPosition?: undefined;
165
170
  inline?: undefined;
166
171
  defaultValue?: undefined;
@@ -191,6 +196,7 @@ declare const _default: ({
191
196
  tableView?: undefined;
192
197
  components?: undefined;
193
198
  logic?: undefined;
199
+ clearOnHide?: undefined;
194
200
  optionsLabelPosition?: undefined;
195
201
  inline?: undefined;
196
202
  defaultValue?: undefined;
@@ -237,6 +243,7 @@ declare const _default: ({
237
243
  rows?: undefined;
238
244
  editor?: undefined;
239
245
  as?: undefined;
246
+ clearOnHide?: undefined;
240
247
  optionsLabelPosition?: undefined;
241
248
  inline?: undefined;
242
249
  defaultValue?: undefined;
@@ -259,6 +266,7 @@ declare const _default: ({
259
266
  editor?: undefined;
260
267
  as?: undefined;
261
268
  logic?: undefined;
269
+ clearOnHide?: undefined;
262
270
  optionsLabelPosition?: undefined;
263
271
  inline?: undefined;
264
272
  defaultValue?: undefined;
@@ -281,6 +289,7 @@ declare const _default: ({
281
289
  editor?: undefined;
282
290
  as?: undefined;
283
291
  logic?: undefined;
292
+ clearOnHide?: undefined;
284
293
  optionsLabelPosition?: undefined;
285
294
  inline?: undefined;
286
295
  defaultValue?: undefined;
@@ -311,6 +320,7 @@ declare const _default: ({
311
320
  editor?: undefined;
312
321
  as?: undefined;
313
322
  logic?: undefined;
323
+ clearOnHide?: undefined;
314
324
  optionsLabelPosition?: undefined;
315
325
  inline?: undefined;
316
326
  defaultValue?: undefined;
@@ -323,6 +333,7 @@ declare const _default: ({
323
333
  placeholder: string;
324
334
  tooltip: string;
325
335
  weight: number;
336
+ clearOnHide: boolean;
326
337
  conditional: {
327
338
  json: {
328
339
  '==': (boolean | {
@@ -345,6 +356,37 @@ declare const _default: ({
345
356
  inline?: undefined;
346
357
  defaultValue?: undefined;
347
358
  values?: undefined;
359
+ } | {
360
+ type: string;
361
+ input: boolean;
362
+ key: string;
363
+ label: string;
364
+ placeholder: string;
365
+ tooltip: string;
366
+ weight: number;
367
+ conditional: {
368
+ json: {
369
+ '==': (boolean | {
370
+ var: string;
371
+ })[];
372
+ '==='?: undefined;
373
+ in?: undefined;
374
+ };
375
+ };
376
+ valueProperty?: undefined;
377
+ dataSrc?: undefined;
378
+ data?: undefined;
379
+ tableView?: undefined;
380
+ components?: undefined;
381
+ rows?: undefined;
382
+ editor?: undefined;
383
+ as?: undefined;
384
+ logic?: undefined;
385
+ clearOnHide?: undefined;
386
+ optionsLabelPosition?: undefined;
387
+ inline?: undefined;
388
+ defaultValue?: undefined;
389
+ values?: undefined;
348
390
  } | {
349
391
  type: string;
350
392
  input: boolean;
@@ -370,6 +412,7 @@ declare const _default: ({
370
412
  editor?: undefined;
371
413
  as?: undefined;
372
414
  logic?: undefined;
415
+ clearOnHide?: undefined;
373
416
  } | {
374
417
  type: string;
375
418
  input: boolean;
@@ -393,6 +436,7 @@ declare const _default: ({
393
436
  editor?: undefined;
394
437
  as?: undefined;
395
438
  logic?: undefined;
439
+ clearOnHide?: undefined;
396
440
  optionsLabelPosition?: undefined;
397
441
  inline?: undefined;
398
442
  defaultValue?: undefined;
@@ -290,8 +290,9 @@ export default [
290
290
  key: 'imageSize',
291
291
  label: 'Image Size',
292
292
  placeholder: '100',
293
- tooltip: 'The image size for previewing images.',
293
+ tooltip: 'The image size, in square pixels, for previewing images.',
294
294
  weight: 40,
295
+ clearOnHide: false,
295
296
  conditional: {
296
297
  json: {
297
298
  '==': [{ var: 'data.image' }, true],
@@ -63,6 +63,13 @@ export default class FormComponent extends Component {
63
63
  checkComponentConditions(data: any, flags: any, row: any): any;
64
64
  calculateValue(data: any, flags: any, row: any): any;
65
65
  setPristine(pristine: any): void;
66
+ /**
67
+ * Determine if an ancestor is hidden by the `hidden` JSON flag alone. An ancestor that has its
68
+ * own condition is governed by that condition instead and is left to `parentConditionallyHidden`
69
+ * — the `hidden` flag and conditionally-hidden are distinct states (FIO-11691).
70
+ * @returns {boolean} - TRUE if an ancestor is hidden in the JSON and has no condition.
71
+ */
72
+ hasAncestorHiddenInJsonOnly(): boolean;
66
73
  /**
67
74
  * Determine if the subform should be submitted.
68
75
  * @returns {*|boolean} - TRUE if the subform should be submitted, FALSE if it should not.
@@ -554,16 +554,38 @@ export default class FormComponent extends Component {
554
554
  this.subForm.setPristine(pristine);
555
555
  }
556
556
  }
557
+ /**
558
+ * Determine if an ancestor is hidden by the `hidden` JSON flag alone. An ancestor that has its
559
+ * own condition is governed by that condition instead and is left to `parentConditionallyHidden`
560
+ * — the `hidden` flag and conditionally-hidden are distinct states (FIO-11691).
561
+ * @returns {boolean} - TRUE if an ancestor is hidden in the JSON and has no condition.
562
+ */
563
+ hasAncestorHiddenInJsonOnly() {
564
+ let currentParent = this.parent;
565
+ while (currentParent) {
566
+ if (currentParent.component.hidden && !currentParent.hasCondition()) {
567
+ return true;
568
+ }
569
+ currentParent = currentParent.parent;
570
+ }
571
+ return false;
572
+ }
557
573
  /**
558
574
  * Determine if the subform should be submitted.
559
575
  * @returns {*|boolean} - TRUE if the subform should be submitted, FALSE if it should not.
560
576
  */
561
577
  get shouldSubmit() {
578
+ const savesAsReference = !this.component.hasOwnProperty('reference') || this.component.reference;
562
579
  const hiddenByJsonOnly = !this.hasCondition() && this.component.hidden;
580
+ // Layout ancestors (fieldset, panel, tabs...) hide us without appearing in our data path, so
581
+ // their hidden state is only reachable by walking the instance chain (FIO-9879).
582
+ const hiddenByAncestor = this.hasAncestorHiddenInJsonOnly() || this.parentConditionallyHidden();
583
+ // Gate on our own clearOnHide, never an ancestor's.
584
+ const clearedWhileHidden = (hiddenByJsonOnly || hiddenByAncestor) && this.component.clearOnHide;
563
585
  return (this.subFormReady &&
564
- (!this.component.hasOwnProperty('reference') || this.component.reference) &&
586
+ savesAsReference &&
565
587
  !this.shouldConditionallyClear() &&
566
- !(hiddenByJsonOnly && this.component.clearOnHide));
588
+ !clearedWhileHidden);
567
589
  }
568
590
  /**
569
591
  * Returns the data for the subform.
@@ -199,7 +199,7 @@ export default class SignatureComponent extends Input {
199
199
  penColor: this.component.penColor,
200
200
  backgroundColor: this.component.backgroundColor,
201
201
  });
202
- this.signaturePad.addEventListener('endStroke', () => this.setValue(this.signaturePad.toDataURL()));
202
+ this.signaturePad.addEventListener('endStroke', () => this.setValue(this.signaturePad.toDataURL(), { modified: true }));
203
203
  this.refs.signatureImage.setAttribute('src', this.signaturePad.toDataURL());
204
204
  this.onDisabled();
205
205
  // Ensure the signature is always the size of its container.
@@ -228,7 +228,7 @@ export default class SignatureComponent extends Input {
228
228
  event.preventDefault();
229
229
  this.showCanvas(true);
230
230
  this.signaturePad.clear();
231
- this.setValue(this.defaultValue);
231
+ this.setValue(this.defaultValue, { modified: true });
232
232
  });
233
233
  this.setValue(this.dataValue);
234
234
  return superAttach;
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "type": "module",
3
- "version": "5.5.2"
3
+ "version": "5.6.0"
4
4
 
5
5
  }
@@ -39,6 +39,14 @@ export class GoogleAddressProvider extends AddressProvider {
39
39
  * @returns {string} The alternative property to use for display value.
40
40
  */
41
41
  get alternativeDisplayValueProperty(): string;
42
+ /**
43
+ * @returns {string} The property to use for display value on places returned by the new Places API.
44
+ */
45
+ get newApiDisplayValueProperty(): string;
46
+ /**
47
+ * @returns {boolean} Whether the provider is configured to use the new Places API.
48
+ */
49
+ get usesNewPlacesApi(): boolean;
42
50
  /**
43
51
  * @param {AutocompleteOptions} options - The autocomplete options.
44
52
  */
@@ -78,7 +86,7 @@ export class GoogleAddressProvider extends AddressProvider {
78
86
  */
79
87
  addRequiredProviderOptions(options: AutocompleteOptions): void;
80
88
  filterPlace(place: any): {};
81
- attachAutocomplete(elem: any, index: any, onSelectAddress: any): any;
89
+ attachAutocomplete(elem: any, index: any, onSelectAddress: any, disable?: boolean): any;
82
90
  search(): Promise<void>;
83
91
  makeRequest(): Promise<void>;
84
92
  getDisplayValue(address: any): any;
@@ -5,6 +5,7 @@ import { AddressProvider } from './AddressProvider';
5
5
  const GOOGLE_MAPS_BASE_URL = 'https://maps.googleapis.com';
6
6
  const GOOGLE_MAPS_JS_URL = `${GOOGLE_MAPS_BASE_URL}/maps/api/js`;
7
7
  const GOOGLE_MAPS_JS_WITH_PARAMS_URL = `${GOOGLE_MAPS_JS_URL}?v=quarterly&libraries=places&loading=async&callback=googleMapsCallback`;
8
+ const NEW_PLACES_API_VERSION = 'newPlacesApi';
8
9
  /**
9
10
  * @typedef {object} AutocompleteOptions
10
11
  * @property {string[]} fields - The fields to include in the autocomplete response.
@@ -71,6 +72,18 @@ export class GoogleAddressProvider extends AddressProvider {
71
72
  get alternativeDisplayValueProperty() {
72
73
  return 'formatted_address';
73
74
  }
75
+ /**
76
+ * @returns {string} The property to use for display value on places returned by the new Places API.
77
+ */
78
+ get newApiDisplayValueProperty() {
79
+ return 'formattedAddress';
80
+ }
81
+ /**
82
+ * @returns {boolean} Whether the provider is configured to use the new Places API.
83
+ */
84
+ get usesNewPlacesApi() {
85
+ return this.options?.version === NEW_PLACES_API_VERSION;
86
+ }
74
87
  /**
75
88
  * @param {AutocompleteOptions} options - The autocomplete options.
76
89
  */
@@ -88,10 +101,11 @@ export class GoogleAddressProvider extends AddressProvider {
88
101
 
89
102
  */
90
103
  setAutocompleteOptions() {
91
- let options = _.get(this.options, 'autocompleteOptions', {});
92
- if (!_.isObject(options)) {
93
- options = {};
94
- }
104
+ // Clone so the stored provider options are never mutated: the required fields
105
+ // differ per Places API version, and stale fields from a previous computation
106
+ // must not leak into the next one (FIO-10428).
107
+ const storedOptions = _.get(this.options, 'autocompleteOptions', {});
108
+ const options = _.isObject(storedOptions) ? _.cloneDeep(storedOptions) : {};
95
109
  this.addRequiredProviderOptions(options);
96
110
  this.autocompleteOptions = options;
97
111
  }
@@ -131,14 +145,9 @@ export class GoogleAddressProvider extends AddressProvider {
131
145
  * @returns {string[]} The required address properties.
132
146
  */
133
147
  getRequiredAddressProperties() {
134
- return [
135
- 'address_components',
136
- 'formatted_address',
137
- 'geometry',
138
- 'place_id',
139
- 'plus_code',
140
- 'types',
141
- ];
148
+ return this.usesNewPlacesApi
149
+ ? ['addressComponents', 'formattedAddress', 'location', 'viewport', 'id', 'plusCode', 'types']
150
+ : ['address_components', 'formatted_address', 'geometry', 'place_id', 'plus_code', 'types'];
142
151
  }
143
152
  /**
144
153
  * Adds the required provider options to the options.
@@ -167,24 +176,75 @@ export class GoogleAddressProvider extends AddressProvider {
167
176
  }
168
177
  return filteredPlace;
169
178
  }
170
- attachAutocomplete(elem, index, onSelectAddress) {
179
+ attachAutocomplete(elem, index, onSelectAddress, disable = false) {
180
+ if (disable) {
181
+ return Promise.resolve();
182
+ }
171
183
  return Formio.libraryReady(this.getLibraryName()).then(() => {
172
- const autocomplete = new google.maps.places.Autocomplete(elem, this.autocompleteOptions);
173
- const listener = autocomplete.addListener('place_changed', () => {
174
- const place = this.filterPlace(autocomplete.getPlace());
175
- place.formattedPlace = _.get(autocomplete, 'gm_accessors_.place.se.formattedPrediction', place[this.alternativeDisplayValueProperty]);
176
- onSelectAddress(place, elem, index);
177
- });
178
- return () => {
179
- if (google?.maps?.event) {
180
- google.maps.event.clearInstanceListeners(autocomplete);
181
- google.maps.event.removeListener(listener);
182
- }
183
- document.querySelectorAll('.pac-container').forEach((node) => {
184
- if (node.parentNode) {
185
- node.parentNode.removeChild(node);
186
- }
184
+ this.setAutocompleteOptions();
185
+ if (!this.usesNewPlacesApi) {
186
+ const autocomplete = new google.maps.places.Autocomplete(elem, this.autocompleteOptions);
187
+ const listener = autocomplete.addListener('place_changed', () => {
188
+ const place = this.filterPlace(autocomplete.getPlace());
189
+ place.formattedPlace = _.get(autocomplete, 'gm_accessors_.place.se.formattedPrediction', place[this.alternativeDisplayValueProperty]);
190
+ onSelectAddress(place, elem, index);
187
191
  });
192
+ return () => {
193
+ if (google?.maps?.event) {
194
+ google.maps.event.clearInstanceListeners(autocomplete);
195
+ google.maps.event.removeListener(listener);
196
+ }
197
+ document.querySelectorAll('.pac-container').forEach((node) => {
198
+ if (node.parentNode) {
199
+ node.parentNode.removeChild(node);
200
+ }
201
+ });
202
+ };
203
+ }
204
+ const { fields, componentRestrictions, ...elementOptions } = this.autocompleteOptions;
205
+ const country = componentRestrictions?.country;
206
+ if (country) {
207
+ // The new element spells legacy componentRestrictions.country as includedRegionCodes.
208
+ elementOptions.includedRegionCodes = _.castArray(country);
209
+ }
210
+ elementOptions.noInputIcon = true;
211
+ const autocomplete = new google.maps.places.PlaceAutocompleteElement(elementOptions);
212
+ if (elem?.value) {
213
+ autocomplete.value = elem.value;
214
+ }
215
+ autocomplete.classList.add('form-control');
216
+ // Replace the existing element with the autocomplete element
217
+ elem.parentNode.replaceChildren(autocomplete);
218
+ const handlePlaceSelect = async (event) => {
219
+ let selectedPlace;
220
+ try {
221
+ const place = event.placePrediction.toPlace();
222
+ await place.fetchFields({
223
+ fields: fields || this.getRequiredAddressProperties(),
224
+ });
225
+ const convertedPlace = {
226
+ addressComponents: place.addressComponents || [],
227
+ formattedAddress: place.formattedAddress || '',
228
+ location: place.location || {},
229
+ viewport: place.viewport || {},
230
+ id: place.id || '',
231
+ plusCode: place.plusCode || {},
232
+ types: place.types || [],
233
+ };
234
+ selectedPlace = this.filterPlace(convertedPlace);
235
+ selectedPlace.formattedPlace = convertedPlace.formattedAddress;
236
+ }
237
+ catch (err) {
238
+ // fetchFields rejections are otherwise swallowed by the Maps event
239
+ // dispatcher; never let a failed details request drop the selection.
240
+ console.warn(`Unable to fetch the selected place's details: ${err}`);
241
+ selectedPlace = { formattedPlace: String(event.placePrediction?.text ?? '') };
242
+ }
243
+ onSelectAddress(selectedPlace, elem, index);
244
+ };
245
+ autocomplete.addEventListener('gmp-select', handlePlaceSelect);
246
+ return () => {
247
+ autocomplete.removeEventListener('gmp-select', handlePlaceSelect);
188
248
  };
189
249
  });
190
250
  }
@@ -195,10 +255,12 @@ export class GoogleAddressProvider extends AddressProvider {
195
255
  return Promise.resolve();
196
256
  }
197
257
  getDisplayValue(address) {
198
- const displayedProperty = _.has(address, this.displayValueProperty)
199
- ? this.displayValueProperty
200
- : this.alternativeDisplayValueProperty;
201
- return _.get(address, displayedProperty, '');
258
+ const displayedProperty = [
259
+ this.displayValueProperty,
260
+ this.alternativeDisplayValueProperty,
261
+ this.newApiDisplayValueProperty,
262
+ ].find((property) => _.has(address, property));
263
+ return displayedProperty ? _.get(address, displayedProperty, '') : '';
202
264
  }
203
265
  /**
204
266
  * Tries to remove the library if api key for loaded script is different.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@formio/js",
3
- "version": "5.5.2",
3
+ "version": "5.6.0",
4
4
  "description": "JavaScript powered Forms with JSON Form Builder",
5
5
  "main": "lib/cjs/index.js",
6
6
  "exports": {
@@ -56,6 +56,8 @@
56
56
  },
57
57
  "homepage": "https://github.com/formio/formio.js#readme",
58
58
  "dependencies": {
59
+ "@formio/bootstrap": "^4.0.5",
60
+ "@formio/core": "^2.8.3",
59
61
  "@formio/text-mask-addons": "^3.8.0-formio.4",
60
62
  "@formio/vanilla-text-mask": "^5.1.1-formio.1",
61
63
  "abortcontroller-polyfill": "^1.7.5",
@@ -69,7 +71,7 @@
69
71
  "core-js": "^3.37.1",
70
72
  "dialog-polyfill": "^0.5.6",
71
73
  "dom-autoscroller": "^2.3.4",
72
- "dompurify": "^3.4.12",
74
+ "dompurify": "^3.4.13",
73
75
  "downloadjs": "^1.4.7",
74
76
  "dragula": "^3.7.3",
75
77
  "eventemitter3": "^5.0.1",
@@ -88,11 +90,10 @@
88
90
  "signature_pad": "^4.2.0",
89
91
  "string-hash": "^1.1.3",
90
92
  "tippy.js": "^6.3.7",
91
- "vanilla-picker": "^2.12.3",
92
- "@formio/bootstrap": "^4.0.5",
93
- "@formio/core": "^2.8.2"
93
+ "vanilla-picker": "^2.12.3"
94
94
  },
95
95
  "devDependencies": {
96
+ "@formio/eslint-config": "0.0.0",
96
97
  "@types/node": "^22.15.19",
97
98
  "ace-builds": "1.34.2",
98
99
  "async-limiter": "^2.0.0",
@@ -145,8 +146,7 @@
145
146
  "webpack-bundle-analyzer": "^4.10.2",
146
147
  "webpack-cli": "^5.1.1",
147
148
  "webpack-node-externals": "^3.0.0",
148
- "webpack-stream": "^7.0.0",
149
- "@formio/eslint-config": "0.0.0"
149
+ "webpack-stream": "^7.0.0"
150
150
  },
151
151
  "ossRepo": {
152
152
  "repo": "https://github.com/formio/formio.js.git",