@sumaris-net/ngx-components 1.20.0-rc5 → 1.20.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +7 -7
  2. package/bundles/sumaris-net.ngx-components.umd.js +1502 -1316
  3. package/bundles/sumaris-net.ngx-components.umd.js.map +1 -1
  4. package/bundles/sumaris-net.ngx-components.umd.min.js +2 -2
  5. package/bundles/sumaris-net.ngx-components.umd.min.js.map +1 -1
  6. package/doc/changelog.md +50 -2
  7. package/esm2015/src/app/admin/services/person.service.js +3 -3
  8. package/esm2015/src/app/admin/users/list/users.js +1 -5
  9. package/esm2015/src/app/core/account/account.js +19 -6
  10. package/esm2015/src/app/core/core.module.js +4 -3
  11. package/esm2015/src/app/core/form/entity-editor.class.js +33 -11
  12. package/esm2015/src/app/core/form/form-buttons-bar.component.js +8 -5
  13. package/esm2015/src/app/core/form/list.form.js +1 -1
  14. package/esm2015/src/app/core/home/home.js +2 -2
  15. package/esm2015/src/app/core/install/install-upgrade-card.component.js +10 -40
  16. package/esm2015/src/app/core/menu/menu.component.js +77 -28
  17. package/esm2015/src/app/core/menu/menu.service.js +37 -12
  18. package/esm2015/src/app/core/offline/update-offline-mode-card.component.js +26 -0
  19. package/esm2015/src/app/core/services/account.service.js +2 -2
  20. package/esm2015/src/app/core/services/model/entity.model.js +3 -5
  21. package/esm2015/src/app/core/services/model/filter.model.js +1 -1
  22. package/esm2015/src/app/core/services/model/referential.model.js +1 -4
  23. package/esm2015/src/app/core/services/platform.service.js +44 -17
  24. package/esm2015/src/app/core/services/storage/entities-storage.service.js +36 -35
  25. package/esm2015/src/app/core/services/storage/entity-store.class.js +4 -5
  26. package/esm2015/src/app/core/table/entities-table-datasource.class.js +129 -193
  27. package/esm2015/src/app/core/table/memory-table.class.js +3 -3
  28. package/esm2015/src/app/core/table/table.class.js +96 -95
  29. package/esm2015/src/app/core/text-popover/text-form.component.js +1 -1
  30. package/esm2015/src/app/shared/dates.js +4 -8
  31. package/esm2015/src/app/shared/files.js +26 -1
  32. package/esm2015/src/app/shared/form/field.component.js +2 -2
  33. package/esm2015/src/app/shared/functions.js +5 -2
  34. package/esm2015/src/app/shared/guard/component-dirty.guard.js +3 -3
  35. package/esm2015/src/app/shared/material/autocomplete/material.autocomplete.js +82 -58
  36. package/esm2015/src/app/shared/material/autocomplete/testing/autocomplete.test.js +24 -9
  37. package/esm2015/src/app/shared/material/chips/material.chips.js +3 -2
  38. package/esm2015/src/app/shared/material/datetime/material.date.js +2 -2
  39. package/esm2015/src/app/shared/material/datetime/material.dateshort.js +2 -2
  40. package/esm2015/src/app/shared/material/datetime/material.datetime.js +7 -8
  41. package/esm2015/src/app/shared/material/datetime/testing/mat-date-time.test.js +6 -4
  42. package/esm2015/src/app/shared/material/latlong/latlong.utils.js +1 -1
  43. package/esm2015/src/app/shared/material/latlong/material.latlong.js +57 -51
  44. package/esm2015/src/app/shared/material/latlong/testing/latlong.test.js +2 -2
  45. package/esm2015/src/app/shared/material/swipe/material.swipe.js +7 -6
  46. package/esm2015/src/app/shared/pipes/form.pipes.js +86 -23
  47. package/esm2015/src/app/shared/pipes/latlong-format.pipe.js +17 -24
  48. package/esm2015/src/app/shared/pipes/number-format.pipe.js +6 -3
  49. package/esm2015/src/app/shared/pipes/pipes.module.js +6 -4
  50. package/esm2015/src/app/shared/platforms.js +2 -1
  51. package/esm2015/src/app/shared/services/job.utils.js +4 -3
  52. package/esm2015/src/app/shared/toolbar/toolbar.js +25 -9
  53. package/esm2015/src/app/shared/validator/validators.js +9 -4
  54. package/esm2015/src/app/vendor.js +7 -1
  55. package/esm2015/sumaris-net.ngx-components.js +8 -7
  56. package/fesm2015/sumaris-net.ngx-components.js +1054 -833
  57. package/fesm2015/sumaris-net.ngx-components.js.map +1 -1
  58. package/package.json +2 -2
  59. package/src/app/core/form/entity-editor.class.d.ts +2 -1
  60. package/src/app/core/form/form-buttons-bar.component.d.ts +3 -1
  61. package/src/app/core/install/install-upgrade-card.component.d.ts +1 -6
  62. package/src/app/core/menu/menu.component.d.ts +12 -7
  63. package/src/app/core/menu/menu.service.d.ts +15 -6
  64. package/src/app/core/offline/update-offline-mode-card.component.d.ts +8 -0
  65. package/src/app/core/services/model/entity.model.d.ts +0 -1
  66. package/src/app/core/services/model/filter.model.d.ts +1 -1
  67. package/src/app/core/services/model/referential.model.d.ts +3 -4
  68. package/src/app/core/services/platform.service.d.ts +1 -0
  69. package/src/app/core/services/storage/entities-storage.service.d.ts +10 -6
  70. package/src/app/core/services/storage/entity-store.class.d.ts +2 -2
  71. package/src/app/core/table/entities-table-datasource.class.d.ts +28 -26
  72. package/src/app/core/table/table.class.d.ts +2 -2
  73. package/src/app/shared/dates.d.ts +3 -3
  74. package/src/app/shared/files.d.ts +10 -0
  75. package/src/app/shared/functions.d.ts +1 -0
  76. package/src/app/shared/material/autocomplete/material.autocomplete.d.ts +2 -1
  77. package/src/app/shared/material/autocomplete/testing/autocomplete.test.d.ts +4 -2
  78. package/src/app/shared/material/chips/material.chips.d.ts +2 -1
  79. package/src/app/shared/material/latlong/latlong.utils.d.ts +2 -2
  80. package/src/app/shared/material/latlong/material.latlong.d.ts +15 -10
  81. package/src/app/shared/material/swipe/material.swipe.d.ts +4 -3
  82. package/src/app/shared/pipes/form.pipes.d.ts +20 -4
  83. package/src/app/shared/pipes/latlong-format.pipe.d.ts +6 -4
  84. package/src/app/shared/pipes/number-format.pipe.d.ts +3 -1
  85. package/src/app/shared/toolbar/toolbar.d.ts +5 -2
  86. package/src/app/shared/validator/validators.d.ts +6 -1
  87. package/src/app/vendor.d.ts +5 -0
  88. package/src/assets/i18n/en-US.json +2 -1
  89. package/src/assets/i18n/en.json +2 -1
  90. package/src/assets/i18n/fr.json +4 -1
  91. package/src/theme/_ngx-components.globals.scss +1 -0
  92. package/src/theme/_ngx-components.scss +32 -20
  93. package/sumaris-net.ngx-components.d.ts +7 -6
  94. package/sumaris-net.ngx-components.metadata.json +1 -1
@@ -32,14 +32,13 @@ import { MatAutocomplete, MatAutocompleteTrigger, MatAutocompleteModule, MAT_AUT
32
32
  import { __awaiter, __decorate } from 'tslib';
33
33
  import { Validators, NG_VALUE_ACCESSOR, FormGroupDirective, AbstractControl, FormGroup, FormArray, FormControl, ReactiveFormsModule, FormBuilder } from '@angular/forms';
34
34
  import { timer, merge, fromEvent, BehaviorSubject, Subscription, Subject, isObservable, from, of, noop as noop$9, Observable, defer, forkJoin, combineLatest, EMPTY } from 'rxjs';
35
- import { filter, first, map, takeUntil, switchMap, startWith, debounceTime, takeWhile, tap, distinctUntilChanged, mergeMap, catchError, throttleTime, skip } from 'rxjs/operators';
35
+ import { filter, first, map, takeUntil, switchMap, tap, debounceTime, startWith, distinctUntilChanged, mergeMap, catchError, throttleTime, skip } from 'rxjs/operators';
36
36
  import { trigger, transition, style, animate, state } from '@angular/animations';
37
37
  import { TranslateService, TranslateModule } from '@ngx-translate/core';
38
38
  import { IonicModule, Platform, IonRouterOutlet, ModalController, PopoverController, IonicSafeString, createAnimation, ToastController, AlertController, IonicRouteStrategy, NavParams, MenuController, IonContent, IonInfiniteScroll } from '@ionic/angular';
39
39
  import * as momentImported from 'moment';
40
40
  import { isMoment } from 'moment';
41
41
  import { MomentDateAdapter, MatMomentDateModule } from '@angular/material-moment-adapter';
42
- import * as momentTZImported from 'moment-timezone';
43
42
  import { Keyboard } from '@ionic-native/keyboard/ngx';
44
43
  import { TextMaskModule } from 'angular2-text-mask';
45
44
  import { NgxMaterialTimepickerModule } from 'ngx-material-timepicker';
@@ -47,7 +46,7 @@ import { MatDatepickerModule } from '@angular/material/datepicker';
47
46
  import { MatDividerModule } from '@angular/material/divider';
48
47
  import { MatTooltipModule } from '@angular/material/tooltip';
49
48
  import { MatChipsModule } from '@angular/material/chips';
50
- import { Router, ActivatedRoute, RouterModule, RouteReuseStrategy } from '@angular/router';
49
+ import { ActivatedRoute, Router, RouterModule, RouteReuseStrategy } from '@angular/router';
51
50
  import { HammerGestureConfig, EventManager } from '@angular/platform-browser';
52
51
  import { HttpResponse, HttpClient, HttpHeaders, HttpEventType, HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
53
52
  import { ColorPickerModule } from 'ngx-color-picker';
@@ -94,6 +93,7 @@ import { NgxJdenticonModule } from 'ngx-jdenticon';
94
93
  import 'tweetnacl-util';
95
94
  import 'scrypt-async';
96
95
  import 'hammerjs';
96
+ import * as momentTZImported from 'moment-timezone';
97
97
 
98
98
  const ENVIRONMENT = new InjectionToken('ENV');
99
99
  class Environment {
@@ -222,6 +222,9 @@ function replaceAll(value, searchString, replacement) {
222
222
  }
223
223
  return value;
224
224
  }
225
+ function escapeRegExp(value) {
226
+ return value.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
227
+ }
225
228
  function removeEnd(value, end) {
226
229
  return (value === null || value === void 0 ? void 0 : value.endsWith(end || '')) ? value.substr(0, value.length - end.length) : value;
227
230
  }
@@ -251,7 +254,7 @@ function suggestFromArray(items, value, opts) {
251
254
  if (isNotNilOrBlank(value)) {
252
255
  // If wildcard, search using regexp
253
256
  if (value.indexOf('*') !== -1) {
254
- value = value.replace('*', '.*');
257
+ value = value.replace(/[*]+/g, '.*');
255
258
  filteredItems = filteredItems.filter(v => keys.findIndex(key => matchUpperCase(getPropertyByPathAsString(v, key), value)) !== -1);
256
259
  }
257
260
  // Else (no wildcard), search using startsWith
@@ -1089,6 +1092,7 @@ const isMobile = (win) => {
1089
1092
  if (_cache.mobile === null || _cache.mobile === undefined) {
1090
1093
  _cache.mobile = isCordova(window)
1091
1094
  || isAndroid(win) || isIOS(window)
1095
+ // Workaround, for MS Windows touch screen (detected as mobile, but forced as desktop)
1092
1096
  || (isTouchUi(win) && !isWindows(win));
1093
1097
  }
1094
1098
  return _cache.mobile;
@@ -1250,7 +1254,7 @@ class MatAutocompleteField {
1250
1254
  return isNil(this.$filteredItems.value);
1251
1255
  }
1252
1256
  get desktop() {
1253
- return !this.mobile;
1257
+ return !this.mobile && !this.multiple;
1254
1258
  }
1255
1259
  ngOnInit() {
1256
1260
  var _a, _b;
@@ -1271,9 +1275,9 @@ class MatAutocompleteField {
1271
1275
  this.equals = this.equals || this.config.equals;
1272
1276
  this.multiple = toBoolean(this.multiple, this.config.multiple);
1273
1277
  this.mobile = toBoolean(this.mobile, this.config.mobile);
1274
- this.suggestLengthThreshold = toNumber(this.suggestLengthThreshold, this.config.suggestLengthThreshold || 0);
1275
- this.showAllOnFocus = toBoolean(this.showAllOnFocus, toBoolean(this.config.showAllOnFocus, this.suggestLengthThreshold <= 0));
1276
- this.showPanelOnFocus = toBoolean(this.showPanelOnFocus, toBoolean(this.config.showPanelOnFocus, true));
1278
+ this.suggestLengthThreshold = toNumber(this.suggestLengthThreshold, this.config.suggestLengthThreshold);
1279
+ this.showAllOnFocus = toBoolean(this.showAllOnFocus, this.config.showAllOnFocus);
1280
+ this.showPanelOnFocus = toBoolean(this.showPanelOnFocus, this.config.showPanelOnFocus);
1277
1281
  this.classList = this.classList || this.config.class;
1278
1282
  this.debounceTime = toNumber(this.debounceTime, this.config.debounceTime);
1279
1283
  this.highlightAccent = toBoolean(this.highlightAccent, toBoolean(this.config.highlightAccent));
@@ -1302,10 +1306,11 @@ class MatAutocompleteField {
1302
1306
  || (classList.includes('min-width-large') && '400px')
1303
1307
  || (classList.includes('min-width-xlarge') && '450px')
1304
1308
  || (classList.includes('mat-autocomplete-panel-full-size') && '100vw')));
1309
+ this.suggestLengthThreshold = this.suggestLengthThreshold || 0;
1310
+ this.showAllOnFocus = toBoolean(this.showAllOnFocus, this.mobile && this.suggestLengthThreshold <= 0);
1305
1311
  this.showPanelOnFocus = toBoolean(this.showPanelOnFocus, true);
1306
1312
  this.debounceTime = toNumber(this.debounceTime, this.mobile ? 650 : 250);
1307
1313
  this.highlightAccent = toBoolean(this.highlightAccent, false);
1308
- this.suggestLengthThreshold = this.suggestLengthThreshold || 0;
1309
1314
  this.matAutocompletePosition = this.matAutocompletePosition || 'auto';
1310
1315
  // No suggestFn: filter on the given items
1311
1316
  if (!this.suggestFn) {
@@ -1327,24 +1332,29 @@ class MatAutocompleteField {
1327
1332
  return this.suggestFn(value, filter); // Loop
1328
1333
  });
1329
1334
  }
1330
- else if (this.mobile) {
1331
- // Manually fill input items, from the given suggest function
1332
- this._subscription.add(this._$filter.pipe(startWith(this.filter), debounceTime(250), takeWhile((_) => this.mobile), // Close subscription, when not mobile (no more mat-select)
1333
- filter(_ => this.suggestLengthThreshold <= 0), switchMap((filter) => this.suggest('*', filter)))
1334
- .subscribe(items => this.$inputItems.next(items)));
1335
+ // Suggest + mobile
1336
+ // => Manually fill input items, from the given suggest function
1337
+ else if (this.mobile || this.multiple) {
1338
+ /*this._subscription.add(
1339
+ this._$filter.pipe(
1340
+ startWith<any, any>(this.filter),
1341
+ debounceTime(250),
1342
+ filter(_ => this.suggestLengthThreshold <= 0),
1343
+ switchMap((filter) => this.suggest('*', filter))
1344
+ )
1345
+ .subscribe(items => this.$inputItems.next(items))
1346
+ );*/
1335
1347
  }
1336
1348
  // Send value to suggest when :
1337
1349
  // - is nil and '*', and no min length threshold
1338
1350
  // - is not nil and not a string type (e.g. object)
1339
1351
  // - is not nil and is a string, and has enough character
1340
- const thresholdFilterFn = (value) => (isNilOrBlank(value) || (value === '*')
1341
- ? (this.suggestLengthThreshold <= 0)
1342
- : (!(typeof value === 'string')
1343
- || value.length >= this.suggestLengthThreshold));
1352
+ const thresholdFilterFn = (value) => this.suggestLengthThreshold <= 0 || (isNotNil(value) && (!(typeof value === 'string')
1353
+ || (value !== '*' && value.trim().length >= this.suggestLengthThreshold)));
1344
1354
  const filteredItemsChanges = merge(
1345
1355
  // Reload event (no distinctUntilChanged() pipe, to force reload even if same value)
1346
1356
  this._reload$
1347
- .pipe(filter(thresholdFilterFn)),
1357
+ .pipe(filter(() => this.enabled), filter(thresholdFilterFn), tap(_ => this.showLoadingSpinner && this.markAsLoading())),
1348
1358
  // Search text (no distinctUntilChanged() because of the debounceTime() pipe)
1349
1359
  this.formControl.valueChanges
1350
1360
  .pipe(filter(value => this.enabled && isNotNilString(value)),
@@ -1352,11 +1362,12 @@ class MatAutocompleteField {
1352
1362
  tap(_ => this.showLoadingSpinner && this.markAsLoading()),
1353
1363
  // Filter if not enough character
1354
1364
  filter(value => {
1355
- if (value.length >= this.suggestLengthThreshold)
1356
- return true;
1357
- // Update display value, to enable the message "not enough characters"
1358
- this.displayValue = value;
1359
- return false;
1365
+ if (value.length <= this.suggestLengthThreshold) {
1366
+ // Update display value, to enable the message "not enough characters"
1367
+ this.displayValue = value;
1368
+ return false;
1369
+ }
1370
+ return true;
1360
1371
  }),
1361
1372
  // Wait a debounce time
1362
1373
  debounceTime(this.debounceTime)
@@ -1365,9 +1376,9 @@ class MatAutocompleteField {
1365
1376
  ),
1366
1377
  // Object set: use it (no distinctUntilChanged() pipe need)
1367
1378
  this.formControl.valueChanges
1368
- .pipe(startWith(this.formControl.value), filter(value => (!this.mobile && isNotNil(value) && typeof value === 'object')), tap(value => {
1379
+ .pipe(startWith(this.formControl.value), filter(value => (isNotNil(value) && typeof value === 'object')), tap(value => {
1369
1380
  // DEBUG
1370
- //if (this.debug) console.debug(this.logPrefix + ' object changes:', value)
1381
+ //if (this.debug) console.debug(this.logPrefix + ' control object changes:', value);
1371
1382
  var _a;
1372
1383
  // Update the display value (if need)
1373
1384
  const displayValue = this.displayWith(value) || '';
@@ -1377,30 +1388,39 @@ class MatAutocompleteField {
1377
1388
  if ((_a = this.matInputText) === null || _a === void 0 ? void 0 : _a.nativeElement) {
1378
1389
  this.matInputText.nativeElement.value = this.displayValue;
1379
1390
  }
1391
+ this.markForCheck();
1380
1392
  }
1381
- })),
1382
- // On dropdown button click (no distinctUntilChanged(), nor debounceTime)
1383
- this.dropButtonClick
1384
- .pipe(filter(event => !this.mobile && this.enabled && (!event || !event.defaultPrevented)), map(_ => this.showAllOnFocus ? '*' : this.formControl.value),
1385
- // Make sure there is enough characters
1386
- filter(thresholdFilterFn)),
1387
- // Merge some observables, with a common distinctUntilChanged() pipe, and NO debounceTime
1393
+ }),
1394
+ // If multiple: do not recompute items (only for the first time, when loading)
1395
+ // when formControl value changes
1396
+ filter(() => !this.multiple || this.loading), map(value => this.showAllOnFocus && this.suggestLengthThreshold <= 0 ? '*' : value)
1397
+ // DEBUG
1398
+ //,tap(value => this.debug && console.debug(this.logPrefix + ' Received an control object value: ', value))
1399
+ ),
1400
+ // Merge some observables, with a common distinctUntilChanged() pipe
1388
1401
  merge(
1389
- // Focus or click => Load all
1390
- merge(this.focused, this.clicked)
1391
- .pipe(filter(_ => this.enabled && !this.mobile), map(_ => this.showAllOnFocus ? '*' : this.formControl.value)
1402
+ // On dropdown button click (no debounceTime)
1403
+ this.dropButtonClick
1404
+ .pipe(filter(event => (!event || !event.defaultPrevented))),
1405
+ // Focus or click => Load all (no debounceTime)
1406
+ merge(this.focused, this.clicked),
1392
1407
  // DEBUG
1393
- //,tap(value => this.debug && console.debug(this.logPrefix + ' Received focused or clicked event: ', value))
1394
- ), merge(this.$inputItems, this._$filter)
1395
- .pipe(filter(_ => this.enabled), map(_ => this.formControl.value)))
1396
- .pipe(
1397
- // Make sure there is enough characters
1398
- filter(thresholdFilterFn),
1408
+ //.pipe(tap(_ => this.debug && console.debug(this.logPrefix + ' Received focused or clicked event'))),
1409
+ // Input items or filter changes
1410
+ merge(this.$inputItems, this._$filter)
1411
+ // DEBUG
1412
+ .pipe(filter(_ => !this.loading)
1413
+ //,tap(_ => this.debug && console.debug(this.logPrefix + ' Received $inputItems or filter event'))
1414
+ ))
1415
+ .pipe(filter(_ => this.enabled), map(_ => this.showAllOnFocus ? '*' : this.formControl.value),
1399
1416
  // Distinguish changes
1400
- // - do NOT filter if loading, to force update after a blur event
1401
- distinctUntilChanged((v1, v2) => !this.loading && (v1 === v2 || this.equals(v1, v2)))
1417
+ distinctUntilChanged((v1, v2) =>
1418
+ // If loading (not loaded yet), simulate that value changed to force load
1419
+ this.loading ? false : (v1 === v2 || this.equals(v1, v2))),
1420
+ // Make sure there is enough characters
1421
+ filter(thresholdFilterFn)
1402
1422
  // DEBUG
1403
- //,tap(value => this.debug && console.debug(this.logPrefix + ' Received update event: ', value))
1423
+ //,tap(value => this.debug && console.debug(this.logPrefix + ' Received update event from observables: ', value))
1404
1424
  ))
1405
1425
  .pipe(
1406
1426
  // Suggest values
@@ -1408,7 +1428,7 @@ class MatAutocompleteField {
1408
1428
  // DEBUG
1409
1429
  //tap(items => this.debug && console.debug(this.logPrefix + ' Received from suggest: ', items)),
1410
1430
  // Store implicit value (will use it onBlur if not other value selected)
1411
- tap(items => this.updateImplicitValue(items)));
1431
+ tap(items => !this.multiple && !this.mobile && this.updateImplicitValue(items)));
1412
1432
  // Fetch more events
1413
1433
  const fetchMoreItemsChanges = this._fetchMore$
1414
1434
  .pipe(tap(event => event === null || event === void 0 ? void 0 : event.preventDefault()), // Avoid to close the mat-select
@@ -1425,7 +1445,7 @@ class MatAutocompleteField {
1425
1445
  fetchMoreItemsChanges)
1426
1446
  .subscribe(items => {
1427
1447
  // Make sure control value is inside (mat-select)
1428
- if (items && this.mobile) {
1448
+ if (items && (this.mobile || this.multiple)) {
1429
1449
  const value = this.formControl.value;
1430
1450
  // If form value is missing: append to list
1431
1451
  if (isNotNil(value)) {
@@ -1443,11 +1463,11 @@ class MatAutocompleteField {
1443
1463
  // Applying implicit value, on blur
1444
1464
  this._subscription.add(this.blurred
1445
1465
  .pipe(
1446
- // Skip if not searchable
1447
- filter(_ => !this.mobile), map(_ => this.formControl.value)
1466
+ // Skip if multiple (no implicit value) or disable
1467
+ filter(_ => !this.multiple && !this.mobile && this.enabled),
1448
1468
  // DEBUG
1449
- //,tap(value => this.debug && console.debug(this.logPrefix + ' Received blurred event: ', value)),
1450
- )
1469
+ //,tap(_ => this.debug && console.debug(this.logPrefix + ' Received blurred event')),
1470
+ map(_ => this.formControl.value))
1451
1471
  .subscribe(value => {
1452
1472
  // When leave component without object, use implicit value if :
1453
1473
  // - an explicit value
@@ -1465,16 +1485,14 @@ class MatAutocompleteField {
1465
1485
  this.formControl.markAsPending({ emitEvent: false, onlySelf: true });
1466
1486
  this.formControl.updateValueAndValidity({ emitEvent: false, onlySelf: true });
1467
1487
  this.checkIfTouched();
1468
- this.$filteredItems.next(undefined);
1488
+ this.markAsLoading();
1469
1489
  }
1470
1490
  this._implicitValue = null; // reset the implicit value
1471
1491
  this.closePanel(); // Clone panel
1472
1492
  }));
1473
1493
  // Listen form control status change
1474
1494
  this._subscription.add(this.formControl.statusChanges
1475
- .pipe(
1476
- //filter(() => this.enabled),
1477
- distinctUntilChanged())
1495
+ .pipe(distinctUntilChanged())
1478
1496
  // Update component state
1479
1497
  .subscribe(() => this.checkIfTouched()));
1480
1498
  this._subscription.add(this.keydownEscape.subscribe(event => {
@@ -1529,8 +1547,10 @@ class MatAutocompleteField {
1529
1547
  * Allow to reload content. Useful when filter has been changed but not detected
1530
1548
  */
1531
1549
  reloadItems(value) {
1550
+ // DEBUG
1551
+ //if (this.debug) console.debug(this.logPrefix + " Asking to reloadItems()");
1532
1552
  // Force a refresh
1533
- this._reload$.next(value || '*');
1553
+ this._reload$.next(value || (this.showAllOnFocus && this.suggestLengthThreshold <= 0 ? '*' : this.formControl.value));
1534
1554
  // Clear the search bar (if hidden)
1535
1555
  if (this.mobile && !this.matSelect.panelOpen && this.ionSearchbar) {
1536
1556
  this.ionSearchbar.value = null;
@@ -1541,6 +1561,7 @@ class MatAutocompleteField {
1541
1561
  // if (this.debug) console.debug(this.logPrefix + ' Writing value: ', value);
1542
1562
  if (value !== this.formControl.value) {
1543
1563
  this.formControl.patchValue(value, { emitEvent: false });
1564
+ this.displayValue = this.displayWith(value);
1544
1565
  this._onChangeCallback(value);
1545
1566
  }
1546
1567
  }
@@ -1642,8 +1663,10 @@ class MatAutocompleteField {
1642
1663
  return;
1643
1664
  // DEBUG
1644
1665
  // console.debug(this.logPrefix + " Received <mat-select> blur event", event);
1666
+ if (event.relatedTarget instanceof HTMLElement && (
1645
1667
  // Ignore event from mat-option
1646
- if (event.relatedTarget instanceof HTMLElement && event.relatedTarget.tagName === 'MAT-OPTION') {
1668
+ (event.relatedTarget.tagName === 'MAT-OPTION')
1669
+ || (event.relatedTarget.tagName === 'INPUT') && event.relatedTarget.classList.contains('searchbar-input'))) {
1647
1670
  event.preventDefault();
1648
1671
  if (event.stopPropagation)
1649
1672
  event.stopPropagation();
@@ -1658,6 +1681,10 @@ class MatAutocompleteField {
1658
1681
  clearValue(event) {
1659
1682
  this.writeValue(null);
1660
1683
  event.stopPropagation();
1684
+ if (this.ionSearchbar) {
1685
+ this.ionSearchbar.value = null;
1686
+ }
1687
+ this.markAsLoading();
1661
1688
  }
1662
1689
  _initAutocompleteInfiniteScroll(threshold) {
1663
1690
  if (!this.autocomplete)
@@ -1724,7 +1751,7 @@ class MatAutocompleteField {
1724
1751
  // Call suggestion function
1725
1752
  let res = yield this.suggestFn(value, filter);
1726
1753
  // DEBUG
1727
- //console.debug(this.logPrefix + ' Filtered items by suggestFn:', value, res);
1754
+ //if (this.debug) console.debug(this.logPrefix + ' Filtered items by suggestFn:', value, res);
1728
1755
  if (!res) {
1729
1756
  this._itemCount = 0;
1730
1757
  this._onFetchMoreCallback = undefined;
@@ -1762,7 +1789,7 @@ class MatAutocompleteField {
1762
1789
  });
1763
1790
  }
1764
1791
  updateImplicitValue(items) {
1765
- if (!this.mobile) {
1792
+ if (!this.multiple) {
1766
1793
  // Store implicit value (will use it onBlur if not other value selected)
1767
1794
  if (items && items.length === 1) {
1768
1795
  this._implicitValue = items[0];
@@ -1815,7 +1842,7 @@ MatAutocompleteField.decorators = [
1815
1842
  { type: Component, args: [{
1816
1843
  // eslint-disable-next-line @angular-eslint/component-selector
1817
1844
  selector: 'mat-autocomplete-field',
1818
- template: "<ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col class=\"ion-no-padding\">\n <!-- readonly -->\n <mat-form-field *ngIf=\"_readonly; else writable\"\n [floatLabel]=\"floatLabel\"\n [title]=\"displayValue\"\n class=\"mat-form-field-disabled\">\n\n <div matPrefix>\n <ng-container *ngTemplateOutlet=\"matPrefixTemplate\"></ng-container>\n </div>\n\n <input matInput hidden type=\"text\"\n [placeholder]=\"placeholder\"\n [formControl]=\"formControl\" readonly>\n <ion-text>{{displayWith(value)}}</ion-text>\n\n <div matSuffix>\n <ng-container *ngTemplateOutlet=\"matSuffixTemplate\"></ng-container>\n </div>\n\n </mat-form-field>\n\n <ng-template #writable>\n <mat-form-field [floatLabel]=\"floatLabel\"\n [class.mat-form-field-disabled]=\"disabled\"\n [title]=\"displayValue\">\n\n <div matPrefix>\n <ng-container *ngTemplateOutlet=\"matPrefixTemplate\"></ng-container>\n </div>\n\n <!-- Mobile or Mutliple (use <mat-select>) -->\n <mat-select #matSelect\n *ngIf=\"mobile || multiple; else desktopTemplate\"\n [disableRipple]=\"true\"\n [placeholder]=\"placeholder\"\n [formControl]=\"formControl\"\n [tabindex]=\"_tabindex\"\n [panelClass]=\"classList\"\n [style.width]=\"panelWidth\"\n (focus)=\"filterMatSelectFocusEvent($event)\"\n (blur)=\"filterMatSelectBlurEvent($event)\"\n (keydown.escape)=\"keydownEscape.emit($event)\"\n (keyup.enter)=\"keyupEnter.emit($event)\"\n [compareWith]=\"equals\"\n [multiple]=\"multiple\"\n [typeaheadDebounceInterval]=\"debounceTime * 10\"\n [required]=\"required\">\n <mat-select-trigger>{{ displayWith(value) }}</mat-select-trigger>\n\n <!-- Search bar -->\n <ion-searchbar *ngIf=\"showSearchBar\" #ionSearchbar\n class=\"mat-select-header\"\n [class.ion-searchbar-sticky]=\"stickySearchBar\"\n inputmode=\"search\"\n autocomplete=\"off\"\n animated=\"true\"\n showClearButton\n [debounce]=\"debounceTime\"\n (ionClear)=\"markAsLoading()\"\n (ionInput)=\"markAsLoading()\"\n (ionChange)=\"ionSearchBarChanged($event, $event.detail.value)\"\n [placeholder]=\"'COMMON.BTN_SEARCH'|translate\"\n ></ion-searchbar>\n\n <!-- Headers -->\n <ion-row class=\"mat-select-header column ion-no-padding\"\n [class.multiple]=\"multiple\"\n [class.ion-searchbar-sticky]=\"showSearchBar && stickySearchBar\">\n <ion-col *ngFor=\"let attr of displayAttributes; index as i\" [size]=\"displayColumnSizes[i]\">\n <ion-label [innerHTML]=\"displayColumnNames[i] | translate\"></ion-label>\n </ion-col>\n </ion-row>\n\n <!-- None option -->\n <mat-option *ngIf=\"!required && !multiple && !clearable\" class=\"ion-no-padding\">\n <ion-row class=\"ion-no-padding\">\n <ion-col class=\"ion-no-padding\">\n <ion-label><i translate>COMMON.EMPTY_OPTION</i></ion-label>\n </ion-col>\n </ion-row>\n </mat-option>\n\n <ng-container *ngIf=\"$filteredItems | async as items; else loadingTemplate\">\n <!-- No item option -->\n <mat-option *ngIf=\"items | isEmptyArray\" class=\"ion-padding text-italic\" disabled>\n <ion-label><i>{{noResultMessage | translate}}</i></ion-label>\n </mat-option>\n\n <!-- Options -->\n <mat-option *ngFor=\"let item of items\" [value]=\"item\" class=\"ion-no-padding\">\n <ion-row [classList]=\"item.classList\">\n <ion-col *ngFor=\"let path of displayAttributes; index as i;\"\n [size]=\"displayColumnSizes[i]\">\n <ion-text [innerHTML]=\"item | propertyGet: path\"></ion-text>\n </ion-col>\n </ion-row>\n </mat-option>\n\n <!-- More item -->\n <mat-option *ngIf=\"_moreItemsCount\" (ngInit)=\"_initMatSelectInfiniteScroll()\"\n class=\"ion-padding\" disabled>\n <ion-skeleton-text [animated]=\"true\" style=\"width: 100%\"></ion-skeleton-text>\n </mat-option>\n </ng-container>\n\n <!-- footer -->\n <ion-row *ngIf=\"itemCount as count\"\n class=\"mat-autocomplete-footer ion-no-padding\">\n <ion-col>\n <ion-text class=\"ion-float-end ion-padding-end\"><i>{{'COMMON.RESULT_COUNT'|translate: {count: count} }}</i></ion-text>\n </ion-col>\n </ion-row>\n\n <!-- Loading spinner -->\n <ng-template #loadingTemplate>\n <mat-option *ngIf=\"(displayValue | strLength) >= suggestLengthThreshold; else waitMoreCharacterTemplate\"\n class=\"ion-padding\" disabled>\n <ion-skeleton-text [animated]=\"true\" style=\"width: 100%\"></ion-skeleton-text>\n </mat-option>\n </ng-template>\n\n <!-- Need more characters -->\n <ng-template #waitMoreCharacterTemplate>\n <mat-option class=\"ion-padding text-italic\" disabled>\n <ion-label>\n {{'INFO.PLEASE_TYPE_MORE_CHARACTERS'|translate:{minLength: suggestLengthThreshold} }}\n </ion-label>\n </mat-option>\n </ng-template>\n </mat-select>\n\n\n <!--\n NOTE :\n - \"selectInputContent($event) || onFocus.emit($event)\" : call onFocus only when to the input is empty (nothing to select)\n -->\n <ng-template #desktopTemplate>\n <input matInput #matInputText type=\"text\"\n [matAutocomplete]=\"autocomplete\"\n [matAutocompletePosition]=\"matAutocompletePosition\"\n [formControl]=\"formControl\"\n [placeholder]=\"placeholder\"\n [appAutofocus]=\"autofocus\"\n [tabindex]=\"_tabindex\"\n [required]=\"required\"\n (click)=\"clicked.emit($event)\"\n (focus)=\"filterInputTextFocusEvent($event)\"\n (blur)=\"filterInputTextBlurEvent($event)\"\n (keydown.escape)=\"keydownEscape.emit($event)\"\n (keyup.enter)=\"keyupEnter.emit($event)\"\n (keyup.arrowdown)=\"!autocomplete.showPanel && dropButtonClick.emit($event)\">\n\n <!-- autocomplete -->\n <mat-autocomplete #autocomplete=\"matAutocomplete\"\n autoActiveFirstOption\n [displayWith]=\"displayWith\"\n [class]=\"classList\"\n [panelWidth]=\"panelWidth\">\n <!-- Headers -->\n <ion-row class=\"mat-autocomplete-header column ion-no-padding\">\n <ion-col *ngFor=\"let attr of displayAttributes; index as i\" [size]=\"displayColumnSizes[i]\">\n <ion-label [innerHTML]=\"displayColumnNames[i] | translate\"></ion-label>\n </ion-col>\n </ion-row>\n\n <!-- Options -->\n <ng-container *ngIf=\"$filteredItems | async as items; else loadingTemplate\">\n <!-- No item option -->\n <mat-option *ngIf=\"items | isEmptyArray\" class=\"ion-padding text-italic\" disabled>\n <ion-label><i>{{noResultMessage | translate}}</i></ion-label>\n </mat-option>\n\n <mat-option *ngFor=\"let item of items\" [value]=\"item\" class=\"ion-no-padding\">\n <ion-row [classList]=\"item.classList\">\n <ion-col *ngFor=\"let path of displayAttributes; index as i;\"\n [size]=\"displayColumnSizes[i]\">\n <ion-text [innerHTML]=\"item | propertyGet: path | highlight: { search: formControl.value, withAccent: highlightAccent }\"></ion-text>\n </ion-col>\n </ion-row>\n </mat-option>\n\n <!-- More item -->\n <mat-option *ngIf=\"_moreItemsCount\" (ngInit)=\"_initAutocompleteInfiniteScroll()\"\n class=\"ion-padding\" disabled>\n <ion-skeleton-text [animated]=\"true\" style=\"width: 100%\"></ion-skeleton-text>\n </mat-option>\n </ng-container>\n\n <!-- Loading spinner -->\n <ng-template #loadingTemplate>\n <mat-option *ngIf=\"(displayValue | strLength) >= suggestLengthThreshold; else waitMoreCharacterTemplate\"\n class=\"ion-padding\" disabled>\n <ion-skeleton-text [animated]=\"true\" style=\"width: 100%\"></ion-skeleton-text>\n </mat-option>\n </ng-template>\n\n <!-- Need more characters -->\n <ng-template #waitMoreCharacterTemplate>\n <mat-option class=\"ion-padding text-italic\" disabled>\n <ion-label>\n {{'INFO.PLEASE_TYPE_MORE_CHARACTERS'|translate:{minLength: suggestLengthThreshold} }}\n </ion-label>\n </mat-option>\n </ng-template>\n\n <!-- footer -->\n <ion-row *ngIf=\"itemCount; let count\" class=\"mat-autocomplete-footer ion-no-padding\">\n <ion-col>\n <ion-text class=\"ion-float-end ion-padding-end\"><i>{{'COMMON.RESULT_COUNT'|translate: {count: count} }}</i></ion-text>\n </ion-col>\n </ion-row>\n\n </mat-autocomplete>\n\n </ng-template>\n\n <div matSuffix>\n <ng-container *ngTemplateOutlet=\"matSuffixTemplate\"></ng-container>\n </div>\n\n <mat-error *ngIf=\"formControl.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngIf=\"formControl.hasError('entity')\" translate>ERROR.FIELD_INVALID</mat-error>\n <ng-content select=\"[matError]\"></ng-content>\n\n </mat-form-field>\n </ng-template>\n </ion-col>\n <ion-col size=\"auto\" class=\"ion-align-self-center\">\n <ng-content select=\"[matAfter]\"></ng-content>\n </ion-col>\n </ion-row>\n</ion-grid>\n\n<ng-template #matPrefixTemplate>\n <ng-content select=\"[matPrefix]\"></ng-content>\n</ng-template>\n\n\n<ng-template #matSuffixTemplate>\n <button matSuffix mat-icon-button tabindex=\"-1\"\n type=\"button\"\n *ngIf=\"desktop\"\n (click)=\"dropButtonClick.emit($event)\"\n [hidden]=\"disabled\">\n <mat-icon class=\"large select-arrow\">arrow_drop_down</mat-icon>\n </button>\n <button matSuffix mat-icon-button tabindex=\"-1\"\n type=\"button\"\n *ngIf=\"clearable\"\n (click)=\"clearValue($event)\"\n [hidden]=\"disabled || !formControl.value\">\n <mat-icon>close</mat-icon>\n </button>\n\n <ng-content select=\"[matSuffix]\"></ng-content>\n</ng-template>\n\n<ng-template #headers>\n <ion-row class=\"mat-autocomplete-header ion-no-padding\">\n <ion-col *ngFor=\"let attr of displayAttributes; index as i\" [size]=\"displayColumnSizes[i]\">\n <ion-label [innerHTML]=\"displayColumnNames[i] | translate\"></ion-label>\n </ion-col>\n </ion-row>\n</ng-template>\n\n<ng-template #options>\n\n\n</ng-template>\n\n",
1845
+ template: "<ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col class=\"ion-no-padding\">\n <!-- readonly -->\n <mat-form-field *ngIf=\"_readonly; else writable\"\n [floatLabel]=\"floatLabel\"\n [appearance]=\"appearance\"\n [title]=\"displayValue\"\n class=\"mat-form-field-disabled\">\n\n <div matPrefix>\n <ng-container *ngTemplateOutlet=\"matPrefixTemplate\"></ng-container>\n </div>\n\n <input matInput hidden type=\"text\"\n [placeholder]=\"placeholder\"\n [formControl]=\"formControl\" readonly>\n <ion-text>{{displayWith(value)}}</ion-text>\n\n <div matSuffix>\n <ng-container *ngTemplateOutlet=\"matSuffixTemplate\"></ng-container>\n </div>\n\n </mat-form-field>\n\n <ng-template #writable>\n <mat-form-field [floatLabel]=\"floatLabel\"\n [class.mat-form-field-disabled]=\"disabled\"\n [title]=\"displayValue\">\n\n <div matPrefix>\n <ng-container *ngTemplateOutlet=\"matPrefixTemplate\"></ng-container>\n </div>\n\n <!-- Mobile or Multiple (use <mat-select>) -->\n <mat-select #matSelect\n *ngIf=\"mobile || multiple; else desktopTemplate\"\n [disableRipple]=\"true\"\n [placeholder]=\"placeholder\"\n [formControl]=\"formControl\"\n [tabindex]=\"_tabindex\"\n [panelClass]=\"classList\"\n [style.width]=\"panelWidth\"\n (focus)=\"filterMatSelectFocusEvent($event)\"\n (blur)=\"filterMatSelectBlurEvent($event)\"\n (keydown.escape)=\"keydownEscape.emit($event)\"\n (keyup.enter)=\"keyupEnter.emit($event)\"\n [compareWith]=\"equals\"\n [multiple]=\"multiple\"\n [typeaheadDebounceInterval]=\"debounceTime * 10\"\n [required]=\"required\">\n <mat-select-trigger>{{ displayWith(value) }}</mat-select-trigger>\n\n <!-- Search bar -->\n <mat-option class=\"mat-select-searchbar\" *ngIf=\"showSearchBar\"\n [class.mat-select-searchbar-sticky]=\"stickySearchBar\"\n disabled>\n <ion-searchbar #ionSearchbar\n inputmode=\"search\"\n autocomplete=\"off\"\n animated=\"true\"\n showClearButton=\"true\"\n [debounce]=\"debounceTime\"\n (ionClear)=\"markAsLoading()\"\n (ionInput)=\"markAsLoading()\"\n (ionChange)=\"ionSearchBarChanged($event, $event.detail.value)\"\n [placeholder]=\"'COMMON.BTN_SEARCH'|translate\"\n ></ion-searchbar>\n </mat-option>\n\n <!-- Headers -->\n <ion-row class=\"mat-select-header column ion-no-padding\"\n [class.multiple]=\"multiple\"\n [class.mat-select-searchbar-sticky]=\"showSearchBar && stickySearchBar\">\n <ion-col *ngFor=\"let attr of displayAttributes; index as i\" [size]=\"displayColumnSizes[i]\">\n <ion-label [innerHTML]=\"displayColumnNames[i] | translate\"></ion-label>\n </ion-col>\n </ion-row>\n\n <!-- None option -->\n <mat-option *ngIf=\"!required && !multiple && !clearable\" class=\"ion-no-padding\">\n <ion-row class=\"ion-no-padding\">\n <ion-col class=\"ion-no-padding\">\n <ion-label><i translate>COMMON.EMPTY_OPTION</i></ion-label>\n </ion-col>\n </ion-row>\n </mat-option>\n\n <ng-container *ngIf=\"$filteredItems | async as items; else loadingTemplate\">\n <!-- No item option -->\n <mat-option *ngIf=\"items | isEmptyArray\" class=\"ion-padding text-italic\" disabled>\n <ion-label><i>{{noResultMessage | translate}}</i></ion-label>\n </mat-option>\n\n <!-- Options -->\n <mat-option *ngFor=\"let item of items\" [value]=\"item\" class=\"ion-no-padding\">\n <ion-row [classList]=\"item.classList\">\n <ion-col *ngFor=\"let path of displayAttributes; index as i;\"\n [size]=\"displayColumnSizes[i]\">\n <ion-text [innerHTML]=\"item | propertyGet: path\"></ion-text>\n </ion-col>\n </ion-row>\n </mat-option>\n\n <!-- More item -->\n <mat-option *ngIf=\"_moreItemsCount\" (ngInit)=\"_initMatSelectInfiniteScroll()\"\n class=\"ion-padding\" disabled>\n <ion-skeleton-text [animated]=\"true\" style=\"width: 100%\"></ion-skeleton-text>\n </mat-option>\n </ng-container>\n\n <!-- footer -->\n <mat-option *ngIf=\"itemCount as count\" class=\"mat-option-footer mat-autocomplete-footer ion-no-padding\" disabled>\n <ion-text class=\"ion-float-end ion-padding-end\"><i>{{'COMMON.RESULT_COUNT'|translate: {count: count} }}</i></ion-text>\n </mat-option>\n\n <!-- Loading spinner -->\n <ng-template #loadingTemplate>\n <mat-option *ngIf=\"(displayValue | strLength) >= suggestLengthThreshold; else waitMoreCharacterTemplate\"\n class=\"ion-padding\" disabled>\n <ion-skeleton-text [animated]=\"true\" style=\"width: 100%\"></ion-skeleton-text>\n </mat-option>\n </ng-template>\n\n <!-- Need more characters -->\n <ng-template #waitMoreCharacterTemplate>\n <mat-option class=\"ion-padding text-italic\" disabled>\n <ion-label>\n {{'INFO.PLEASE_TYPE_MORE_CHARACTERS'|translate:{minLength: suggestLengthThreshold} }}\n </ion-label>\n </mat-option>\n </ng-template>\n </mat-select>\n\n\n <!--\n NOTE :\n - \"selectInputContent($event) || onFocus.emit($event)\" : call onFocus only when to the input is empty (nothing to select)\n -->\n <ng-template #desktopTemplate>\n <input matInput #matInputText type=\"text\"\n [matAutocomplete]=\"autocomplete\"\n [matAutocompletePosition]=\"matAutocompletePosition\"\n [formControl]=\"formControl\"\n [placeholder]=\"placeholder\"\n [appAutofocus]=\"autofocus\"\n [tabindex]=\"_tabindex\"\n [required]=\"required\"\n (click)=\"clicked.emit($event)\"\n (focus)=\"filterInputTextFocusEvent($event)\"\n (blur)=\"filterInputTextBlurEvent($event)\"\n (keydown.escape)=\"keydownEscape.emit($event)\"\n (keyup.enter)=\"keyupEnter.emit($event)\"\n (keyup.arrowdown)=\"!autocomplete.showPanel && dropButtonClick.emit($event)\">\n\n <!-- autocomplete -->\n <mat-autocomplete #autocomplete=\"matAutocomplete\"\n autoActiveFirstOption\n [displayWith]=\"displayWith\"\n [class]=\"classList\"\n [panelWidth]=\"panelWidth\">\n <!-- Headers -->\n <ion-row class=\"mat-autocomplete-header column ion-no-padding\">\n <ion-col *ngFor=\"let attr of displayAttributes; index as i\" [size]=\"displayColumnSizes[i]\">\n <ion-label [innerHTML]=\"displayColumnNames[i] | translate\"></ion-label>\n </ion-col>\n </ion-row>\n\n <!-- Options -->\n <ng-container *ngIf=\"$filteredItems | async as items; else loadingTemplate\">\n <!-- No item option -->\n <mat-option *ngIf=\"items | isEmptyArray\" class=\"ion-padding text-italic\" disabled>\n <ion-label><i>{{noResultMessage | translate}}</i></ion-label>\n </mat-option>\n\n <mat-option *ngFor=\"let item of items\" [value]=\"item\" class=\"ion-no-padding\">\n <ion-row [classList]=\"item.classList\">\n <ion-col *ngFor=\"let path of displayAttributes; index as i;\"\n [size]=\"displayColumnSizes[i]\">\n <ion-text [innerHTML]=\"item | propertyGet: path | highlight: { search: formControl.value, withAccent: highlightAccent }\"></ion-text>\n </ion-col>\n </ion-row>\n </mat-option>\n\n <!-- More item -->\n <mat-option *ngIf=\"_moreItemsCount\" (ngInit)=\"_initAutocompleteInfiniteScroll()\"\n class=\"ion-padding\" disabled>\n <ion-skeleton-text [animated]=\"true\" style=\"width: 100%\"></ion-skeleton-text>\n </mat-option>\n </ng-container>\n\n <!-- Loading spinner -->\n <ng-template #loadingTemplate>\n <mat-option *ngIf=\"(displayValue | strLength) >= suggestLengthThreshold; else waitMoreCharacterTemplate\"\n class=\"ion-padding\" disabled>\n <ion-skeleton-text [animated]=\"true\" style=\"width: 100%\"></ion-skeleton-text>\n </mat-option>\n </ng-template>\n\n <!-- Need more characters -->\n <ng-template #waitMoreCharacterTemplate>\n <mat-option class=\"ion-padding text-italic\" disabled>\n <ion-label>\n {{'INFO.PLEASE_TYPE_MORE_CHARACTERS'|translate:{minLength: suggestLengthThreshold} }}\n </ion-label>\n </mat-option>\n </ng-template>\n\n <!-- footer -->\n <ion-row *ngIf=\"itemCount; let count\" class=\"mat-autocomplete-footer ion-no-padding\">\n <ion-col>\n <ion-text class=\"ion-float-end ion-padding-end\"><i>{{'COMMON.RESULT_COUNT'|translate: {count: count} }}</i></ion-text>\n </ion-col>\n </ion-row>\n\n </mat-autocomplete>\n\n </ng-template>\n\n <div matSuffix>\n <ng-container *ngTemplateOutlet=\"matSuffixTemplate\"></ng-container>\n </div>\n\n <mat-error *ngIf=\"formControl.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngIf=\"formControl.hasError('entity')\" translate>ERROR.FIELD_INVALID</mat-error>\n <ng-content select=\"[matError]\"></ng-content>\n\n </mat-form-field>\n </ng-template>\n </ion-col>\n <ion-col size=\"auto\" class=\"ion-align-self-center\">\n <ng-content select=\"[matAfter]\"></ng-content>\n </ion-col>\n </ion-row>\n</ion-grid>\n\n<ng-template #matPrefixTemplate>\n <ng-content select=\"[matPrefix]\"></ng-content>\n</ng-template>\n\n\n<ng-template #matSuffixTemplate>\n <button matSuffix mat-icon-button tabindex=\"-1\"\n type=\"button\"\n *ngIf=\"!mobile && !multiple\"\n (click)=\"dropButtonClick.emit($event)\"\n [hidden]=\"disabled\">\n <mat-icon class=\"large select-arrow\">arrow_drop_down</mat-icon>\n </button>\n <button matSuffix mat-icon-button tabindex=\"-1\"\n type=\"button\"\n *ngIf=\"clearable\"\n (click)=\"clearValue($event)\"\n [hidden]=\"disabled || !formControl.value\">\n <mat-icon>close</mat-icon>\n </button>\n\n <ng-content select=\"[matSuffix]\"></ng-content>\n</ng-template>\n\n<ng-template #headers>\n <ion-row class=\"mat-autocomplete-header ion-no-padding\">\n <ion-col *ngFor=\"let attr of displayAttributes; index as i\" [size]=\"displayColumnSizes[i]\">\n <ion-label [innerHTML]=\"displayColumnNames[i] | translate\"></ion-label>\n </ion-col>\n </ion-row>\n</ng-template>\n\n<ng-template #options>\n\n\n</ng-template>\n\n",
1819
1846
  providers: [{
1820
1847
  provide: NG_VALUE_ACCESSOR,
1821
1848
  multi: true,
@@ -1836,6 +1863,7 @@ MatAutocompleteField.propDecorators = {
1836
1863
  formControl: [{ type: Input }],
1837
1864
  formControlName: [{ type: Input }],
1838
1865
  floatLabel: [{ type: Input }],
1866
+ appearance: [{ type: Input }],
1839
1867
  placeholder: [{ type: Input }],
1840
1868
  suggestFn: [{ type: Input }],
1841
1869
  required: [{ type: Input }],
@@ -2222,52 +2250,49 @@ function roundFloat(input, maxDecimals) {
2222
2250
  return input;
2223
2251
  }
2224
2252
 
2225
- /**
2226
- * @deprecated use 'latitudeFormat' or 'longitudeFormat' instead
2227
- */
2228
- class LatLongFormatPipe {
2229
- transform(value, args) {
2230
- return ((!args.type || args.type !== 'latitude') ? formatLongitude : formatLatitude)(value, args);
2231
- }
2232
- }
2233
- LatLongFormatPipe.ɵprov = ɵɵdefineInjectable({ factory: function LatLongFormatPipe_Factory() { return new LatLongFormatPipe(); }, token: LatLongFormatPipe, providedIn: "root" });
2234
- LatLongFormatPipe.decorators = [
2235
- { type: Pipe, args: [{
2236
- name: 'latLongFormat'
2237
- },] },
2238
- { type: Injectable, args: [{ providedIn: 'root' },] }
2239
- ];
2240
2253
  class LatitudeFormatPipe {
2241
2254
  constructor() {
2242
2255
  this.transform = formatLatitude;
2243
2256
  }
2244
2257
  }
2245
- LatitudeFormatPipe.ɵprov = ɵɵdefineInjectable({ factory: function LatitudeFormatPipe_Factory() { return new LatitudeFormatPipe(); }, token: LatitudeFormatPipe, providedIn: "root" });
2246
2258
  LatitudeFormatPipe.decorators = [
2247
2259
  { type: Pipe, args: [{
2248
2260
  name: 'latitudeFormat'
2249
- },] },
2250
- { type: Injectable, args: [{ providedIn: 'root' },] }
2261
+ },] }
2251
2262
  ];
2252
2263
  class LongitudeFormatPipe {
2253
2264
  constructor() {
2254
2265
  this.transform = formatLongitude;
2255
2266
  }
2256
2267
  }
2257
- LongitudeFormatPipe.ɵprov = ɵɵdefineInjectable({ factory: function LongitudeFormatPipe_Factory() { return new LongitudeFormatPipe(); }, token: LongitudeFormatPipe, providedIn: "root" });
2258
2268
  LongitudeFormatPipe.decorators = [
2259
2269
  { type: Pipe, args: [{
2260
2270
  name: 'longitudeFormat'
2261
- },] },
2262
- { type: Injectable, args: [{ providedIn: 'root' },] }
2271
+ },] }
2272
+ ];
2273
+ /**
2274
+ * @deprecated use 'latitudeFormat' or 'longitudeFormat' instead
2275
+ */
2276
+ class LatLongFormatPipe {
2277
+ transform(value, opts) {
2278
+ return ((!opts || (opts === null || opts === void 0 ? void 0 : opts.type) !== 'latitude') ? formatLongitude : formatLatitude)(value, opts);
2279
+ }
2280
+ }
2281
+ LatLongFormatPipe.decorators = [
2282
+ { type: Pipe, args: [{
2283
+ name: 'latLongFormat'
2284
+ },] }
2263
2285
  ];
2264
2286
 
2265
2287
  class NumberFormatPipe {
2266
- transform(val, numberFormatOptions) {
2288
+ transform(val, opts) {
2267
2289
  // Format the output to display any way you want here.
2268
2290
  // For instance:
2269
2291
  if (val !== undefined && val !== null) {
2270
- return val.toLocaleString(undefined, numberFormatOptions);
2292
+ if (opts === null || opts === void 0 ? void 0 : opts.fixedDecimals) {
2293
+ return val.toFixed(opts.fixedDecimals);
2294
+ }
2295
+ return val.toLocaleString(undefined, opts);
2271
2296
  }
2272
2297
  else {
2273
2298
  return '';
@@ -2380,15 +2405,9 @@ FileSizePipe.decorators = [
2380
2405
  ];
2381
2406
 
2382
2407
  const moment$1 = momentImported;
2383
- const tz = momentTZImported;
2384
2408
  const DATE_UNIX_TIMESTAMP = 'X';
2385
2409
  const DATE_UNIX_MS_TIMESTAMP = 'x';
2386
2410
  class DateUtils {
2387
- constructor() {
2388
- this.toDateISOString = toDateISOString;
2389
- this.fromDateISOString = fromDateISOString;
2390
- this.toDuration = toDuration;
2391
- }
2392
2411
  static min(date1, date2) {
2393
2412
  return date1 && (!date2 || date1.isSameOrBefore(date2)) ? date1 : date2;
2394
2413
  }
@@ -2438,6 +2457,9 @@ class DateUtils {
2438
2457
  return date.day() === weekday;
2439
2458
  }
2440
2459
  }
2460
+ DateUtils.toDateISOString = toDateISOString;
2461
+ DateUtils.fromDateISOString = fromDateISOString;
2462
+ DateUtils.toDuration = toDuration;
2441
2463
  function toDateISOString(value) {
2442
2464
  if (!value)
2443
2465
  return undefined;
@@ -2908,7 +2930,7 @@ const moment$2 = momentImported;
2908
2930
  // @dynamic
2909
2931
  class SharedValidators {
2910
2932
  static getDoubleRegexp(maxDecimals) {
2911
- if (isNil(maxDecimals) || maxDecimals === -1)
2933
+ if (isNil(maxDecimals))
2912
2934
  return this._REGEXP_CACHE.DOUBLE_BY_MAX_DECIMALS.NO_LIMIT;
2913
2935
  if (maxDecimals < 0)
2914
2936
  throw new Error(`Invalid maxDecimals value: ${maxDecimals}`);
@@ -2969,7 +2991,7 @@ class SharedValidators {
2969
2991
  }
2970
2992
  return null;
2971
2993
  }
2972
- static double(opts) {
2994
+ static decimal(opts) {
2973
2995
  const regexp = this.getDoubleRegexp(opts === null || opts === void 0 ? void 0 : opts.maxDecimals);
2974
2996
  return (control) => {
2975
2997
  let value = control.value;
@@ -2978,7 +3000,7 @@ class SharedValidators {
2978
3000
  return null;
2979
3001
  }
2980
3002
  if (isNotNil(value) && value !== '' && !regexp.test(value)) {
2981
- return { maxDecimals: { maxDecimals: opts === null || opts === void 0 ? void 0 : opts.maxDecimals } };
3003
+ return isNotNil(opts === null || opts === void 0 ? void 0 : opts.maxDecimals) ? { maxDecimals: { maxDecimals: opts.maxDecimals } } : { decimal: true };
2982
3004
  }
2983
3005
  return null;
2984
3006
  };
@@ -3110,6 +3132,7 @@ SharedValidators.I18N_ERROR_KEYS = {
3110
3132
  dateMinDuration: 'ERROR.FIELD_NOT_VALID_DATE_MIN_DURATION',
3111
3133
  dateMaxDuration: 'ERROR.FIELD_NOT_VALID_DATE_MAX_DURATION',
3112
3134
  maxDecimals: 'ERROR.FIELD_MAXIMUM_DECIMALS',
3135
+ double: 'ERROR.FIELD_NOT_VALID_DOUBLE',
3113
3136
  integer: 'ERROR.FIELD_NOT_VALID_INTEGER',
3114
3137
  email: 'ERROR.FIELD_NOT_VALID_EMAIL',
3115
3138
  latitude: 'ERROR.FIELD_NOT_VALID_LATITUDE',
@@ -3118,6 +3141,10 @@ SharedValidators.I18N_ERROR_KEYS = {
3118
3141
  unique: 'ERROR.FIELD_NOT_UNIQUE',
3119
3142
  entity: 'ERROR.FIELD_INVALID'
3120
3143
  };
3144
+ /**
3145
+ * @deprecated use decimal
3146
+ */
3147
+ SharedValidators.double = SharedValidators.decimal;
3121
3148
  // @dynamic
3122
3149
  class SharedFormGroupValidators {
3123
3150
  static dateRange(startDateField, endDateField, msg) {
@@ -4132,8 +4159,8 @@ class FormErrorTranslatePipe {
4132
4159
  // if there is a subscription to onLangChange, clean it
4133
4160
  this._dispose();
4134
4161
  // subscribe to onTranslationChange event, in case the translations change
4135
- if (!this._onFormStatusChange) {
4136
- this._onFormStatusChange = form.statusChanges.subscribe((status) => {
4162
+ if (!this._onFormStatusChanges) {
4163
+ this._onFormStatusChanges = form.statusChanges.subscribe((status) => {
4137
4164
  this._updateValue(form, opts, status);
4138
4165
  });
4139
4166
  }
@@ -4156,9 +4183,9 @@ class FormErrorTranslatePipe {
4156
4183
  * Clean any existing subscription to change events
4157
4184
  */
4158
4185
  _dispose() {
4159
- if (typeof this._onFormStatusChange !== 'undefined') {
4160
- this._onFormStatusChange.unsubscribe();
4161
- this._onFormStatusChange = undefined;
4186
+ if (typeof this._onFormStatusChanges !== 'undefined') {
4187
+ this._onFormStatusChanges.unsubscribe();
4188
+ this._onFormStatusChanges = undefined;
4162
4189
  }
4163
4190
  }
4164
4191
  }
@@ -4177,48 +4204,112 @@ class FormGetPipe {
4177
4204
  return form.get(path);
4178
4205
  }
4179
4206
  }
4180
- FormGetPipe.ɵprov = ɵɵdefineInjectable({ factory: function FormGetPipe_Factory() { return new FormGetPipe(); }, token: FormGetPipe, providedIn: "root" });
4181
4207
  FormGetPipe.decorators = [
4182
4208
  { type: Pipe, args: [{
4183
4209
  name: 'formGet'
4184
- },] },
4185
- { type: Injectable, args: [{ providedIn: 'root' },] }
4210
+ },] }
4186
4211
  ];
4187
4212
  class FormGetControlPipe {
4188
4213
  transform(form, path) {
4189
- return form.get(path);
4214
+ return (form && path && form.get(path) || form);
4190
4215
  }
4191
4216
  }
4192
- FormGetControlPipe.ɵprov = ɵɵdefineInjectable({ factory: function FormGetControlPipe_Factory() { return new FormGetControlPipe(); }, token: FormGetControlPipe, providedIn: "root" });
4193
4217
  FormGetControlPipe.decorators = [
4194
4218
  { type: Pipe, args: [{
4195
4219
  name: 'formGetControl'
4196
- },] },
4197
- { type: Injectable, args: [{ providedIn: 'root' },] }
4220
+ },] }
4198
4221
  ];
4199
4222
  class FormGetArrayPipe {
4200
4223
  transform(form, path) {
4201
- return form.get(path);
4224
+ return (form && path && form.get(path) || form);
4202
4225
  }
4203
4226
  }
4204
- FormGetArrayPipe.ɵprov = ɵɵdefineInjectable({ factory: function FormGetArrayPipe_Factory() { return new FormGetArrayPipe(); }, token: FormGetArrayPipe, providedIn: "root" });
4205
4227
  FormGetArrayPipe.decorators = [
4206
4228
  { type: Pipe, args: [{
4207
4229
  name: 'formGetArray'
4208
- },] },
4209
- { type: Injectable, args: [{ providedIn: 'root' },] }
4230
+ },] }
4210
4231
  ];
4211
4232
  class FormGetGroupPipe {
4212
4233
  transform(form, path) {
4213
- return form.get(path);
4234
+ return (form && path && form.get(path) || form);
4214
4235
  }
4215
4236
  }
4216
- FormGetGroupPipe.ɵprov = ɵɵdefineInjectable({ factory: function FormGetGroupPipe_Factory() { return new FormGetGroupPipe(); }, token: FormGetGroupPipe, providedIn: "root" });
4217
4237
  FormGetGroupPipe.decorators = [
4218
4238
  { type: Pipe, args: [{
4219
4239
  name: 'formGetGroup'
4220
- },] },
4221
- { type: Injectable, args: [{ providedIn: 'root' },] }
4240
+ },] }
4241
+ ];
4242
+ class FormGetValuePipe {
4243
+ constructor(adapter, _ref) {
4244
+ this.adapter = adapter;
4245
+ this._ref = _ref;
4246
+ this.value = undefined;
4247
+ this._lastControl = null;
4248
+ }
4249
+ transform(form, path, listenStatusChanges = false) {
4250
+ if (!form) {
4251
+ this._dispose();
4252
+ return undefined;
4253
+ }
4254
+ // if we ask another time for the same form and opts, return the last value
4255
+ if (form === this._lastControl && equals(path, this._lastPath)) {
4256
+ return this.value;
4257
+ }
4258
+ // store the query, in case it changes
4259
+ this._lastControl = form;
4260
+ // store the params, in case they change
4261
+ this._lastPath = path;
4262
+ // set the value
4263
+ const control = path ? form.get(path) : form;
4264
+ this.value = control === null || control === void 0 ? void 0 : control.value;
4265
+ // if there is a subscription to onLangChange, clean it
4266
+ this._dispose();
4267
+ // subscribe to valueChanges event, in case control value change
4268
+ if (!this._onControlValueChanges && control) {
4269
+ this._onControlValueChanges = control.valueChanges.subscribe((value) => {
4270
+ if (value !== this.value) {
4271
+ this.value = value;
4272
+ this._ref.markForCheck();
4273
+ }
4274
+ });
4275
+ }
4276
+ if (listenStatusChanges && !this._onControlStatusChanges && control) {
4277
+ this._onControlStatusChanges = control.statusChanges.subscribe((status) => {
4278
+ const value = control.value;
4279
+ if (value !== this.value) {
4280
+ this.value = value;
4281
+ this._ref.markForCheck();
4282
+ }
4283
+ });
4284
+ }
4285
+ return this.value;
4286
+ }
4287
+ ngOnDestroy() {
4288
+ this._dispose();
4289
+ }
4290
+ /**
4291
+ * Clean any existing subscription to change events
4292
+ */
4293
+ _dispose() {
4294
+ if (typeof this._onControlValueChanges !== 'undefined') {
4295
+ this._onControlValueChanges.unsubscribe();
4296
+ this._onControlValueChanges = undefined;
4297
+ }
4298
+ if (typeof this._onControlStatusChanges !== 'undefined') {
4299
+ this._onControlStatusChanges.unsubscribe();
4300
+ this._onControlStatusChanges = undefined;
4301
+ }
4302
+ }
4303
+ }
4304
+ FormGetValuePipe.decorators = [
4305
+ { type: Pipe, args: [{
4306
+ name: 'formGetValue',
4307
+ pure: false
4308
+ },] }
4309
+ ];
4310
+ FormGetValuePipe.ctorParameters = () => [
4311
+ { type: FormErrorTranslator },
4312
+ { type: ChangeDetectorRef }
4222
4313
  ];
4223
4314
 
4224
4315
  class SharedPipesModule {
@@ -4267,7 +4358,8 @@ SharedPipesModule.decorators = [
4267
4358
  FormGetPipe,
4268
4359
  FormGetControlPipe,
4269
4360
  FormGetArrayPipe,
4270
- FormGetGroupPipe
4361
+ FormGetGroupPipe,
4362
+ FormGetValuePipe
4271
4363
  ],
4272
4364
  exports: [
4273
4365
  PropertyGetPipe,
@@ -4306,7 +4398,8 @@ SharedPipesModule.decorators = [
4306
4398
  FormGetPipe,
4307
4399
  FormGetControlPipe,
4308
4400
  FormGetArrayPipe,
4309
- FormGetGroupPipe
4401
+ FormGetGroupPipe,
4402
+ FormGetValuePipe
4310
4403
  ]
4311
4404
  },] }
4312
4405
  ];
@@ -4583,10 +4676,10 @@ class MatLatLongField {
4583
4676
  this.disabling = false;
4584
4677
  this.writing = false;
4585
4678
  this._readonly = false;
4679
+ this.required = false;
4680
+ this.floatLabel = 'auto';
4586
4681
  this.maxDecimals = LAT_LONG_DEFAULT_MAX_DECIMALS;
4587
4682
  this.placeholderChar = DEFAULT_PLACEHOLDER_CHAR;
4588
- this.floatLabel = 'auto';
4589
- this.required = false;
4590
4683
  this.onBlur = new EventEmitter();
4591
4684
  this.onFocus = new EventEmitter();
4592
4685
  }
@@ -4601,7 +4694,6 @@ class MatLatLongField {
4601
4694
  return this._readonly;
4602
4695
  }
4603
4696
  ngOnInit() {
4604
- this.mobile = isNotNil(this.mobile) ? this.mobile : isMobile(window);
4605
4697
  this.type = this.type || 'latitude';
4606
4698
  this.pattern = this.pattern || 'DDMM';
4607
4699
  this.mask = MASKS[this.type] && MASKS[this.type][this.pattern];
@@ -4632,8 +4724,8 @@ class MatLatLongField {
4632
4724
  this.maxDecimals = DEFAULT_MAX_DECIMALS;
4633
4725
  }
4634
4726
  this.showSignControl = this.pattern !== 'DD';
4635
- this.formatFn = this.type === 'latitude' ? formatLatitude : formatLongitude;
4636
- this.formatFnOptions = { pattern: this.pattern, maxDecimals: this.maxDecimals, hideSign: this.showSignControl };
4727
+ this._formatFn = this.type === 'latitude' ? formatLatitude : formatLongitude;
4728
+ this._formatOptions = { pattern: this.pattern, maxDecimals: this.maxDecimals, hideSign: this.showSignControl };
4637
4729
  this.inputPlaceholder = 'COMMON.LAT_LONG.' + (this.type === 'longitude' && 'D' || '') + this.pattern + '_PLACEHOLDER';
4638
4730
  this.textFormControl = this.formBuilder.control(this.required ? [null, Validators.required] : [null]);
4639
4731
  this.textMaskConfig = { mask: this.mask, keepCharPositions: true, placeholderChar: this.placeholderChar };
@@ -4667,7 +4759,7 @@ class MatLatLongField {
4667
4759
  return;
4668
4760
  this.value = (typeof obj === 'string') ? parseFloat(obj.replace(/,/g, '.')) : obj;
4669
4761
  this.writing = true;
4670
- const strValue = this.formatFn(this.value, Object.assign(Object.assign({}, this.formatFnOptions), { placeholderChar: this.placeholderChar }));
4762
+ const strValue = this._formatFn(this.value, Object.assign(Object.assign({}, this._formatOptions), { placeholderChar: this.placeholderChar }));
4671
4763
  const sign = isNotNil(this.value) ? (this.value < 0 ? -1 : 1) :
4672
4764
  // Use default sign, if any
4673
4765
  (this.defaultSign ? (this.defaultSign === '-' ? -1 : 1) : null);
@@ -4700,41 +4792,13 @@ class MatLatLongField {
4700
4792
  this.disabling = false;
4701
4793
  this.markForCheck();
4702
4794
  }
4703
- onFormChange(strValue) {
4704
- if (this.writing)
4705
- return; // Skip if call by self
4706
- this.writing = true;
4707
- if (this.textFormControl.invalid || this.signFormControl.invalid) {
4708
- this.formControl.markAsPending();
4709
- this.formControl.setErrors(Object.assign(Object.assign(Object.assign({}, this.formControl.errors), this.textFormControl.errors), this.signFormControl.errors));
4710
- this.writing = false;
4711
- return;
4712
- }
4713
- const parsedValue = isNotNilOrBlank(strValue) ? parseLatitudeOrLongitude(strValue, this.pattern, 7 /*=precision of the converted double value */, this.placeholderChar) : null;
4714
- // DEBUG
4715
- //console.debug('parsedValue=', parsedValue);
4716
- if (isNaN(parsedValue)) {
4717
- this.formControl.markAsPending();
4718
- this.formControl.setErrors(this.type === 'latitude' ? { latitude: true } : { longitude: true });
4719
- this.writing = false;
4720
- return;
4721
- }
4722
- const sign = (this.pattern === 'DD') ? 1 /*ignore sign*/ : (this.signFormControl.value || 1);
4723
- this.value = isNotNil(parsedValue) ? sign * parsedValue : null;
4724
- // Get the model value
4725
- //console.debug("[mat-latlon] Setting value {" + this.value + "} parsed from {" + strValue + "}");
4726
- this.formControl.patchValue(this.value, { emitEvent: false });
4727
- this.writing = false;
4728
- this.markForCheck();
4729
- this._onChangeCallback(this.value);
4730
- }
4731
- checkIfTouched() {
4732
- if (this.formControl.touched || this.textFormControl.touched) {
4733
- this.markForCheck();
4734
- this._onTouchedCallback();
4735
- return true;
4736
- }
4737
- return false;
4795
+ /**
4796
+ * Format value to display as string (no placeholder char, and show sign)
4797
+ * @param value
4798
+ * @param opts
4799
+ */
4800
+ _format(value, opts) {
4801
+ return this._formatFn(value, Object.assign(Object.assign({}, this._formatOptions), { hideSign: false, placeholderChar: '' }));
4738
4802
  }
4739
4803
  _onFocus(event) {
4740
4804
  // Apply the default sign, when pattern is DD and field is empty
@@ -4743,7 +4807,8 @@ class MatLatLongField {
4743
4807
  // Compute the text value, using the default sign
4744
4808
  const defaultSign = this.defaultSign === '-' ? -1 : 1;
4745
4809
  if (this.pattern === 'DD') {
4746
- let valueStr = this.formatFn(defaultSign, Object.assign(Object.assign({}, this.formatFnOptions), { placeholderChar: this.placeholderChar }));
4810
+ let valueStr = this._formatFn(defaultSign, Object.assign(Object.assign({}, this._formatOptions), { placeholderChar: this.placeholderChar }));
4811
+ // Keep only the sign ('+' or '-') and placeholder chars, but NOT the '1'
4747
4812
  valueStr = valueStr && valueStr.replace('1', this.placeholderChar);
4748
4813
  // Wait end of focus animation (label should move to top)
4749
4814
  setTimeout(() => {
@@ -4843,6 +4908,42 @@ class MatLatLongField {
4843
4908
  }
4844
4909
  }
4845
4910
  }
4911
+ onFormChange(strValue) {
4912
+ if (this.writing)
4913
+ return; // Skip if call by self
4914
+ this.writing = true;
4915
+ if (this.textFormControl.invalid || this.signFormControl.invalid) {
4916
+ this.formControl.markAsPending();
4917
+ this.formControl.setErrors(Object.assign(Object.assign(Object.assign({}, this.formControl.errors), this.textFormControl.errors), this.signFormControl.errors));
4918
+ this.writing = false;
4919
+ return;
4920
+ }
4921
+ const parsedValue = isNotNilOrBlank(strValue) ? parseLatitudeOrLongitude(strValue, this.pattern, 7 /*=precision of the converted double value */, this.placeholderChar) : null;
4922
+ // DEBUG
4923
+ //console.debug('parsedValue=', parsedValue);
4924
+ if (isNaN(parsedValue)) {
4925
+ this.formControl.markAsPending();
4926
+ this.formControl.setErrors(this.type === 'latitude' ? { latitude: true } : { longitude: true });
4927
+ this.writing = false;
4928
+ return;
4929
+ }
4930
+ const sign = (this.pattern === 'DD') ? 1 /*ignore sign*/ : (this.signFormControl.value || 1);
4931
+ this.value = isNotNil(parsedValue) ? sign * parsedValue : null;
4932
+ // Get the model value
4933
+ //console.debug("[mat-latlon] Setting value {" + this.value + "} parsed from {" + strValue + "}");
4934
+ this.formControl.patchValue(this.value, { emitEvent: false });
4935
+ this.writing = false;
4936
+ this.markForCheck();
4937
+ this._onChangeCallback(this.value);
4938
+ }
4939
+ checkIfTouched() {
4940
+ if (this.formControl.touched || this.textFormControl.touched) {
4941
+ this.markForCheck();
4942
+ this._onTouchedCallback();
4943
+ return true;
4944
+ }
4945
+ return false;
4946
+ }
4846
4947
  markForCheck() {
4847
4948
  this.cd.markForCheck();
4848
4949
  }
@@ -4850,7 +4951,7 @@ class MatLatLongField {
4850
4951
  MatLatLongField.decorators = [
4851
4952
  { type: Component, args: [{
4852
4953
  selector: 'mat-latlong-field',
4853
- template: "<!-- readonly -->\n<mat-form-field *ngIf=\"readonly; else writable\"\n [floatLabel]=\"floatLabel\"\n class=\"mat-form-field-disabled\">\n\n <div matPrefix>\n <ng-container *ngTemplateOutlet=\"matPrefixTemplate\"></ng-container>\n </div>\n\n <input matInput hidden type=\"text\"\n readonly\n [placeholder]=\"placeholder\"\n [formControl]=\"formControl\">\n <ion-text>{{formatFn(value, formatFnOptions)}}</ion-text>\n\n <div matSuffix>\n <ng-container *ngTemplateOutlet=\"matSuffixTemplate\"></ng-container>\n </div>\n</mat-form-field>\n\n<!-- writable -->\n<ng-template #writable>\n\n <ion-grid class=\"ion-no-padding\">\n <ion-row class=\"ion-no-padding no-wrap\">\n <ion-col class=\"text ion-no-padding\">\n <mat-form-field [floatLabel]=\"floatLabel\"\n [class.mat-form-field-disabled]=\"formControl.disabled\"\n [class.mat-form-field-invalid]=\"formControl.touched && formControl.invalid\">\n\n <mat-label *ngIf=\"placeholder\">{{placeholder}}</mat-label>\n\n <div matPrefix>\n <ng-container *ngTemplateOutlet=\"matPrefixTemplate\"></ng-container>\n </div>\n\n <input matInput #inputElement\n type=\"text\"\n autocomplete=\"off\"\n [textMask]=\"textMaskConfig\"\n [formControl]=\"textFormControl\"\n [placeholder]=\"inputPlaceholder|translate\"\n (keypress)=\"onKeypress($event)\"\n (keydown.tab)=\"moveCaretToSeparator($event, true)\"\n (keydown.shift.tab)=\"moveCaretToSeparator($event, false)\"\n (focus)=\"_onFocus($event)\"\n (blur)=\"_onBlur($event)\"\n [required]=\"required\"\n [tabindex]=\"tabindex\">\n\n <div *ngIf=\"!showSignControl\" matSuffix #injectMatSuffix></div>\n\n <!--<div matSuffix *ngIf=\"!showSignControl\"><ng-content select=\"[matSuffix]\"></ng-content></div>-->\n\n <div class=\"mat-form-field-subscript mat-form-field-subscript-wrapper\">\n\n <!-- errors -->\n <ng-container *ngIf=\"formControl.touched\">\n <mat-error *ngIf=\"formControl.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngIf=\"formControl.hasError('latitude')\" translate>ERROR.FIELD_NOT_VALID_LATITUDE</mat-error>\n <mat-error *ngIf=\"formControl.hasError('longitude')\" translate>ERROR.FIELD_NOT_VALID_LONGITUDE</mat-error>\n <ng-content select=\"mat-error\"></ng-content>\n </ng-container>\n\n <!-- mat hint -->\n <div class=\"mat-form-field-hint-wrapper\" [hidden]=\"formControl.invalid\">\n <div class=\"mat-form-field-hint-spacer\"></div>\n <ng-content select=\"mat-hint\"></ng-content>\n </div>\n </div>\n </mat-form-field>\n </ion-col>\n\n <!-- sign -->\n <ion-col class=\"sign ion-no-padding\" [ngSwitch]=\"type\" *ngIf=\"showSignControl\">\n\n <!-- sign on latitude -->\n <mat-form-field *ngSwitchCase=\"'latitude'\"\n floatLabel=\"never\"\n [class.mat-form-field-invalid]=\"formControl.touched && signFormControl.invalid\">\n <mat-label *ngIf=\"placeholder && floatLabel != 'never'\" translate>COMMON.LAT_LONG.LAT_SIGN_PLACEHOLDER</mat-label>\n <mat-select [formControl]=\"signFormControl\"\n [tabindex]=\"tabindex+1\">\n <mat-option [value]=\"1\">{{'COMMON.LAT_LONG.LAT_SIGN_N'|translate}}</mat-option>\n <mat-option [value]=\"-1\" >{{'COMMON.LAT_LONG.LAT_SIGN_S'|translate}}</mat-option>\n </mat-select>\n\n\n <div matSuffix>\n <ng-container *ngTemplateOutlet=\"matSuffixTemplate\"></ng-container>\n </div>\n </mat-form-field>\n\n <!-- sign on longitude -->\n <mat-form-field floatLabel=\"never\"\n *ngSwitchCase=\"'longitude'\"\n [class.mat-form-field-invalid]=\"formControl.touched && signFormControl.invalid\">\n <mat-label *ngIf=\"placeholder && floatLabel != 'never'\" translate>COMMON.LAT_LONG.LONG_SIGN_PLACEHOLDER</mat-label>\n <mat-select [formControl]=\"signFormControl\"\n [tabindex]=\"tabindex+2\">\n <mat-option [value]=\"1\" translate>{{'COMMON.LAT_LONG.LONG_SIGN_E'|translate}}</mat-option>\n <mat-option [value]=\"-1\" translate>{{'COMMON.LAT_LONG.LONG_SIGN_W'|translate}}</mat-option>\n </mat-select>\n\n <div matSuffix>\n <ng-container *ngTemplateOutlet=\"matSuffixTemplate\"></ng-container>\n </div>\n </mat-form-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n</ng-template>\n\n<ng-template #matPrefixTemplate>\n <ng-content select=\"[matPrefix]\"></ng-content>\n</ng-template>\n\n\n<ng-template #matSuffixTemplate>\n <ng-content select=\"[matSuffix]\"></ng-content>\n</ng-template>\n",
4954
+ template: "<!-- readonly -->\n<mat-form-field *ngIf=\"readonly; else writable\"\n [floatLabel]=\"floatLabel\"\n class=\"mat-form-field-disabled\">\n\n <div matPrefix>\n <ng-container *ngTemplateOutlet=\"matPrefixTemplate\"></ng-container>\n </div>\n\n <input matInput hidden type=\"text\"\n readonly\n [placeholder]=\"placeholder\"\n [formControl]=\"formControl\">\n <ion-text>{{_format(value) }}</ion-text>\n\n <div matSuffix>\n <ng-container *ngTemplateOutlet=\"matSuffixTemplate\"></ng-container>\n </div>\n</mat-form-field>\n\n<!-- writable -->\n<ng-template #writable>\n\n <ion-grid class=\"ion-no-padding\">\n <ion-row class=\"ion-no-padding no-wrap\">\n <ion-col class=\"text ion-no-padding\">\n <mat-form-field [floatLabel]=\"floatLabel\"\n [class.mat-form-field-disabled]=\"formControl.disabled\"\n [class.mat-form-field-invalid]=\"formControl.touched && formControl.invalid\">\n\n <mat-label *ngIf=\"placeholder\">{{placeholder}}</mat-label>\n\n <div matPrefix>\n <ng-container *ngTemplateOutlet=\"matPrefixTemplate\"></ng-container>\n </div>\n\n <input matInput #inputElement\n type=\"text\"\n autocomplete=\"off\"\n [textMask]=\"textMaskConfig\"\n [formControl]=\"textFormControl\"\n [placeholder]=\"inputPlaceholder|translate\"\n (keypress)=\"onKeypress($event)\"\n (keydown.tab)=\"moveCaretToSeparator($event, true)\"\n (keydown.shift.tab)=\"moveCaretToSeparator($event, false)\"\n (focus)=\"_onFocus($event)\"\n (blur)=\"_onBlur($event)\"\n [required]=\"required\"\n [tabindex]=\"tabindex\">\n\n <div matSuffix *ngIf=\"!showSignControl\">\n <ng-container *ngTemplateOutlet=\"matSuffixTemplate\"></ng-container>\n </div>\n\n <div class=\"mat-form-field-subscript mat-form-field-subscript-wrapper\">\n\n <!-- errors -->\n <ng-container *ngIf=\"formControl.touched && formControl.errors|mapKeys|arrayFirst; let errorKey\" [ngSwitch]=\"errorKey\">\n <mat-error *ngSwitchCase=\"'required'\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngSwitchCase=\"'latitude'\" translate>ERROR.FIELD_NOT_VALID_LATITUDE</mat-error>\n <mat-error *ngSwitchCase=\"'longitude'\" translate>ERROR.FIELD_NOT_VALID_LONGITUDE</mat-error>\n <mat-error *ngSwitchCase=\"'min'\">{{'ERROR.FIELD_MIN_COMPACT'|translate: {min: _format(formControl.errors.min.min) } }}</mat-error>\n <mat-error *ngSwitchCase=\"'max'\">{{'ERROR.FIELD_MAX_COMPACT'|translate: {max: _format(formControl.errors.max.max) } }}</mat-error>\n <mat-error *ngSwitchCase=\"'msg'\">{{(formControl.errors.msg?.key || formControl.errors.msg) | translate: formControl.errors.msg?.params}}</mat-error>\n </ng-container>\n <ng-content select=\"mat-error\"></ng-content>\n\n <!-- mat hint -->\n <div class=\"mat-form-field-hint-wrapper\" [class.cdk-visually-hidden]=\"formControl.invalid\">\n <div class=\"mat-form-field-hint-spacer\"></div>\n <ng-content select=\"mat-hint\"></ng-content>\n </div>\n </div>\n </mat-form-field>\n </ion-col>\n\n <!-- sign -->\n <ion-col class=\"sign ion-no-padding\" [ngSwitch]=\"type\" *ngIf=\"showSignControl\">\n\n <!-- sign on latitude -->\n <mat-form-field *ngSwitchCase=\"'latitude'\"\n floatLabel=\"never\"\n [class.mat-form-field-invalid]=\"formControl.touched && signFormControl.invalid\">\n <mat-label *ngIf=\"placeholder && floatLabel != 'never'\" translate>COMMON.LAT_LONG.LAT_SIGN_PLACEHOLDER</mat-label>\n <mat-select [formControl]=\"signFormControl\"\n [tabindex]=\"tabindex+1\">\n <mat-option [value]=\"1\">{{'COMMON.LAT_LONG.LAT_SIGN_N'|translate}}</mat-option>\n <mat-option [value]=\"-1\" >{{'COMMON.LAT_LONG.LAT_SIGN_S'|translate}}</mat-option>\n </mat-select>\n\n <div matSuffix>\n <ng-container *ngTemplateOutlet=\"matSuffixTemplate\"></ng-container>\n </div>\n </mat-form-field>\n\n <!-- sign on longitude -->\n <mat-form-field floatLabel=\"never\"\n *ngSwitchCase=\"'longitude'\"\n [class.mat-form-field-invalid]=\"formControl.touched && signFormControl.invalid\">\n <mat-label *ngIf=\"placeholder && floatLabel != 'never'\" translate>COMMON.LAT_LONG.LONG_SIGN_PLACEHOLDER</mat-label>\n <mat-select [formControl]=\"signFormControl\"\n [tabindex]=\"tabindex+2\">\n <mat-option [value]=\"1\" translate>{{'COMMON.LAT_LONG.LONG_SIGN_E'|translate}}</mat-option>\n <mat-option [value]=\"-1\" translate>{{'COMMON.LAT_LONG.LONG_SIGN_W'|translate}}</mat-option>\n </mat-select>\n\n <div matSuffix>\n <ng-container *ngTemplateOutlet=\"matSuffixTemplate\"></ng-container>\n </div>\n </mat-form-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n</ng-template>\n\n<ng-template #matPrefixTemplate>\n <ng-content select=\"[matPrefix]\"></ng-content>\n</ng-template>\n\n\n<ng-template #matSuffixTemplate>\n <ng-content select=\"[matSuffix]\"></ng-content>\n</ng-template>\n",
4854
4955
  providers: [
4855
4956
  {
4856
4957
  provide: NG_VALUE_ACCESSOR,
@@ -4859,7 +4960,7 @@ MatLatLongField.decorators = [
4859
4960
  }
4860
4961
  ],
4861
4962
  changeDetection: ChangeDetectionStrategy.OnPush,
4862
- styles: [":host{display:inline-block;width:100%;min-width:140px;position:relative;--ion-grid-column-padding:0}mat-form-field .datetime-md{padding:0!important}ion-row{flex-wrap:nowrap}ion-row ion-col.text{min-width:80px}ion-row ion-col.sign{min-width:60px;max-width:70px}mat-form-field{width:100%}mat-form-field .mat-form-field-subscript-wrapper{overflow:visible;top:100%}mat-form-field .mat-form-field-subscript-wrapper .mat-form-field-hint-wrapper{display:flex}mat-form-field .mat-form-field-subscript-wrapper .mat-form-field-hint-wrapper .mat-form-field-hint-spacer{flex:1 0 1em}mat-error{text-align:right;width:100%}"]
4963
+ styles: [":host{display:inline-block;width:100%;min-width:140px;position:relative;--ion-grid-column-padding:0}mat-form-field .datetime-md{padding:0!important}ion-row{flex-wrap:nowrap}ion-row ion-col.text{min-width:80px}ion-row ion-col.sign{min-width:40px;max-width:55px}mat-form-field{width:100%}mat-form-field .mat-form-field-subscript-wrapper{overflow:visible;white-space:nowrap;top:100%}mat-form-field .mat-form-field-subscript-wrapper .mat-form-field-hint-wrapper{display:flex}mat-form-field .mat-form-field-subscript-wrapper .mat-form-field-hint-wrapper .mat-form-field-hint-spacer{flex:1 0 1em}mat-error{text-align:right;width:100%}"]
4863
4964
  },] }
4864
4965
  ];
4865
4966
  MatLatLongField.ctorParameters = () => [
@@ -4869,19 +4970,18 @@ MatLatLongField.ctorParameters = () => [
4869
4970
  { type: FormGroupDirective, decorators: [{ type: Optional }] }
4870
4971
  ];
4871
4972
  MatLatLongField.propDecorators = {
4872
- mobile: [{ type: Input }],
4873
4973
  formControl: [{ type: Input }],
4874
4974
  formControlName: [{ type: Input }],
4875
- placeholder: [{ type: Input }],
4876
4975
  type: [{ type: Input }],
4877
4976
  pattern: [{ type: Input, args: ['latLongPattern',] }],
4977
+ required: [{ type: Input }],
4978
+ floatLabel: [{ type: Input }],
4979
+ placeholder: [{ type: Input }],
4980
+ tabindex: [{ type: Input }],
4878
4981
  defaultSign: [{ type: Input }],
4879
4982
  maxDecimals: [{ type: Input }],
4880
4983
  placeholderChar: [{ type: Input }],
4881
- floatLabel: [{ type: Input }],
4882
4984
  readonly: [{ type: Input }],
4883
- required: [{ type: Input }],
4884
- tabindex: [{ type: Input }],
4885
4985
  onBlur: [{ type: Output }],
4886
4986
  onFocus: [{ type: Output }],
4887
4987
  inputElement: [{ type: ViewChild, args: ['inputElement',] }]
@@ -5234,7 +5334,7 @@ class MatDate {
5234
5334
  MatDate.decorators = [
5235
5335
  { type: Component, args: [{
5236
5336
  selector: 'mat-date-field',
5237
- template: "<!-- readonly -->\n<mat-form-field *ngIf=\"readonly; else writable\"\n [floatLabel]=\"floatLabel\"\n class=\"mat-form-field-disabled\">\n\n <div matPrefix>\n <ng-container *ngTemplateOutlet=\"matPrefixTemplate\"></ng-container>\n </div>\n\n <input matInput hidden type=\"text\"\n readonly\n [placeholder]=\"placeholder\"\n [formControl]=\"formControl\">\n <ion-text>{{formControl.value|dateFormat: {pattern: datePattern} }}</ion-text>\n</mat-form-field>\n\n<!-- writable -->\n<ng-template #writable>\n <mat-form-field [floatLabel]=\"floatLabel\">\n\n <mat-label>{{placeholder}}</mat-label>\n\n <div matPrefix>\n <ng-container *ngTemplateOutlet=\"matPrefixTemplate\"></ng-container>\n </div>\n\n <input matInput #matInput autocomplete=\"off\" type=\"text\"\n *ngIf=\"!mobile\"\n [formControl]=\"textControl\"\n [textMask]=\"{mask: mask, keepCharPositions: true, placeholderChar: placeholderChar}\"\n [placeholder]=\"'COMMON.DATE_PLACEHOLDER'|translate\"\n (blur)=\"checkIfTouched()\"\n (keyup.arrowdown)=\"openDatePicker($event, datePicker)\"\n (keyup.escape)=\"preventEvent($event)\"\n [required]=\"required\"\n [tabindex]=\"tabindex\"\n [appAutofocus]=\"autofocus\">\n <input matInput #matInput autocomplete=\"off\" type=\"text\"\n *ngIf=\"mobile\"\n [formControl]=\"textControl\"\n (click)=\"openDatePickerIfMobile($event, datePicker)\"\n [required]=\"required\"\n [tabindex]=\"tabindex\"\n readonly>\n\n <!-- Hide the final input -->\n <input matInput type=\"text\" [formControl]=\"formControl\"\n hidden\n [matDatepicker]=\"datePicker\"\n [matDatepickerFilter]=\"datePickerFilter\"\n (dateChange)=\"onDatePickerChange($event)\">\n\n <button type=\"button\" mat-icon-button tabindex=\"-1\" matSuffix\n (click)=\"openDatePicker($event, datePicker)\"\n [disabled]=\"formControl.disabled\">\n <div *ngIf=\"mobile; then iconDate; else iconDesktop\"></div>\n </button>\n <button matSuffix mat-icon-button tabindex=\"-1\"\n type=\"button\"\n *ngIf=\"clearable\"\n (click)=\"clear()\"\n [hidden]=\"formControl.disabled || !formControl.value\">\n <mat-icon>close</mat-icon>\n </button>\n\n <!-- errors -->\n <mat-error *ngIf=\"formControl.touched && formControl.errors|mapKeys|arrayFirst; let errorKey\">\n <ng-container [ngSwitch]=\"errorKey\">\n <span *ngSwitchCase=\"'required'\" translate>ERROR.FIELD_REQUIRED</span>\n <span *ngSwitchCase=\"'validDate'\" translate>ERROR.FIELD_NOT_VALID_DATE</span>\n <span *ngSwitchCase=\"'dateIsAfter'\">{{'ERROR.FIELD_NOT_VALID_DATE_AFTER' | translate: formControl.errors.dateIsAfter }}</span>\n <span *ngSwitchCase=\"'dateIsBefore'\">{{'ERROR.FIELD_NOT_VALID_DATE_BEFORE' | translate: formControl.errors.dateIsBefore }}</span>\n <span *ngSwitchCase=\"'dateRange'\" translate>ERROR.FIELD_NOT_VALID_DATE_RANGE</span>\n <span *ngSwitchCase=\"'dateMaxDuration'\" translate>ERROR.FIELD_NOT_VALID_DATE_MAX_DURATION</span>\n <span *ngSwitchCase=\"'dateMinDuration'\" translate>ERROR.FIELD_NOT_VALID_DATE_MIN_DURATION</span>\n <span *ngSwitchCase=\"'msg'\">{{(formControl.errors.msg?.key || formControl.errors.msg) | translate: formControl.errors.msg?.params}}</span>\n </ng-container>\n </mat-error>\n <ng-content select=\"mat-error\"></ng-content>\n\n <!-- mat hint -->\n <div class=\"mat-form-field-hint-wrapper\" [class.cdk-visually-hidden]=\"formControl.invalid\">\n <div class=\"mat-form-field-hint-spacer\"></div>\n <ng-content select=\"mat-hint\"></ng-content>\n </div>\n </mat-form-field>\n\n <mat-datepicker #datePicker\n [touchUi]=\"mobile\"\n [disabled]=\"formControl.disabled\"\n [startAt]=\"startDate\"\n ></mat-datepicker>\n\n\n\n</ng-template>\n\n<ng-template #iconDesktop>\n <mat-icon>keyboard_arrow_down</mat-icon>\n</ng-template>\n\n<ng-template #iconDate>\n <mat-icon>date_range</mat-icon>\n</ng-template>\n\n<ng-template #matPrefixTemplate>\n <ng-content select=\"[matPrefix]\"></ng-content>\n</ng-template>\n",
5337
+ template: "<!-- readonly -->\n<mat-form-field *ngIf=\"readonly; else writable\"\n [floatLabel]=\"floatLabel\"\n class=\"mat-form-field-disabled\">\n\n <div matPrefix>\n <ng-container *ngTemplateOutlet=\"matPrefixTemplate\"></ng-container>\n </div>\n\n <input matInput hidden type=\"text\"\n readonly\n [placeholder]=\"placeholder\"\n [formControl]=\"formControl\">\n <ion-text>{{formControl.value|dateFormat: {pattern: datePattern} }}</ion-text>\n</mat-form-field>\n\n<!-- writable -->\n<ng-template #writable>\n <mat-form-field [floatLabel]=\"floatLabel\">\n\n <mat-label>{{placeholder}}</mat-label>\n\n <div matPrefix>\n <ng-container *ngTemplateOutlet=\"matPrefixTemplate\"></ng-container>\n </div>\n\n <input matInput #matInput autocomplete=\"off\" type=\"text\"\n *ngIf=\"!mobile\"\n [formControl]=\"textControl\"\n [textMask]=\"{mask: mask, keepCharPositions: true, placeholderChar: placeholderChar}\"\n [placeholder]=\"'COMMON.DATE_PLACEHOLDER'|translate\"\n (blur)=\"checkIfTouched()\"\n (keyup.arrowdown)=\"openDatePicker($event, datePicker)\"\n (keyup.escape)=\"preventEvent($event)\"\n [required]=\"required\"\n [tabindex]=\"tabindex\"\n [appAutofocus]=\"autofocus\">\n <input matInput #matInput autocomplete=\"off\" type=\"text\"\n *ngIf=\"mobile\"\n [formControl]=\"textControl\"\n (click)=\"openDatePickerIfMobile($event, datePicker)\"\n [required]=\"required\"\n [tabindex]=\"tabindex\"\n readonly>\n\n <!-- Hide the final input -->\n <input matInput type=\"text\" [formControl]=\"formControl\"\n hidden\n [matDatepicker]=\"datePicker\"\n [matDatepickerFilter]=\"datePickerFilter\"\n (dateChange)=\"onDatePickerChange($event)\">\n\n <button type=\"button\" mat-icon-button tabindex=\"-1\" matSuffix\n (click)=\"openDatePicker($event, datePicker)\"\n [disabled]=\"formControl.disabled\">\n <div *ngIf=\"mobile; then iconDate; else iconDesktop\"></div>\n </button>\n <button matSuffix mat-icon-button tabindex=\"-1\"\n type=\"button\"\n *ngIf=\"clearable\"\n (click)=\"clear()\"\n [hidden]=\"formControl.disabled || !formControl.value\">\n <mat-icon>close</mat-icon>\n </button>\n\n <!-- errors -->\n <ng-container *ngIf=\"formControl.touched && formControl.errors|mapKeys|arrayFirst; let errorKey\" [ngSwitch]=\"errorKey\">\n <mat-error *ngSwitchCase=\"'required'\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngSwitchCase=\"'validDate'\" translate>ERROR.FIELD_NOT_VALID_DATE</mat-error>\n <mat-error *ngSwitchCase=\"'dateIsAfter'\">{{'ERROR.FIELD_NOT_VALID_DATE_AFTER' | translate: formControl.errors.dateIsAfter }}</mat-error>\n <mat-error *ngSwitchCase=\"'dateIsBefore'\">{{'ERROR.FIELD_NOT_VALID_DATE_BEFORE' | translate: formControl.errors.dateIsBefore }}</mat-error>\n <mat-error *ngSwitchCase=\"'dateRange'\" translate>ERROR.FIELD_NOT_VALID_DATE_RANGE</mat-error>\n <mat-error *ngSwitchCase=\"'dateMaxDuration'\" translate>ERROR.FIELD_NOT_VALID_DATE_MAX_DURATION</mat-error>\n <mat-error *ngSwitchCase=\"'dateMinDuration'\" translate>ERROR.FIELD_NOT_VALID_DATE_MIN_DURATION</mat-error>\n <mat-error *ngSwitchCase=\"'msg'\">{{(formControl.errors.msg?.key || formControl.errors.msg) | translate: formControl.errors.msg?.params}}</mat-error>\n </ng-container>\n <ng-content select=\"mat-error\"></ng-content>\n\n <!-- mat hint -->\n <div class=\"mat-form-field-hint-wrapper\" [class.cdk-visually-hidden]=\"formControl.invalid\">\n <div class=\"mat-form-field-hint-spacer\"></div>\n <ng-content select=\"mat-hint\"></ng-content>\n </div>\n </mat-form-field>\n\n <mat-datepicker #datePicker\n [touchUi]=\"mobile\"\n [disabled]=\"formControl.disabled\"\n [startAt]=\"startDate\"\n ></mat-datepicker>\n\n\n\n</ng-template>\n\n<ng-template #iconDesktop>\n <mat-icon>keyboard_arrow_down</mat-icon>\n</ng-template>\n\n<ng-template #iconDate>\n <mat-icon>date_range</mat-icon>\n</ng-template>\n\n<ng-template #matPrefixTemplate>\n <ng-content select=\"[matPrefix]\"></ng-content>\n</ng-template>\n",
5238
5338
  providers: [
5239
5339
  DEFAULT_VALUE_ACCESSOR,
5240
5340
  ],
@@ -5331,7 +5431,7 @@ class MatDateTime {
5331
5431
  // Workaround because ion-datetime has issue (do not returned a ISO date)
5332
5432
  this._keyboardHideDelay = this.mobile && KEYBOARD_HIDE_DELAY_MS || 0;
5333
5433
  this.required = isNotNilBoolean(this.required) ? this.required : (this.formControl.validator === Validators.required || isBlankString(this.required));
5334
- // Add 'validDate' validator (when existing validator are null or required, to be sure to keep it)
5434
+ // Add 'validDate' validator (when existing validator are null or required, to be sure to keep)
5335
5435
  if (!this.formControl.validator || this.formControl.validator === Validators.required) {
5336
5436
  this.formControl.setValidators(this.required ? [Validators.required, SharedValidators.validDate] : SharedValidators.validDate);
5337
5437
  }
@@ -5340,12 +5440,9 @@ class MatDateTime {
5340
5440
  [this.formControl.validator, SharedValidators.validDate]);
5341
5441
  }
5342
5442
  // Create the day control
5343
- this.dayControl = this.formBuilder.control(null, this.required ? Validators.required : null);
5443
+ this.dayControl = this.formBuilder.control(null);
5344
5444
  // Create the hour control
5345
- const timeValidator = this.required ?
5346
- (this.mobile ? Validators.required : Validators.compose([Validators.required, Validators.pattern(HOUR_REGEXP)])) :
5347
- (this.mobile ? null : Validators.pattern(HOUR_REGEXP));
5348
- this.hourControl = this.formBuilder.control(null, timeValidator);
5445
+ this.hourControl = this.formBuilder.control(null, this.mobile ? null : Validators.pattern(HOUR_REGEXP));
5349
5446
  // Get patterns to display date and date+time
5350
5447
  this._subscription.add(this.translate.get(['COMMON.DATE_PATTERN', 'COMMON.DATE_TIME_PATTERN'])
5351
5448
  .subscribe((patterns) => this.updatePattern(patterns)));
@@ -5528,7 +5625,9 @@ class MatDateTime {
5528
5625
  if (incompleteValue || this.dayControl.invalid || this.hourControl.invalid) {
5529
5626
  this.formControl.markAsPending({ onlySelf: true });
5530
5627
  this.formControl.setErrors(Object.assign(Object.assign(Object.assign({ validDate: incompleteValue }, this.formControl.errors), this.dayControl.errors), this.hourControl.errors));
5531
- this.formControl.markAsDirty();
5628
+ if (this.dayControl.dirty || this.hourControl.dirty) {
5629
+ this.formControl.markAsDirty();
5630
+ }
5532
5631
  this._writing = false;
5533
5632
  return;
5534
5633
  }
@@ -5907,7 +6006,7 @@ class MatDateShort {
5907
6006
  MatDateShort.decorators = [
5908
6007
  { type: Component, args: [{
5909
6008
  selector: 'mat-date-short-field',
5910
- template: "<!-- readonly -->\n<mat-form-field *ngIf=\"readonly else writable\">\n <input matInput hidden type=\"text\" readonly=\"true\" [formControl]=\"formControl\">\n <ion-text>{{formControl.value|dateFormat: {pattern: displayPattern} }}</ion-text>\n</mat-form-field>\n\n<ng-template #writable>\n <mat-form-field [floatLabel]=\"floatLabel\">\n\n <mat-label>{{placeholder}}</mat-label>\n\n <div matPrefix><ng-content select=\"[matPrefix]\"></ng-content></div>\n\n <input matInput #matInput autocomplete=\"off\" type=\"text\"\n *ngIf=\"!mobile\"\n [formControl]=\"dayControl\"\n [textMask]=\"{mask: dayMask, keepCharPositions: true, placeholderChar: placeholderChar}\"\n [placeholder]=\"'COMMON.DATE_YEAR_PLACEHOLDER'|translate\"\n (blur)=\"checkIfTouched()\"\n (keyup.arrowdown)=\"openDatePicker($event, datePicker1)\"\n (keyup.escape)=\"preventEvent($event)\"\n (click)=\"matInput.select()\"\n [required]=\"required\"\n [tabindex]=\"tabindex\">\n <input matInput #matInput autocomplete=\"off\" type=\"text\"\n *ngIf=\"mobile\"\n [formControl]=\"dayControl\"\n (click)=\"openDatePickerIfMobile($event, datePicker1)\"\n [required]=\"required\"\n [tabindex]=\"tabindex\"\n readonly>\n\n <!-- Hide the final control -->\n <input matInput type=\"text\"\n [formControl]=\"formControl\"\n hidden\n [matDatepicker]=\"datePicker1\"\n (dateChange)=\"onDatePickerChange($event)\">\n\n\n <button matSuffix mat-icon-button tabindex=\"-1\"\n type=\"button\"\n (click)=\"openDatePicker($event, datePicker1)\"\n [disabled]=\"formControl.disabled\">\n <div *ngIf=\"mobile; then iconDate; else iconDesktop\"></div>\n </button>\n <button matSuffix mat-icon-button tabindex=\"-1\"\n type=\"button\"\n *ngIf=\"clearable\"\n (click)=\"writeValue(null)\"\n [hidden]=\"formControl.disabled || !formControl.value\">\n <mat-icon>close</mat-icon>\n </button>\n\n <mat-error *ngIf=\"formControl.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngIf=\"formControl.hasError('validDate')\" translate>ERROR.FIELD_NOT_VALID_DATE</mat-error>\n <mat-error *ngIf=\"formControl.hasError('dateIsAfter')\">{{'ERROR.FIELD_NOT_VALID_DATE_AFTER' | translate: formControl.errors.dateIsAfter }}</mat-error>\n <mat-error *ngIf=\"formControl.hasError('dateRange')\" translate>ERROR.FIELD_NOT_VALID_DATE_RANGE</mat-error>\n <mat-error *ngIf=\"!formControl.hasError('dateRange') && formControl.hasError('dateMaxDuration')\" translate>ERROR.FIELD_NOT_VALID_DATE_MAX_DURATION</mat-error>\n <mat-error *ngIf=\"!formControl.hasError('dateRange') && formControl.hasError('dateMinDuration')\" translate>ERROR.FIELD_NOT_VALID_DATE_MIN_DURATION</mat-error>\n <mat-error *ngIf=\"formControl.hasError('msg')\">{{formControl.errors.msg}}</mat-error>\n <ng-content select=\"mat-error\"></ng-content>\n </mat-form-field>\n\n <mat-datepicker #datePicker1\n startView=\"multi-year\"\n [touchUi]=\"mobile\"\n [disabled]=\"formControl.disabled\"\n (yearSelected)=\"closeDatePicker($event, datePicker1)\"\n [startAt]=\"startDate\"></mat-datepicker>\n\n <ng-template #iconDesktop>\n <mat-icon>keyboard_arrow_down</mat-icon>\n </ng-template>\n\n <ng-template #iconDate>\n <mat-icon>date_range</mat-icon>\n </ng-template>\n\n</ng-template>\n",
6009
+ template: "<!-- readonly -->\n<mat-form-field *ngIf=\"readonly else writable\">\n <div matPrefix>\n <ng-container *ngTemplateOutlet=\"matPrefixTemplate\"></ng-container>\n </div>\n\n <input matInput hidden type=\"text\" readonly=\"true\" [formControl]=\"formControl\">\n <ion-text>{{formControl.value|dateFormat: {pattern: displayPattern} }}</ion-text>\n\n <div matSuffix>\n <ng-container *ngTemplateOutlet=\"matSuffixTemplate\"></ng-container>\n </div>\n</mat-form-field>\n\n<ng-template #writable>\n <mat-form-field [floatLabel]=\"floatLabel\">\n\n <mat-label>{{placeholder}}</mat-label>\n\n <div matPrefix><ng-content select=\"[matPrefix]\"></ng-content></div>\n\n <input matInput #matInput autocomplete=\"off\" type=\"text\"\n *ngIf=\"!mobile\"\n [formControl]=\"dayControl\"\n [textMask]=\"{mask: dayMask, keepCharPositions: true, placeholderChar: placeholderChar}\"\n [placeholder]=\"'COMMON.DATE_YEAR_PLACEHOLDER'|translate\"\n (blur)=\"checkIfTouched()\"\n (keyup.arrowdown)=\"openDatePicker($event, datePicker1)\"\n (keyup.escape)=\"preventEvent($event)\"\n (click)=\"matInput.select()\"\n [required]=\"required\"\n [tabindex]=\"tabindex\">\n <input matInput #matInput autocomplete=\"off\" type=\"text\"\n *ngIf=\"mobile\"\n [formControl]=\"dayControl\"\n (click)=\"openDatePickerIfMobile($event, datePicker1)\"\n [required]=\"required\"\n [tabindex]=\"tabindex\"\n readonly>\n\n <!-- Hide the final control -->\n <input matInput type=\"text\"\n [formControl]=\"formControl\"\n hidden\n [matDatepicker]=\"datePicker1\"\n (dateChange)=\"onDatePickerChange($event)\">\n\n\n <button matSuffix mat-icon-button tabindex=\"-1\"\n type=\"button\"\n (click)=\"openDatePicker($event, datePicker1)\"\n [disabled]=\"formControl.disabled\">\n <div *ngIf=\"mobile; then iconDate; else iconDesktop\"></div>\n </button>\n <button matSuffix mat-icon-button tabindex=\"-1\"\n type=\"button\"\n *ngIf=\"clearable\"\n (click)=\"writeValue(null)\"\n [hidden]=\"formControl.disabled || !formControl.value\">\n <mat-icon>close</mat-icon>\n </button>\n\n\n <ng-container *ngIf=\"formControl.touched && formControl.errors|mapKeys|arrayFirst; let errorKey\" [ngSwitch]=\"errorKey\">\n <mat-error *ngSwitchCase=\"'required'\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngSwitchCase=\"'validDate'\" translate>ERROR.FIELD_NOT_VALID_DATE</mat-error>\n <mat-error *ngSwitchCase=\"'dateIsAfter'\">{{'ERROR.FIELD_NOT_VALID_DATE_AFTER' | translate: formControl.errors.dateIsAfter }}</mat-error>\n <mat-error *ngSwitchCase=\"'dateRange'\" translate>ERROR.FIELD_NOT_VALID_DATE_RANGE</mat-error>\n <mat-error *ngSwitchCase=\"'dateMaxDuration'\" translate>ERROR.FIELD_NOT_VALID_DATE_MAX_DURATION</mat-error>\n <mat-error *ngSwitchCase=\"'dateMinDuration'\" translate>ERROR.FIELD_NOT_VALID_DATE_MIN_DURATION</mat-error>\n <mat-error *ngSwitchCase=\"'msg'\">{{(formControl.errors.msg?.key || formControl.errors.msg) | translate: formControl.errors.msg?.params}}</mat-error>\n </ng-container>\n <ng-content select=\"mat-error\"></ng-content>\n\n <div matSuffix>\n <ng-container *ngTemplateOutlet=\"matSuffixTemplate\"></ng-container>\n </div>\n </mat-form-field>\n\n <mat-datepicker #datePicker1\n startView=\"multi-year\"\n [touchUi]=\"mobile\"\n [disabled]=\"formControl.disabled\"\n (yearSelected)=\"closeDatePicker($event, datePicker1)\"\n [startAt]=\"startDate\"></mat-datepicker>\n\n <ng-template #iconDesktop>\n <mat-icon>keyboard_arrow_down</mat-icon>\n </ng-template>\n\n <ng-template #iconDate>\n <mat-icon>date_range</mat-icon>\n </ng-template>\n\n</ng-template>\n\n<ng-template #matPrefixTemplate>\n <ng-content select=\"[matPrefix]\"></ng-content>\n</ng-template>\n\n<ng-template #matSuffixTemplate>\n <ng-content select=\"[matSuffix]\"></ng-content>\n</ng-template>\n",
5911
6010
  providers: [
5912
6011
  DEFAULT_VALUE_ACCESSOR$2,
5913
6012
  ],
@@ -7045,8 +7144,8 @@ class MatSwipeField {
7045
7144
  this.required = false;
7046
7145
  this.clearable = false;
7047
7146
  this.onClick = new EventEmitter();
7048
- this.onBlur = new EventEmitter();
7049
- this.onFocus = new EventEmitter();
7147
+ this.blurred = new EventEmitter();
7148
+ this.focused = new EventEmitter();
7050
7149
  this.selectInputContent = selectInputContent;
7051
7150
  }
7052
7151
  set tabindex(value) {
@@ -7286,7 +7385,7 @@ class MatSwipeField {
7286
7385
  MatSwipeField.decorators = [
7287
7386
  { type: Component, args: [{
7288
7387
  selector: 'mat-swipe-field',
7289
- template: "<mat-form-field [floatLabel]=\"showSlides && (!floatLabel || floatLabel === 'auto') ? 'always': floatLabel\"\n [class]=\"classList\"\n [class.mat-form-field-disabled]=\"disabled\">\n <mat-label *ngIf=\"placeholder && floatLabel!=='never'\">\n {{placeholder}}\n </mat-label>\n <input matInput #fakeInput type=\"text\"\n [formControl]=\"formControl\"\n [readonly]=\"true\"\n (focus)=\"_onFocusFakeInput($event)\"\n [required]=\"required\"\n [class.cdk-visually-hidden]=\"showSlides\"\n [tabindex]=\"tabindex\">\n\n <div class=\"slides-container\" [class.hidden]=\"!showSlides\">\n <ion-slides #slides\n [options]=\"slidesOptions\"\n (ionSlidesDidLoad)=\"slidesLoaded()\"\n (ionSlideReachStart)=\"reachStart(true)\"\n (ionSlideReachEnd)=\"reachEnd(true)\"\n (ionSlideNextEnd)=\"reachStart(false)\"\n (ionSlidePrevEnd)=\"reachEnd(false)\"\n (ionSlideDidChange)=\"slideChanged()\"\n >\n <ion-slide *ngFor=\"let item of $items | async\">\n <ion-label>{{ displayWith(item) }}</ion-label>\n </ion-slide>\n\n </ion-slides>\n </div>\n\n <button #prevButton\n mat-icon-button\n type=\"button\"\n class=\"button-prev\"\n [hidden]=\"!showSlides\"\n [disabled]=\"previousDisabled\"\n (click)=\"previous()\">\n <mat-icon>chevron_left</mat-icon>\n </button>\n <button #nextButton\n mat-icon-button\n type=\"button\"\n class=\"button-next\"\n [hidden]=\"!showSlides\"\n [disabled]=\"nextDisabled\"\n (click)=\"next()\">\n <mat-icon>chevron_right</mat-icon>\n </button>\n\n <button matSuffix mat-icon-button tabindex=\"-1\"\n type=\"button\"\n *ngIf=\"clearable\"\n (click)=\"clearValue($event)\"\n [hidden]=\"disabled || !formControl.value\">\n <mat-icon>close</mat-icon>\n </button>\n\n <mat-error *ngIf=\"formControl.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngIf=\"formControl.hasError('entity')\" translate>ERROR.FIELD_INVALID</mat-error>\n\n</mat-form-field>\n",
7388
+ template: "<mat-form-field [floatLabel]=\"showSlides && (!floatLabel || floatLabel === 'auto') ? 'always': floatLabel\"\n [appearance]=\"appearance\"\n [class]=\"classList\"\n [class.mat-form-field-disabled]=\"disabled\">\n <mat-label *ngIf=\"placeholder && floatLabel!=='never'\">\n {{placeholder}}\n </mat-label>\n <input matInput #fakeInput type=\"text\"\n [formControl]=\"formControl\"\n [readonly]=\"true\"\n (focus)=\"_onFocusFakeInput($event)\"\n [required]=\"required\"\n [class.cdk-visually-hidden]=\"showSlides\"\n [tabindex]=\"tabindex\">\n\n <div class=\"slides-container\" [class.hidden]=\"!showSlides\">\n <ion-slides #slides\n [options]=\"slidesOptions\"\n (ionSlidesDidLoad)=\"slidesLoaded()\"\n (ionSlideReachStart)=\"reachStart(true)\"\n (ionSlideReachEnd)=\"reachEnd(true)\"\n (ionSlideNextEnd)=\"reachStart(false)\"\n (ionSlidePrevEnd)=\"reachEnd(false)\"\n (ionSlideDidChange)=\"slideChanged()\"\n >\n <ion-slide *ngFor=\"let item of $items | async\">\n <ion-label>{{ displayWith(item) }}</ion-label>\n </ion-slide>\n\n </ion-slides>\n </div>\n\n <button #prevButton\n mat-icon-button\n type=\"button\"\n class=\"button-prev\"\n [hidden]=\"!showSlides\"\n [disabled]=\"previousDisabled\"\n (click)=\"previous()\">\n <mat-icon>chevron_left</mat-icon>\n </button>\n <button #nextButton\n mat-icon-button\n type=\"button\"\n class=\"button-next\"\n [hidden]=\"!showSlides\"\n [disabled]=\"nextDisabled\"\n (click)=\"next()\">\n <mat-icon>chevron_right</mat-icon>\n </button>\n\n <button matSuffix mat-icon-button tabindex=\"-1\"\n type=\"button\"\n *ngIf=\"clearable\"\n (click)=\"clearValue($event)\"\n [hidden]=\"disabled || !formControl.value\">\n <mat-icon>close</mat-icon>\n </button>\n\n <mat-error *ngIf=\"formControl.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngIf=\"formControl.hasError('entity')\" translate>ERROR.FIELD_INVALID</mat-error>\n\n</mat-form-field>\n",
7290
7389
  providers: [DEFAULT_VALUE_ACCESSOR$5],
7291
7390
  changeDetection: ChangeDetectionStrategy.OnPush,
7292
7391
  styles: [":host{display:inline-block;width:100%;position:relative}input.hidden{display:none}.slides-container .swiper-slide{font-size:16px!important}.slides-container.hidden{visibility:hidden;height:0!important}.button-next,.button-prev{position:absolute;top:calc(50% - 20px);z-index:10}.button-prev{left:0;right:auto}.button-next{left:auto;right:0}"]
@@ -7301,6 +7400,7 @@ MatSwipeField.propDecorators = {
7301
7400
  formControl: [{ type: Input }],
7302
7401
  formControlName: [{ type: Input }],
7303
7402
  floatLabel: [{ type: Input }],
7403
+ appearance: [{ type: Input }],
7304
7404
  placeholder: [{ type: Input }],
7305
7405
  debug: [{ type: Input }],
7306
7406
  required: [{ type: Input }],
@@ -7314,8 +7414,8 @@ MatSwipeField.propDecorators = {
7314
7414
  tabindex: [{ type: Input }],
7315
7415
  items: [{ type: Input }],
7316
7416
  onClick: [{ type: Output, args: ['click',] }],
7317
- onBlur: [{ type: Output, args: ['blur',] }],
7318
- onFocus: [{ type: Output, args: ['focus',] }],
7417
+ blurred: [{ type: Output, args: ['blur',] }],
7418
+ focused: [{ type: Output, args: ['focus',] }],
7319
7419
  fakeInput: [{ type: ViewChild, args: ['fakeInput',] }],
7320
7420
  slides: [{ type: ViewChild, args: ['slides', { static: true },] }],
7321
7421
  prevButton: [{ type: ViewChild, args: ['prevButton',] }],
@@ -7907,7 +8007,7 @@ MatChipsField.decorators = [
7907
8007
  { type: Component, args: [{
7908
8008
  // eslint-disable-next-line @angular-eslint/component-selector
7909
8009
  selector: 'mat-chips-field',
7910
- template: "<ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col class=\"ion-no-padding\">\n <mat-form-field [floatLabel]=\"floatLabel\" class=\"material-chips\" [class.mat-form-field-disabled]=\"disabled\">\n <div matPrefix>\n <ng-container *ngTemplateOutlet=\"matPrefixTemplate\"></ng-container>\n </div>\n <mat-label>{{placeholder}}</mat-label>\n <mat-chip-list #chipList\n (focus)=\"filterChipsFocusEvent($event)\" >\n <mat-chip\n *ngFor=\"let item of value\"\n [selectable]=\"false\"\n [removable]=\"!disabled\"\n (removed)=\"remove(item)\"\n [color]=\"chipColor\"\n >\n {{displayWith(item)}}\n <mat-icon matChipRemove *ngIf=\"!disabled\">cancel</mat-icon>\n </mat-chip>\n\n <input #matInputText type=\"text\"\n [matAutocomplete]=\"autocomplete\"\n [matAutocompletePosition]=\"matAutocompletePosition\"\n [formControl]=\"inputControl\"\n [appAutofocus]=\"autofocus\"\n [tabindex]=\"_tabindex\"\n [readonly]=\"disabled\"\n [hidden]=\"disabled\"\n [required]=\"required\"\n (click)=\"clicked.emit($event)\"\n (focus)=\"filterInputTextFocusEvent($event)\"\n (blur)=\"filterInputTextBlurEvent($event)\"\n (keydown.escape)=\"keydownEscape.emit($event)\"\n (keyup.enter)=\"keyupEnter.emit($event)\"\n (keyup.arrowdown)=\"!autocomplete.showPanel && dropButtonClick.emit($event)\"\n [matChipInputFor]=\"chipList\"\n >\n </mat-chip-list>\n\n <!-- autocomplete -->\n <mat-autocomplete #autocomplete=\"matAutocomplete\"\n (optionSelected)=\"add($event)\"\n autoActiveFirstOption\n [displayWith]=\"displayWith\"\n [class]=\"classList\"\n [panelWidth]=\"panelWidth\">\n <ion-row class=\"mat-autocomplete-header ion-no-padding column\">\n <ion-col *ngFor=\"let attr of displayAttributes; index as i\" [size]=\"displayColumnSizes[i]\">\n <ion-label [innerHTML]=\"displayColumnNames[i] | translate\"></ion-label>\n </ion-col>\n </ion-row>\n\n <ng-container *ngIf=\"$filteredItems | async as items; else loadingTemplate\">\n <!-- No item option -->\n <mat-option *ngIf=\"items | isEmptyArray\" class=\"ion-padding text-italic\" disabled>\n <ion-label>{{noResultMessage | translate}}</ion-label>\n </mat-option>\n\n <mat-option *ngFor=\"let item of items\" [value]=\"item\" class=\"ion-no-padding\">\n <ion-row [classList]=\"item.classList\">\n <ion-col *ngFor=\"let path of displayAttributes; index as i;\"\n [size]=\"displayColumnSizes[i]\">\n <ion-text [innerHTML]=\"item | propertyGet: path | highlight: { search: inputControl.value, withAccent: highlightAccent } \"></ion-text>\n </ion-col>\n </ion-row>\n </mat-option>\n\n <!-- More item -->\n <mat-option *ngIf=\"_moreItemsCount\" (ngInit)=\"_initAutocompleteInfiniteScroll()\"\n class=\"ion-padding\" disabled>\n <ion-skeleton-text [animated]=\"true\" style=\"width: 100%\"></ion-skeleton-text>\n </mat-option>\n </ng-container>\n\n <!-- footer -->\n <ion-row *ngIf=\"itemCount as count\"\n class=\"mat-autocomplete-footer ion-no-padding\">\n <ion-col>\n <ion-text class=\"ion-float-end ion-padding-end\"><i>{{'COMMON.RESULT_COUNT'|translate: {count: count} }}</i></ion-text>\n </ion-col>\n </ion-row>\n\n <!-- Loading spinner -->\n <ng-template #loadingTemplate>\n <mat-option *ngIf=\"(matInputText.value | strLength) >= suggestLengthThreshold; else waitMoreCharacterTemplate\"\n class=\"ion-padding\" disabled>\n <ion-skeleton-text [animated]=\"true\" style=\"width: 100%\"></ion-skeleton-text>\n </mat-option>\n </ng-template>\n\n <!-- Need more characters -->\n <ng-template #waitMoreCharacterTemplate>\n <mat-option class=\"ion-padding text-italic\" disabled>\n <ion-label>\n {{'INFO.PLEASE_TYPE_MORE_CHARACTERS'|translate:{minLength: suggestLengthThreshold} }}\n </ion-label>\n </mat-option>\n </ng-template>\n </mat-autocomplete>\n\n <div matSuffix>\n <ng-container *ngTemplateOutlet=\"matSuffixTemplate\"></ng-container>\n </div>\n\n <mat-error *ngIf=\"formControl.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <ng-content select=\"[matError]\"></ng-content>\n\n </mat-form-field>\n\n </ion-col>\n <ion-col size=\"auto\" class=\"ion-align-self-center\">\n <ng-content select=\"[matAfter]\"></ng-content>\n </ion-col>\n </ion-row>\n</ion-grid>\n\n<ng-template #matPrefixTemplate>\n <ng-content select=\"[matPrefix]\"></ng-content>\n</ng-template>\n\n\n<ng-template #matSuffixTemplate>\n <button mat-icon-button tabindex=\"-1\"\n type=\"button\"\n (click)=\"dropButtonClick.emit($event)\"\n [hidden]=\"disabled\">\n <mat-icon class=\"large select-arrow\">arrow_drop_down</mat-icon>\n </button>\n <button mat-icon-button tabindex=\"-1\"\n type=\"button\"\n *ngIf=\"clearable\"\n (click)=\"clearValue($event)\"\n [hidden]=\"disabled || !formControl.value\">\n <mat-icon>close</mat-icon>\n </button>\n\n <ng-content select=\"[matSuffix]\"></ng-content>\n</ng-template>\n",
8010
+ template: "<ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col class=\"ion-no-padding\">\n <mat-form-field [floatLabel]=\"floatLabel\"\n [appearance]=\"appearance\"\n class=\"material-chips\" [class.mat-form-field-disabled]=\"disabled\">\n <div matPrefix>\n <ng-container *ngTemplateOutlet=\"matPrefixTemplate\"></ng-container>\n </div>\n <mat-label>{{placeholder}}</mat-label>\n <mat-chip-list #chipList\n (focus)=\"filterChipsFocusEvent($event)\" >\n <mat-chip\n *ngFor=\"let item of value\"\n [selectable]=\"false\"\n [removable]=\"!disabled\"\n (removed)=\"remove(item)\"\n [color]=\"chipColor\"\n >\n {{displayWith(item)}}\n <mat-icon matChipRemove *ngIf=\"!disabled\">cancel</mat-icon>\n </mat-chip>\n\n <input #matInputText type=\"text\"\n [matAutocomplete]=\"autocomplete\"\n [matAutocompletePosition]=\"matAutocompletePosition\"\n [formControl]=\"inputControl\"\n [appAutofocus]=\"autofocus\"\n [tabindex]=\"_tabindex\"\n [readonly]=\"disabled\"\n [hidden]=\"disabled\"\n [required]=\"required\"\n (click)=\"clicked.emit($event)\"\n (focus)=\"filterInputTextFocusEvent($event)\"\n (blur)=\"filterInputTextBlurEvent($event)\"\n (keydown.escape)=\"keydownEscape.emit($event)\"\n (keyup.enter)=\"keyupEnter.emit($event)\"\n (keyup.arrowdown)=\"!autocomplete.showPanel && dropButtonClick.emit($event)\"\n [matChipInputFor]=\"chipList\"\n >\n </mat-chip-list>\n\n <!-- autocomplete -->\n <mat-autocomplete #autocomplete=\"matAutocomplete\"\n (optionSelected)=\"add($event)\"\n autoActiveFirstOption\n [displayWith]=\"displayWith\"\n [class]=\"classList\"\n [panelWidth]=\"panelWidth\">\n <ion-row class=\"mat-autocomplete-header ion-no-padding column\">\n <ion-col *ngFor=\"let attr of displayAttributes; index as i\" [size]=\"displayColumnSizes[i]\">\n <ion-label [innerHTML]=\"displayColumnNames[i] | translate\"></ion-label>\n </ion-col>\n </ion-row>\n\n <ng-container *ngIf=\"$filteredItems | async as items; else loadingTemplate\">\n <!-- No item option -->\n <mat-option *ngIf=\"items | isEmptyArray\" class=\"ion-padding text-italic\" disabled>\n <ion-label>{{noResultMessage | translate}}</ion-label>\n </mat-option>\n\n <mat-option *ngFor=\"let item of items\" [value]=\"item\" class=\"ion-no-padding\">\n <ion-row [classList]=\"item.classList\">\n <ion-col *ngFor=\"let path of displayAttributes; index as i;\"\n [size]=\"displayColumnSizes[i]\">\n <ion-text [innerHTML]=\"item | propertyGet: path | highlight: { search: inputControl.value, withAccent: highlightAccent } \"></ion-text>\n </ion-col>\n </ion-row>\n </mat-option>\n\n <!-- More item -->\n <mat-option *ngIf=\"_moreItemsCount\" (ngInit)=\"_initAutocompleteInfiniteScroll()\"\n class=\"ion-padding\" disabled>\n <ion-skeleton-text [animated]=\"true\" style=\"width: 100%\"></ion-skeleton-text>\n </mat-option>\n </ng-container>\n\n <!-- footer -->\n <ion-row *ngIf=\"itemCount as count\"\n class=\"mat-autocomplete-footer ion-no-padding\">\n <ion-col>\n <ion-text class=\"ion-float-end ion-padding-end\"><i>{{'COMMON.RESULT_COUNT'|translate: {count: count} }}</i></ion-text>\n </ion-col>\n </ion-row>\n\n <!-- Loading spinner -->\n <ng-template #loadingTemplate>\n <mat-option *ngIf=\"(matInputText.value | strLength) >= suggestLengthThreshold; else waitMoreCharacterTemplate\"\n class=\"ion-padding\" disabled>\n <ion-skeleton-text [animated]=\"true\" style=\"width: 100%\"></ion-skeleton-text>\n </mat-option>\n </ng-template>\n\n <!-- Need more characters -->\n <ng-template #waitMoreCharacterTemplate>\n <mat-option class=\"ion-padding text-italic\" disabled>\n <ion-label>\n {{'INFO.PLEASE_TYPE_MORE_CHARACTERS'|translate:{minLength: suggestLengthThreshold} }}\n </ion-label>\n </mat-option>\n </ng-template>\n </mat-autocomplete>\n\n <div matSuffix>\n <ng-container *ngTemplateOutlet=\"matSuffixTemplate\"></ng-container>\n </div>\n\n <mat-error *ngIf=\"formControl.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <ng-content select=\"[matError]\"></ng-content>\n\n </mat-form-field>\n\n </ion-col>\n <ion-col size=\"auto\" class=\"ion-align-self-center\">\n <ng-content select=\"[matAfter]\"></ng-content>\n </ion-col>\n </ion-row>\n</ion-grid>\n\n<ng-template #matPrefixTemplate>\n <ng-content select=\"[matPrefix]\"></ng-content>\n</ng-template>\n\n\n<ng-template #matSuffixTemplate>\n <button mat-icon-button tabindex=\"-1\"\n type=\"button\"\n (click)=\"dropButtonClick.emit($event)\"\n [hidden]=\"disabled\">\n <mat-icon class=\"large select-arrow\">arrow_drop_down</mat-icon>\n </button>\n <button mat-icon-button tabindex=\"-1\"\n type=\"button\"\n *ngIf=\"clearable\"\n (click)=\"clearValue($event)\"\n [hidden]=\"disabled || !formControl.value\">\n <mat-icon>close</mat-icon>\n </button>\n\n <ng-content select=\"[matSuffix]\"></ng-content>\n</ng-template>\n",
7911
8011
  providers: [{
7912
8012
  provide: NG_VALUE_ACCESSOR,
7913
8013
  multi: true,
@@ -7928,6 +8028,7 @@ MatChipsField.propDecorators = {
7928
8028
  formControl: [{ type: Input }],
7929
8029
  formControlName: [{ type: Input }],
7930
8030
  floatLabel: [{ type: Input }],
8031
+ appearance: [{ type: Input }],
7931
8032
  placeholder: [{ type: Input }],
7932
8033
  suggestFn: [{ type: Input }],
7933
8034
  required: [{ type: Input }],
@@ -8276,7 +8377,8 @@ AppGestureConfig.decorators = [
8276
8377
  class ToolbarToken {
8277
8378
  }
8278
8379
  class ToolbarComponent {
8279
- constructor(router, routerOutlet, cd, progressBarService) {
8380
+ constructor(route, router, routerOutlet, cd, progressBarService) {
8381
+ this.route = route;
8280
8382
  this.router = router;
8281
8383
  this.routerOutlet = routerOutlet;
8282
8384
  this.cd = cd;
@@ -8362,15 +8464,28 @@ class ToolbarComponent {
8362
8464
  return __awaiter(this, void 0, void 0, function* () {
8363
8465
  console.debug('[toolbar] calling goBack()');
8364
8466
  if (this._backHref) {
8365
- yield this.router.navigateByUrl(this._backHref);
8467
+ const replaceUrl = !this.routerOutlet.canGoBack();
8468
+ // Relative href
8469
+ if (this._backHref.startsWith('.')) {
8470
+ yield this.router.navigate([this._backHref], { relativeTo: this.route, replaceUrl });
8471
+ }
8472
+ else {
8473
+ yield this.router.navigateByUrl(this._backHref, { replaceUrl });
8474
+ }
8366
8475
  }
8367
8476
  else if (this.routerOutlet.canGoBack()) {
8368
8477
  yield this.routerOutlet.pop();
8369
8478
  }
8370
8479
  else if (this._defaultBackHref) {
8371
- yield this.router.navigateByUrl(this._defaultBackHref, {
8372
- replaceUrl: true
8373
- });
8480
+ // Relative href
8481
+ if (this._defaultBackHref.startsWith('.')) {
8482
+ yield this.router.navigate([this._defaultBackHref], { relativeTo: this.route, replaceUrl: true });
8483
+ }
8484
+ else {
8485
+ yield this.router.navigateByUrl(this._defaultBackHref, {
8486
+ replaceUrl: true
8487
+ });
8488
+ }
8374
8489
  }
8375
8490
  else {
8376
8491
  console.error('[toolbar] Cannot go back. Missing attribute \'defaultBackHref\' or \'backHref\'');
@@ -8429,7 +8544,7 @@ class ToolbarComponent {
8429
8544
  ToolbarComponent.decorators = [
8430
8545
  { type: Component, args: [{
8431
8546
  selector: 'app-toolbar',
8432
- template: "<ion-header [class]=\"class\">\n <ion-toolbar [color]=\"color\">\n\n <ion-buttons slot=\"start\">\n\n <!-- back button -->\n <ion-button class=\"back-button\"\n *ngIf=\"canGoBack\"\n (click)=\"doBackClick($event)\"\n routerDirection=\"back\" >\n <ion-icon slot=\"icon-only\" name=\"arrow-back\"></ion-icon>\n </ion-button>\n\n <!-- show menu button -->\n <ion-menu-toggle [hidden]=\"canGoBack || !canShowMenu\">\n <ion-button>\n <ion-icon slot=\"icon-only\" name=\"menu\"></ion-icon>\n </ion-button>\n </ion-menu-toggle>\n\n <ng-content select=\"[slot=start]\"></ng-content>\n </ion-buttons>\n\n <ion-title [innerHTML]=\"title\"></ion-title>\n\n <ng-content select=\"ion-title\"></ng-content>\n\n <ng-content select=\"ion-buttons[slot=end]\"></ng-content>\n\n <ion-buttons slot=\"end\" collapse=\"true\">\n\n <ng-content select=\"[slot=end]\"></ng-content>\n\n <!-- search bar -->\n <ion-searchbar #searchbar animated\n type=\"search\"\n *ngIf=\"hasSearch\"\n [class.cdk-visually-hidden]=\"!showSearchBar\"\n show-cancel-button=\"always\"\n (ionChange)=\"onSearch.emit($event)\"\n (ionCancel)=\"toggleSearchBar()\">\n </ion-searchbar>\n\n <!-- search button -->\n <ion-button (click)=\"toggleSearchBar()\"\n *ngIf=\"hasSearch && !showSearchBar\">\n <ion-icon slot=\"icon-only\" name=\"search\"></ion-icon>\n </ion-button>\n\n <!-- close button -->\n <ion-button *ngIf=\"hasClose\"\n (tap)=\"tapClose($event)\"\n class=\"visible-xs visible-sm visible-mobile\">\n <ion-icon slot=\"icon-only\" name=\"close\"></ion-icon>\n </ion-button>\n\n <!-- validate button -->\n <ion-button *ngIf=\"hasValidate\"\n (tap)=\"tapValidate($event)\"\n class=\"visible-xs visible-sm visible-mobile\">\n <ion-icon slot=\"icon-only\" name=\"save\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n\n <!-- progress bar -->\n <mat-progress-bar [color]=\"color\" [mode]=\"progressBarMode$ | async\">\n </mat-progress-bar>\n</ion-header>\n",
8547
+ template: "<ion-header [class]=\"class\">\n <ion-toolbar [color]=\"color\">\n\n <ion-buttons slot=\"start\">\n\n <!-- back button -->\n <ion-button class=\"back-button\"\n *ngIf=\"canGoBack\"\n (click)=\"doBackClick($event)\"\n routerDirection=\"back\" >\n <ion-icon slot=\"icon-only\" name=\"arrow-back\"></ion-icon>\n </ion-button>\n\n <!-- show menu button -->\n <ion-menu-toggle [menu]=\"'left'\"\n [class.cdk-visually-hidden]=\"canGoBack || !canShowMenu\">\n <ion-button>\n <ion-icon slot=\"icon-only\" name=\"menu\"></ion-icon>\n </ion-button>\n </ion-menu-toggle>\n\n <ng-content select=\"[slot=start]\"></ng-content>\n </ion-buttons>\n\n <ion-title [innerHTML]=\"title\"></ion-title>\n\n <ng-content select=\"ion-title\"></ng-content>\n\n <ng-content select=\"ion-buttons[slot=end]\"></ng-content>\n\n <ion-buttons slot=\"end\" collapse=\"true\">\n\n <ng-content select=\"[slot=end]\"></ng-content>\n\n <!-- search bar -->\n <ion-searchbar #searchbar animated\n type=\"search\"\n *ngIf=\"hasSearch\"\n [class.cdk-visually-hidden]=\"!showSearchBar\"\n show-cancel-button=\"always\"\n (ionChange)=\"onSearch.emit($event)\"\n (ionCancel)=\"toggleSearchBar()\">\n </ion-searchbar>\n\n <!-- search button -->\n <ion-button (click)=\"toggleSearchBar()\"\n *ngIf=\"hasSearch && !showSearchBar\">\n <ion-icon slot=\"icon-only\" name=\"search\"></ion-icon>\n </ion-button>\n\n <!-- close button -->\n <ion-button *ngIf=\"hasClose\"\n (tap)=\"tapClose($event)\"\n class=\"visible-xs visible-sm visible-mobile\">\n <ion-icon slot=\"icon-only\" name=\"close\"></ion-icon>\n </ion-button>\n\n <!-- validate button -->\n <ion-button *ngIf=\"hasValidate\"\n (tap)=\"tapValidate($event)\"\n class=\"visible-xs visible-sm visible-mobile\">\n <ion-icon slot=\"icon-only\" name=\"save\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n\n <!-- progress bar -->\n <mat-progress-bar [color]=\"color\" [mode]=\"progressBarMode$ | async\">\n </mat-progress-bar>\n</ion-header>\n",
8433
8548
  providers: [
8434
8549
  { provide: ToolbarToken, useExisting: ToolbarComponent }
8435
8550
  ],
@@ -8438,6 +8553,7 @@ ToolbarComponent.decorators = [
8438
8553
  },] }
8439
8554
  ];
8440
8555
  ToolbarComponent.ctorParameters = () => [
8556
+ { type: ActivatedRoute },
8441
8557
  { type: Router },
8442
8558
  { type: IonRouterOutlet },
8443
8559
  { type: ChangeDetectorRef },
@@ -8459,7 +8575,8 @@ ToolbarComponent.propDecorators = {
8459
8575
  onValidateAndClose: [{ type: Output }],
8460
8576
  onBackClick: [{ type: Output }],
8461
8577
  onSearch: [{ type: Output }],
8462
- searchbar: [{ type: ViewChild, args: ['searchbar', { static: true },] }]
8578
+ searchbar: [{ type: ViewChild, args: ['searchbar', { static: true },] }],
8579
+ menuToggle: [{ type: ViewChild, args: ['menuToggle', { static: true },] }]
8463
8580
  };
8464
8581
 
8465
8582
  class MatPaginatorI18n extends MatPaginatorIntl {
@@ -8746,7 +8863,7 @@ class AppFormField {
8746
8863
  AppFormField.decorators = [
8747
8864
  { type: Component, args: [{
8748
8865
  selector: 'app-form-field',
8749
- template: "<ng-container [ngSwitch]=\"type\">\n\n <!-- integer -->\n <mat-form-field *ngSwitchCase=\"'integer'\" [floatLabel]=\"floatLabel\" [class]=\"classList\">\n <input matInput #matInput\n type=\"number\"\n autocomplete=\"off\"\n [readonly]=\"readonly\"\n [min]=\"definition.minValue\"\n [max]=\"definition.maxValue\"\n pattern=\"-?[0-9]*\"\n step=\"1\"\n [appAutofocus]=\"autofocus\"\n [placeholder]=\"placeholder\"\n (keyup.enter)=\"onKeyupEnter.emit($event)\"\n (keypress)=\"filterNumberInput($event, false)\"\n [formControl]=\"formControl\"\n [required]=\"required\"\n [tabIndex]=\"tabindex\"\n (click)=\"selectInputContent($event)\">\n <mat-error *ngIf=\"formControl.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngIf=\"formControl.hasError('min')\">\n {{(compact ? 'ERROR.FIELD_MIN_COMPACT' : 'ERROR.FIELD_MIN') | translate:formControl.errors['min'] }}</mat-error>\n <mat-error *ngIf=\"formControl.hasError('max')\">\n {{(compact ? 'ERROR.FIELD_MAX_COMPACT' : 'ERROR.FIELD_MAX') | translate:formControl.errors['max'] }}</mat-error>\n <mat-error *ngIf=\"formControl.hasError('pattern')\">\n {{'ERROR.FIELD_NOT_VALID_INTEGER'| translate }}</mat-error>\n <mat-error *ngIf=\"formControl.hasError('integer')\">\n {{'ERROR.FIELD_NOT_VALID_INTEGER'| translate }}</mat-error>\n\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n </mat-form-field>\n\n <!-- double -->\n <mat-form-field *ngSwitchCase=\"'double'\" [floatLabel]=\"floatLabel\" [class]=\"classList\">\n\n <input matInput #matInput\n type=\"number\"\n decimal=\"true\"\n autocomplete=\"off\"\n [min]=\"definition.minValue\"\n [max]=\"definition.maxValue\"\n [appAutofocus]=\"autofocus\"\n [readonly]=\"readonly\"\n [placeholder]=\"placeholder\"\n [step]=\"numberInputStep\"\n (keypress)=\"filterNumberInput($event, true)\"\n [formControl]=\"formControl\"\n [required]=\"required\"\n [tabIndex]=\"tabindex\"\n (click)=\"selectInputContent($event)\">\n <mat-error *ngIf=\"formControl.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngIf=\"formControl.hasError('min')\">\n {{ (compact ? 'ERROR.FIELD_MIN_COMPACT' : 'ERROR.FIELD_MIN')| translate:formControl.errors['min'] }}</mat-error>\n <mat-error *ngIf=\"formControl.hasError('max')\">\n {{(compact ? 'ERROR.FIELD_MAX_COMPACT' : 'ERROR.FIELD_MAX') | translate:formControl.errors['max'] }}</mat-error>\n <mat-error *ngIf=\"formControl.hasError('maxDecimals')\">\n {{ (compact ? 'ERROR.FIELD_MAXIMUM_DECIMALS_COMPACT' : 'ERROR.FIELD_MAXIMUM_DECIMALS')| translate:{max:\n definition.maximumNumberDecimals} }}</mat-error>\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n </mat-form-field>\n\n <!-- boolean -->\n <mat-boolean-field *ngSwitchCase=\"'boolean'\" #matInput\n [class]=\"classList\"\n [formControl]=\"formControl\"\n [placeholder]=\"placeholder\"\n [floatLabel]=\"floatLabel\"\n [required]=\"required\"\n [compact]=\"compact\"\n [tabindex]=\"tabindex\"\n [readonly]=\"readonly\">\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n </mat-boolean-field>\n\n <!-- date -->\n <mat-date-field *ngSwitchCase=\"'date'\" #matInput\n [class]=\"classList\"\n [formControl]=\"formControl\"\n [placeholder]=\"compact ? ('COMMON.DATE_PLACEHOLDER'|translate): placeholder\"\n [floatLabel]=\"floatLabel\"\n [required]=\"required\"\n [readonly]=\"readonly\"\n [compact]=\"compact\"\n [tabindex]=\"tabindex\"\n [autofocus]=\"autofocus\">\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n </mat-date-field>\n\n <!-- date time -->\n <mat-date-time-field *ngSwitchCase=\"'dateTime'\" #matInput\n [class]=\"classList\"\n [formControl]=\"formControl\"\n [placeholder]=\"compact ? ('COMMON.DATE_TIME_PLACEHOLDER'|translate): placeholder\"\n [floatLabel]=\"floatLabel\"\n [required]=\"required\"\n [readonly]=\"readonly\"\n [compact]=\"compact\"\n [tabindex]=\"tabindex\"\n [autofocus]=\"autofocus\">\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n </mat-date-time-field>\n\n <!-- enum -->\n <mat-form-field *ngSwitchCase=\"'enum'\" [floatLabel]=\"floatLabel\" [class]=\"classList\">\n <mat-select [formControl]=\"formControl\"\n [placeholder]=\"placeholder\"\n [required]=\"required\"\n [tabindex]=\"tabindex\"\n (keyup.enter)=\"onKeyupEnter.emit($event)\">\n <mat-option *ngFor=\"let item of definition.values\" [value]=\"item.key || item\">{{ (item.value || item) | translate }}</mat-option>\n </mat-select>\n\n <mat-error *ngIf=\"formControl.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n </mat-form-field>\n\n <!-- color -->\n <mat-form-field *ngSwitchCase=\"'color'\" [floatLabel]=\"floatLabel\" [class]=\"classList\">\n\n <ion-icon margin-right name=\"color-fill\" matPrefix></ion-icon>\n\n <input matInput autocomplete=\"off\"\n [appAutofocus]=\"autofocus\"\n [formControl]=\"formControl\"\n [style.color]=\"getColorContrast(formControl.value)\"\n [style.background]=\"formControl.value\"\n [colorPicker]=\"formControl.value\"\n (colorPickerChange)=\"writeValue($event)\"\n [cpSaveClickOutside]=\"true\"\n cpPosition=\"top\"\n cpOutputFormat=\"hex\"\n [cpOKButton]=\"false\"\n [required]=\"required\"/>\n <mat-error *ngIf=\"formControl.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n </mat-form-field>\n\n <!-- string -->\n <mat-form-field *ngSwitchCase=\"'string'\" [floatLabel]=\"floatLabel\" [class]=\"classList\">\n <input matInput #matInput\n autocomplete=\"off\"\n [appAutofocus]=\"autofocus\"\n [formControl]=\"formControl\"\n [placeholder]=\"placeholder\"\n [required]=\"required\"\n [readonly]=\"readonly\"\n [tabIndex]=\"tabindex\"\n (click)=\"selectInputContent($event)\"\n (keypress)=\"filterAlphanumericalInput($event)\"\n (keyup.enter)=\"onKeyupEnter.emit($event)\">\n <mat-error *ngIf=\"formControl.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n </mat-form-field>\n\n <!-- auto-complete -->\n <mat-autocomplete-field *ngSwitchCase=\"'entity'\"\n [class]=\"classList\"\n [autofocus]=\"autofocus\"\n [placeholder]=\"placeholder\"\n [floatLabel]=\"floatLabel\"\n [formControl]=\"formControl\"\n [config]=\"definition.autocomplete\"\n [required]=\"required\"\n [displayWith]=\"getDisplayValueFn(definition)\">\n <ng-content select=\"[matPrefix]\"></ng-content>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </mat-autocomplete-field>\n\n <!-- multi auto-complete -->\n <mat-chips-field *ngSwitchCase=\"'entities'\"\n [class]=\"classList\"\n [placeholder]=\"placeholder\"\n [floatLabel]=\"floatLabel\"\n [formControl]=\"formControl\"\n [config]=\"definition.autocomplete\"\n [required]=\"required\"\n [displayWith]=\"getDisplayValueFn(definition)\">\n <ng-content select=\"[matPrefix]\"></ng-content>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </mat-chips-field>\n\n <!-- other -->\n <div *ngSwitchDefault>\n <mat-error *ngIf=\"type\">Unknown type {{type}} for option {{definition.key}}. Please report this error.</mat-error>\n <mat-error *ngIf=\"!type\">Error on option field. Please check console log for details.</mat-error>\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n </div>\n\n</ng-container>\n",
8866
+ template: "<ng-container [ngSwitch]=\"type\">\n\n <!-- integer -->\n <mat-form-field *ngSwitchCase=\"'integer'\" [floatLabel]=\"floatLabel\" [class]=\"classList\">\n <input matInput #matInput\n type=\"number\"\n autocomplete=\"off\"\n [readonly]=\"readonly\"\n [min]=\"definition.minValue\"\n [max]=\"definition.maxValue\"\n pattern=\"-?[0-9]*\"\n step=\"1\"\n [appAutofocus]=\"autofocus\"\n [placeholder]=\"placeholder\"\n (keyup.enter)=\"onKeyupEnter.emit($event)\"\n (keypress)=\"filterNumberInput($event, false)\"\n [formControl]=\"formControl\"\n [required]=\"required\"\n [tabIndex]=\"tabindex\"\n (click)=\"selectInputContent($event)\">\n <mat-error *ngIf=\"formControl.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngIf=\"formControl.hasError('min')\">\n {{(compact ? 'ERROR.FIELD_MIN_COMPACT' : 'ERROR.FIELD_MIN') | translate:formControl.errors['min'] }}</mat-error>\n <mat-error *ngIf=\"formControl.hasError('max')\">\n {{(compact ? 'ERROR.FIELD_MAX_COMPACT' : 'ERROR.FIELD_MAX') | translate:formControl.errors['max'] }}</mat-error>\n <mat-error *ngIf=\"formControl.hasError('pattern')\">\n {{'ERROR.FIELD_NOT_VALID_INTEGER'| translate }}</mat-error>\n <mat-error *ngIf=\"formControl.hasError('integer')\">\n {{'ERROR.FIELD_NOT_VALID_INTEGER'| translate }}</mat-error>\n\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n </mat-form-field>\n\n <!-- double -->\n <mat-form-field *ngSwitchCase=\"'double'\" [floatLabel]=\"floatLabel\" [class]=\"classList\">\n\n <input matInput #matInput\n type=\"number\"\n decimal=\"true\"\n autocomplete=\"off\"\n [min]=\"definition.minValue\"\n [max]=\"definition.maxValue\"\n [appAutofocus]=\"autofocus\"\n [readonly]=\"readonly\"\n [placeholder]=\"placeholder\"\n [step]=\"numberInputStep\"\n (keypress)=\"filterNumberInput($event, true)\"\n [formControl]=\"formControl\"\n [required]=\"required\"\n [tabIndex]=\"tabindex\"\n (click)=\"selectInputContent($event)\">\n <mat-error *ngIf=\"formControl.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngIf=\"formControl.hasError('decimal')\" translate>ERROR.FIELD_NOT_VALID_DECIMAL</mat-error>\n <mat-error *ngIf=\"formControl.hasError('min')\">\n {{ (compact ? 'ERROR.FIELD_MIN_COMPACT' : 'ERROR.FIELD_MIN')| translate:formControl.errors['min'] }}</mat-error>\n <mat-error *ngIf=\"formControl.hasError('max')\">\n {{(compact ? 'ERROR.FIELD_MAX_COMPACT' : 'ERROR.FIELD_MAX') | translate:formControl.errors['max'] }}</mat-error>\n <mat-error *ngIf=\"formControl.hasError('maxDecimals')\">\n {{ (compact ? 'ERROR.FIELD_MAXIMUM_DECIMALS_COMPACT' : 'ERROR.FIELD_MAXIMUM_DECIMALS')| translate:{maxDecimals:\n definition.maximumNumberDecimals} }}</mat-error>\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n </mat-form-field>\n\n <!-- boolean -->\n <mat-boolean-field *ngSwitchCase=\"'boolean'\" #matInput\n [class]=\"classList\"\n [formControl]=\"formControl\"\n [placeholder]=\"placeholder\"\n [floatLabel]=\"floatLabel\"\n [required]=\"required\"\n [compact]=\"compact\"\n [tabindex]=\"tabindex\"\n [readonly]=\"readonly\">\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n </mat-boolean-field>\n\n <!-- date -->\n <mat-date-field *ngSwitchCase=\"'date'\" #matInput\n [class]=\"classList\"\n [formControl]=\"formControl\"\n [placeholder]=\"compact ? ('COMMON.DATE_PLACEHOLDER'|translate): placeholder\"\n [floatLabel]=\"floatLabel\"\n [required]=\"required\"\n [readonly]=\"readonly\"\n [compact]=\"compact\"\n [tabindex]=\"tabindex\"\n [autofocus]=\"autofocus\">\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n </mat-date-field>\n\n <!-- date time -->\n <mat-date-time-field *ngSwitchCase=\"'dateTime'\" #matInput\n [class]=\"classList\"\n [formControl]=\"formControl\"\n [placeholder]=\"compact ? ('COMMON.DATE_TIME_PLACEHOLDER'|translate): placeholder\"\n [floatLabel]=\"floatLabel\"\n [required]=\"required\"\n [readonly]=\"readonly\"\n [compact]=\"compact\"\n [tabindex]=\"tabindex\"\n [autofocus]=\"autofocus\">\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n </mat-date-time-field>\n\n <!-- enum -->\n <mat-form-field *ngSwitchCase=\"'enum'\" [floatLabel]=\"floatLabel\" [class]=\"classList\">\n <mat-select [formControl]=\"formControl\"\n [placeholder]=\"placeholder\"\n [required]=\"required\"\n [tabindex]=\"tabindex\"\n (keyup.enter)=\"onKeyupEnter.emit($event)\">\n <mat-option *ngFor=\"let item of definition.values\" [value]=\"item.key || item\">{{ (item.value || item) | translate }}</mat-option>\n </mat-select>\n\n <mat-error *ngIf=\"formControl.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n </mat-form-field>\n\n <!-- color -->\n <mat-form-field *ngSwitchCase=\"'color'\" [floatLabel]=\"floatLabel\" [class]=\"classList\">\n\n <ion-icon margin-right name=\"color-fill\" matPrefix></ion-icon>\n\n <input matInput autocomplete=\"off\"\n [appAutofocus]=\"autofocus\"\n [formControl]=\"formControl\"\n [style.color]=\"getColorContrast(formControl.value)\"\n [style.background]=\"formControl.value\"\n [colorPicker]=\"formControl.value\"\n (colorPickerChange)=\"writeValue($event)\"\n [cpSaveClickOutside]=\"true\"\n cpPosition=\"top\"\n cpOutputFormat=\"hex\"\n [cpOKButton]=\"false\"\n [required]=\"required\"/>\n <mat-error *ngIf=\"formControl.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n </mat-form-field>\n\n <!-- string -->\n <mat-form-field *ngSwitchCase=\"'string'\" [floatLabel]=\"floatLabel\" [class]=\"classList\">\n <input matInput #matInput\n autocomplete=\"off\"\n [appAutofocus]=\"autofocus\"\n [formControl]=\"formControl\"\n [placeholder]=\"placeholder\"\n [required]=\"required\"\n [readonly]=\"readonly\"\n [tabIndex]=\"tabindex\"\n (click)=\"selectInputContent($event)\"\n (keypress)=\"filterAlphanumericalInput($event)\"\n (keyup.enter)=\"onKeyupEnter.emit($event)\">\n <mat-error *ngIf=\"formControl.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n </mat-form-field>\n\n <!-- auto-complete -->\n <mat-autocomplete-field *ngSwitchCase=\"'entity'\"\n [class]=\"classList\"\n [autofocus]=\"autofocus\"\n [placeholder]=\"placeholder\"\n [floatLabel]=\"floatLabel\"\n [formControl]=\"formControl\"\n [config]=\"definition.autocomplete\"\n [required]=\"required\"\n [displayWith]=\"getDisplayValueFn(definition)\">\n <ng-content select=\"[matPrefix]\"></ng-content>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </mat-autocomplete-field>\n\n <!-- multi auto-complete -->\n <mat-chips-field *ngSwitchCase=\"'entities'\"\n [class]=\"classList\"\n [placeholder]=\"placeholder\"\n [floatLabel]=\"floatLabel\"\n [formControl]=\"formControl\"\n [config]=\"definition.autocomplete\"\n [required]=\"required\"\n [displayWith]=\"getDisplayValueFn(definition)\">\n <ng-content select=\"[matPrefix]\"></ng-content>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </mat-chips-field>\n\n <!-- other -->\n <div *ngSwitchDefault>\n <mat-error *ngIf=\"type\">Unknown type {{type}} for option {{definition.key}}. Please report this error.</mat-error>\n <mat-error *ngIf=\"!type\">Error on option field. Please check console log for details.</mat-error>\n <div matSuffix>\n <ng-content select=\"[matSuffix]\"></ng-content>\n </div>\n </div>\n\n</ng-container>\n",
8750
8867
  providers: [
8751
8868
  {
8752
8869
  provide: NG_VALUE_ACCESSOR,
@@ -9730,10 +9847,6 @@ class EntityUtils {
9730
9847
  return isNotNil(obj.__typename) && (typeof obj.fromObject === 'function') && (typeof obj.asObject === 'function');
9731
9848
  }
9732
9849
  // Check that the object has a NOT nil attribute (ID by default)
9733
- static isNotEntity(obj) {
9734
- return isNil(obj.__typename) || (typeof obj.fromObject !== 'function') || (typeof obj.asObject !== 'function');
9735
- }
9736
- // Check that the object has a NOT nil attribute (ID by default)
9737
9850
  static isNotEmpty(obj, checkedAttribute) {
9738
9851
  return !!obj && obj[checkedAttribute] !== null && obj[checkedAttribute] !== undefined;
9739
9852
  }
@@ -9813,6 +9926,8 @@ class EntityUtils {
9813
9926
  (items || []).forEach(EntityUtils.cleanIdAndUpdateDate);
9814
9927
  }
9815
9928
  static sort(data, sortBy, sortDirection) {
9929
+ if (!data || data.length <= 1 || !sortBy)
9930
+ return data;
9816
9931
  return data.sort(this.sortComparator(sortBy, sortDirection));
9817
9932
  }
9818
9933
  static sortComparator(sortBy, sortDirection) {
@@ -11837,11 +11952,10 @@ class EntityStore {
11837
11952
  /**
11838
11953
  *
11839
11954
  * @param opts
11840
- * opts.force : force all entities to be persist, and not only if dirty
11955
+ * opts.skipIfPristine = false persist all entities, even if not dirty
11841
11956
  */
11842
- persist(opts) {
11957
+ persist(opts = { skipIfPristine: true }) {
11843
11958
  return __awaiter(this, void 0, void 0, function* () {
11844
- opts = Object.assign({ skipIfPristine: this.policy.skipNonLocalEntities }, opts);
11845
11959
  // Skip is not dirty
11846
11960
  if (opts.skipIfPristine && !this._dirty) {
11847
11961
  console.debug(`[entity-storage] Persisting ${this.name} not need. Skip`);
@@ -12051,7 +12165,7 @@ class EntityStore {
12051
12165
  if (variables.size === 0)
12052
12166
  return { data: [], total };
12053
12167
  // Sort by
12054
- if (data.length && variables.sortBy) {
12168
+ if (variables.sortBy) {
12055
12169
  EntityUtils.sort(data, variables.sortBy, variables.sortDirection);
12056
12170
  }
12057
12171
  // Slice in a page (using offset and size)
@@ -12124,6 +12238,33 @@ class EntitiesStorage extends StartableService {
12124
12238
  get dirty() {
12125
12239
  return this._dirty || Object.values(this._data).some(store => store.dirty);
12126
12240
  }
12241
+ ngOnStart() {
12242
+ return __awaiter(this, void 0, void 0, function* () {
12243
+ const now = Date.now();
12244
+ console.info(`[entities-storage] Starting entity storage...`);
12245
+ // Restore stores
12246
+ yield this.restoreLocally();
12247
+ // Start a save timer
12248
+ this.registerSubscription(merge(this._$save, timer(this._saveTimerPeriod, this._saveTimerPeriod))
12249
+ .pipe(
12250
+ // Avoid to many call (e.g. when $save AND timer are triggered
12251
+ throttleTime(this._saveTimerPeriod))
12252
+ .subscribe(() => this.storeLocally({ skipIfPristine: true })));
12253
+ // Save, when platform is in pause
12254
+ this.registerSubscription(this.platform.pause.subscribe(() => __awaiter(this, void 0, void 0, function* () {
12255
+ console.warn(`[entities-storage] Platform will pause: saving...`);
12256
+ yield this.storeLocally({ skipIfPristine: true });
12257
+ console.warn(`[entities-storage] Platform will pause: saving [OK]`);
12258
+ })));
12259
+ console.info(`[entities-storage] Starting [OK] in ${Date.now() - now}ms`);
12260
+ return this._data;
12261
+ });
12262
+ }
12263
+ ngOnStop() {
12264
+ return __awaiter(this, void 0, void 0, function* () {
12265
+ yield this.storeLocally({ skipIfPristine: true });
12266
+ });
12267
+ }
12127
12268
  watchAll(entityName, variables, opts) {
12128
12269
  // Make sure store is ready
12129
12270
  if (!this.started) {
@@ -12389,34 +12530,8 @@ class EntitiesStorage extends StartableService {
12389
12530
  this._$save.emit();
12390
12531
  });
12391
12532
  }
12392
- persist() {
12393
- if (this._dirty) {
12394
- return this.storeLocally();
12395
- }
12396
- return Promise.resolve();
12397
- }
12398
- ngOnStart() {
12399
- return __awaiter(this, void 0, void 0, function* () {
12400
- const now = Date.now();
12401
- console.info(`[entities-storage] Starting entity storage...`);
12402
- // Restore sequences
12403
- yield this.restoreLocally();
12404
- // Start a save timer
12405
- this.registerSubscription(merge(this._$save, timer(this._saveTimerPeriod, this._saveTimerPeriod))
12406
- .pipe(
12407
- // Avoid to many call (e.g. when $save AND timer are triggered
12408
- throttleTime(this._saveTimerPeriod))
12409
- .subscribe(() => this.storeLocally()));
12410
- console.info(`[entities-storage] Starting [OK] in ${Date.now() - now}ms`);
12411
- return this._data;
12412
- });
12413
- }
12414
- ngOnStop() {
12415
- return __awaiter(this, void 0, void 0, function* () {
12416
- if (this.dirty) {
12417
- yield this.storeLocally();
12418
- }
12419
- });
12533
+ persist(opts = { skipIfPristine: true }) {
12534
+ return this.storeLocally(opts);
12420
12535
  }
12421
12536
  /* -- protected methods -- */
12422
12537
  getEntityStore(name, opts) {
@@ -12455,9 +12570,9 @@ class EntitiesStorage extends StartableService {
12455
12570
  console.debug(`[entities-storage] Restoring entities [OK] ${entitiesCount} entities found in ${Date.now() - now}ms`);
12456
12571
  });
12457
12572
  }
12458
- storeLocally() {
12573
+ storeLocally(opts = { skipIfPristine: true }) {
12459
12574
  return __awaiter(this, void 0, void 0, function* () {
12460
- if (!this.dirty)
12575
+ if (opts.skipIfPristine && !this.dirty)
12461
12576
  return; // skip
12462
12577
  // Saving progress: report this save later
12463
12578
  if (this._saving) {
@@ -12481,11 +12596,11 @@ class EntitiesStorage extends StartableService {
12481
12596
  console.warn(`[entities-storage] Persisting ${entityName}: store not found!`);
12482
12597
  return;
12483
12598
  }
12484
- // SKip store if not dirty
12485
- if (!entityStore.dirty)
12486
- return Promise.resolve(); // Skip
12599
+ // SKip store if pristine
12600
+ if (opts.skipIfPristine && !entityStore.dirty)
12601
+ return Promise.resolve();
12487
12602
  // Persist store
12488
- return (entityStore.dirty ? entityStore.persist() : Promise.resolve(entityStore.length))
12603
+ return entityStore.persist(opts)
12489
12604
  .then(count => {
12490
12605
  // If no entity found, remove from the entity names array
12491
12606
  if (!count) {
@@ -12734,9 +12849,6 @@ class ReferentialUtils {
12734
12849
  // A referential entity should always have a 'id' filled (can be negative is local and temporary)
12735
12850
  return EntityUtils.isNotEmpty(obj, 'id');
12736
12851
  }
12737
- static isNotEmptyReferential(obj) {
12738
- return EntityUtils.isNotEmpty(obj, 'id');
12739
- }
12740
12852
  static isEmpty(obj) {
12741
12853
  return EntityUtils.isEmpty(obj, 'id');
12742
12854
  }
@@ -14637,7 +14749,7 @@ class AccountService extends BaseGraphqlService {
14637
14749
  return this._$additionalFields.getValue();
14638
14750
  }
14639
14751
  get $additionalFields() {
14640
- return this._$additionalFields;
14752
+ return this._$additionalFields.asObservable();
14641
14753
  }
14642
14754
  getAdditionalField(key) {
14643
14755
  return this._$additionalFields.getValue().find(f => f.key === key);
@@ -15684,6 +15796,196 @@ ConfigService.ctorParameters = () => [
15684
15796
  { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [APP_CONFIG_OPTIONS,] }] }
15685
15797
  ];
15686
15798
 
15799
+ class UploadFile extends File {
15800
+ }
15801
+ class FileResponse {
15802
+ constructor(init) {
15803
+ this.type = HttpEventType.Response;
15804
+ this.body = isNotNil(init === null || init === void 0 ? void 0 : init.body) ? init.body : null;
15805
+ this.status = (init === null || init === void 0 ? void 0 : init.status) || 0;
15806
+ this.statusText = (init === null || init === void 0 ? void 0 : init.statusText) || 'OK';
15807
+ }
15808
+ }
15809
+ function isProgressEvent(event) {
15810
+ return event && (event.type === HttpEventType.UploadProgress
15811
+ || event.type === HttpEventType.DownloadProgress);
15812
+ }
15813
+ function isResponseEvent(event) {
15814
+ return event && event.type === HttpEventType.Response;
15815
+ }
15816
+
15817
+ class UploadFilePopover {
15818
+ constructor(popoverController, translate, cd) {
15819
+ this.popoverController = popoverController;
15820
+ this.translate = translate;
15821
+ this.cd = cd;
15822
+ this.uniqueFile = false;
15823
+ this.instantUpload = false;
15824
+ this.importing = false;
15825
+ }
15826
+ get files() {
15827
+ return this.uploader.files;
15828
+ }
15829
+ get disabled() {
15830
+ return this.importing || isEmptyArray(this.files);
15831
+ }
15832
+ get valid() {
15833
+ return isNotEmptyArray(this.files);
15834
+ }
15835
+ onValidate(event) {
15836
+ return __awaiter(this, void 0, void 0, function* () {
15837
+ event === null || event === void 0 ? void 0 : event.stopPropagation();
15838
+ // Avoid multiple call
15839
+ if (this.importing)
15840
+ return;
15841
+ console.debug('[upload-file-popover] Validate form: will upload all files...');
15842
+ try {
15843
+ this.importing = true;
15844
+ this.resetError();
15845
+ try {
15846
+ // Wait processing files finished
15847
+ yield this.uploader.waitIdle();
15848
+ // upload all files
15849
+ yield this.uploader.uploadFiles();
15850
+ const files = (this.uploader.processedFiles || [])
15851
+ .filter(file => !file.deleting); // Remove deleting file (deletion in progress)
15852
+ // Nothing to upload: close popover
15853
+ if (isEmptyArray(files))
15854
+ return this.cancel();
15855
+ // check errors
15856
+ const errors = files.map(file => file.error).filter(isNotNilOrBlank);
15857
+ // If error: stop
15858
+ if (errors.length) {
15859
+ errors.forEach((error) => console.error(error));
15860
+ throw { message: 'FILE.UPLOAD.ERROR' };
15861
+ }
15862
+ // return files
15863
+ yield this.popoverController.dismiss(files);
15864
+ }
15865
+ catch (err) {
15866
+ this.error = (err && err.message) || err;
15867
+ }
15868
+ }
15869
+ finally {
15870
+ this.importing = false;
15871
+ this.cd.markForCheck();
15872
+ }
15873
+ });
15874
+ }
15875
+ cancel() {
15876
+ return this.popoverController.dismiss();
15877
+ }
15878
+ resetError() {
15879
+ if (this.error) {
15880
+ this.error = null;
15881
+ this.cd.markForCheck();
15882
+ }
15883
+ }
15884
+ }
15885
+ UploadFilePopover.decorators = [
15886
+ { type: Component, args: [{
15887
+ selector: 'app-upload-file-popover',
15888
+ template: "\n<ion-content class=\"ion-no-padding\" scrollY=\"false\">\n <div class=\"modal-content\">\n <app-upload-file\n #uploader\n [uploadFn]=\"uploadFn\"\n [deleteFn]=\"deleteFn\"\n [fileExtension]=\"fileExtension\"\n [instantUpload]=\"instantUpload\"\n [uniqueFile]=\"uniqueFile\"\n [maxParallelUpload]=\"maxParallelUpload\"\n ></app-upload-file>\n </div>\n</ion-content>\n\n<ion-footer>\n <ion-toolbar>\n <ion-row class=\"ion-no-padding\" nowrap>\n <ion-col>\n <div *ngIf=\"importing\" class=\"importing-info\">\n <ion-text color=\"medium\"><small translate>FILE.UPLOAD.IMPORTING</small></ion-text>\n &nbsp;<ion-spinner name=\"dots\" color=\"accent\"></ion-spinner>\n </div>\n <ion-item *ngIf=\"error\" lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"ion-text-wrap\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n </ion-col>\n <ion-col size=\"auto\">\n <ion-button fill=\"clear\" color=\"dark\" (click)=\"cancel()\">\n <ion-label translate>COMMON.BTN_CANCEL</ion-label>\n </ion-button>\n <ion-button\n [fill]=\"disabled ? 'clear' : 'solid'\"\n (click)=\"onValidate($event)\"\n (keyup.enter)=\"onValidate($event)\"\n color=\"tertiary\"\n [disabled]=\"disabled\"\n >\n <ion-label translate>COMMON.BTN_IMPORT</ion-label>\n </ion-button>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n</ion-footer>\n",
15889
+ encapsulation: ViewEncapsulation.None,
15890
+ changeDetection: ChangeDetectionStrategy.OnPush,
15891
+ styles: [".importing-info{-webkit-padding-start:var(--ion-padding);padding-inline-start:var(--ion-padding);display:inline-flex;flex-wrap:nowrap;padding-top:8px}"]
15892
+ },] }
15893
+ ];
15894
+ UploadFilePopover.ctorParameters = () => [
15895
+ { type: PopoverController },
15896
+ { type: TranslateService },
15897
+ { type: ChangeDetectorRef }
15898
+ ];
15899
+ UploadFilePopover.propDecorators = {
15900
+ uploader: [{ type: ViewChild, args: ['uploader', { static: true },] }],
15901
+ fileExtension: [{ type: Input }],
15902
+ title: [{ type: Input }],
15903
+ uniqueFile: [{ type: Input }],
15904
+ instantUpload: [{ type: Input }],
15905
+ uploadFn: [{ type: Input }],
15906
+ deleteFn: [{ type: Input }],
15907
+ maxParallelUpload: [{ type: Input }]
15908
+ };
15909
+
15910
+ const MimeTypes = Object.freeze({
15911
+ ANDROID_APK: 'application/vnd.android.package-archive'
15912
+ });
15913
+ class FilesUtils {
15914
+ static showUploadPopover(popoverController, event, opts) {
15915
+ return __awaiter(this, void 0, void 0, function* () {
15916
+ const modal = yield popoverController.create({
15917
+ component: UploadFilePopover,
15918
+ componentProps: opts,
15919
+ backdropDismiss: false,
15920
+ keyboardClose: false,
15921
+ event,
15922
+ translucent: true,
15923
+ cssClass: 'popover-large',
15924
+ });
15925
+ yield modal.present();
15926
+ const { data, role } = yield modal.onDidDismiss();
15927
+ return {
15928
+ role,
15929
+ data: data ? data : undefined
15930
+ };
15931
+ });
15932
+ }
15933
+ static readAsText(file, encoding) {
15934
+ const $progress = new Subject();
15935
+ const reader = new FileReader();
15936
+ encoding = (encoding || 'UTF-8').toLowerCase();
15937
+ // Listen progress
15938
+ reader.onprogress = (e) => {
15939
+ const total = e.total || 1;
15940
+ const loaded = e.total || 0;
15941
+ if (loaded < total)
15942
+ $progress.next({ type: HttpEventType.UploadProgress, loaded, total });
15943
+ };
15944
+ // Listen end
15945
+ reader.onloadend = (e) => {
15946
+ const body = reader.result;
15947
+ if (typeof body === 'string') {
15948
+ $progress.next(new FileResponse({ body }));
15949
+ $progress.complete();
15950
+ }
15951
+ else {
15952
+ console.error(`Cannot read file '${file.name}' as text`);
15953
+ $progress.error(`Cannot read file as text`);
15954
+ }
15955
+ };
15956
+ // Listen error
15957
+ reader.onerror = (event) => {
15958
+ console.error('[files] Error while reading file:', event);
15959
+ $progress.error(event);
15960
+ };
15961
+ // Start reading the file
15962
+ reader.readAsText(file, encoding);
15963
+ return $progress.asObservable();
15964
+ }
15965
+ }
15966
+ class UriUtils {
15967
+ /**
15968
+ * Extract the filename, from an uri (using the last slash)
15969
+ * @param uri
15970
+ */
15971
+ static getFilename(uri) {
15972
+ if (!uri)
15973
+ return uri;
15974
+ let filename = uri.trim();
15975
+ // Get last part (or all string, if no '/')
15976
+ const lastSlashIndex = filename.lastIndexOf('/');
15977
+ if (lastSlashIndex !== -1 && lastSlashIndex !== uri.length - 1) {
15978
+ filename = filename.substring(lastSlashIndex + 1);
15979
+ }
15980
+ // Remove query params
15981
+ const queryParamIndex = filename.indexOf('?');
15982
+ if (queryParamIndex !== -1) {
15983
+ filename = filename.substring(0, queryParamIndex);
15984
+ }
15985
+ return filename;
15986
+ }
15987
+ }
15988
+
15687
15989
  const moment$4 = momentImported;
15688
15990
  const AndroidOsEnvironment = Object.freeze({
15689
15991
  DIRECTORY_DOWNLOADS: 'Download',
@@ -15720,7 +16022,7 @@ class PlatformService extends StartableService {
15720
16022
  switch (platformName) {
15721
16023
  case 'mobile':
15722
16024
  case 'mobileweb':
15723
- // Exclude Windows touch screens - see SUMARIS issue #323
16025
+ // Exclude Windows touch screens - see sumaris-app issues #323 and #346
15724
16026
  return this.platform.is(platformName) && !isWindows(window);
15725
16027
  default:
15726
16028
  return this.platform.is(platformName);
@@ -15774,6 +16076,7 @@ class PlatformService extends StartableService {
15774
16076
  this._cordova = this.is('cordova');
15775
16077
  this._android = this.is('android');
15776
16078
  this._ios = this.is('ios');
16079
+ this.configureClassList(this._mobile);
15777
16080
  this.configureCordovaPlugins();
15778
16081
  // Force some settings
15779
16082
  this.settings.mobile = this._mobile;
@@ -15808,7 +16111,7 @@ class PlatformService extends StartableService {
15808
16111
  this.accountService.tokenType = tokenType;
15809
16112
  });
15810
16113
  // Hide the splashscreen (if mobile) - after 1s - and play start sound
15811
- if (this.mobile) {
16114
+ if (this._mobile) {
15812
16115
  setTimeout(() => {
15813
16116
  var _a;
15814
16117
  (_a = this.splashScreen) === null || _a === void 0 ? void 0 : _a.hide();
@@ -15844,34 +16147,48 @@ class PlatformService extends StartableService {
15844
16147
  return this._downloadingJobs.get(uri);
15845
16148
  }
15846
16149
  download(request) {
16150
+ var _a, _b;
15847
16151
  if (!request || !request.uri)
15848
16152
  throw new Error('Missing argument \'request\' or \'request.uri\'');
15849
16153
  if (this._android && this.downloader) {
15850
16154
  // Check if not already downloading file
15851
- let location$ = this._downloadingJobs.get(request.uri);
15852
- if (location$)
15853
- return location$;
15854
- request = Object.assign(Object.assign({ visibleInDownloadsUi: true, notificationVisibility: NotificationVisibility.VisibleNotifyCompleted, title: request.title || request.filename }, request), { destinationInExternalPublicDir: Object.assign({ dirType: AndroidOsEnvironment.DIRECTORY_DOWNLOADS, subPath: request.filename || request.title || '' }, request.destinationInExternalPublicDir) });
15855
- console.debug('[platform] Downloading, using request: ' + JSON.stringify(request));
16155
+ let job$ = this._downloadingJobs.get(request.uri);
16156
+ if (job$)
16157
+ return job$;
16158
+ // Compute subPath (final filename)
16159
+ let subPath = ((_a = request.destinationInExternalPublicDir) === null || _a === void 0 ? void 0 : _a.subPath) || ((_b = request.destinationInExternalFilesDir) === null || _b === void 0 ? void 0 : _b.subPath)
16160
+ || request.filename || UriUtils.getFilename(request.uri)
16161
+ || request.title || 'download';
16162
+ subPath = subPath.toLowerCase()
16163
+ // Remove spaces
16164
+ .replace(/\s+/g, '-');
16165
+ // Force APK to finish with '.apk'
16166
+ if (request.mimeType === MimeTypes.ANDROID_APK && !subPath.toLowerCase().endsWith('.apk')) {
16167
+ subPath += '.apk';
16168
+ }
16169
+ const downloadRequest = Object.assign(Object.assign({ visibleInDownloadsUi: true, notificationVisibility: NotificationVisibility.VisibleNotifyCompleted }, request), { filename: undefined, destinationInExternalPublicDir: !request.destinationInExternalFilesDir
16170
+ ? Object.assign(Object.assign({ dirType: AndroidOsEnvironment.DIRECTORY_DOWNLOADS }, request.destinationInExternalPublicDir), { subPath }) : undefined, destinationInExternalFilesDir: request.destinationInExternalFilesDir
16171
+ ? Object.assign(Object.assign({ dirType: AndroidOsEnvironment.DIRECTORY_DOWNLOADS }, request.destinationInExternalFilesDir), { subPath }) : undefined });
15856
16172
  // Start download
15857
- location$ = from(this.downloader.download(request));
16173
+ console.debug('[platform] Downloading, using request: ' + JSON.stringify(downloadRequest));
16174
+ job$ = from(this.downloader.download(downloadRequest));
15858
16175
  // Remember this request uri
15859
- this._downloadingJobs.set(request.uri, location$);
15860
- return location$
16176
+ this._downloadingJobs.set(downloadRequest.uri, job$);
16177
+ return job$
15861
16178
  .pipe(tap(location => {
15862
16179
  console.info('[platform] File successfully downloaded at:' + location);
15863
- // Forget the request
15864
- this._downloadingJobs.delete(request.uri);
16180
+ // Forget job
16181
+ this._downloadingJobs.delete(downloadRequest.uri);
15865
16182
  return location;
15866
16183
  }), catchError(err => {
15867
16184
  var _a;
15868
16185
  console.error('[platform] Unable to download: ' + (err && err.message || err));
15869
16186
  // Forget the request
15870
- this._downloadingJobs.delete(request.uri);
16187
+ this._downloadingJobs.delete(downloadRequest.uri);
15871
16188
  // Retry with another location - issue in older Android version
15872
- if (((_a = request.destinationInExternalPublicDir) === null || _a === void 0 ? void 0 : _a.dirType) === AndroidOsEnvironment.DIRECTORY_DOWNLOADS) {
15873
- request.destinationInExternalPublicDir.dirType = AndroidOsEnvironment.DIRECTORY_DOWNLOADS_OLD;
15874
- return this.download(request);
16189
+ if (((_a = downloadRequest.destinationInExternalPublicDir) === null || _a === void 0 ? void 0 : _a.dirType) === AndroidOsEnvironment.DIRECTORY_DOWNLOADS) {
16190
+ downloadRequest.destinationInExternalPublicDir.dirType = AndroidOsEnvironment.DIRECTORY_DOWNLOADS_OLD;
16191
+ return this.download(downloadRequest);
15875
16192
  }
15876
16193
  throw err;
15877
16194
  }));
@@ -15896,6 +16213,17 @@ class PlatformService extends StartableService {
15896
16213
  //}
15897
16214
  }
15898
16215
  /* -- protected methods -- */
16216
+ configureClassList(mobile, win = window) {
16217
+ var _a, _b;
16218
+ if (mobile)
16219
+ return; // Nothing to do
16220
+ // Workaround, for MS Windows touch screen (detected as mobile, but forced as desktop)
16221
+ if (!mobile && (((_a = win.document.documentElement) === null || _a === void 0 ? void 0 : _a.classList.contains('plt-mobile')) || ((_b = win.document.documentElement.classList) === null || _b === void 0 ? void 0 : _b.contains('plt-mobileweb')))) {
16222
+ console.warn('[platform] Force class list to: <html class="plt-desktop">. Workaround for Windows touch screen.');
16223
+ win.document.documentElement.classList.remove('plt-mobile', 'plt-mobileweb');
16224
+ win.document.documentElement.classList.add('plt-desktop');
16225
+ }
16226
+ }
15899
16227
  configureCordovaPlugins() {
15900
16228
  console.info('[platform] Configuring Cordova plugins...');
15901
16229
  if (this.statusBar) {
@@ -16223,24 +16551,6 @@ MatStepperI18n.ctorParameters = () => [
16223
16551
  { type: TranslateService }
16224
16552
  ];
16225
16553
 
16226
- class UploadFile extends File {
16227
- }
16228
- class FileResponse {
16229
- constructor(init) {
16230
- this.type = HttpEventType.Response;
16231
- this.body = isNotNil(init === null || init === void 0 ? void 0 : init.body) ? init.body : null;
16232
- this.status = (init === null || init === void 0 ? void 0 : init.status) || 0;
16233
- this.statusText = (init === null || init === void 0 ? void 0 : init.statusText) || 'OK';
16234
- }
16235
- }
16236
- function isProgressEvent(event) {
16237
- return event && (event.type === HttpEventType.UploadProgress
16238
- || event.type === HttpEventType.DownloadProgress);
16239
- }
16240
- function isResponseEvent(event) {
16241
- return event && event.type === HttpEventType.Response;
16242
- }
16243
-
16244
16554
  class UploadFileComponent {
16245
16555
  constructor(cd, translate) {
16246
16556
  this.cd = cd;
@@ -16435,99 +16745,6 @@ UploadFileComponent.propDecorators = {
16435
16745
  maxParallelUpload: [{ type: Input }]
16436
16746
  };
16437
16747
 
16438
- class UploadFilePopover {
16439
- constructor(popoverController, translate, cd) {
16440
- this.popoverController = popoverController;
16441
- this.translate = translate;
16442
- this.cd = cd;
16443
- this.uniqueFile = false;
16444
- this.instantUpload = false;
16445
- this.importing = false;
16446
- }
16447
- get files() {
16448
- return this.uploader.files;
16449
- }
16450
- get disabled() {
16451
- return this.importing || isEmptyArray(this.files);
16452
- }
16453
- get valid() {
16454
- return isNotEmptyArray(this.files);
16455
- }
16456
- onValidate(event) {
16457
- return __awaiter(this, void 0, void 0, function* () {
16458
- event === null || event === void 0 ? void 0 : event.stopPropagation();
16459
- // Avoid multiple call
16460
- if (this.importing)
16461
- return;
16462
- console.debug('[upload-file-popover] Validate form: will upload all files...');
16463
- try {
16464
- this.importing = true;
16465
- this.resetError();
16466
- try {
16467
- // Wait processing files finished
16468
- yield this.uploader.waitIdle();
16469
- // upload all files
16470
- yield this.uploader.uploadFiles();
16471
- const files = (this.uploader.processedFiles || [])
16472
- .filter(file => !file.deleting); // Remove deleting file (deletion in progress)
16473
- // Nothing to upload: close popover
16474
- if (isEmptyArray(files))
16475
- return this.cancel();
16476
- // check errors
16477
- const errors = files.map(file => file.error).filter(isNotNilOrBlank);
16478
- // If error: stop
16479
- if (errors.length) {
16480
- errors.forEach((error) => console.error(error));
16481
- throw { message: 'FILE.UPLOAD.ERROR' };
16482
- }
16483
- // return files
16484
- yield this.popoverController.dismiss(files);
16485
- }
16486
- catch (err) {
16487
- this.error = (err && err.message) || err;
16488
- }
16489
- }
16490
- finally {
16491
- this.importing = false;
16492
- this.cd.markForCheck();
16493
- }
16494
- });
16495
- }
16496
- cancel() {
16497
- return this.popoverController.dismiss();
16498
- }
16499
- resetError() {
16500
- if (this.error) {
16501
- this.error = null;
16502
- this.cd.markForCheck();
16503
- }
16504
- }
16505
- }
16506
- UploadFilePopover.decorators = [
16507
- { type: Component, args: [{
16508
- selector: 'app-upload-file-popover',
16509
- template: "\n<ion-content class=\"ion-no-padding\" scrollY=\"false\">\n <div class=\"modal-content\">\n <app-upload-file\n #uploader\n [uploadFn]=\"uploadFn\"\n [deleteFn]=\"deleteFn\"\n [fileExtension]=\"fileExtension\"\n [instantUpload]=\"instantUpload\"\n [uniqueFile]=\"uniqueFile\"\n [maxParallelUpload]=\"maxParallelUpload\"\n ></app-upload-file>\n </div>\n</ion-content>\n\n<ion-footer>\n <ion-toolbar>\n <ion-row class=\"ion-no-padding\" nowrap>\n <ion-col>\n <div *ngIf=\"importing\" class=\"importing-info\">\n <ion-text color=\"medium\"><small translate>FILE.UPLOAD.IMPORTING</small></ion-text>\n &nbsp;<ion-spinner name=\"dots\" color=\"accent\"></ion-spinner>\n </div>\n <ion-item *ngIf=\"error\" lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"ion-text-wrap\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n </ion-col>\n <ion-col size=\"auto\">\n <ion-button fill=\"clear\" color=\"dark\" (click)=\"cancel()\">\n <ion-label translate>COMMON.BTN_CANCEL</ion-label>\n </ion-button>\n <ion-button\n [fill]=\"disabled ? 'clear' : 'solid'\"\n (click)=\"onValidate($event)\"\n (keyup.enter)=\"onValidate($event)\"\n color=\"tertiary\"\n [disabled]=\"disabled\"\n >\n <ion-label translate>COMMON.BTN_IMPORT</ion-label>\n </ion-button>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n</ion-footer>\n",
16510
- encapsulation: ViewEncapsulation.None,
16511
- changeDetection: ChangeDetectionStrategy.OnPush,
16512
- styles: [".importing-info{-webkit-padding-start:var(--ion-padding);padding-inline-start:var(--ion-padding);display:inline-flex;flex-wrap:nowrap;padding-top:8px}"]
16513
- },] }
16514
- ];
16515
- UploadFilePopover.ctorParameters = () => [
16516
- { type: PopoverController },
16517
- { type: TranslateService },
16518
- { type: ChangeDetectorRef }
16519
- ];
16520
- UploadFilePopover.propDecorators = {
16521
- uploader: [{ type: ViewChild, args: ['uploader', { static: true },] }],
16522
- fileExtension: [{ type: Input }],
16523
- title: [{ type: Input }],
16524
- uniqueFile: [{ type: Input }],
16525
- instantUpload: [{ type: Input }],
16526
- uploadFn: [{ type: Input }],
16527
- deleteFn: [{ type: Input }],
16528
- maxParallelUpload: [{ type: Input }]
16529
- };
16530
-
16531
16748
  const APP_TESTING_PAGES = new InjectionToken('testingPages');
16532
16749
  class SharedTestsPage {
16533
16750
  constructor(route, router, translate, pages) {
@@ -16910,8 +17127,8 @@ class ComponentDirtyGuard {
16910
17127
  }
16911
17128
  canDeactivate(component, currentRoute, currentState, nextState) {
16912
17129
  return __awaiter(this, void 0, void 0, function* () {
16913
- const dirty = component.dirty;
16914
- const valid = component.valid;
17130
+ const dirty = component === null || component === void 0 ? void 0 : component.dirty;
17131
+ const valid = component === null || component === void 0 ? void 0 : component.valid;
16915
17132
  let can = {
16916
17133
  confirmed: !dirty,
16917
17134
  save: false,
@@ -17791,6 +18008,7 @@ class JobUtils {
17791
18008
  return progression;
17792
18009
  }
17793
18010
  static fetchAllPages(loadPageFn, opts) {
18011
+ var _a, _b;
17794
18012
  return __awaiter(this, void 0, void 0, function* () {
17795
18013
  const maxProgression = toNumber(opts === null || opts === void 0 ? void 0 : opts.maxProgression, 100);
17796
18014
  const progression = opts === null || opts === void 0 ? void 0 : opts.progression;
@@ -17823,7 +18041,7 @@ class JobUtils {
17823
18041
  progressionStep = maxProgression / pageCount;
17824
18042
  // DEBUG
17825
18043
  if (opts && opts.logPrefix)
17826
- console.debug(`${opts.logPrefix} {pageCount: ${pageCount}, progression: ${progression.value}, pageProgressionStep: ${progressionStep}}`);
18044
+ console.debug(`${opts.logPrefix} {pageCount: ${pageCount}, progress: ${(_a = progression.value) === null || _a === void 0 ? void 0 : _a.toFixed(2)}, pageProgressStep: ${progressionStep === null || progressionStep === void 0 ? void 0 : progressionStep.toFixed(1)}}`);
17827
18045
  }
17828
18046
  }
17829
18047
  // Increment progression on each iteration (if possible)
@@ -17850,7 +18068,7 @@ class JobUtils {
17850
18068
  }
17851
18069
  // DEBUG
17852
18070
  if (opts && opts.logPrefix)
17853
- console.debug(`${opts.logPrefix} All pages fetched - {progression: ${progression.value}, lastProgressionStep: ${progressionStep}`);
18071
+ console.debug(`${opts.logPrefix} All pages fetched - {progress: ${(_b = progression.value) === null || _b === void 0 ? void 0 : _b.toFixed(2)}, lastProgressStep: ${progressionStep === null || progressionStep === void 0 ? void 0 : progressionStep.toFixed(1)}`);
17854
18072
  // Final increment
17855
18073
  if (progression && progressionStep > 0) {
17856
18074
  progression.next(progression.value + progressionStep);
@@ -17863,60 +18081,6 @@ class JobUtils {
17863
18081
  }
17864
18082
  }
17865
18083
 
17866
- class FilesUtils {
17867
- static showUploadPopover(popoverController, event, opts) {
17868
- return __awaiter(this, void 0, void 0, function* () {
17869
- const modal = yield popoverController.create({
17870
- component: UploadFilePopover,
17871
- componentProps: opts,
17872
- backdropDismiss: false,
17873
- keyboardClose: false,
17874
- event,
17875
- translucent: true,
17876
- cssClass: 'popover-large',
17877
- });
17878
- yield modal.present();
17879
- const { data, role } = yield modal.onDidDismiss();
17880
- return {
17881
- role,
17882
- data: data ? data : undefined
17883
- };
17884
- });
17885
- }
17886
- static readAsText(file, encoding) {
17887
- const $progress = new Subject();
17888
- const reader = new FileReader();
17889
- encoding = (encoding || 'UTF-8').toLowerCase();
17890
- // Listen progress
17891
- reader.onprogress = (e) => {
17892
- const total = e.total || 1;
17893
- const loaded = e.total || 0;
17894
- if (loaded < total)
17895
- $progress.next({ type: HttpEventType.UploadProgress, loaded, total });
17896
- };
17897
- // Listen end
17898
- reader.onloadend = (e) => {
17899
- const body = reader.result;
17900
- if (typeof body === 'string') {
17901
- $progress.next(new FileResponse({ body }));
17902
- $progress.complete();
17903
- }
17904
- else {
17905
- console.error(`Cannot read file '${file.name}' as text`);
17906
- $progress.error(`Cannot read file as text`);
17907
- }
17908
- };
17909
- // Listen error
17910
- reader.onerror = (event) => {
17911
- console.error('[files] Error while reading file:', event);
17912
- $progress.error(event);
17913
- };
17914
- // Start reading the file
17915
- reader.readAsText(file, encoding);
17916
- return $progress.asObservable();
17917
- }
17918
- }
17919
-
17920
18084
  /**
17921
18085
  * Define here theme colors
17922
18086
  */
@@ -18723,7 +18887,7 @@ HomePage.decorators = [
18723
18887
  { type: Component, args: [{
18724
18888
  moduleId: module.id.toString(),
18725
18889
  selector: 'app-page-home',
18726
- template: "<app-toolbar [canGoBack]=\"false\"\n visible-xs visible-sm visible-mobile>\n\n <!-- change locale button -->\n <ion-button slot=\"end\" *ngIf=\"currentLocaleCode; let code\"\n [matMenuTriggerFor]=\"localeMenu\">\n <ion-label class=\"ion-text-uppercase\">&nbsp;{{ code }}</ion-label>\n <mat-icon slot=\"end\">arrow_drop_down</mat-icon>\n </ion-button>\n</app-toolbar>\n\n<!-- change locale menu -->\n<mat-menu #localeMenu=\"matMenu\">\n <button *ngFor=\"let item of locales\"\n mat-menu-item\n (click)=\"changeLanguage(item.key)\">\n <ion-label>{{item.value}}</ion-label>\n </button>\n</mat-menu>\n\n<ion-content [ngStyle]=\"contentStyle\" no-padding-xs>\n\n <!-- loading spinner -->\n <div class=\"loading-page\" [class.cdk-visually-hidden]=\"!loading\">\n <div class=\"spinner\" *ngIf=\"loading && showSpinner\">\n <p>Loading...</p>\n <div class=\"sk-cube1 sk-cube\"></div>\n <div class=\"sk-cube2 sk-cube\"></div>\n <div class=\"sk-cube4 sk-cube\"></div>\n <div class=\"sk-cube3 sk-cube\"></div>\n </div>\n </div>\n\n <!-- Desktop: translucent top toolbar -->\n <div hidden-xs hidden-sm hidden-mobile>\n <ion-toolbar translucent>\n <ion-buttons slot=\"start\">\n <ion-menu-toggle>\n <ion-button color=\"light\" fill=\"clear\">\n <ion-icon slot=\"icon-only\" name=\"menu\"></ion-icon>\n </ion-button>\n </ion-menu-toggle>\n </ion-buttons>\n\n <ion-buttons slot=\"end\">\n <!-- change locale button -->\n <ion-button color=\"secondary\" fill=\"solid\"\n *ngIf=\"currentLocaleCode; let code\"\n [matMenuTriggerFor]=\"localeMenu\">\n <ion-label class=\"ion-text-uppercase\">&nbsp;{{ code }}</ion-label>\n <mat-icon slot=\"end\">arrow_drop_down</mat-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </div>\n\n <!-- Install (and upgrade) card -->\n <app-install-upgrade-card [isLogin]=\"isLogin\"\n showInstallButton=\"true\">\n </app-install-upgrade-card>\n\n <!-- Welcome card -->\n <ion-card *ngIf=\"!loading\"\n class=\"main welcome ion-padding ion-text-center ion-align-self-center\"\n @fadeInAnimation>\n <ion-card-header>\n <ion-card-title class=\"ion-text-center\">\n <span *ngIf=\"isWeb\" [innerHTML]=\"'HOME.WELCOME_WEB'|translate: {appName: appName}\"></span>\n <span *ngIf=\"!isWeb\" [innerHTML]=\"'HOME.WELCOME_APP'|translate: {appName: appName}\"></span>\n </ion-card-title>\n <ion-card-subtitle [innerHTML]=\"description\">\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content class=\"ion-no-padding\">\n <ion-text color=\"primary\">\n <img class=\"logo\" *ngIf=\"logo\" src=\"{{ logo }}\">\n </ion-text>\n <!-- register help text -->\n <ion-text *ngIf=\"!isLogin && canRegister\">\n <br/>\n <span translate>HOME.REGISTER_HELP</span>\n </ion-text>\n </ion-card-content>\n\n <ion-footer class=\"ion-padding-top\">\n\n <!-- If NOT login -->\n <ng-container *ngIf=\"!isLogin; else loginButtons;\">\n\n <ion-button *ngIf=\"canRegister\" expand=\"block\" color=\"tertiary\" (click)=\"register()\">\n <span translate>HOME.BTN_REGISTER</span>\n </ion-button>\n <ion-button expand=\"block\" color=\"light\" [routerLink]=\"['/']\" (click)=\"login()\">\n <span translate>AUTH.BTN_LOGIN</span>\n </ion-button>\n\n </ng-container>\n\n <!-- If user login -->\n <ng-template #loginButtons>\n\n <!-- Feature buttons -->\n <ng-container *ngIf=\"$filteredButtons | async as buttons\">\n <ng-container *ngFor=\"let item of buttons\">\n <ion-button *ngIf=\"item.path\"\n expand=\"block\" color=\"tertiary\"\n [class]=\"item.cssClass\"\n [routerLink]=\"item.path\"\n routerDirection=\"root\">\n <ion-icon slot=\"start\" class=\"ion-float-start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" class=\"ion-float-start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-text>{{'HOME.BTN_DATA_ENTRY'|translate: {name: (item.title | translate) } }}</ion-text>\n </ion-button>\n\n <!-- divider -->\n <div *ngIf=\"!item.path && !item.action\" [class]=\"item.cssClass\">\n <ion-label translate>{{item.title}}&nbsp;</ion-label>\n </div>\n </ng-container>\n\n <!--<p *ngIf=\"buttons.length\" class=\"visible-mobile\">&nbsp;</p>-->\n </ng-container>\n\n <ion-button *ngIf=\"showAccountButton\" expand=\"block\" color=\"secondary\" [routerLink]=\"['/account']\">\n <ion-icon slot=\"start\" class=\"ion-float-start\" name=\"person-circle\"></ion-icon>\n <ion-text translate>HOME.BTN_MY_ACCOUNT</ion-text>\n </ion-button>\n\n <p hidden-xs hidden-sm hidden-mobile>\n <ion-text [innerHTML]=\"'HOME.NOT_THIS_ACCOUNT_QUESTION' | translate: {displayName: accountName }\"></ion-text>\n <br/>\n <ion-text>\n <a href=\"#\" (click)=\"logout($event)\">\n <span translate>HOME.BTN_DISCONNECT</span>\n </a>\n </ion-text>\n </p>\n\n\n </ng-template>\n\n </ion-footer>\n </ion-card>\n\n <!-- Page history -->\n <ion-grid *ngIf=\"!loading && isLogin && pageHistory.length; else bottomBanner\"\n class=\"history-container ion-align-self-center\">\n <ion-row>\n <ion-col size=\"12\" size-xl=\"\" *ngFor=\"let page of pageHistory; trackBy: getPagePath\"\n class=\"ion-text-center\">\n <ion-card class=\"ion-align-self-start ion-text-start\" @fadeInAnimation>\n <ion-card-header class=\"ion-no-padding\">\n <!-- top bar -->\n <ion-card-subtitle>\n <button type=\"button\" tabindex=\"-1\"\n (click)=\"settings.removePageHistory(page.path)\"\n mat-icon-button class=\"ion-float-start ion-no-margin\">\n <mat-icon>close</mat-icon>\n </button>\n <ion-label [innerHTML]=\"page.subtitle|translate\"></ion-label>\n <ion-text class=\"ion-float-end\" [title]=\"page.time|dateFormat:{time: true}\">\n <small><ion-icon name=\"time-outline\"></ion-icon>&nbsp;{{ page.time|dateFromNow }}</small>&nbsp;\n </ion-text>\n </ion-card-subtitle>\n\n <!-- main page -->\n <ion-card-title class=\"ion-no-margin ion-no-padding\">\n <ion-item detail=\"true\"\n tappable class=\"text-1x\"\n [routerLink]=\"page.path\"\n routerDirection=\"root\" lines=\"none\">\n <!-- page icon-->\n <ion-icon *ngIf=\"page.icon\" slot=\"start\" [name]=\"page.icon\"></ion-icon>\n <mat-icon *ngIf=\"page.matIcon\" slot=\"start\">{{page.matIcon}}</mat-icon>\n\n <ion-label color=\"primary\" [innerHTML]=\"page.title\"></ion-label>\n </ion-item>\n </ion-card-title>\n </ion-card-header>\n\n <!-- children pages -->\n <ion-card-content class=\"ion-no-padding ion-padding-start\" *ngIf=\"page.children?.length\">\n <ion-list class=\"ion-no-padding\">\n <ion-item detail=\"true\"\n *ngFor=\"let childPage of page.children\"\n tappable class=\"text-1x\"\n [routerLink]=\"childPage.path\"\n routerDirection=\"root\">\n <!-- page icon-->\n <ion-icon *ngIf=\"childPage.icon\" slot=\"start\" color=\"dark\" [name]=\"childPage.icon\"></ion-icon>\n <mat-icon *ngIf=\"childPage.matIcon\" slot=\"start\" color=\"dark\">{{childPage.matIcon}}</mat-icon>\n\n <ion-label color=\"dark\" [innerHTML]=\"childPage.title\"></ion-label>\n </ion-item>\n </ion-list>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n <!-- Display configured Departments logos -->\n <ng-template #bottomBanner>\n <div class=\"bottom-banner ion-text-center ion-padding\" *ngIf=\"!loadingBanner\" @fadeInAnimation>\n <a href=\"{{ item.siteUrl }}\" *ngFor=\"let item of $partners | async \">\n <img class=\"logo\" src=\"{{ item.logo }}\" alt=\"{{item.label}}\" [title]=\"item.label\" />\n </a>\n </div>\n </ng-template>\n\n\n</ion-content>\n",
18890
+ template: "<app-toolbar [canGoBack]=\"false\"\n visible-xs visible-sm visible-mobile>\n\n <!-- change locale button -->\n <ion-button slot=\"end\" *ngIf=\"currentLocaleCode; let code\"\n [matMenuTriggerFor]=\"localeMenu\">\n <ion-label class=\"ion-text-uppercase\">&nbsp;{{ code }}</ion-label>\n <mat-icon slot=\"end\">arrow_drop_down</mat-icon>\n </ion-button>\n</app-toolbar>\n\n<!-- change locale menu -->\n<mat-menu #localeMenu=\"matMenu\">\n <button *ngFor=\"let item of locales\"\n mat-menu-item\n (click)=\"changeLanguage(item.key)\">\n <ion-label>{{item.value}}</ion-label>\n </button>\n</mat-menu>\n\n<ion-content [ngStyle]=\"contentStyle\" no-padding-xs>\n\n <!-- loading spinner -->\n <div class=\"loading-page\" [class.cdk-visually-hidden]=\"!loading\">\n <div class=\"spinner\" *ngIf=\"loading && showSpinner\">\n <p [innerHTML]=\"'COMMON.LOADING_DOTS'|translate\"></p>\n <div class=\"sk-cube1 sk-cube\"></div>\n <div class=\"sk-cube2 sk-cube\"></div>\n <div class=\"sk-cube4 sk-cube\"></div>\n <div class=\"sk-cube3 sk-cube\"></div>\n </div>\n </div>\n\n <!-- Desktop: translucent top toolbar -->\n <div hidden-xs hidden-sm hidden-mobile>\n <ion-toolbar translucent>\n <ion-buttons slot=\"start\">\n <ion-menu-toggle>\n <ion-button color=\"light\" fill=\"clear\">\n <ion-icon slot=\"icon-only\" name=\"menu\"></ion-icon>\n </ion-button>\n </ion-menu-toggle>\n </ion-buttons>\n\n <ion-buttons slot=\"end\">\n <!-- change locale button -->\n <ion-button color=\"secondary\" fill=\"solid\"\n *ngIf=\"currentLocaleCode; let code\"\n [matMenuTriggerFor]=\"localeMenu\">\n <ion-label class=\"ion-text-uppercase\">&nbsp;{{ code }}</ion-label>\n <mat-icon slot=\"end\">arrow_drop_down</mat-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </div>\n\n <!-- Install (and upgrade) card -->\n <app-install-upgrade-card [isLogin]=\"isLogin\"\n showInstallButton=\"true\">\n </app-install-upgrade-card>\n\n <!-- Welcome card -->\n <ion-card *ngIf=\"!loading\"\n class=\"main welcome ion-padding ion-text-center ion-align-self-center\"\n @fadeInAnimation>\n <ion-card-header>\n <ion-card-title class=\"ion-text-center\">\n <span *ngIf=\"isWeb\" [innerHTML]=\"'HOME.WELCOME_WEB'|translate: {appName: appName}\"></span>\n <span *ngIf=\"!isWeb\" [innerHTML]=\"'HOME.WELCOME_APP'|translate: {appName: appName}\"></span>\n </ion-card-title>\n <ion-card-subtitle [innerHTML]=\"description\">\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content class=\"ion-no-padding\">\n <ion-text color=\"primary\">\n <img class=\"logo\" *ngIf=\"logo\" src=\"{{ logo }}\">\n </ion-text>\n <!-- register help text -->\n <ion-text *ngIf=\"!isLogin && canRegister\">\n <br/>\n <span translate>HOME.REGISTER_HELP</span>\n </ion-text>\n </ion-card-content>\n\n <ion-footer class=\"ion-padding-top\">\n\n <!-- If NOT login -->\n <ng-container *ngIf=\"!isLogin; else loginButtons;\">\n\n <ion-button *ngIf=\"canRegister\" expand=\"block\" color=\"tertiary\" (click)=\"register()\">\n <span translate>HOME.BTN_REGISTER</span>\n </ion-button>\n <ion-button expand=\"block\" color=\"light\" [routerLink]=\"['/']\" (click)=\"login()\">\n <span translate>AUTH.BTN_LOGIN</span>\n </ion-button>\n\n </ng-container>\n\n <!-- If user login -->\n <ng-template #loginButtons>\n\n <!-- Feature buttons -->\n <ng-container *ngIf=\"$filteredButtons | async as buttons\">\n <ng-container *ngFor=\"let item of buttons\">\n <ion-button *ngIf=\"item.path\"\n expand=\"block\" color=\"tertiary\"\n [class]=\"item.cssClass\"\n [routerLink]=\"item.path\"\n routerDirection=\"root\">\n <ion-icon slot=\"start\" class=\"ion-float-start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" class=\"ion-float-start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-text>{{'HOME.BTN_DATA_ENTRY'|translate: {name: (item.title | translate) } }}</ion-text>\n </ion-button>\n\n <!-- divider -->\n <div *ngIf=\"!item.path && !item.action\" [class]=\"item.cssClass\">\n <ion-label translate>{{item.title}}&nbsp;</ion-label>\n </div>\n </ng-container>\n\n <!--<p *ngIf=\"buttons.length\" class=\"visible-mobile\">&nbsp;</p>-->\n </ng-container>\n\n <ion-button *ngIf=\"showAccountButton\" expand=\"block\" color=\"secondary\" [routerLink]=\"['/account']\">\n <ion-icon slot=\"start\" class=\"ion-float-start\" name=\"person-circle\"></ion-icon>\n <ion-text translate>HOME.BTN_MY_ACCOUNT</ion-text>\n </ion-button>\n\n <p hidden-xs hidden-sm hidden-mobile>\n <ion-text [innerHTML]=\"'HOME.NOT_THIS_ACCOUNT_QUESTION' | translate: {displayName: accountName }\"></ion-text>\n <br/>\n <ion-text>\n <a href=\"#\" (click)=\"logout($event)\">\n <span translate>HOME.BTN_DISCONNECT</span>\n </a>\n </ion-text>\n </p>\n\n\n </ng-template>\n\n </ion-footer>\n </ion-card>\n\n <!-- Page history -->\n <ion-grid *ngIf=\"!loading && isLogin && pageHistory.length; else bottomBanner\"\n class=\"history-container ion-align-self-center\">\n <ion-row>\n <ion-col size=\"12\" size-xl=\"\" *ngFor=\"let page of pageHistory; trackBy: getPagePath\"\n class=\"ion-text-center\">\n <ion-card class=\"ion-align-self-start ion-text-start\" @fadeInAnimation>\n <ion-card-header class=\"ion-no-padding\">\n <!-- top bar -->\n <ion-card-subtitle>\n <button type=\"button\" tabindex=\"-1\"\n (click)=\"settings.removePageHistory(page.path)\"\n mat-icon-button class=\"ion-float-start ion-no-margin\">\n <mat-icon>close</mat-icon>\n </button>\n <ion-label [innerHTML]=\"page.subtitle|translate\"></ion-label>\n <ion-text class=\"ion-float-end\" [title]=\"page.time|dateFormat:{time: true}\">\n <small><ion-icon name=\"time-outline\"></ion-icon>&nbsp;{{ page.time|dateFromNow }}</small>&nbsp;\n </ion-text>\n </ion-card-subtitle>\n\n <!-- main page -->\n <ion-card-title class=\"ion-no-margin ion-no-padding\">\n <ion-item detail=\"true\"\n tappable class=\"text-1x\"\n [routerLink]=\"page.path\"\n routerDirection=\"root\" lines=\"none\">\n <!-- page icon-->\n <ion-icon *ngIf=\"page.icon\" slot=\"start\" [name]=\"page.icon\"></ion-icon>\n <mat-icon *ngIf=\"page.matIcon\" slot=\"start\">{{page.matIcon}}</mat-icon>\n\n <ion-label color=\"primary\" [innerHTML]=\"page.title\"></ion-label>\n </ion-item>\n </ion-card-title>\n </ion-card-header>\n\n <!-- children pages -->\n <ion-card-content class=\"ion-no-padding ion-padding-start\" *ngIf=\"page.children?.length\">\n <ion-list class=\"ion-no-padding\">\n <ion-item detail=\"true\"\n *ngFor=\"let childPage of page.children\"\n tappable class=\"text-1x\"\n [routerLink]=\"childPage.path\"\n routerDirection=\"root\">\n <!-- page icon-->\n <ion-icon *ngIf=\"childPage.icon\" slot=\"start\" color=\"dark\" [name]=\"childPage.icon\"></ion-icon>\n <mat-icon *ngIf=\"childPage.matIcon\" slot=\"start\" color=\"dark\">{{childPage.matIcon}}</mat-icon>\n\n <ion-label color=\"dark\" [innerHTML]=\"childPage.title\"></ion-label>\n </ion-item>\n </ion-list>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n <!-- Display configured Departments logos -->\n <ng-template #bottomBanner>\n <div class=\"bottom-banner ion-text-center ion-padding\" *ngIf=\"!loadingBanner\" @fadeInAnimation>\n <a href=\"{{ item.siteUrl }}\" *ngFor=\"let item of $partners | async \">\n <img class=\"logo\" src=\"{{ item.logo }}\" alt=\"{{item.label}}\" [title]=\"item.label\" />\n </a>\n </div>\n </ng-template>\n\n\n</ion-content>\n",
18727
18891
  changeDetection: ChangeDetectionStrategy.OnPush,
18728
18892
  animations: [fadeInAnimation, slideUpDownAnimation],
18729
18893
  styles: ["h1,h2,h3,h4,h5{white-space:normal}.center,ion-content ion-card.welcome img{text-align:center;display:inline-block}ion-content{--background:transparent;background-size:cover;-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;display:inline-block;padding:15px}ion-content,ion-content .loading-page{background-color:var(--ion-color-primary)}ion-content .loading-page{display:block;height:100%;width:100%;position:absolute;z-index:5;transition:background .5s linear;-webkit-transition:background 1.5s linear}ion-content .loading-page.hidden{background-color:rgba(var(--ion-color-primary),0)}ion-content ion-card{z-index:9}ion-content ion-card ion-card-header{display:block}ion-content ion-card ion-card-header ion-card-subtitle,ion-content ion-card ion-card-header ion-card-title{display:block;width:100%}ion-content ion-card.main{min-width:240px;max-width:400px;margin-left:auto;margin-right:auto}ion-content ion-card.welcome{background-color:hsla(0,0%,100%,.7)}ion-content ion-card.welcome button{margin-top:16px}ion-content ion-card.welcome img{max-width:250px}ion-content .history-container{margin-left:auto;margin-right:auto}ion-content .history-container ion-card{display:inline-block;box-sizing:border-box;z-index:8;min-width:240px;max-width:400px;width:100%;margin-left:auto;margin-right:auto;background-color:hsla(0,0%,100%,.7)}ion-content .history-container ion-card ion-card-header ion-card-subtitle{height:40px}ion-content .history-container ion-card ion-card-header ion-card-subtitle button[float-start]{z-index:99;margin:0}ion-content .history-container ion-card ion-card-header ion-card-subtitle ion-label{line-height:40px}ion-content .history-container ion-card ion-card-header ion-card-subtitle ion-label[float-end]{padding-right:16px;font-weight:400}ion-content .history-container ion-card ion-card-header ion-card-title ion-item{--ion-item-background:$transparent;--ion-item-icon-color:var(--ion-color-primary);--ion-item-text-color:var(--ion-color-primary)}ion-content .history-container ion-card ion-card-header ion-card-title ion-item ion-icon[slot=start],ion-content .history-container ion-card ion-card-header ion-card-title ion-item mat-icon[slot=start]{margin-right:unset;-webkit-margin-end:16px!important;margin-inline-end:16px!important;color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor}ion-content .history-container ion-card ion-card-header ion-card-title ion-item ion-label,ion-content .history-container ion-card ion-card-header ion-card-title ion-item ion-text{color:var(--ion-item-text-color)!important;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%}ion-content .history-container ion-card ion-card-content ion-list{--ion-item-background:var(--ion-color-transparent)}ion-content .history-container ion-card ion-card-content ion-list ion-item{--ion-item-icon-color:var(--ion-color-dark);--ion-item-text-color:var(--ion-color-dark)}ion-content .history-container ion-card ion-card-content ion-list ion-item ion-icon[slot=start],ion-content .history-container ion-card ion-card-content ion-list ion-item mat-icon[slot=start]{color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor;margin-right:unset;-webkit-margin-end:16px!important;margin-inline-end:16px!important}ion-content .history-container ion-card ion-card-content ion-list ion-item ion-label,ion-content .history-container ion-card ion-card-content ion-list ion-item ion-text{color:var(--ion-item-text-color)!important}ion-content .bottom-banner{background-color:hsla(0,0%,100%,.7);min-width:240px;z-index:0;position:absolute;left:16px;right:16px;bottom:16px}ion-content .bottom-banner img.logo{max-height:50px;margin-left:8px}ion-content ion-text{text-align:center}@media screen and (max-width:575px){ion-content ion-card{min-width:160px;width:calc(100% - 20px);max-width:100%;margin-left:10px;margin-right:10px}ion-content ion-card h1{margin-top:0;font-size:20px}ion-content ion-card ion-card-content ion-button{margin-top:8px}ion-content .bottom-banner{display:block;position:unset}ion-content .bottom-banner img.logo{max-height:30px}}@media screen and (max-width:767px) and (min-width:576px){ion-content{min-height:555px}ion-content .bottom-banner img.logo{max-height:40px}}@media screen and (min-width:768px){ion-content{min-height:calc(100% - 100px)}}"]
@@ -18970,8 +19134,9 @@ class AccountPage extends AppForm {
18970
19134
  this.setValue(this.accountService.account);
18971
19135
  this.markAsPristine();
18972
19136
  }));
18973
- this.registerSubscription(this.accountService.$additionalFields.subscribe((additionalFields) => {
18974
- this.additionalFields = additionalFields.slice();
19137
+ this.registerSubscription(this.accountService.$additionalFields
19138
+ .subscribe((additionalFields) => {
19139
+ this.additionalFields = additionalFields.slice(); // Copy
18975
19140
  if (accountService.isLogin()) {
18976
19141
  this.onLogin(this.accountService.account);
18977
19142
  }
@@ -19105,9 +19270,21 @@ class AccountPage extends AppForm {
19105
19270
  this.form.controls.pubkey.disable();
19106
19271
  // Always disable some additional fields
19107
19272
  this.additionalFields
19108
- .filter(field => field.extra && field.extra.account && field.extra.account.disabled || false)
19109
19273
  .forEach(field => {
19110
- this.form.controls[field.key].disable();
19274
+ var _a, _b;
19275
+ const control = this.form.controls[field.key];
19276
+ if (!control)
19277
+ return; // Skip
19278
+ const disabled = ((_a = field.extra) === null || _a === void 0 ? void 0 : _a.account) && field.extra.account.disabled || false;
19279
+ if (disabled && control.enabled) {
19280
+ control.disable();
19281
+ }
19282
+ const required = ((_b = field.extra) === null || _b === void 0 ? void 0 : _b.account) && field.extra.account.required || false;
19283
+ if (!field.required && required) {
19284
+ control.setValidators(control.validator
19285
+ ? Validators.compose([control.validator, Validators.required])
19286
+ : Validators.required);
19287
+ }
19111
19288
  });
19112
19289
  this.markForCheck();
19113
19290
  }
@@ -19164,11 +19341,62 @@ EntityMetadataComponent.propDecorators = {
19164
19341
  showRecorder: [{ type: Input }]
19165
19342
  };
19166
19343
 
19344
+ const DEFAULT_MENU_SHOW_WHEN = 'lg';
19345
+ class MenuService {
19346
+ constructor() {
19347
+ this._$toggled = new Subject();
19348
+ this._$opened = new Subject();
19349
+ this._$splitPaneWhen = new BehaviorSubject(DEFAULT_MENU_SHOW_WHEN);
19350
+ this._$enabled = new BehaviorSubject(true);
19351
+ }
19352
+ get opened() {
19353
+ return this._$opened.asObservable();
19354
+ }
19355
+ get splitPaneWhen() {
19356
+ return this._$splitPaneWhen.asObservable();
19357
+ }
19358
+ get enabled() {
19359
+ return this._$enabled.asObservable();
19360
+ }
19361
+ toggle() {
19362
+ this._$toggled.next();
19363
+ }
19364
+ enable(value) {
19365
+ this._$enabled.next(value);
19366
+ }
19367
+ setSplitPaneShowWhen(value) {
19368
+ if (this._$splitPaneWhen.value !== value) {
19369
+ this._$splitPaneWhen.next(value);
19370
+ }
19371
+ }
19372
+ toggleSplitPaneWhen() {
19373
+ if (this._$splitPaneWhen.value !== false) {
19374
+ this._$splitPaneWhen.next(false);
19375
+ }
19376
+ else {
19377
+ this._$splitPaneWhen.next(DEFAULT_MENU_SHOW_WHEN);
19378
+ }
19379
+ }
19380
+ _markAsOpened() {
19381
+ this._$opened.next(true);
19382
+ }
19383
+ _markAsClosed() {
19384
+ this._$opened.next(false);
19385
+ }
19386
+ }
19387
+ MenuService.ɵprov = ɵɵdefineInjectable({ factory: function MenuService_Factory() { return new MenuService(); }, token: MenuService, providedIn: "root" });
19388
+ MenuService.decorators = [
19389
+ { type: Injectable, args: [{
19390
+ providedIn: 'root'
19391
+ },] }
19392
+ ];
19393
+
19167
19394
  class FormButtonsBarToken {
19168
19395
  }
19169
19396
  class FormButtonsBarComponent {
19170
- constructor(hotkeys) {
19397
+ constructor(hotkeys, menu) {
19171
19398
  this.hotkeys = hotkeys;
19399
+ this.menu = menu;
19172
19400
  this._subscription = new Subscription();
19173
19401
  this.disabled = false;
19174
19402
  this.disabledCancel = false;
@@ -19200,15 +19428,16 @@ class FormButtonsBarComponent {
19200
19428
  FormButtonsBarComponent.decorators = [
19201
19429
  { type: Component, args: [{
19202
19430
  selector: 'app-form-buttons-bar',
19203
- template: "<ion-toolbar [class]=\"classList\">\n <ion-row class=\"ion-no-padding\" nowrap>\n <ion-col>\n <ng-content></ng-content>\n </ion-col>\n\n <ion-col size=\"auto\">\n\n <!-- close (go back) -->\n <ion-button fill=\"clear\" color=\"dark\" (click)=\"onBack.emit($event)\"\n (keyup.enter)=\"onBack.emit($event)\"\n *ngIf=\"onBack.observers | isNotEmptyArray\">\n <ion-label translate>COMMON.BTN_CLOSE</ion-label>\n </ion-button>\n\n <!-- reload -->\n <ion-button fill=\"clear\" color=\"dark\" (click)=\"onCancel.emit($event)\"\n (keyup.enter)=\"onCancel.emit($event)\"\n [disabled]=\"disabledCancel\">\n <ion-icon slot=\"start\" name=\"refresh\"></ion-icon>\n <ion-label translate>COMMON.BTN_RESET</ion-label>\n </ion-button>\n\n <!-- next -->\n <ion-button fill=\"solid\" (click)=\"onNext.emit($event)\" (keyup.enter)=\"onNext.emit($event)\" *ngIf=\"onNext.observers | isNotEmptyArray\">\n <ion-label translate>COMMON.BTN_NEXT</ion-label>\n <ion-icon slot=\"end\" name=\"arrow-forward\"></ion-icon>\n </ion-button>\n\n <!-- save -->\n <ion-button [fill]=\"disabled ? 'clear' : 'solid'\"\n (click)=\"onSave.emit($event)\"\n (keyup.enter)=\"onSave.emit($event)\" color=\"danger\"\n [disabled]=\"disabled\">\n <ion-label [innerHTML]=\"'COMMON.BTN_SAVE_WITH_SHORTCUT'|translate\" [title]=\"'COMMON.BTN_SAVE_WITH_SHORTCUT_HELP'|translate\"></ion-label>\n </ion-button>\n </ion-col>\n </ion-row>\n</ion-toolbar>\n",
19431
+ template: "\n<ion-toolbar [class]=\"classList\">\n <ion-row class=\"ion-no-padding\" nowrap>\n <!-- show menu button -->\n <ion-col size=\"auto\" class=\"hidden-xs hidden-sm\">\n <ion-menu-toggle>\n <button\n mat-icon-button\n color=\"accent\"\n [title]=\"'COMMON.BTN_SHOW_MENU' | translate\"\n (click)=\"menu.toggleSplitPaneWhen()\">\n <mat-icon><span>&#xbb;</span></mat-icon>\n </button>\n </ion-menu-toggle>\n </ion-col>\n\n <ion-col>\n <ng-content></ng-content>\n </ion-col>\n\n <ion-col size=\"auto\">\n\n <!-- close (go back) -->\n <ion-button fill=\"clear\" color=\"dark\" (click)=\"onBack.emit($event)\"\n (keyup.enter)=\"onBack.emit($event)\"\n *ngIf=\"onBack.observers | isNotEmptyArray\">\n <ion-label translate>COMMON.BTN_CLOSE</ion-label>\n </ion-button>\n\n <!-- reload -->\n <ion-button fill=\"clear\" color=\"dark\" (click)=\"onCancel.emit($event)\"\n (keyup.enter)=\"onCancel.emit($event)\"\n [disabled]=\"disabledCancel\">\n <ion-icon slot=\"start\" name=\"refresh\"></ion-icon>\n <ion-label translate>COMMON.BTN_RESET</ion-label>\n </ion-button>\n\n <!-- next -->\n <ion-button fill=\"solid\" (click)=\"onNext.emit($event)\" (keyup.enter)=\"onNext.emit($event)\" *ngIf=\"onNext.observers | isNotEmptyArray\">\n <ion-label translate>COMMON.BTN_NEXT</ion-label>\n <ion-icon slot=\"end\" name=\"arrow-forward\"></ion-icon>\n </ion-button>\n\n <!-- save -->\n <ion-button [fill]=\"disabled ? 'clear' : 'solid'\"\n (click)=\"onSave.emit($event)\"\n (keyup.enter)=\"onSave.emit($event)\" color=\"danger\"\n [disabled]=\"disabled\">\n <ion-label [innerHTML]=\"'COMMON.BTN_SAVE_WITH_SHORTCUT'|translate\" [title]=\"'COMMON.BTN_SAVE_WITH_SHORTCUT_HELP'|translate\"></ion-label>\n </ion-button>\n </ion-col>\n </ion-row>\n</ion-toolbar>\n",
19204
19432
  providers: [
19205
19433
  { provide: FormButtonsBarToken, useExisting: FormButtonsBarComponent }
19206
19434
  ],
19207
- styles: [":host{width:100%}:host ion-toolbar{height:var(--app-form-buttons-bar-height);padding:0 6px}:host ion-col{padding:0}"]
19435
+ styles: [":host{width:100%}:host ion-toolbar{height:var(--app-form-buttons-bar-height);padding:0 6px}:host ion-col{padding:0}ion-menu-toggle button{top:4px}"]
19208
19436
  },] }
19209
19437
  ];
19210
19438
  FormButtonsBarComponent.ctorParameters = () => [
19211
- { type: Hotkeys }
19439
+ { type: Hotkeys },
19440
+ { type: MenuService }
19212
19441
  ];
19213
19442
  FormButtonsBarComponent.propDecorators = {
19214
19443
  disabled: [{ type: Input }],
@@ -20084,7 +20313,7 @@ AppListForm.decorators = [
20084
20313
  selector: 'app-list-form',
20085
20314
  template: "\n<ng-container *ngIf=\"!loading; else listSkeleton\">\n\n <!-- List -->\n <ion-list class=\"ion-no-padding\" >\n\n <ng-container *ngFor=\"let itemControl of itemControls; index as i; last as last\">\n <ion-item *ngIf=\"itemControl.value; let item\"\n [class.disabled]=\"disabled\" tappable\n (click)=\"onItemClick($event, item)\"\n [color]=\"selection.isSelected(item) ? 'secondary100' : undefined\"\n [button]=\"selectionChanges.observers.length > 0\">\n <ion-label>{{displayWith(item)}}</ion-label>\n\n <!-- buttons -->\n <ion-buttons slot=\"end\" *ngIf=\"enabled\" class=\"action-buttons\">\n <ion-button fill=\"clear\" (click)=\"removeAt(i)\">\n <ion-icon slot=\"icon-only\" name=\"trash\"></ion-icon>\n </ion-button>\n\n <ion-button *ngFor=\"let button of options.buttons\"\n fill=\"clear\" [color]=\"button.color\"\n [class.cdk-visually-hidden]=\"button.disabled|async\"\n (click)=\"onItemButtonClick(button, $event, item, i)\"\n [title]=\"(button.title || '')|translate\">\n <ion-icon slot=\"icon-only\" [name]=\"button.icon\"></ion-icon>\n </ion-button>\n\n </ion-buttons>\n </ion-item>\n </ng-container>\n </ion-list>\n\n <!-- Add button -->\n <ion-button color=\"tertiary\" fill=\"clear\"\n *ngIf=\"enabled && onNewItem.observers.length; else emptyText\"\n [title]=\"'COMMON.BTN_ADD'|translate\"\n (click)=\"onNewClick($event)\" >\n <ion-icon slot=\"start\" name=\"add\"></ion-icon>\n <ion-label translate>COMMON.BTN_ADD</ion-label>\n </ion-button>\n\n <!-- Empty (if readonly) -->\n <ng-template #emptyText>\n <ion-text *ngIf=\"!length\" color=\"medium\"><i translate>COMMON.EMPTY_OPTION</i></ion-text>\n </ng-template>\n</ng-container>\n\n<ng-template #listSkeleton>\n\n <ion-list>\n <ng-container *ngTemplateOutlet=\"itemSkeleton\"></ng-container>\n <ng-container *ngTemplateOutlet=\"itemSkeleton\"></ng-container>\n <ng-container *ngTemplateOutlet=\"itemSkeleton\"></ng-container>\n </ion-list>\n\n</ng-template>\n\n<ng-template #itemSkeleton>\n\n <ion-item>\n <ion-label>\n <ion-skeleton-text animated style=\"width: 60%\"></ion-skeleton-text>\n </ion-label>\n </ion-item>\n\n</ng-template>\n",
20086
20315
  changeDetection: ChangeDetectionStrategy.OnPush,
20087
- styles: [":host{--ion-item-background-selected:var(--ion-color-secondary100);--ion-item-text-color-selected:var(--ion-color-secondary100-contrast);--ion-item-icon-color-selected:var(--ion-color-secondary100-contrast)}ion-item.disabled ion-label{color:var(--ion-color-medium-shade,grey)}ion-item .action-buttons{visibility:hidden;display:none}ion-item:hover{height:48px!important}ion-item:hover .action-buttons{margin-top:-1px;visibility:visible;display:block}ion-item.selected{background:var(--ion-item-background-selected)!important;--color-hover:var(--ion-item-background-selected)!important}ion-item.selected ion-label,ion-item.selected ion-text{color:var(--ion-item-text-color-selected)!important}ion-item.selected:hover{--color-hover:var(--ion-item-background-selected)!important;--ion-item-icon-color-selected:var(--ion-item-icon-color)!important;--ion-item-text-color-selected:var(--ion-item-text-color)!important}"]
20316
+ styles: [":host{--ion-item-background-selected:var(--ion-color-secondary100);--ion-item-color-selected:var(--ion-color-secondary100-contrast)}ion-item.disabled ion-label{color:var(--ion-color-medium-shade,grey)}ion-item{--min-height:48px;background-color:var(--ion-item-background)}ion-item ion-label,ion-item ion-text{color:var(--ion-item-color)}ion-item .action-buttons{visibility:hidden;display:none}ion-item:hover{height:var(--min-height);--ion-item-background-color:var(--ion-color-light);--ion-item-color:var(--ion-color-light-contrast)}ion-item:hover .action-buttons{margin-top:-1px;visibility:visible;display:block}ion-item.selected{--ion-item-background-color-hover:var(--ion-item-background-selected)!important;--ion-item-color-hover:var(--ion-item-color-selected)!important}"]
20088
20317
  },] }
20089
20318
  ];
20090
20319
  AppListForm.ctorParameters = () => [
@@ -20145,9 +20374,6 @@ PersonToStringPipe.decorators = [
20145
20374
  { type: Injectable, args: [{ providedIn: 'root' },] }
20146
20375
  ];
20147
20376
 
20148
- const MimeTypes = Object.freeze({
20149
- ANDROID_APK: 'application/vnd.android.package-archive'
20150
- });
20151
20377
  class AppInstallUpgradeCard {
20152
20378
  constructor(modalCtrl, configService, toastController, alertController, translate, cd, platform, network, environment) {
20153
20379
  this.modalCtrl = modalCtrl;
@@ -20160,7 +20386,6 @@ class AppInstallUpgradeCard {
20160
20386
  this.network = network;
20161
20387
  this.environment = environment;
20162
20388
  this._subscription = new Subscription();
20163
- this._showUpdateOfflineFeature = false;
20164
20389
  this.loading = true;
20165
20390
  this.hasDownloader = false;
20166
20391
  this.downloading = false;
@@ -20168,16 +20393,6 @@ class AppInstallUpgradeCard {
20168
20393
  this.showUpgradeWarning = true;
20169
20394
  this.showOfflineWarning = true;
20170
20395
  this.showInstallButton = false;
20171
- this.onUpdateOfflineModeClick = new EventEmitter();
20172
- }
20173
- set showUpdateOfflineFeature(value) {
20174
- if (value === this._showUpdateOfflineFeature)
20175
- return; // Skip
20176
- this._showUpdateOfflineFeature = value;
20177
- this.markForCheck();
20178
- }
20179
- get showUpdateOfflineFeature() {
20180
- return this._showUpdateOfflineFeature;
20181
20396
  }
20182
20397
  ngOnInit() {
20183
20398
  this.offline = this.network.offline;
@@ -20201,7 +20416,6 @@ class AppInstallUpgradeCard {
20201
20416
  ngOnDestroy() {
20202
20417
  this._subscription.unsubscribe();
20203
20418
  this._subscription = null;
20204
- this.onUpdateOfflineModeClick.complete();
20205
20419
  this.installLinks = null;
20206
20420
  this.upgradeLinks = null;
20207
20421
  }
@@ -20352,10 +20566,10 @@ class AppInstallUpgradeCard {
20352
20566
  let title;
20353
20567
  let mimeType;
20354
20568
  // Get file name
20355
- let filename = this.getFilename(url);
20569
+ let filename = UriUtils.getFilename(url);
20356
20570
  let version = minVersion || 'latest';
20357
20571
  // OK, this is a downloadable APK file (e.g. NOT a link to a playstore)
20358
- if (filename === null || filename === void 0 ? void 0 : filename.endsWith('.apk')) {
20572
+ if (filename === null || filename === void 0 ? void 0 : filename.toLowerCase().endsWith('.apk')) {
20359
20573
  // Define mime type
20360
20574
  mimeType = MimeTypes.ANDROID_APK;
20361
20575
  // Get the file version (if any)
@@ -20366,11 +20580,9 @@ class AppInstallUpgradeCard {
20366
20580
  filename = `${name}-v${version}.apk`.toLowerCase();
20367
20581
  title = `${name} v${version}`;
20368
20582
  }
20369
- else {
20370
- // Replace 'latest' with the app version, if present
20371
- if (filename.indexOf('latest')) {
20372
- filename = filename.replace('latest', version);
20373
- }
20583
+ // Replace 'latest' with the app version, if present in the filename
20584
+ else if (filename.indexOf('latest') && version !== 'latest') {
20585
+ filename = filename.replace('latest', version);
20374
20586
  }
20375
20587
  }
20376
20588
  result.push({ name, url, platform: 'android', version, filename, title, mimeType });
@@ -20382,18 +20594,6 @@ class AppInstallUpgradeCard {
20382
20594
  //}
20383
20595
  return result;
20384
20596
  }
20385
- getFilename(url) {
20386
- if (!url)
20387
- return;
20388
- // Get last part (or all string, if no '/')
20389
- let filename = url.substring(url.lastIndexOf('/') + 1);
20390
- const queryParamIndex = filename.indexOf('?');
20391
- if (queryParamIndex !== -1) {
20392
- // Remove query params
20393
- return filename.substring(0, queryParamIndex);
20394
- }
20395
- return filename;
20396
- }
20397
20597
  listenDownloadJobs() {
20398
20598
  // Listening downloading promise, if exists
20399
20599
  (this.upgradeLinks || []).forEach(link => {
@@ -20491,7 +20691,7 @@ class AppInstallUpgradeCard {
20491
20691
  AppInstallUpgradeCard.decorators = [
20492
20692
  { type: Component, args: [{
20493
20693
  selector: 'app-install-upgrade-card',
20494
- template: "\n\n<!-- Offline mode card-->\n<ion-card *ngIf=\"showOfflineWarning && (!loading && offline || waitingNetwork)\"\n color=\"accent\"\n class=\"main ion-no-margin\"\n @slideUpDownAnimation>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row>\n <ion-col>\n <ion-text class=\"ion-text-wrap\" *ngIf=\"!waitingNetwork; else waitingNetworkText\">\n <h4>\n <b *ngIf=\"isLogin; else notLogin\" [innerHTML]=\"'NETWORK.INFO.OFFLINE_OR_UNAUTHORIZED'| translate\"></b>\n <ng-template #notLogin>\n <b [innerHTML]=\"'NETWORK.INFO.OFFLINE'| translate\"></b>\n </ng-template>\n </h4>\n <h3>\n <small [innerHTML]=\"'NETWORK.INFO.OFFLINE_HELP'|translate\"></small>\n </h3>\n </ion-text>\n <ng-template #waitingNetworkText>\n <ion-text class=\"ion-text-wrap\" [innerHTML]=\"'NETWORK.INFO.RETRY_TO_CONNECT'| translate\"></ion-text>\n </ng-template>\n </ion-col>\n <ion-col size=\"auto\">\n\n <!-- Retry button -->\n <ion-button *ngIf=\"!waitingNetwork; else waitingSpinner\" color=\"tertiary\" class=\"ion-float-end\"\n (click)=\"tryOnline()\">\n <span translate>NETWORK.BTN_CHECK_ALIVE</span>\n </ion-button>\n\n </ion-col>\n </ion-row>\n </ion-grid>\n\n </ion-card-content>\n</ion-card>\n\n<!-- Upgrade links -->\n<ion-card *ngIf=\"showUpgradeWarning && !loading && !offline && upgradeLinks\"\n color=\"accent\"\n class=\"ion-no-margin\"\n @slideUpDownAnimation>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row *ngFor=\"let link of upgradeLinks; last as last\">\n <ion-col>\n <ion-text class=\"ion-text-wrap\">\n <h3>\n <span *ngIf=\"link.version\" [innerHTML]=\"'INFO.UPDATE_APP_TO_VERSION'| translate: link\"></span>\n <span *ngIf=\"!link.version\" [innerHTML]=\"'INFO.UPDATE_APP'| translate: link\"></span>\n </h3>\n <h4>\n <small *ngIf=\"last\" [innerHTML]=\"'INFO.UPDATE_APP_HELP'|translate\"></small>\n </h4>\n </ion-text>\n </ion-col>\n\n <ion-col size=\"auto\">\n <ng-container *ngTemplateOutlet=\"downloadButton; context: { $implicit: link }\"></ng-container>\n </ion-col>\n </ion-row>\n\n </ion-grid>\n\n </ion-card-content>\n</ion-card>\n\n<!-- Install links -->\n<ion-card *ngIf=\"showInstallButton && !loading && !offline && installLinks\"\n color=\"secondary\"\n class=\"ion-no-margin\"\n @slideUpDownAnimation>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row *ngFor=\"let link of installLinks; last as last\">\n <ion-col>\n <ion-text class=\"ion-text-wrap\">\n <h3 [innerHTML]=\"'INFO.DOWNLOAD_APP_TITLE'| translate: {name: link.name, platform: getPlatformName(link.platform) }\"></h3>\n <h4><small *ngIf=\"last\" [innerHTML]=\"'INFO.DOWNLOAD_APP_HELP'|translate\"></small></h4>\n </ion-text>\n </ion-col>\n <ion-col size=\"auto\">\n <ng-container *ngTemplateOutlet=\"downloadButton; context: { $implicit: link }\"></ng-container>\n </ion-col>\n </ion-row>\n\n </ion-grid>\n\n </ion-card-content>\n</ion-card>\n\n<!-- Update offline feature card-->\n<ion-card *ngIf=\"showUpdateOfflineFeature && !loading && !offline\"\n color=\"accent\"\n class=\"main ion-no-margin\"\n @slideUpDownAnimation>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row>\n <ion-col>\n <ion-text class=\"ion-text-wrap\">\n <h4>\n <b [innerHTML]=\"'NETWORK.INFO.UPDATE_OFFLINE_MODE'| translate\"></b>\n </h4>\n <h3>\n <small [innerHTML]=\"'NETWORK.INFO.UPDATE_OFFLINE_MODE_HELP'|translate\"></small>\n </h3>\n </ion-text>\n </ion-col>\n <ion-col size=\"auto\">\n\n <!-- Retry button -->\n <ion-button color=\"tertiary\" class=\"ion-float-end\"\n (click)=\"onUpdateOfflineModeClick.emit($event)\">\n <span translate>NETWORK.BTN_UPDATE</span>\n </ion-button>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n </ion-card-content>\n</ion-card>\n\n<!-- Display a download button, depending on the link URL, and the device platform -->\n<ng-template #downloadButton let-link>\n\n <ng-container *ngIf=\"link.filename; else redirectButton\">\n\n <ng-container *ngIf=\"hasDownloader; else webDownloadButton\">\n <!-- Download using platform -->\n <ion-button (click)=\"download($event, asLink(link))\"\n *ngIf=\"!link.location; else openButton\"\n [disabled]=\"link.downloading\"\n color=\"tertiary\" class=\"ion-float-end\">\n <ion-icon slot=\"start\" *ngIf=\"!link.downloading\" name=\"download\"></ion-icon>\n <ion-spinner slot=\"start\" class=\"ion-no-padding\" *ngIf=\"link.downloading\"></ion-spinner>\n <ion-label translate>COMMON.BTN_DOWNLOAD</ion-label>\n </ion-button>\n\n <!-- Open APK button -->\n <ng-template #openButton>\n <ion-button *ngIf=\"link.location\"\n (click)=\"openFile($event, link)\"\n color=\"tertiary\" class=\"ion-float-end\" >\n <ion-label translate>COMMON.BTN_INSTALL</ion-label>\n </ion-button>\n </ng-template>\n </ng-container>\n\n <!-- Web download button -->\n <ng-template #webDownloadButton>\n <ion-button [download]=\"link.downloadFilename\"\n [href]=\"link.url\"\n color=\"tertiary\" class=\"ion-float-end\" >\n <ion-label translate>COMMON.BTN_DOWNLOAD</ion-label>\n </ion-button>\n </ng-template>\n </ng-container>\n\n <!-- Web redirect button -->\n <ng-template #redirectButton>\n <ion-button [href]=\"link.url\" rel=\"external\" color=\"tertiary\" class=\"ion-float-end\" target=\"_blank\">\n <ion-label translate>COMMON.BTN_SHOW_MORE</ion-label>\n </ion-button>\n </ng-template>\n</ng-template>\n\n\n<!-- Waiting spinner -->\n<ng-template #waitingSpinner>\n <ion-spinner color=\"light\"></ion-spinner>\n</ng-template>\n",
20694
+ template: "\n\n<!-- Offline mode card-->\n<ion-card *ngIf=\"showOfflineWarning && (!loading && offline || waitingNetwork)\"\n color=\"accent\"\n class=\"main ion-no-margin\"\n @slideUpDownAnimation>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row>\n <ion-col>\n <ion-text class=\"ion-text-wrap\" *ngIf=\"!waitingNetwork; else waitingNetworkText\">\n <h4>\n <b *ngIf=\"isLogin; else notLogin\" [innerHTML]=\"'NETWORK.INFO.OFFLINE_OR_UNAUTHORIZED'| translate\"></b>\n <ng-template #notLogin>\n <b [innerHTML]=\"'NETWORK.INFO.OFFLINE'| translate\"></b>\n </ng-template>\n </h4>\n <h3>\n <small [innerHTML]=\"'NETWORK.INFO.OFFLINE_HELP'|translate\"></small>\n </h3>\n </ion-text>\n <ng-template #waitingNetworkText>\n <ion-text class=\"ion-text-wrap\" [innerHTML]=\"'NETWORK.INFO.RETRY_TO_CONNECT'| translate\"></ion-text>\n </ng-template>\n </ion-col>\n <ion-col size=\"auto\">\n\n <!-- Retry button -->\n <ion-button *ngIf=\"!waitingNetwork; else waitingSpinner\" color=\"tertiary\" class=\"ion-float-end\"\n (click)=\"tryOnline()\">\n <span translate>NETWORK.BTN_CHECK_ALIVE</span>\n </ion-button>\n\n </ion-col>\n </ion-row>\n </ion-grid>\n\n </ion-card-content>\n</ion-card>\n\n<!-- Upgrade links -->\n<ion-card *ngIf=\"showUpgradeWarning && !loading && !offline && upgradeLinks\"\n color=\"accent\"\n class=\"ion-no-margin\"\n @slideUpDownAnimation>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row *ngFor=\"let link of upgradeLinks; last as last\">\n <ion-col>\n <ion-text class=\"ion-text-wrap\">\n <h3>\n <span *ngIf=\"link.version\" [innerHTML]=\"'INFO.UPDATE_APP_TO_VERSION'| translate: link\"></span>\n <span *ngIf=\"!link.version\" [innerHTML]=\"'INFO.UPDATE_APP'| translate: link\"></span>\n </h3>\n <h4>\n <small *ngIf=\"last\" [innerHTML]=\"'INFO.UPDATE_APP_HELP'|translate\"></small>\n </h4>\n </ion-text>\n </ion-col>\n\n <ion-col size=\"auto\">\n <ng-container *ngTemplateOutlet=\"downloadButton; context: { $implicit: link }\"></ng-container>\n </ion-col>\n </ion-row>\n\n </ion-grid>\n\n </ion-card-content>\n</ion-card>\n\n<!-- Install links -->\n<ion-card *ngIf=\"showInstallButton && !loading && !offline && installLinks\"\n color=\"secondary\"\n class=\"ion-no-margin\"\n @slideUpDownAnimation>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row *ngFor=\"let link of installLinks; last as last\">\n <ion-col>\n <ion-text class=\"ion-text-wrap\">\n <h3 [innerHTML]=\"'INFO.DOWNLOAD_APP_TITLE'| translate: {name: link.name, platform: getPlatformName(link.platform) }\"></h3>\n <h4><small *ngIf=\"last\" [innerHTML]=\"'INFO.DOWNLOAD_APP_HELP'|translate\"></small></h4>\n </ion-text>\n </ion-col>\n <ion-col size=\"auto\">\n <ng-container *ngTemplateOutlet=\"downloadButton; context: { $implicit: link }\"></ng-container>\n </ion-col>\n </ion-row>\n\n </ion-grid>\n\n </ion-card-content>\n</ion-card>\n\n<!-- Display a download button, depending on the link URL, and the device platform -->\n<ng-template #downloadButton let-link>\n\n <ng-container *ngIf=\"link.filename; else redirectButton\">\n\n <ng-container *ngIf=\"hasDownloader; else webDownloadButton\">\n <!-- Download using platform -->\n <ion-button (click)=\"download($event, asLink(link))\"\n *ngIf=\"!link.location; else openButton\"\n [disabled]=\"link.downloading\"\n color=\"tertiary\" class=\"ion-float-end\">\n <ion-icon slot=\"start\" *ngIf=\"!link.downloading\" name=\"download\"></ion-icon>\n <ion-spinner slot=\"start\" class=\"ion-no-padding\" *ngIf=\"link.downloading\"></ion-spinner>\n <ion-label translate>COMMON.BTN_DOWNLOAD</ion-label>\n </ion-button>\n\n <!-- Open APK button -->\n <ng-template #openButton>\n <ion-button *ngIf=\"link.location\"\n (click)=\"openFile($event, link)\"\n color=\"tertiary\" class=\"ion-float-end\" >\n <ion-label translate>COMMON.BTN_INSTALL</ion-label>\n </ion-button>\n </ng-template>\n </ng-container>\n\n <!-- Web download button -->\n <ng-template #webDownloadButton>\n <ion-button [download]=\"link.downloadFilename\"\n [href]=\"link.url\"\n color=\"tertiary\" class=\"ion-float-end\" >\n <ion-label translate>COMMON.BTN_DOWNLOAD</ion-label>\n </ion-button>\n </ng-template>\n </ng-container>\n\n <!-- Web redirect button -->\n <ng-template #redirectButton>\n <ion-button [href]=\"link.url\" rel=\"external\" color=\"tertiary\" class=\"ion-float-end\" target=\"_blank\">\n <ion-label translate>COMMON.BTN_SHOW_MORE</ion-label>\n </ion-button>\n </ng-template>\n</ng-template>\n\n\n<!-- Waiting spinner -->\n<ng-template #waitingSpinner>\n <ion-spinner color=\"light\"></ion-spinner>\n</ng-template>\n",
20495
20695
  changeDetection: ChangeDetectionStrategy.OnPush,
20496
20696
  animations: [slideUpDownAnimation],
20497
20697
  styles: ["ion-text small{font-size:85%}"]
@@ -20512,9 +20712,7 @@ AppInstallUpgradeCard.propDecorators = {
20512
20712
  isLogin: [{ type: Input }],
20513
20713
  showUpgradeWarning: [{ type: Input }],
20514
20714
  showOfflineWarning: [{ type: Input }],
20515
- showInstallButton: [{ type: Input }],
20516
- showUpdateOfflineFeature: [{ type: Input }],
20517
- onUpdateOfflineModeClick: [{ type: Output }]
20715
+ showInstallButton: [{ type: Input }]
20518
20716
  };
20519
20717
 
20520
20718
  class IsLoginAccountPipe {
@@ -20627,33 +20825,7 @@ ActionsColumnComponent.propDecorators = {
20627
20825
  forward: [{ type: Output }]
20628
20826
  };
20629
20827
 
20630
- class MenuService {
20631
- constructor() {
20632
- this._toggledSubject = new Subject();
20633
- this._visibleSubject = new Subject();
20634
- }
20635
- get menuToggled$() {
20636
- return this._toggledSubject.asObservable();
20637
- }
20638
- get menuVisible$() {
20639
- return this._visibleSubject.asObservable();
20640
- }
20641
- toggleMenu() {
20642
- this._toggledSubject.next();
20643
- }
20644
- menuVisible(value) {
20645
- this._visibleSubject.next(value);
20646
- }
20647
- }
20648
- MenuService.ɵprov = ɵɵdefineInjectable({ factory: function MenuService_Factory() { return new MenuService(); }, token: MenuService, providedIn: "root" });
20649
- MenuService.decorators = [
20650
- { type: Injectable, args: [{
20651
- providedIn: 'root'
20652
- },] }
20653
- ];
20654
-
20655
20828
  const APP_MENU_ITEMS = new InjectionToken('menuItems');
20656
- const SPLIT_PANE_SHOW_WHEN = 'lg';
20657
20829
  class MenuComponent {
20658
20830
  constructor(platformService, accountService, router, menu, modalCtrl, alertController, translate, configService, cd, menuService, environment, items) {
20659
20831
  this.platformService = platformService;
@@ -20672,28 +20844,35 @@ class MenuComponent {
20672
20844
  this.loading = true;
20673
20845
  this.isLogin = false;
20674
20846
  this.$items = new BehaviorSubject(undefined);
20675
- this.appVersion = this.environment.version;
20847
+ this.id = 'menu';
20848
+ this.menuId = 'left';
20676
20849
  this.side = 'left';
20850
+ this.contentId = 'menu-content';
20851
+ this.appVersion = this.environment.version;
20677
20852
  this._debug = !environment.production;
20678
20853
  }
20679
20854
  ngOnInit() {
20680
20855
  return __awaiter(this, void 0, void 0, function* () {
20681
- this.splitPaneOpened = true;
20682
- this.splitPane.when = SPLIT_PANE_SHOW_WHEN;
20856
+ this.splitPane.when = yield firstNotNilPromise(this.menuService.splitPaneWhen);
20683
20857
  // Wait platform started
20684
20858
  yield this.platformService.ready();
20685
- this.splitPaneOpened = true;
20686
20859
  // Listen to menu service event
20687
- this.menuService.menuToggled$
20688
- .subscribe(() => this.toggleSplitPane());
20860
+ this.menuService.splitPaneWhen
20861
+ .subscribe((value) => this.setSplitPaneWhen(value));
20862
+ this.menuService.enabled
20863
+ .subscribe((value) => this.enable(value));
20864
+ this.menuService.opened
20865
+ // Avoid duplicated events
20866
+ .pipe(distinctUntilChanged())
20867
+ .subscribe((value) => value ? this.open() : this.close());
20689
20868
  // Update component when refresh is need (=login events or config changed)
20690
20869
  this._subscription.add(merge(this.accountService.onLogin, this.accountService.onLogout, this.configService.config
20691
20870
  .pipe(tap(config => this._config = config)))
20692
20871
  .pipe(
20693
20872
  // Wait account service ready (can be restarted)
20694
- mergeMap(() => this.accountService.ready()))
20695
- .subscribe(account => {
20696
- if (this.accountService.isLogin()) {
20873
+ mergeMap(() => this.accountService.ready()), map(() => this.accountService.isLogin()), distinctUntilChanged())
20874
+ .subscribe(login => {
20875
+ if (login) {
20697
20876
  this.onLogin(this.accountService.account);
20698
20877
  }
20699
20878
  else {
@@ -20721,8 +20900,6 @@ class MenuComponent {
20721
20900
  if (!skipRedirect)
20722
20901
  console.debug('[menu] logout');
20723
20902
  this.isLogin = false;
20724
- //this.splitPaneOpened = false;
20725
- //this.splitPane.when = false;
20726
20903
  this.accountAvatar = null;
20727
20904
  this.accountEmail = null;
20728
20905
  this.accountName = null;
@@ -20751,7 +20928,8 @@ class MenuComponent {
20751
20928
  text: translations['COMMON.BTN_CANCEL'],
20752
20929
  role: 'cancel',
20753
20930
  cssClass: 'secondary'
20754
- }, {
20931
+ },
20932
+ {
20755
20933
  text: translations['AUTH.LOGOUT.BTN_CONFIRM'],
20756
20934
  cssClass: 'ion-color-primary',
20757
20935
  handler: () => {
@@ -20766,9 +20944,40 @@ class MenuComponent {
20766
20944
  yield alert.present();
20767
20945
  });
20768
20946
  }
20769
- menuClose() {
20947
+ enable(value) {
20948
+ return __awaiter(this, void 0, void 0, function* () {
20949
+ const enabled = yield this.menu.isEnabled(this.menuId);
20950
+ if (enabled !== value) {
20951
+ yield this.menu.enable(value, this.menuId);
20952
+ if (!value)
20953
+ yield this.close();
20954
+ }
20955
+ });
20956
+ }
20957
+ open() {
20958
+ return __awaiter(this, void 0, void 0, function* () {
20959
+ console.debug('[menu] Checking open event...');
20960
+ const opened = yield this.menu.isOpen(this.menuId);
20961
+ if (!opened) {
20962
+ console.debug('[menu] Opening menu');
20963
+ yield this.menu.open(this.menuId);
20964
+ // Propage to service
20965
+ this.menuService._markAsOpened();
20966
+ }
20967
+ return true;
20968
+ });
20969
+ }
20970
+ close() {
20770
20971
  return __awaiter(this, void 0, void 0, function* () {
20771
- return this.menu.close('left');
20972
+ console.debug('[menu] Checking close event...');
20973
+ const opened = yield this.menu.isOpen(this.menuId);
20974
+ if (opened) {
20975
+ console.debug('[menu] Closing menu');
20976
+ yield this.menu.close(this.menuId);
20977
+ // Propage to service
20978
+ this.menuService._markAsClosed();
20979
+ }
20980
+ return true;
20772
20981
  });
20773
20982
  }
20774
20983
  openAboutModal(event) {
@@ -20777,24 +20986,33 @@ class MenuComponent {
20777
20986
  return modal.present();
20778
20987
  });
20779
20988
  }
20780
- toggleSplitPane(event) {
20989
+ setSplitPaneWhen(value) {
20990
+ return __awaiter(this, void 0, void 0, function* () {
20991
+ console.debug('[menu] Checking splitPane when...');
20992
+ if (this.splitPane.when !== value) {
20993
+ console.debug('[menu] Set splitPane when to ' + value);
20994
+ this.splitPane.when = value;
20995
+ this.detectChanges();
20996
+ // Propagate to the menu service
20997
+ setTimeout(() => this.menuService.setSplitPaneShowWhen(value), 200);
20998
+ }
20999
+ });
21000
+ }
21001
+ toggleSplitPaneShow(event) {
20781
21002
  if (event && event.defaultPrevented)
20782
21003
  return;
20783
21004
  if (event)
20784
21005
  event.preventDefault();
20785
- this.splitPaneOpened = !this.splitPaneOpened;
20786
- if (!this.splitPaneOpened) {
20787
- this.splitPane.when = false;
21006
+ if (this.splitPane.when === false) {
21007
+ return this.setSplitPaneWhen(DEFAULT_MENU_SHOW_WHEN);
20788
21008
  }
20789
21009
  else {
20790
- this.splitPane.when = SPLIT_PANE_SHOW_WHEN;
21010
+ return this.setSplitPaneWhen(false);
20791
21011
  }
20792
- this.menuService.menuVisible(this.splitPaneOpened);
20793
- this.detectChanges();
20794
21012
  }
20795
21013
  doAction(action, event) {
20796
21014
  return __awaiter(this, void 0, void 0, function* () {
20797
- this.menuClose();
21015
+ this.close();
20798
21016
  switch (action) {
20799
21017
  case 'logout':
20800
21018
  yield this.logout();
@@ -20880,7 +21098,7 @@ class MenuComponent {
20880
21098
  MenuComponent.decorators = [
20881
21099
  { type: Component, args: [{
20882
21100
  selector: 'app-menu',
20883
- template: "<ion-split-pane #splitPane contentId=\"menu-content\" (swiperight)=\"onSwipeRight($event)\">\n\n <ion-menu id=\"menu\" menuId=\"left\" contentId=\"menu-content\">\n <ion-header>\n\n <ion-toolbar @fadeInAnimation *ngIf=\"isLogin; else notLogin\" class=\"ion-toolbar-top\">\n <ion-grid>\n <ion-row>\n <ion-col size=\"4\">\n <button type=\"button\" mat-flat-button\n class=\"user-avatar\" [class.primary]=\"!accountAvatar\"\n [style.background-image]=\"'url('+(accountAvatar||'./assets/img/person.png')+')'\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"ion-color-primary\"\n (click)=\"menuClose()\">\n </button>\n </ion-col>\n <ion-col size=\"8\" class=\"user-logo\">\n <img *ngIf=\"logo\" src=\"{{logo}}\" [title]=\"'APP_NAME'|translate: {appName: appName}\" width=\"108px;\"/>\n <span *ngIf=\"!logo\" width=\"108px;\">{{appName}}</span>\n </ion-col>\n </ion-row>\n <ion-row class=\"ion-no-padding\">\n\n <button mat-button type=\"button\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"ion-color-primary\"\n (click)=\"menuClose()\">\n <ion-label color=\"primary\" class=\"ion-text-wrap ion-text-start\">\n <h3 class=\"no-margin username\">\n <b>{{accountName}}</b>\n </h3>\n <h4>{{accountEmail}}</h4>\n </ion-label>\n </button>\n\n </ion-row>\n </ion-grid>\n\n </ion-toolbar>\n\n <!-- User not logged -->\n <ng-template #notLogin>\n <mat-toolbar class=\"ion-padding\" @fadeInAnimation\n style=\"height: unset; display: block; margin: auto; text-align: center;\">\n <img *ngIf=\"logo\" src=\"{{logo}}\" [title]=\"'APP_NAME'|translate: {appName: appName}\" width=\"150px;\">\n <span *ngIf=\"!logo\" style=\"width: 150px\">{{appName}}</span>\n </mat-toolbar>\n </ng-template>\n </ion-header>\n\n <ion-content [class.has-user-header]=\"isLogin\">\n\n <ion-list lines=\"none\">\n <ion-menu-toggle auto-hide=\"false\"\n [class.flex-spacer]=\"item.cssClass == 'flex-spacer'\"\n *ngFor=\"let item of $items | async\">\n\n <!-- link -->\n <ion-item *ngIf=\"!loading && item.path\"\n @fadeInAnimation\n class=\"{{item.cssClass}} {{item.color}} text-1x\"\n tappable\n [routerLink]=\"item.path\"\n routerDirection=\"root\"\n routerLinkActive=\"selected\"\n [routerLinkActiveOptions]=\"{exact: (item.path === '/')}\">\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-label translate>{{item.title}}</ion-label>\n </ion-item>\n\n <!-- action -->\n <ion-item *ngIf=\"!loading && item.action\"\n @fadeInAnimation\n class=\"{{item.cssClass}} {{item.color}} text-1x\"\n tappable\n (click)=\"doAction(item.action, $event)\">\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-label translate>{{item.title}}</ion-label>\n </ion-item>\n\n <!-- divider -->\n <ion-item-divider @fadeInAnimation\n class=\"{{item.cssClass}} {{item.color}}\"\n *ngIf=\"!loading && !item.path && !item.action\">\n <ion-label translate>{{item.title}}</ion-label>\n </ion-item-divider>\n\n </ion-menu-toggle>\n </ion-list>\n </ion-content>\n\n <ion-footer class=\"hidden-xs hidden-sm\">\n <ion-toolbar>\n\n <ion-buttons slot=\"start\">\n <ion-button mat-icon-button color=\"accent\" (click)=\"openAboutModal($event)\">\n <mat-icon slot=\"icon-only\">help_outline</mat-icon>\n </ion-button>\n </ion-buttons>\n\n <ion-title (click)=\"openAboutModal($event)\" color=\"medium\">\n {{'MENU.FOOTER_VERSION_ABOUT'| translate: {version: appVersion} }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <button mat-icon-button color=\"accent\" (click)=\"toggleSplitPane($event)\"\n class=\"hidden-xs hidden-sm hidden-md\"\n [title]=\"(splitPaneOpened ? 'COMMON.BTN_HIDE_MENU' : 'COMMON.BTN_SHOW_MENU') |translate\">\n <mat-icon><span>{{splitPaneOpened ? '&#xab;' : '&#xbb;'}}</span></mat-icon>\n </button>\n </ion-buttons>\n </ion-toolbar>\n </ion-footer>\n\n </ion-menu>\n\n <ng-content></ng-content>\n\n</ion-split-pane>\n",
21101
+ template: "<ion-split-pane #splitPane [contentId]=\"contentId\" (swiperight)=\"onSwipeRight($event)\">\n\n <ion-menu [id]=\"id\" [menuId]=\"menuId\" contentId=\"menu-content\">\n <ion-header>\n\n <ion-toolbar @fadeInAnimation *ngIf=\"isLogin; else notLogin\" class=\"ion-toolbar-top\">\n <ion-grid>\n <ion-row>\n <ion-col size=\"4\">\n <button type=\"button\" mat-flat-button\n class=\"user-avatar\" [class.primary]=\"!accountAvatar\"\n [style.background-image]=\"'url('+(accountAvatar||'./assets/img/person.png')+')'\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"ion-color-primary\"\n (click)=\"close()\">\n </button>\n </ion-col>\n <ion-col size=\"8\" class=\"user-logo\">\n <img *ngIf=\"logo\" src=\"{{logo}}\" [title]=\"'APP_NAME'|translate: {appName: appName}\" width=\"108px;\"/>\n <span *ngIf=\"!logo\" width=\"108px;\">{{appName}}</span>\n </ion-col>\n </ion-row>\n <ion-row class=\"ion-no-padding\">\n\n <button mat-button type=\"button\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"ion-color-primary\"\n (click)=\"close()\">\n <ion-label color=\"primary\" class=\"ion-text-wrap ion-text-start\">\n <h3 class=\"no-margin username\">\n <b>{{accountName}}</b>\n </h3>\n <h4>{{accountEmail}}</h4>\n </ion-label>\n </button>\n\n </ion-row>\n </ion-grid>\n\n </ion-toolbar>\n\n <!-- User not logged -->\n <ng-template #notLogin>\n <mat-toolbar class=\"ion-padding\" @fadeInAnimation\n style=\"height: unset; display: block; margin: auto; text-align: center;\">\n <img *ngIf=\"logo\" src=\"{{logo}}\" [title]=\"'APP_NAME'|translate: {appName: appName}\" width=\"150px;\">\n <span *ngIf=\"!logo\" style=\"width: 150px\">{{appName}}</span>\n </mat-toolbar>\n </ng-template>\n </ion-header>\n\n <ion-content [class.has-user-header]=\"isLogin\">\n\n <ion-list lines=\"none\">\n <ion-menu-toggle auto-hide=\"false\"\n [class.flex-spacer]=\"item.cssClass == 'flex-spacer'\"\n *ngFor=\"let item of $items | async\">\n\n <!-- link -->\n <ion-item *ngIf=\"!loading && item.path\"\n @fadeInAnimation\n class=\"{{item.cssClass}} {{item.color}} text-1x\"\n tappable\n [routerLink]=\"item.path\"\n routerDirection=\"root\"\n routerLinkActive=\"selected\"\n [routerLinkActiveOptions]=\"{exact: (item.path === '/')}\">\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-label translate>{{item.title}}</ion-label>\n </ion-item>\n\n <!-- action -->\n <ion-item *ngIf=\"!loading && item.action\"\n @fadeInAnimation\n class=\"{{item.cssClass}} {{item.color}} text-1x\"\n tappable\n (click)=\"doAction(item.action, $event)\">\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-label translate>{{item.title}}</ion-label>\n </ion-item>\n\n <!-- divider -->\n <ion-item-divider @fadeInAnimation\n class=\"{{item.cssClass}} {{item.color}}\"\n *ngIf=\"!loading && !item.path && !item.action\">\n <ion-label translate>{{item.title}}</ion-label>\n </ion-item-divider>\n\n </ion-menu-toggle>\n </ion-list>\n </ion-content>\n\n <ion-footer class=\"hidden-xs hidden-sm\">\n <ion-toolbar>\n\n <ion-buttons slot=\"start\">\n <ion-button mat-icon-button color=\"accent\" (click)=\"openAboutModal($event)\">\n <mat-icon slot=\"icon-only\">help_outline</mat-icon>\n </ion-button>\n </ion-buttons>\n\n <ion-title (click)=\"openAboutModal($event)\" color=\"medium\">\n {{'MENU.FOOTER_VERSION_ABOUT'| translate: {version: appVersion} }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <button mat-icon-button color=\"accent\" (click)=\"toggleSplitPaneShow($event)\"\n class=\"hidden-xs hidden-sm hidden-md\"\n [title]=\"(splitPane.when ? 'COMMON.BTN_HIDE_MENU' : 'COMMON.BTN_SHOW_MENU') |translate\">\n <mat-icon><span>{{splitPane.when ? '&#xab;' : '&#xbb;'}}</span></mat-icon>\n </button>\n </ion-buttons>\n </ion-toolbar>\n </ion-footer>\n\n </ion-menu>\n\n <ng-content></ng-content>\n\n</ion-split-pane>\n",
20884
21102
  animations: [fadeInAnimation],
20885
21103
  changeDetection: ChangeDetectionStrategy.OnPush,
20886
21104
  styles: ["ion-menu{--ion-item-background:transparent;--ion-item-divider-background:transparent;--ion-item-icon-color:var(--ion-color-primary-tint);--ion-item-text-color:var(--ion-color-primary-tint);--ion-item-background-selected:var(--ion-color-secondary100);--ion-item-text-color-selected:var(--ion-color-primary);--ion-item-icon-color-selected:var(--ion-color-primary);--ion-item-text-color-disable:var(--ion-color-medium);--ion-item-icon-color-disable:var(--ion-color-medium)}ion-menu ion-header ion-text{color:var(--ion-color-primary)}ion-menu ion-header .user-avatar{background-size:cover;background-repeat:no-repeat;background-position:50%;background-color:var(--ion-color-secondary);border:1px solid var(--ion-color-primary);overflow:hidden!important;font-size:var(--avatar-size,60px)!important;line-height:var(--avatar-size,60px);height:var(--avatar-size,60px)!important;width:var(--avatar-size,60px)!important;border-radius:50%;display:inline-block}ion-menu ion-header .user-logo{text-align:right}ion-menu ion-header .user-logo img{max-width:120px;max-height:var(--avatar-size,60px);width:auto}ion-menu ion-header .username{padding-top:0;margin-top:0;margin-bottom:0;width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}ion-menu ion-header button[mat-button]{padding:0;text-align:start!important;width:100%}ion-menu .scroll-content{margin-top:79px!important}ion-menu .has-user-header .scroll-content{margin-top:188px!important}ion-menu ion-content.has-profile-header{--offset-top:188px}ion-menu ion-content.no-profile-header{--offset-top:79px}ion-menu ion-content ion-list{min-height:100%;display:flex;flex-direction:column;justify-content:flex-start}ion-menu ion-content ion-list ion-menu-toggle.flex-spacer{flex:1 1 auto;display:flex;flex-direction:column;justify-content:flex-end}ion-menu ion-content ion-list ion-item.primary{--ion-item-icon-color:var(--ion-color-primary-tint);--ion-item-text-color:var(--ion-color-primary-tint)}ion-menu ion-content ion-list ion-item.secondary{--ion-item-icon-color:var(--ion-color-secondary-tint);--ion-item-text-color:var(--ion-color-secondary-tint)}ion-menu ion-content ion-list ion-item.tertiary{--ion-item-icon-color:var(--ion-color-tertiary-tint);--ion-item-text-color:var(--ion-color-tertiary-tint)}ion-menu ion-content ion-list ion-item.danger{--ion-item-icon-color:var(--ion-color-danger-tint);--ion-item-text-color:var(--ion-color-danger-tint)}ion-menu ion-content ion-list ion-item.medium{--ion-item-icon-color:var(--ion-color-medium-shade);--ion-item-text-color:var(--ion-color-medium-shade)}ion-menu ion-content ion-list ion-item.dark{--ion-item-icon-color:var(--ion-color-dark-tint);--ion-item-text-color:var(--ion-color-dark-tint)}ion-menu ion-content ion-list ion-item ion-icon,ion-menu ion-content ion-list ion-item mat-icon{color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor}ion-menu ion-content ion-list ion-item ion-label,ion-menu ion-content ion-list ion-item ion-text{color:var(--ion-item-text-color)!important}ion-menu ion-content ion-list ion-item.selected{background-color:var(--ion-item-background-selected)!important;--color-hover:var(--ion-item-background-selected)!important}ion-menu ion-content ion-list ion-item.selected ion-icon,ion-menu ion-content ion-list ion-item.selected mat-icon{color:var(--ion-item-icon-color-selected)!important;fill:currentColor;stroke:currentColor}ion-menu ion-content ion-list ion-item.selected ion-label,ion-menu ion-content ion-list ion-item.selected ion-text{color:var(--ion-item-text-color-selected)!important}ion-menu ion-content ion-list ion-item.selected:hover{--color-hover:var(--ion-item-background-selected)!important;--ion-item-icon-color-selected:var(--ion-item-icon-color)!important;--ion-item-text-color-selected:var(--ion-item-text-color)!important}ion-menu ion-footer{display:block!important}ion-menu ion-footer ion-toolbar ion-title{cursor:pointer;font-size:12pt;font-weight:400;text-align:center;padding:0 8px}ion-menu ion-footer ion-toolbar ion-button{--width:40px}@media screen and (max-width:767px){ion-menu ion-footer{display:none!important;visibility:hidden!important}}@media screen and (min-width:768px){ion-menu ion-scroll{overflow-y:auto!important}ion-menu .user-avatar{font-size:var(--avatar-size,80px)!important;line-height:var(--avatar-size,80px);height:var(--avatar-size,80px)!important;width:var(--avatar-size,80px)!important}ion-menu .user-logo img{max-height:var(--avatar-size,80px)}}"]
@@ -20901,10 +21119,13 @@ MenuComponent.ctorParameters = () => [
20901
21119
  { type: Array, decorators: [{ type: Optional }, { type: Inject, args: [APP_MENU_ITEMS,] }] }
20902
21120
  ];
20903
21121
  MenuComponent.propDecorators = {
21122
+ id: [{ type: Input }],
21123
+ menuId: [{ type: Input }],
21124
+ side: [{ type: Input }],
21125
+ contentId: [{ type: Input }],
20904
21126
  logo: [{ type: Input }],
20905
21127
  appName: [{ type: Input }],
20906
21128
  appVersion: [{ type: Input }],
20907
- side: [{ type: Input }],
20908
21129
  splitPane: [{ type: ViewChild, args: ['splitPane', { static: true },] }]
20909
21130
  };
20910
21131
 
@@ -20958,6 +21179,30 @@ AppIconComponent.propDecorators = {
20958
21179
  ref: [{ type: Input }]
20959
21180
  };
20960
21181
 
21182
+ class AppUpdateOfflineModeCard {
21183
+ constructor() {
21184
+ this.onUpdateClick = new EventEmitter();
21185
+ }
21186
+ ngOnDestroy() {
21187
+ this.onUpdateClick.complete();
21188
+ }
21189
+ }
21190
+ AppUpdateOfflineModeCard.decorators = [
21191
+ { type: Component, args: [{
21192
+ selector: 'app-update-offline-mode-card',
21193
+ template: "\n<!-- Update offline feature card-->\n<ion-card color=\"accent\"\n class=\"main ion-no-margin\"\n @slideUpDownAnimation>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row *ngIf=\"!progressionValue; else progression\">\n <ion-col>\n <ion-text class=\"ion-text-wrap\" >\n <h4>\n <b [innerHTML]=\"'NETWORK.INFO.UPDATE_OFFLINE_MODE'| translate\"></b>\n </h4>\n <h3>\n <small [innerHTML]=\"'NETWORK.INFO.UPDATE_OFFLINE_MODE_HELP'|translate\"></small>\n </h3>\n </ion-text>\n </ion-col>\n\n <!-- Update button -->\n <ion-col size=\"auto\">\n <ion-button color=\"tertiary\" class=\"ion-float-end\"\n (click)=\"onUpdateClick.emit($event)\">\n <span translate>NETWORK.BTN_UPDATE</span>\n </ion-button>\n </ion-col>\n </ion-row>\n\n <ng-template #progression>\n <ion-row>\n <ion-col>\n <ion-text class=\"ion-text-wrap\" [innerHTML]=\"progressionMessage\"></ion-text>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col>\n <ion-progress-bar [value]=\"progressionValue / 100\"></ion-progress-bar>\n </ion-col>\n </ion-row>\n </ng-template>\n </ion-grid>\n\n\n </ion-card-content>\n</ion-card>\n",
21194
+ changeDetection: ChangeDetectionStrategy.OnPush,
21195
+ animations: [slideUpDownAnimation],
21196
+ styles: ["ion-text small{font-size:85%}"]
21197
+ },] }
21198
+ ];
21199
+ AppUpdateOfflineModeCard.ctorParameters = () => [];
21200
+ AppUpdateOfflineModeCard.propDecorators = {
21201
+ progressionMessage: [{ type: Input }],
21202
+ progressionValue: [{ type: Input }],
21203
+ onUpdateClick: [{ type: Output }]
21204
+ };
21205
+
20961
21206
  class CoreModule {
20962
21207
  static forRoot() {
20963
21208
  console.info('[core] Creating module (root)');
@@ -20978,7 +21223,6 @@ CoreModule.decorators = [
20978
21223
  CacheModule,
20979
21224
  IonicStorageModule,
20980
21225
  NgxJdenticonModule,
20981
- TranslateModule.forChild(),
20982
21226
  // Sub modules
20983
21227
  AppGraphQLModule,
20984
21228
  AppMenuModule
@@ -21005,6 +21249,7 @@ CoreModule.decorators = [
21005
21249
  // Network
21006
21250
  SelectPeerModal,
21007
21251
  AppInstallUpgradeCard,
21252
+ AppUpdateOfflineModeCard,
21008
21253
  // Other components
21009
21254
  TableSelectColumnsComponent,
21010
21255
  ActionsColumnComponent,
@@ -21040,6 +21285,7 @@ CoreModule.decorators = [
21040
21285
  AppPropertiesForm,
21041
21286
  AppListForm,
21042
21287
  AppInstallUpgradeCard,
21288
+ AppUpdateOfflineModeCard,
21043
21289
  DepartmentToStringPipe,
21044
21290
  AppIconComponent,
21045
21291
  TextPopover,
@@ -21048,6 +21294,9 @@ CoreModule.decorators = [
21048
21294
  },] }
21049
21295
  ];
21050
21296
 
21297
+ const moment$5 = momentImported;
21298
+ const tz = momentTZImported;
21299
+
21051
21300
  class AuthGuardService {
21052
21301
  constructor(accountService, modalCtrl, router, environment) {
21053
21302
  this.accountService = accountService;
@@ -21567,6 +21816,8 @@ class AppTableUtils {
21567
21816
  }
21568
21817
  }
21569
21818
 
21819
+ class AppTableDataSourceOptions {
21820
+ }
21570
21821
  // @dynamic
21571
21822
  // eslint-disable-next-line @angular-eslint/directive-class-suffix
21572
21823
  class EntitiesTableDataSource extends TableDataSource {
@@ -21575,27 +21826,34 @@ class EntitiesTableDataSource extends TableDataSource {
21575
21826
  *
21576
21827
  * @param dataService A service to load and save data
21577
21828
  * @param dataType Type of data contained by the Table. If not specified, then `data` with at least one element must be specified.
21829
+ * @param environment
21578
21830
  * @param validatorService Service that create instances of the FormGroup used to validate row fields.
21579
21831
  * @param config Additional configuration for table.
21580
21832
  */
21581
21833
  constructor(dataType, dataService, validatorService, config) {
21582
- super([], dataType, validatorService, Object.assign({ dataServiceOptions: {}, debug: config && config.suppressErrors === false, keepOriginalDataAfterConfirm: false }, config));
21834
+ super([], dataType, validatorService, config);
21583
21835
  this.dataService = dataService;
21584
21836
  this._creating = false;
21585
21837
  this._saving = false;
21838
+ this._useValidator = false;
21586
21839
  this._stopWatching$ = new Subject();
21587
21840
  this._editingRowCount = 0;
21588
21841
  this._fetchMoreFn = null;
21589
21842
  this.loadingSubject = new BehaviorSubject(undefined);
21843
+ this._options = Object.assign({ dataServiceOptions: {}, debug: config && config.suppressErrors === false, keepOriginalDataAfterConfirm: false }, config);
21844
+ this._useValidator = isNotNil(validatorService);
21590
21845
  this._logTypeName = removeEnd((new dataType()).__typename || 'UnknownVO', 'VO');
21591
21846
  // For DEV ONLY
21592
- this._debug = this.config.debug === true;
21847
+ this._debug = this._options.debug === true;
21593
21848
  }
21594
21849
  get serviceOptions() {
21595
- return this.config.dataServiceOptions;
21850
+ return this._options.dataServiceOptions;
21596
21851
  }
21597
21852
  set serviceOptions(value) {
21598
- this.config.dataServiceOptions = value;
21853
+ this._options.dataServiceOptions = value;
21854
+ }
21855
+ get options() {
21856
+ return this._options;
21599
21857
  }
21600
21858
  get loaded() {
21601
21859
  return this.loadingSubject.value === false; // Should be false when undefined (initial state)
@@ -21603,15 +21861,12 @@ class EntitiesTableDataSource extends TableDataSource {
21603
21861
  get loading() {
21604
21862
  return this.loadingSubject.value !== false; // Should be true when undefined (initial state)
21605
21863
  }
21606
- connect(collectionViewer) {
21607
- // DEBUG console.debug("[entities-datasource] connect");
21608
- return super.connect(collectionViewer);
21609
- }
21610
- disconnect(collectionViewer) {
21611
- super.disconnect(collectionViewer);
21864
+ ngOnDestroy() {
21612
21865
  this._stopWatching$.next();
21613
21866
  this._stopWatching$.complete();
21867
+ this._stopWatching$.unsubscribe();
21614
21868
  this.loadingSubject.complete();
21869
+ this.loadingSubject.unsubscribe();
21615
21870
  }
21616
21871
  watchAll(offset, size, sortBy, sortDirection, filter) {
21617
21872
  this._stopWatching$.next();
@@ -21655,26 +21910,27 @@ class EntitiesTableDataSource extends TableDataSource {
21655
21910
  // Get all rows
21656
21911
  const rows = yield this.getRows();
21657
21912
  // Finish editing all rows
21658
- const confirmed = yield this.confirmRows(rows);
21913
+ const invalidRows = rows.filter(row => row.editing && !row.confirmEditCreate());
21659
21914
  // Cannot finish some rows: error
21660
- if (!confirmed) {
21915
+ if (invalidRows.length) {
21661
21916
  // log errors
21662
21917
  if (this._debug)
21663
- this.getEditingRows(rows).forEach(row => AppTableUtils.logRowErrors(row, `[table-datasource] ${this._logTypeName} row #${row.id}`));
21918
+ invalidRows.forEach(row => AppTableUtils.logRowErrors(row, `[table-datasource] ${this._logTypeName} row #${row.id}`));
21664
21919
  // Stop with an error
21665
21920
  throw { code: ErrorCodes.TABLE_INVALID_ROW_ERROR, message: 'ERROR.TABLE_INVALID_ROW_ERROR' };
21666
21921
  }
21667
21922
  this._editingRowCount = 0;
21668
21923
  let data;
21669
21924
  let dataToSave;
21670
- if (!!this.validatorService) {
21925
+ if (this._useValidator) {
21671
21926
  dataToSave = [];
21672
21927
  data = rows.map(row => {
21673
- const entity = this.toEntity(row.currentData);
21928
+ const currentData = new this.dataConstructor();
21929
+ currentData.fromObject(row.currentData);
21674
21930
  // Filter to keep only dirty row
21675
21931
  if (onlyDirtyRows && row.validator.dirty)
21676
- dataToSave.push(entity);
21677
- return entity;
21932
+ dataToSave.push(currentData);
21933
+ return currentData;
21678
21934
  });
21679
21935
  if (!onlyDirtyRows)
21680
21936
  dataToSave = data;
@@ -21711,33 +21967,25 @@ class EntitiesTableDataSource extends TableDataSource {
21711
21967
  }
21712
21968
  });
21713
21969
  }
21714
- getDataFromRows(rows, options = { originalData: false, toEntity: false }) {
21715
- if (options.toEntity) {
21716
- return super.getDataFromRows(rows, options)
21717
- .map(data => this.toEntity(data));
21970
+ updateDatasource(data, opts) {
21971
+ if (this._debug)
21972
+ console.debug(`[table-datasource] Updating datasource with data:`, data);
21973
+ super.updateDatasource(data, opts);
21974
+ if (!opts || opts.emitEvent !== false) {
21975
+ this.markAsLoaded();
21718
21976
  }
21719
- return super.getDataFromRows(rows, options);
21720
21977
  }
21721
- toEntity(data) {
21722
- if (EntityUtils.isNotEntity(data)) {
21723
- const entity = new this.dataConstructor();
21724
- entity.fromObject(data);
21725
- return entity;
21726
- }
21727
- return data;
21978
+ // Overwrite default signature
21979
+ createNew(insertAt) {
21980
+ this.asyncCreateNew(insertAt);
21728
21981
  }
21729
- updateDatasource(data, opts = { emitEvent: true }) {
21730
- const _super = Object.create(null, {
21731
- updateDatasource: { get: () => super.updateDatasource }
21732
- });
21733
- return __awaiter(this, void 0, void 0, function* () {
21734
- if (this._debug)
21735
- console.debug(`[table-datasource] Updating datasource with data:`, data);
21736
- yield _super.updateDatasource.call(this, data, opts);
21737
- if (opts.emitEvent) {
21738
- this.markAsLoaded();
21739
- }
21740
- });
21982
+ connect(collectionViewer) {
21983
+ // DEBUG console.debug("[entities-datasource] connect");
21984
+ return super.connect(collectionViewer);
21985
+ }
21986
+ disconnect(collectionViewer) {
21987
+ super.disconnect(collectionViewer);
21988
+ this._stopWatching$.next();
21741
21989
  }
21742
21990
  waitIdle(debounceTimeMs) {
21743
21991
  return firstFalsePromise(this.loadingSubject
@@ -21746,60 +21994,34 @@ class EntitiesTableDataSource extends TableDataSource {
21746
21994
  ));
21747
21995
  }
21748
21996
  confirmCreate(row) {
21749
- const _super = Object.create(null, {
21750
- confirmCreate: { get: () => super.confirmCreate }
21751
- });
21752
- return __awaiter(this, void 0, void 0, function* () {
21753
- if (!(yield _super.confirmCreate.call(this, row)))
21754
- return false;
21755
- if (row.editing && row.validator) {
21756
- console.warn('[table-datasource] Row still has {editing: true} after confirmCreate()! Force editing to false');
21757
- row.validator.disable({ onlySelf: true, emitEvent: false });
21758
- }
21759
- return true;
21760
- });
21997
+ if (!super.confirmCreate(row))
21998
+ return false;
21999
+ if (row.editing && row.validator) {
22000
+ console.warn('[table-datasource] Row still has {editing: true} after confirmCreate()! Force editing to false');
22001
+ row.validator.disable({ onlySelf: true, emitEvent: false });
22002
+ }
22003
+ return true;
21761
22004
  }
21762
22005
  confirmEdit(row) {
21763
- const _super = Object.create(null, {
21764
- confirmEdit: { get: () => super.confirmEdit }
21765
- });
21766
- return __awaiter(this, void 0, void 0, function* () {
21767
- if (!(yield _super.confirmEdit.call(this, row)))
21768
- return false;
21769
- if (row.editing && row.validator) {
21770
- console.warn('[table-datasource] Row still has {editing: true} after confirmCreate()! Force editing to false');
21771
- row.validator.disable({ onlySelf: true, emitEvent: false });
21772
- }
21773
- return true;
21774
- });
22006
+ if (!super.confirmEdit(row))
22007
+ return false;
22008
+ if (row.editing && row.validator) {
22009
+ console.warn('[table-datasource] Row still has {editing: true} after confirmCreate()! Force editing to false');
22010
+ row.validator.disable({ onlySelf: true, emitEvent: false });
22011
+ }
22012
+ return true;
21775
22013
  }
21776
22014
  startEdit(row) {
21777
- const _super = Object.create(null, {
21778
- startEdit: { get: () => super.startEdit }
21779
- });
21780
- return __awaiter(this, void 0, void 0, function* () {
21781
- if (row.editing)
21782
- return; // Already editing
21783
- if (this._debug)
21784
- console.debug('[table-datasource] Start to edit row', row);
21785
- const done = yield _super.startEdit.call(this, row);
21786
- if (done)
21787
- this._editingRowCount++;
21788
- return done;
21789
- });
22015
+ if (this._debug)
22016
+ console.debug('[table-datasource] Start to edit row', row);
22017
+ row.startEdit();
22018
+ this._editingRowCount++;
21790
22019
  }
21791
- cancel(row) {
21792
- const _super = Object.create(null, {
21793
- cancel: { get: () => super.cancel }
21794
- });
21795
- return __awaiter(this, void 0, void 0, function* () {
21796
- if (this._debug)
21797
- console.debug('[table-datasource] Cancelling row', row);
21798
- const done = yield _super.cancel.call(this, row);
21799
- if (done)
21800
- this._editingRowCount--;
21801
- return done;
21802
- });
22020
+ cancelOrDelete(row) {
22021
+ if (this._debug)
22022
+ console.debug('[table-datasource] Cancelling or deleting row', row);
22023
+ row.cancelOrDelete();
22024
+ this._editingRowCount--;
21803
22025
  }
21804
22026
  handleError(error, message) {
21805
22027
  const errorMsg = error && error.message || error;
@@ -21814,77 +22036,59 @@ class EntitiesTableDataSource extends TableDataSource {
21814
22036
  throw error;
21815
22037
  }
21816
22038
  delete(id) {
21817
- const _super = Object.create(null, {
21818
- delete: { get: () => super.delete }
21819
- });
21820
- return __awaiter(this, void 0, void 0, function* () {
21821
- // If new row: not need to propagate to the dataService
21822
- if (id === -1) {
21823
- return _super.delete.call(this, id);
21824
- }
21825
- const row = this.getRow(id);
21826
- if (!row) {
21827
- console.error(`[table-datasource] Row to delete with id=${id} not found`);
21828
- return;
21829
- }
21830
- this.markAsLoading();
21831
- try {
21832
- yield this.dataService.deleteAll([row.currentData], this.serviceOptions);
21833
- // Make sure row has been deleted (because GrapQHl cache remove can failed)
21834
- yield sleep(300);
22039
+ // If new row: not need to propagate to the dataService
22040
+ if (id === -1) {
22041
+ return super.delete(id);
22042
+ }
22043
+ const row = this.getRow(id);
22044
+ if (!row) {
22045
+ console.error(`[table-datasource] Row to delete with id=${id} not found`);
22046
+ return;
22047
+ }
22048
+ this.loadingSubject.next(true);
22049
+ this.dataService.deleteAll([row.currentData], this.serviceOptions)
22050
+ .catch(err => this.handleErrorPromise(err))
22051
+ .then(() => {
22052
+ setTimeout(() => {
22053
+ // make sure row has been deleted (because GrapQHl cache remove can failed)
21835
22054
  const present = this.getRow(id) === row;
21836
- if (present) {
21837
- console.warn('[table-datasource] Force deletion of 1 row (Is service applying deletion to observable ?)');
21838
- yield _super.delete.call(this, id);
21839
- }
21840
- }
21841
- catch (err) {
21842
- this.handleErrorPromise(err);
21843
- }
21844
- finally {
21845
- this.markAsLoaded();
21846
- }
22055
+ if (present)
22056
+ super.delete(id);
22057
+ this.loadingSubject.next(false);
22058
+ }, 300);
21847
22059
  });
21848
22060
  }
21849
22061
  deleteAll(rows) {
21850
- const _super = Object.create(null, {
21851
- delete: { get: () => super.delete }
21852
- });
21853
- return __awaiter(this, void 0, void 0, function* () {
21854
- this.markAsLoading();
21855
- const data = this.getDataFromRows(rows);
21856
- const rowsById = rows.reduce((res, row) => {
21857
- res[row.id] = row;
21858
- return res;
21859
- }, {});
21860
- const self = this;
21861
- try {
21862
- yield this.dataService.deleteAll(data, this.serviceOptions);
21863
- // Workaround, to be sure all rows has been deleted
21864
- // Sometimes, the service miss deletion, or GrapQHl cache remove failed
21865
- const rowNotDeleted = Object.getOwnPropertyNames(rowsById).reduce((res, id) => {
21866
- const row = rowsById[id];
21867
- const present = self.getRow(+id) === row;
21868
- return present ? res.concat(row) : res;
21869
- }, []).sort((a, b) => a.id > b.id ? -1 : 1);
21870
- // Apply missing deletion
21871
- if (isNotEmptyArray(rowNotDeleted)) {
21872
- console.warn(`[table-datasource] Force deletion of ${rowNotDeleted.length} rows (Is service applying deletion to observable ?)`);
21873
- yield Promise.all(rowNotDeleted.map(r => _super.delete.call(this, r.id)));
21874
- }
21875
- }
21876
- catch (err) {
21877
- this.handleErrorPromise(err);
21878
- }
21879
- finally {
21880
- this.markAsLoaded();
22062
+ this.markAsLoading();
22063
+ const data = rows.map(r => r.currentData);
22064
+ const rowsById = rows.reduce((res, row) => {
22065
+ res[row.id] = row;
22066
+ return res;
22067
+ }, {});
22068
+ const self = this;
22069
+ const selfDelete = super.delete;
22070
+ return this.dataService.deleteAll(data, this.serviceOptions)
22071
+ .catch(err => this.handleErrorPromise(err))
22072
+ .then(() => {
22073
+ // Workaround, to be sure all rows has been deleted
22074
+ // Sometimes, the service miss deletion, or GrapQHl cache remove failed
22075
+ const rowNotDeleted = Object.getOwnPropertyNames(rowsById).reduce((res, id) => {
22076
+ const row = rowsById[id];
22077
+ const present = self.getRow(+id) === row;
22078
+ return present ? res.concat(row) : res;
22079
+ }, []).sort((a, b) => a.id > b.id ? -1 : 1);
22080
+ // Apply missing deletion
22081
+ if (isNotEmptyArray(rowNotDeleted)) {
22082
+ console.warn(`[table-datasource] Force deletion of ${rowNotDeleted.length} rows (Is service applying deletion to observable ?)`);
22083
+ rowNotDeleted.forEach(r => selfDelete.call(self, r.id));
21881
22084
  }
22085
+ this.loadingSubject.next(false);
21882
22086
  });
21883
22087
  }
21884
22088
  getRows() {
21885
- return this.rowsSubject.toPromise();
22089
+ return firstNotNilPromise(this.connect(null));
21886
22090
  }
21887
- createNew(insertAt) {
22091
+ asyncCreateNew(insertAt) {
21888
22092
  const _super = Object.create(null, {
21889
22093
  createNew: { get: () => super.createNew }
21890
22094
  });
@@ -21892,39 +22096,22 @@ class EntitiesTableDataSource extends TableDataSource {
21892
22096
  if (this._creating)
21893
22097
  return; // Avoid multiple call (should never occur)
21894
22098
  this._creating = true;
21895
- const done = yield _super.createNew.call(this, insertAt);
21896
- if (done && this.config.onRowCreated) {
21897
- const row = this.getRow(-1);
21898
- try {
21899
- yield this.config.onRowCreated(row);
21900
- }
21901
- catch (err) {
21902
- console.error(err && err.message || err, err);
21903
- // Continue
22099
+ _super.createNew.call(this, insertAt);
22100
+ const row = this.getRow(-1);
22101
+ if (row && this._options && this._options.onRowCreated) {
22102
+ const res = this._options.onRowCreated(row);
22103
+ // If async function, wait the end before ending
22104
+ if (res instanceof Promise) {
22105
+ try {
22106
+ yield res;
22107
+ }
22108
+ catch (err) {
22109
+ console.error(err && err.message || err, err);
22110
+ }
21904
22111
  }
21905
22112
  }
21906
- if (done)
21907
- this._editingRowCount++;
22113
+ this._editingRowCount++;
21908
22114
  this._creating = false;
21909
- return done;
21910
- });
21911
- }
21912
- createRowValidator() {
21913
- const _super = Object.create(null, {
21914
- createRowValidator: { get: () => super.createRowValidator }
21915
- });
21916
- return __awaiter(this, void 0, void 0, function* () {
21917
- const validator = yield _super.createRowValidator.call(this);
21918
- if (validator && this.config.onRowValidator) {
21919
- try {
21920
- yield this.config.onRowValidator(validator);
21921
- }
21922
- catch (err) {
21923
- console.error(err && err.message || err, err);
21924
- // Continue
21925
- }
21926
- }
21927
- return validator;
21928
22115
  });
21929
22116
  }
21930
22117
  getData() {
@@ -21939,7 +22126,7 @@ class EntitiesTableDataSource extends TableDataSource {
21939
22126
  });
21940
22127
  return __awaiter(this, void 0, void 0, function* () {
21941
22128
  if (!this._fetchMoreFn)
21942
- return false;
22129
+ return false; // Avoid multiple call
21943
22130
  if (this._editingRowCount > 0) {
21944
22131
  console.warn('Cannot fetch more because still editing row');
21945
22132
  return;
@@ -21953,10 +22140,9 @@ class EntitiesTableDataSource extends TableDataSource {
21953
22140
  if (isNotEmptyArray(res === null || res === void 0 ? void 0 : res.data))
21954
22141
  return false;
21955
22142
  // Update the data source
21956
- // TODO review this, to keep existing rows, instead of creating new TableElement + validator !
21957
22143
  const existingData = yield this.getData();
21958
- yield _super.updateDatasource.call(this, existingData.concat(...res.data), opts);
21959
- // Store next fetchMore function
22144
+ _super.updateDatasource.call(this, existingData.concat(...res.data), opts);
22145
+ // Remember fetchMore
21960
22146
  this._fetchMoreFn = res.fetchMore;
21961
22147
  return true;
21962
22148
  });
@@ -21979,8 +22165,8 @@ EntitiesTableDataSource.decorators = [
21979
22165
  EntitiesTableDataSource.ctorParameters = () => [
21980
22166
  { type: Function },
21981
22167
  { type: undefined },
21982
- { type: undefined },
21983
- { type: undefined }
22168
+ { type: ValidatorService },
22169
+ { type: AppTableDataSourceOptions }
21984
22170
  ];
21985
22171
 
21986
22172
  const SETTINGS_DISPLAY_COLUMNS = 'displayColumns';
@@ -22372,6 +22558,7 @@ class AppTable {
22372
22558
  }
22373
22559
  }
22374
22560
  ngOnDestroy() {
22561
+ var _a;
22375
22562
  this._subscription.unsubscribe();
22376
22563
  // Unsubscribe column value changes
22377
22564
  Object.keys(this._cellValueChangesDefs).forEach(col => this.stopCellValueChanges(col, true));
@@ -22396,6 +22583,7 @@ class AppTable {
22396
22583
  this.onError.unsubscribe();
22397
22584
  this._destroy$.next();
22398
22585
  this._destroy$.unsubscribe();
22586
+ (_a = this._dataSource) === null || _a === void 0 ? void 0 : _a.ngOnDestroy();
22399
22587
  }
22400
22588
  updateView(res, opts) {
22401
22589
  return __awaiter(this, void 0, void 0, function* () {
@@ -22437,7 +22625,7 @@ class AppTable {
22437
22625
  this._dataSourceLoadingSubscription.unsubscribe();
22438
22626
  this._subscription.remove(this._dataSourceLoadingSubscription);
22439
22627
  }
22440
- (_a = this._dataSource) === null || _a === void 0 ? void 0 : _a.disconnect();
22628
+ (_a = this._dataSource) === null || _a === void 0 ? void 0 : _a.ngOnDestroy();
22441
22629
  this._dataSource = null;
22442
22630
  }
22443
22631
  addColumnDef(column) {
@@ -22492,15 +22680,18 @@ class AppTable {
22492
22680
  return true;
22493
22681
  }
22494
22682
  confirmAndForward(event, row) {
22495
- return __awaiter(this, void 0, void 0, function* () {
22496
- row = row || this.editedRow;
22497
- const done = yield this.confirmEditCreate(event, row);
22498
- if (done) {
22499
- // Edit next row
22500
- this.editRowById(event, row.id + 1, { focusColumn: this.firstUserColumn });
22683
+ var _a;
22684
+ row = row || this.editedRow;
22685
+ if (!this.confirmEditCreate(event, row)) {
22686
+ // If pending: Wait end of validation, then loop
22687
+ if ((_a = row.validator) === null || _a === void 0 ? void 0 : _a.pending) {
22688
+ return AppFormUtils.waitWhilePending(row.validator).then(() => this.confirmAndForward(event, row));
22501
22689
  }
22502
- return done;
22503
- });
22690
+ return false;
22691
+ }
22692
+ // Edit next row
22693
+ this.editRowById(event, row.id + 1, { focusColumn: this.firstUserColumn });
22694
+ return true;
22504
22695
  }
22505
22696
  editRowById(event, id, opts) {
22506
22697
  return __awaiter(this, void 0, void 0, function* () {
@@ -22524,44 +22715,48 @@ class AppTable {
22524
22715
  */
22525
22716
  confirmEditCreate(event, row) {
22526
22717
  var _a;
22527
- return __awaiter(this, void 0, void 0, function* () {
22528
- row = row || this.editedRow;
22529
- if (!row || !row.editing)
22530
- return true; // no row to confirm
22531
- // Stop event
22532
- event === null || event === void 0 ? void 0 : event.stopPropagation();
22533
- // Confirmation edition or creation
22534
- const done = yield row.confirmEditCreate();
22535
- if (done) {
22536
- // If edit finished, forget edited row
22537
- if (row === this.editedRow) {
22538
- this.editedRow = undefined;
22539
- }
22540
- // Mark table as dirty (if row is dirty)
22541
- if ((_a = row.validator) === null || _a === void 0 ? void 0 : _a.dirty) {
22542
- this.markAsDirty({ emitEvent: false /* because of resetError() */ });
22543
- }
22544
- // Clear error
22545
- this.resetError();
22546
- // Emit the confirm event
22547
- this.onConfirmEditCreateRow.next(row);
22548
- }
22549
- else if (row.validator) {
22550
- // NOT confirmed = row has error
22551
- if (this.debug) {
22552
- console.warn('[table] Cannot confirm row, because invalid');
22553
- AppFormUtils.logFormErrors(row.validator, '[table] ');
22554
- }
22555
- // fix: mark all controls as touched to show errors
22556
- row.validator.markAllAsTouched();
22557
- // Compute row error, and propagate to table's error
22558
- if (this.propagateRowError) {
22559
- const error = this.getRowError(row);
22560
- this.setError(error);
22561
- }
22718
+ row = row || this.editedRow;
22719
+ if (!row || !row.editing)
22720
+ return true; // no row to confirm
22721
+ // Stop event
22722
+ event === null || event === void 0 ? void 0 : event.stopPropagation();
22723
+ // Confirmation edition or creation
22724
+ const confirmed = row.confirmEditCreate();
22725
+ if (confirmed) {
22726
+ // If edit finished, forget edited row
22727
+ if (row === this.editedRow) {
22728
+ this.editedRow = undefined;
22562
22729
  }
22563
- return done;
22564
- });
22730
+ // Mark table as dirty (if row is dirty)
22731
+ if ((_a = row.validator) === null || _a === void 0 ? void 0 : _a.dirty) {
22732
+ this.markAsDirty({ emitEvent: false /* because of resetError() */ });
22733
+ }
22734
+ // Clear error
22735
+ this.resetError();
22736
+ // Emit the confirm event
22737
+ this.onConfirmEditCreateRow.next(row);
22738
+ return true; // Continue
22739
+ }
22740
+ if (row.validator) {
22741
+ // If pending: Wait end of validation, then loop
22742
+ if (row.validator.pending) {
22743
+ AppFormUtils.waitWhilePending(row.validator).then(() => this.confirmEditCreate(event, row));
22744
+ return false;
22745
+ }
22746
+ // NOT confirmed = row has error
22747
+ if (this.debug) {
22748
+ console.warn('[table] Cannot confirm row, because invalid');
22749
+ AppFormUtils.logFormErrors(row.validator, '[table] ');
22750
+ }
22751
+ // fix: mark all controls as touched to show errors
22752
+ row.validator.markAllAsTouched();
22753
+ // Compute row error, and propagate to table's error
22754
+ if (this.propagateRowError) {
22755
+ const error = this.getRowError(row);
22756
+ this.setError(error);
22757
+ }
22758
+ }
22759
+ return false;
22565
22760
  }
22566
22761
  cancelOrDelete(event, row, opts) {
22567
22762
  // Delete new row
@@ -22606,7 +22801,6 @@ class AppTable {
22606
22801
  // Keep edited row id (should be done BEFORE confirmEditCreate() )
22607
22802
  this.previouslyEditedRowId = opts.keepEditing ? ((((_a = this.editedRow) === null || _a === void 0 ? void 0 : _a.editing) ? this.editedRow.id : undefined) || ((_b = this.singleSelectedRow) === null || _b === void 0 ? void 0 : _b.id)) : undefined;
22608
22803
  const previouslyEditedData = opts.keepEditing ? (isNotNil(this.previouslyEditedRowId) && ((_c = this.editedRow) === null || _c === void 0 ? void 0 : _c.currentData) || ((_d = this.singleSelectedRow) === null || _d === void 0 ? void 0 : _d.currentData)) : undefined;
22609
- // TODO: remove this, has it redundant with dataSource.confirmAllRows() called in dataSource.save()
22610
22804
  if (!this.confirmEditCreate()) {
22611
22805
  throw { code: ErrorCodes.TABLE_INVALID_ROW_ERROR, message: 'ERROR.TABLE_INVALID_ROW_ERROR' };
22612
22806
  }
@@ -23164,7 +23358,7 @@ class AppTable {
23164
23358
  console.warn('[table] Opening row details, but data has no id!');
23165
23359
  return false;
23166
23360
  }
23167
- return yield this.router.navigate([id], {
23361
+ return this.router.navigate(['.', id.toString()], {
23168
23362
  relativeTo: this.route,
23169
23363
  queryParams: {}
23170
23364
  });
@@ -23261,9 +23455,7 @@ class AppTable {
23261
23455
  addRowToTable(insertAt) {
23262
23456
  return __awaiter(this, void 0, void 0, function* () {
23263
23457
  this.focusFirstColumn = true;
23264
- const done = yield this._dataSource.createNew(insertAt);
23265
- if (!done)
23266
- return undefined;
23458
+ yield this._dataSource.asyncCreateNew(insertAt);
23267
23459
  this.editedRow = this._dataSource.getRow(-1);
23268
23460
  // Emit start editing event
23269
23461
  this.onStartEditingRow.emit(this.editedRow);
@@ -23394,64 +23586,59 @@ class AppTable {
23394
23586
  }
23395
23587
  }
23396
23588
  deleteNewRow(event, row) {
23397
- return __awaiter(this, void 0, void 0, function* () {
23398
- if (row.id !== -1)
23399
- throw new Error('Row must have id = -1');
23400
- event === null || event === void 0 ? void 0 : event.stopPropagation();
23401
- this.selection.clear();
23402
- this.editedRow = undefined; // unselect row
23403
- const done = yield this._dataSource.cancelOrDelete(row);
23404
- if (done) {
23405
- this.onCancelOrDeleteRow.next(row);
23406
- this.resetError();
23407
- this.totalRowCount--;
23408
- this.visibleRowCount--;
23409
- }
23410
- return done;
23411
- });
23589
+ if (row.id !== -1)
23590
+ throw new Error('Row must have id = -1');
23591
+ event === null || event === void 0 ? void 0 : event.stopPropagation();
23592
+ this.selection.clear();
23593
+ this.editedRow = undefined; // unselect row
23594
+ this._dataSource.cancelOrDelete(row);
23595
+ this.onCancelOrDeleteRow.next(row);
23596
+ this.resetError();
23597
+ this.totalRowCount--;
23598
+ this.visibleRowCount--;
23412
23599
  }
23413
23600
  deleteExistingRow(event, row, opts) {
23414
23601
  // Make sure row will be cancelled, and NOT deleted
23415
23602
  if (row.id === -1)
23416
23603
  throw new Error('Row must have an id');
23417
23604
  if (event === null || event === void 0 ? void 0 : event.defaultPrevented)
23418
- return Promise.resolve(false); // SKip
23605
+ return 0; // SKip
23419
23606
  event === null || event === void 0 ? void 0 : event.preventDefault();
23420
- return this.deleteRow(null, row, opts);
23607
+ this.deleteRow(null, row, opts);
23421
23608
  }
23422
23609
  cancelExistingRow(event, row, opts) {
23423
23610
  var _a;
23424
- return __awaiter(this, void 0, void 0, function* () {
23425
- // Make sure row will be cancelled, and NOT deleted
23426
- if (row.id === -1 || !row.editing)
23427
- throw new Error('Row cannot be canceling, but only deleting');
23428
- const confirmed = (!opts || opts.interactive !== false);
23429
- // Ask user confirmation, if cancel
23430
- if (!confirmed && ((_a = row.validator) === null || _a === void 0 ? void 0 : _a.dirty) && (this.confirmBeforeCancel || this.onBeforeCancelRows.observers.length > 0)) {
23431
- event.stopPropagation();
23432
- const confirm = yield this.canCancelRows([row], opts);
23433
- // If not confirmed: stop
23434
- if (!confirm)
23435
- return false;
23436
- }
23437
- const keepEditing = row.editing && (!opts || opts.keepEditing !== false);
23438
- this.editedRow = undefined; // unselect row
23439
- this._dataSource.cancelOrDelete(row);
23440
- this.onCancelOrDeleteRow.next(row);
23441
- // Mark row as pristine
23442
- this.checkIfRowPristine(row);
23443
- // Restore editing state
23444
- if (keepEditing) {
23445
- this.editRow(undefined, row);
23446
- }
23447
- return true;
23448
- });
23611
+ // Make sure row will be cancelled, and NOT deleted
23612
+ if (row.id === -1 || !row.editing)
23613
+ throw new Error('Row cannot be canceling, but only deleting');
23614
+ const confirmed = (!opts || opts.interactive !== false);
23615
+ // Ask user confirmation, if cancel
23616
+ if (!confirmed && ((_a = row.validator) === null || _a === void 0 ? void 0 : _a.dirty) && (this.confirmBeforeCancel || this.onBeforeCancelRows.observers.length > 0)) {
23617
+ event.stopPropagation();
23618
+ this.canCancelRows([row], opts)
23619
+ .then(confirm => {
23620
+ // If confirmed, loop
23621
+ if (confirm)
23622
+ this.cancelExistingRow(event, row, { interactive: false });
23623
+ });
23624
+ return;
23625
+ }
23626
+ const keepEditing = row.editing && (!opts || opts.keepEditing !== false);
23627
+ this.editedRow = undefined; // unselect row
23628
+ this._dataSource.cancelOrDelete(row);
23629
+ this.onCancelOrDeleteRow.next(row);
23630
+ // Mark row as pristine
23631
+ this.checkIfRowPristine(row);
23632
+ // Restore editing state
23633
+ if (keepEditing) {
23634
+ this.editRow(undefined, row);
23635
+ }
23449
23636
  }
23450
23637
  checkIfRowPristine(row, opts) {
23451
23638
  var _a, _b;
23452
23639
  return __awaiter(this, void 0, void 0, function* () {
23453
23640
  // Mark row as pristine
23454
- const markRowAsPristine = ((_a = this.dataSource) === null || _a === void 0 ? void 0 : _a.config.keepOriginalDataAfterConfirm) === true;
23641
+ const markRowAsPristine = ((_a = this.dataSource) === null || _a === void 0 ? void 0 : _a.options.keepOriginalDataAfterConfirm) === true;
23455
23642
  if (markRowAsPristine) {
23456
23643
  (_b = row.validator) === null || _b === void 0 ? void 0 : _b.markAsPristine();
23457
23644
  // Check if table is now pristine
@@ -24259,6 +24446,12 @@ class AppEntityEditor extends AppTabEditor {
24259
24446
  this.onUpdateView.complete();
24260
24447
  this.onUpdateView.unsubscribe();
24261
24448
  }
24449
+ waitIdle(opts) {
24450
+ // Wait end of saving
24451
+ if (this.saving)
24452
+ return waitFor(() => this.saving !== true);
24453
+ return super.waitIdle(opts);
24454
+ }
24262
24455
  /**
24263
24456
  * Load data from the snapshot route
24264
24457
  *
@@ -24377,7 +24570,7 @@ class AppEntityEditor extends AppTabEditor {
24377
24570
  const idChanged = isNotNil(data.id)
24378
24571
  && this.previousDataId !== undefined // Ignore if first loading (=undefined)
24379
24572
  && this.previousDataId !== data.id;
24380
- opts = Object.assign({ updateRoute: this._autoUpdateRoute && idChanged && !this.loading, openTabIndex: this._autoOpenNextTab && idChanged && isNil(this.previousDataId) && this.computeNextTabIndex() }, opts);
24573
+ opts = Object.assign({ updateRoute: this._autoUpdateRoute && idChanged && !this.loading }, opts);
24381
24574
  this.data = data;
24382
24575
  this.previousDataId = data.id || null;
24383
24576
  yield this.ready();
@@ -24428,18 +24621,25 @@ class AppEntityEditor extends AppTabEditor {
24428
24621
  // Save the opened tab into the queryParams
24429
24622
  this.queryParams.tab = this.selectedTabIndex;
24430
24623
  // Update route location
24431
- const forcedQueryParams = {};
24432
- forcedQueryParams[this._pathIdAttribute] = data && isNotNil(data.id) ? data.id : 'new';
24433
- if (data && isNotNil(data.id)) {
24624
+ if (isNotNil(data === null || data === void 0 ? void 0 : data.id)) {
24625
+ const queryParams = Object.assign({}, this.queryParams);
24626
+ queryParams[this._pathIdAttribute] = data.id;
24627
+ if (this.debug)
24628
+ console.debug('[entity-editor] Updating route using params: ', queryParams);
24434
24629
  yield this.router.navigate(['.'], {
24435
24630
  relativeTo: this.route,
24436
- queryParams: Object.assign(Object.assign({}, this.queryParams), forcedQueryParams)
24631
+ queryParams
24437
24632
  });
24438
24633
  }
24634
+ // Move to ../new
24439
24635
  else {
24636
+ const queryParams = Object.assign({}, this.queryParams);
24637
+ queryParams[this._pathIdAttribute] = 'new';
24638
+ if (this.debug)
24639
+ console.debug('[entity-editor] Updating route to \'../new\' using params: ', queryParams);
24440
24640
  yield this.router.navigate(['..', 'new'], {
24441
24641
  relativeTo: this.route,
24442
- queryParams: Object.assign(Object.assign({}, this.queryParams), forcedQueryParams)
24642
+ queryParams
24443
24643
  });
24444
24644
  }
24445
24645
  yield this.updateRoute(data, this.queryParams);
@@ -24449,7 +24649,7 @@ class AppEntityEditor extends AppTabEditor {
24449
24649
  * Update the route location, and open the next tab
24450
24650
  */
24451
24651
  updateTabIndex(tabIndex) {
24452
- if (isNotNil(tabIndex) && tabIndex > this.selectedTabIndex) {
24652
+ if (isNotNil(tabIndex) && tabIndex !== this.selectedTabIndex) {
24453
24653
  this.selectedTabIndex = tabIndex;
24454
24654
  this.markForCheck();
24455
24655
  }
@@ -24505,6 +24705,8 @@ class AppEntityEditor extends AppTabEditor {
24505
24705
  if (this.debug)
24506
24706
  console.debug('[data-editor] Saving data...');
24507
24707
  try {
24708
+ // Compute next tab index (if enable)
24709
+ const nextTabIndex = this._autoOpenNextTab && isNil(this.previousDataId) && this.computeNextTabIndex();
24508
24710
  // Get data
24509
24711
  const data = yield this.getValue();
24510
24712
  this.disable();
@@ -24512,7 +24714,9 @@ class AppEntityEditor extends AppTabEditor {
24512
24714
  const updatedData = yield this.dataService.save(data, opts);
24513
24715
  yield this.onEntitySaved(updatedData);
24514
24716
  // Update the view (e.g metadata)
24515
- yield this.updateView(updatedData, opts);
24717
+ yield this.updateView(updatedData, Object.assign({
24718
+ // Open next tab only if user has not changed it
24719
+ openTabIndex: nextTabIndex > this._selectedTabIndex ? nextTabIndex : undefined }, opts));
24516
24720
  // Subscribe to remote changes
24517
24721
  if (!this._listenChangesSubscription)
24518
24722
  this.startListenRemoteChanges();
@@ -24732,7 +24936,8 @@ class AppEntityEditor extends AppTabEditor {
24732
24936
  // If NOT data, then add to page history
24733
24937
  if (!this.isNewData) {
24734
24938
  const page = yield this.computePageHistory(title);
24735
- return this.addToPageHistory(page);
24939
+ if (page)
24940
+ return this.addToPageHistory(page);
24736
24941
  }
24737
24942
  });
24738
24943
  }
@@ -24745,6 +24950,8 @@ class AppEntityEditor extends AppTabEditor {
24745
24950
  }
24746
24951
  computePageHistory(title) {
24747
24952
  return __awaiter(this, void 0, void 0, function* () {
24953
+ if (this.debug)
24954
+ console.debug('[entity-editor] Computing page history, using url: ' + this.router.url);
24748
24955
  return {
24749
24956
  title,
24750
24957
  path: this.router.url
@@ -24775,7 +24982,8 @@ class AppEntityEditor extends AppTabEditor {
24775
24982
  fetchPolicy: 'no-cache' }, opts));
24776
24983
  }
24777
24984
  markForCheck() {
24778
- this.cd.markForCheck();
24985
+ var _a;
24986
+ (_a = this.cd) === null || _a === void 0 ? void 0 : _a.markForCheck();
24779
24987
  }
24780
24988
  /* -- private functions -- */
24781
24989
  /**
@@ -24860,7 +25068,7 @@ AppInMemoryTable.ctorParameters = () => [
24860
25068
  { type: Function },
24861
25069
  { type: InMemoryEntitiesService },
24862
25070
  { type: ValidatorService },
24863
- { type: undefined },
25071
+ { type: AppTableDataSourceOptions },
24864
25072
  { type: undefined }
24865
25073
  ];
24866
25074
  AppInMemoryTable.propDecorators = {
@@ -25884,7 +26092,7 @@ class PersonService extends BaseEntityService {
25884
26092
  }
25885
26093
  suggest(value, filter, sortBy, sortDirection) {
25886
26094
  return __awaiter(this, void 0, void 0, function* () {
25887
- if (ReferentialUtils.isNotEmpty(value))
26095
+ if (EntityUtils.isNotEmpty(value, 'id'))
25888
26096
  return { data: [value] };
25889
26097
  value = (typeof value === 'string' && value !== '*') && value || undefined;
25890
26098
  sortBy = sortBy || filter.searchAttribute || (filter.searchAttributes && filter.searchAttributes[0]);
@@ -26036,11 +26244,7 @@ class UsersPage extends AppTable {
26036
26244
  switch (authTokenType) {
26037
26245
  case "basic":
26038
26246
  this.setShowColumn('pubkey', false, { emitEvent: false });
26039
- this.setShowColumn('username', true, { emitEvent: false });
26040
- this.setShowColumn('usernameExtranet', true, { emitEvent: false });
26041
- break;
26042
26247
  case "basic-and-token":
26043
- this.setShowColumn('pubkey', true, { emitEvent: false });
26044
26248
  this.setShowColumn('username', true, { emitEvent: false });
26045
26249
  this.setShowColumn('usernameExtranet', true, { emitEvent: false });
26046
26250
  break;
@@ -26252,6 +26456,7 @@ class AutocompleteTestPage {
26252
26456
  missingEntity: [null, SharedValidators.entity],
26253
26457
  disableEntity: [null, SharedValidators.entity],
26254
26458
  entities: [null, SharedFormArrayValidators.requiredArrayMinLength(1)],
26459
+ farEntity: [null, SharedValidators.entity]
26255
26460
  });
26256
26461
  this.form.get('disableEntity').disable();
26257
26462
  }
@@ -26260,8 +26465,7 @@ class AutocompleteTestPage {
26260
26465
  this.autocompleteFields.add('entity-suggestFn', {
26261
26466
  suggestFn: (value, filter) => this.suggest(value, filter),
26262
26467
  attributes: ['label', 'name'],
26263
- displayWith: this.entityToString,
26264
- showAllOnFocus: false
26468
+ displayWith: this.entityToString
26265
26469
  });
26266
26470
  // From items array
26267
26471
  this.autocompleteFields.add('entity-items', {
@@ -26289,22 +26493,34 @@ class AutocompleteTestPage {
26289
26493
  attributes: ['label', 'name', 'description', 'comments'],
26290
26494
  displayWith: this.entityToString
26291
26495
  });
26292
- // For mutliple value
26496
+ // Far entity, in the list
26497
+ this.autocompleteFields.add('entity-far', {
26498
+ suggestFn: (value, filter) => this.suggest(value, filter),
26499
+ attributes: ['label', 'name'],
26500
+ displayWith: this.entityToString
26501
+ });
26502
+ // For multiple value
26293
26503
  this.autocompleteFields.add('entities-items-filter', {
26294
26504
  service: {
26295
26505
  suggest: (value, filter) => this.suggest(value, filter)
26296
26506
  },
26297
26507
  attributes: ['label', 'name'],
26298
26508
  multiple: true
26299
- //displayWith: this.entitiesToString
26300
26509
  });
26301
- this.loadData();
26510
+ }
26511
+ ngAfterViewInit() {
26512
+ setTimeout(() => {
26513
+ this.farEntityField.suggestLengthThreshold = 2;
26514
+ this.farEntityField.reloadItems();
26515
+ }, 2000);
26516
+ setTimeout(() => this.loadData(), 1000);
26302
26517
  }
26303
26518
  // Load the form with data
26304
26519
  loadData() {
26305
26520
  return __awaiter(this, void 0, void 0, function* () {
26306
26521
  const data = {
26307
26522
  entity: deepCopy(FAKE_ENTITIES)[0],
26523
+ farEntity: deepCopy(FAKE_ENTITIES)[2],
26308
26524
  // This item is NOT in the items list => i should be displayed anyway
26309
26525
  missingEntity: {
26310
26526
  id: -1, label: '??', name: 'Missing item'
@@ -26341,7 +26557,7 @@ class AutocompleteTestPage {
26341
26557
  total,
26342
26558
  data: res.data,
26343
26559
  fetchMore: (variables) => __awaiter(this, void 0, void 0, function* () {
26344
- yield sleep(1000);
26560
+ yield sleep(1000); // Simulate access to server
26345
26561
  return this.suggest(value, Object.assign(Object.assign({}, filter), variables));
26346
26562
  })
26347
26563
  };
@@ -26384,12 +26600,15 @@ class AutocompleteTestPage {
26384
26600
  AutocompleteTestPage.decorators = [
26385
26601
  { type: Component, args: [{
26386
26602
  selector: 'app-autocomplete-test',
26387
- template: "<ion-header>\n <ion-toolbar color=\"primary\">\n\n <ion-buttons slot=\"start\">\n <ion-back-button></ion-back-button>\n </ion-buttons>\n\n <ion-title>Autocomplete field test page</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content>\n\n <!-- Tab nav - mobile/desktop mode -->\n <nav mat-tab-nav-bar>\n <a mat-tab-link\n [active]=\"mode==='mobile'\"\n (click)=\"toggleMode('mobile')\">\n <mat-label>Mobile</mat-label>\n </a>\n <a mat-tab-link [active]=\"mode==='desktop'\"\n (click)=\"toggleMode('desktop')\">\n <mat-label>Desktop</mat-label>\n </a>\n <a mat-tab-link [active]=\"mode==='memory'\"\n (click)=\"toggleMode('memory')\">\n <mat-label>Memory leak debug</mat-label>\n </a>\n <a mat-tab-link\n [active]=\"mode==='temp'\"\n (click)=\"toggleMode('temp')\">\n <mat-label>Temporary</mat-label>\n </a>\n </nav>\n\n <form class=\"form-container\" [formGroup]=\"form\" (ngSubmit)=\"doSubmit($event)\">\n\n <!--<ion-grid *ngIf=\"mode === 'temp'\">\n <ion-row>\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Items is an Observable\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>items: Observable&lt;any[]&gt;</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [logPrefix]=\"'[combo-desktop-2]'\"\n [compareWith]=\"compareWithFn\"\n [mobile]=\"false\"\n ></mat-autocomplete-field>\n </ion-card-content>\n\n </ion-card>\n </ion-col>\n </ion-row>\n\n\n </ion-grid>-->\n\n\n <ion-grid *ngIf=\"mode === 'memory'\">\n\n <!-- debugging memory leak -->\n <ion-row>\n <ion-col>\n <ion-text><h4>Debug memory leak</h4></ion-text>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col size=\"2\">\n <mat-form-field floatLabel=\"always\">\n <input matInput type=\"text\" hidden placeholder=\"Items type\">\n <mat-select (selectionChange)=\"memoryAutocompleteFieldName=$event.value\" [value]=\"memoryAutocompleteFieldName\">\n <mat-option value=\"entity-$items\">Observable</mat-option>\n <mat-option value=\"entity-suggestFn\">Suggest function</mat-option>\n </mat-select>\n </mat-form-field>\n </ion-col>\n <ion-col size=\"1\" class=\"ion-no-padding\">\n <mat-form-field floatLabel=\"always\">\n <input matInput type=\"text\" hidden placeholder=\"Mobile ?\">\n <mat-checkbox (change)=\"memoryMobile=$event.checked\" [checked]=\"memoryMobile\">\n </mat-checkbox>\n </mat-form-field>\n </ion-col>\n\n <ion-col size=\"2\">\n <ion-button *ngIf=\"!memoryTimer\" (click)=\"startMemoryTimer()\" color=\"tertiary\">Start</ion-button>\n <ion-button *ngIf=\"memoryTimer\" (click)=\"stopMemoryTimer()\" color=\"tertiary\">Stop</ion-button>\n </ion-col>\n </ion-row>\n\n <ion-row>\n <ion-col>\n <mat-autocomplete-field formControlName=\"entity\"\n *ngIf=\"!memoryHide && memoryAutocompleteFieldName\"\n [config]=\"autocompleteFields.get(memoryAutocompleteFieldName)\"\n [mobile]=\"memoryMobile\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-memory-leak]'\">\n </mat-autocomplete-field>\n </ion-col>\n </ion-row>\n\n </ion-grid>\n\n <!-- Mobile mode -->\n <ion-grid *ngIf=\"mode === 'mobile'\">\n\n <ion-row>\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Suggest() function\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>mobile: true, suggestFn: (searchText, filter) =&gt; any[]</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-suggestFn')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-1]'\"\n [required]=\"true\"\n placeholder=\"Suggest field\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Items is an Observable\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>mobile: true, items: Observable&lt;any[]&gt;</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-observable]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Control value if missing in items\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>mobile: true, items: Observable&lt;any[]&gt;</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"missingEntity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [clearable]=\"true\"\n [logPrefix]=\"'[combo-mobile-2]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n search with a suggestLengthThreshold\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>suggest: (value, filter) => LoadResult&lt;any&gt;\nsuggestLengthThreshold: '3'</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-filter')\"\n [placeholder]=\"'Entity'\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n suggestLengthThreshold=\"3\"\n [logPrefix]=\"'[combo-mobile-suggestLengthThreshold]'\"\n required\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Multiple value\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>multiple: true</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entities\"\n [config]=\"autocompleteFields.get('entities-items-filter')\"\n [placeholder]=\"'Multiple'\"\n mobile=\"true\"\n multiple=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-multiple]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Disabled control\n </ion-label>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"disableEntity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-2]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n </ion-row>\n\n <ion-row>\n <ion-col size=\"9\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Change filter\n </ion-label>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre></pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col>\n <ion-button (click)=\"updateFilter(filterChangeField, 'entity-items-filter')\">Filter on: {{autocompleteFields.get('entity-items-filter').filter?.searchAttribute }}</ion-button>\n </ion-col>\n <ion-col>\n <mat-autocomplete-field formControlName=\"entity\" #filterChangeField\n [config]=\"autocompleteFields.get('entity-items-filter')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-filter]'\"\n ></mat-autocomplete-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col size=\"3\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Large combo\n </ion-label>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>class=\"min-width-large\"</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-large')\"\n [mobile]=\"true\"\n class=\"min-width-large\"\n panelWidth=\"400px\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-large]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col size=\"6\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Readonly toggle\n </ion-text>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <ion-grid>\n <ion-row>\n <ion-col size=\"3\">\n <mat-checkbox (change)=\"readonlyField.readonly=$event.checked\" [checked]=\"readonlyField.readonly\">\n Readonly ?\n </mat-checkbox>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col>\n <mat-autocomplete-field #readonlyField formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items')\"\n [placeholder]=\"'Entity'\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-readonly]'\"\n [readonly]=\"true\"\n [clearable]=\"!readonlyField.readonly\">\n </mat-autocomplete-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Fullscreen\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>class: 'mat-autocomplete-panel-full-size'</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"true\"\n [class]=\"'mat-autocomplete-panel-full-size'\"\n [matAutocompletePosition]=\"'above'\"\n [logPrefix]=\"'[combo-mobile-full-size]'\"\n [debug]=\"true\"\n [showAllOnFocus]=\"true\"\n [showPanelOnFocus]=\"true\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n <!-- Desktop mode -->\n <ion-grid *ngIf=\"mode === 'desktop'\">\n <ion-row>\n <ion-col>\n <ion-text><h4>Desktop mode</h4></ion-text>\n </ion-col>\n </ion-row>\n <ion-row>\n\n\n <ion-col size=\"6\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n items from suggest function\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>suggest: (value, filter) => LoadResult&lt;any&gt;</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-filter')\"\n [placeholder]=\"'Entity'\"\n [mobile]=\"false\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-suggest]'\">\n </mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col size=\"6\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n search with a suggestLengthThreshold\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>suggest: (value, filter) => LoadResult&lt;any&gt;\nsuggestLengthThreshold: '3'</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-filter')\"\n [placeholder]=\"'Entity'\"\n [mobile]=\"false\"\n [equals]=\"equals\"\n suggestLengthThreshold=\"3\"\n [logPrefix]=\"'[combo-desktop-suggestLengthThreshold]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col size=\"6\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Items is an array\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>items: [], value: {{stringify(form.controls.entity.value)}}</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items')\"\n [logPrefix]=\"'[combo-desktop-array-1]'\"\n [equals]=\"equals\"\n [mobile]=\"false\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Items is an Observable\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>items: Observable&lt;any[]&gt;</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [logPrefix]=\"'[combo-desktop-2]'\"\n [equals]=\"equals\"\n [mobile]=\"false\"\n ></mat-autocomplete-field>\n </ion-card-content>\n\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Multiple value\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>multiple: true</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entities\"\n [config]=\"autocompleteFields.get('entities-items-filter')\"\n [placeholder]=\"'Multiple'\"\n mobile=\"false\"\n multiple=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-multiple]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Control value if missing in items\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>items: Observable&lt;any[]&gt;</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"missingEntity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-missing]'\"\n [mobile]=\"false\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Full size panel\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>class: 'mat-autocomplete-panel-full-size'</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"false\"\n [class]=\"'mat-autocomplete-panel-full-size'\"\n [matAutocompletePosition]=\"'above'\"\n [logPrefix]=\"'[combo-desktop-full-size]'\"\n [debug]=\"true\"\n [showAllOnFocus]=\"true\"\n [showPanelOnFocus]=\"true\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Disabled control\n </ion-label>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"disableEntity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"false\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-disable]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Readonly control\n </ion-label>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"false\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-readonly]'\"\n [readonly]=\"true\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n\n <ion-row>\n <ion-col size=\"9\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Full size combo\n </ion-label>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>class=\"mat-autocomplete-panel-full-size\"</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-large')\"\n [mobile]=\"false\"\n class=\"mat-autocomplete-panel-full-size\"\n panelWidth=\"100vw\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-full-size]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col size=\"3\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Large combo\n </ion-label>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>panelWidth: string</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-large')\"\n [mobile]=\"false\"\n panelWidth=\"400px\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-large]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col size=\"6\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Readonly toggle\n </ion-text>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <ion-grid>\n <ion-row>\n <ion-col size=\"3\">\n <mat-checkbox (change)=\"readonlyField.readonly=$event.checked\" [checked]=\"readonlyField.readonly\">\n Readonly ?\n </mat-checkbox>\n </ion-col>\n <ion-col size=\"3\">\n <mat-checkbox #showIcons [checked]=\"false\">\n Icons ?\n </mat-checkbox>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col>\n <mat-autocomplete-field #readonlyField formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-filter')\"\n [placeholder]=\"'Entity'\"\n [mobile]=\"false\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-readonly]'\"\n [readonly]=\"true\"\n [clearable]=\"!readonlyField.readonly\">\n <mat-icon matPrefix *ngIf=\"showIcons.checked\">keyboard_arrow_right</mat-icon>\n <mat-icon matSuffix *ngIf=\"showIcons.checked\">keyboard_arrow_left</mat-icon>\n </mat-autocomplete-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n\n </ion-grid>\n\n </form>\n\n</ion-content>\n"
26603
+ template: "<ion-header>\n <ion-toolbar color=\"primary\">\n\n <ion-buttons slot=\"start\">\n <ion-back-button></ion-back-button>\n </ion-buttons>\n\n <ion-title>Autocomplete field test page</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content>\n\n <!-- Tab nav - mobile/desktop mode -->\n <nav mat-tab-nav-bar>\n <a mat-tab-link\n [active]=\"mode==='mobile'\"\n (click)=\"toggleMode('mobile')\">\n <mat-label>Mobile</mat-label>\n </a>\n <a mat-tab-link [active]=\"mode==='desktop'\"\n (click)=\"toggleMode('desktop')\">\n <mat-label>Desktop</mat-label>\n </a>\n <a mat-tab-link [active]=\"mode==='memory'\"\n (click)=\"toggleMode('memory')\">\n <mat-label>Memory leak debug</mat-label>\n </a>\n <a mat-tab-link\n [active]=\"mode==='temp'\"\n (click)=\"toggleMode('temp')\">\n <mat-label>Temporary</mat-label>\n </a>\n </nav>\n\n <form class=\"form-container\" [formGroup]=\"form\" (ngSubmit)=\"doSubmit($event)\">\n\n <!--<ion-grid *ngIf=\"mode === 'temp'\">\n <ion-row>\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Items is an Observable\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>items: Observable&lt;any[]&gt;</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [logPrefix]=\"'[combo-desktop-2]'\"\n [compareWith]=\"compareWithFn\"\n [mobile]=\"false\"\n ></mat-autocomplete-field>\n </ion-card-content>\n\n </ion-card>\n </ion-col>\n </ion-row>\n\n\n </ion-grid>-->\n\n\n <ion-grid *ngIf=\"mode === 'memory'\">\n\n <!-- debugging memory leak -->\n <ion-row>\n <ion-col>\n <ion-text><h4>Debug memory leak</h4></ion-text>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col size=\"2\">\n <mat-form-field floatLabel=\"always\">\n <input matInput type=\"text\" hidden placeholder=\"Items type\">\n <mat-select (selectionChange)=\"memoryAutocompleteFieldName=$event.value\" [value]=\"memoryAutocompleteFieldName\">\n <mat-option value=\"entity-$items\">Observable</mat-option>\n <mat-option value=\"entity-suggestFn\">Suggest function</mat-option>\n </mat-select>\n </mat-form-field>\n </ion-col>\n <ion-col size=\"1\" class=\"ion-no-padding\">\n <mat-form-field floatLabel=\"always\">\n <input matInput type=\"text\" hidden placeholder=\"Mobile ?\">\n <mat-checkbox (change)=\"memoryMobile=$event.checked\" [checked]=\"memoryMobile\">\n </mat-checkbox>\n </mat-form-field>\n </ion-col>\n\n <ion-col size=\"2\">\n <ion-button *ngIf=\"!memoryTimer\" (click)=\"startMemoryTimer()\" color=\"tertiary\">Start</ion-button>\n <ion-button *ngIf=\"memoryTimer\" (click)=\"stopMemoryTimer()\" color=\"tertiary\">Stop</ion-button>\n </ion-col>\n </ion-row>\n\n <ion-row>\n <ion-col>\n <mat-autocomplete-field formControlName=\"entity\"\n *ngIf=\"!memoryHide && memoryAutocompleteFieldName\"\n [config]=\"autocompleteFields.get(memoryAutocompleteFieldName)\"\n [mobile]=\"memoryMobile\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-memory-leak]'\">\n </mat-autocomplete-field>\n </ion-col>\n </ion-row>\n\n </ion-grid>\n\n <!-- Mobile mode -->\n <ion-grid *ngIf=\"mode === 'mobile'\">\n\n <ion-row>\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Entity far in the list (not in first page)\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre style=\"white-space: normal !important;\">value: {{stringify(form.controls.farEntity.value)}}</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"farEntity\" #farEntityField\n [config]=\"autocompleteFields.get('entity-far')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-far]'\"\n [debug]=\"true\"\n [required]=\"true\"\n [clearable]=\"true\"\n placeholder=\"Far entity (in the list)\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Suggest() function\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>mobile: true, suggestFn: (searchText, filter) =&gt; any[]</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-suggestFn')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-1]'\"\n [required]=\"true\"\n placeholder=\"Suggest field\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Items is an Observable\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>mobile: true, items: Observable&lt;any[]&gt;</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-observable]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Control value if missing in items\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>mobile: true, items: Observable&lt;any[]&gt;</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"missingEntity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [clearable]=\"true\"\n [logPrefix]=\"'[combo-mobile-2]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n search with a suggestLengthThreshold\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>suggest: (value, filter) => LoadResult&lt;any&gt;\nsuggestLengthThreshold: '3'</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-filter')\"\n [placeholder]=\"'Entity'\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n suggestLengthThreshold=\"3\"\n [logPrefix]=\"'[combo-mobile-suggestLengthThreshold]'\"\n required\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Multiple value\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>multiple: true</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entities\"\n [config]=\"autocompleteFields.get('entities-items-filter')\"\n [placeholder]=\"'Multiple'\"\n mobile=\"true\"\n multiple=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-multiple]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Disabled control\n </ion-label>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"disableEntity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-2]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n </ion-row>\n\n <ion-row>\n <ion-col size=\"9\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Change filter\n </ion-label>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre></pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col>\n <ion-button (click)=\"updateFilter(filterChangeField, 'entity-items-filter')\">Filter on: {{autocompleteFields.get('entity-items-filter').filter?.searchAttribute }}</ion-button>\n </ion-col>\n <ion-col>\n <mat-autocomplete-field formControlName=\"entity\" #filterChangeField\n [config]=\"autocompleteFields.get('entity-items-filter')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-filter]'\"\n ></mat-autocomplete-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col size=\"3\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Large combo\n </ion-label>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>class=\"min-width-large\"</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-large')\"\n [mobile]=\"true\"\n class=\"min-width-large\"\n panelWidth=\"400px\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-large]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col size=\"6\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Readonly toggle\n </ion-text>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <ion-grid>\n <ion-row>\n <ion-col size=\"3\">\n <mat-checkbox (change)=\"readonlyField.readonly=$event.checked\" [checked]=\"readonlyField.readonly\">\n Readonly ?\n </mat-checkbox>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col>\n <mat-autocomplete-field #readonlyField formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items')\"\n [placeholder]=\"'Entity'\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-readonly]'\"\n [readonly]=\"true\"\n [clearable]=\"!readonlyField.readonly\">\n </mat-autocomplete-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Fullscreen\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>class: 'mat-autocomplete-panel-full-size'</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"true\"\n [class]=\"'mat-autocomplete-panel-full-size'\"\n [matAutocompletePosition]=\"'above'\"\n [logPrefix]=\"'[combo-mobile-full-size]'\"\n [debug]=\"true\"\n [showAllOnFocus]=\"true\"\n [showPanelOnFocus]=\"true\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n <!-- Desktop mode -->\n <ion-grid *ngIf=\"mode === 'desktop'\">\n <ion-row>\n <ion-col>\n <ion-text><h4>Desktop mode</h4></ion-text>\n </ion-col>\n </ion-row>\n <ion-row>\n\n\n <ion-col size=\"6\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n items from suggest function\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>suggest: (value, filter) => LoadResult&lt;any&gt;</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-filter')\"\n [placeholder]=\"'Entity'\"\n [mobile]=\"false\"\n [equals]=\"equals\"\n [clearable]=\"true\"\n [logPrefix]=\"'[combo-desktop-suggest]'\">\n </mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col size=\"6\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n search with a suggestLengthThreshold\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>suggest: (value, filter) => LoadResult&lt;any&gt;\nsuggestLengthThreshold: '3'</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-filter')\"\n [placeholder]=\"'Entity'\"\n [mobile]=\"false\"\n [equals]=\"equals\"\n suggestLengthThreshold=\"3\"\n [logPrefix]=\"'[combo-desktop-suggestLengthThreshold]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col size=\"6\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Items is an array\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>items: [], value: {{stringify(form.controls.entity.value)}}</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items')\"\n [logPrefix]=\"'[combo-desktop-array-1]'\"\n [equals]=\"equals\"\n [mobile]=\"false\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Items is an Observable\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>items: Observable&lt;any[]&gt;</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [logPrefix]=\"'[combo-desktop-2]'\"\n [equals]=\"equals\"\n [mobile]=\"false\"\n ></mat-autocomplete-field>\n </ion-card-content>\n\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Multiple value\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>multiple: true</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entities\"\n [config]=\"autocompleteFields.get('entities-items-filter')\"\n [placeholder]=\"'Multiple'\"\n mobile=\"false\"\n multiple=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-multiple]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Control value if missing in items\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>items: Observable&lt;any[]&gt;</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"missingEntity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-missing]'\"\n [mobile]=\"false\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Full size panel\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>class: 'mat-autocomplete-panel-full-size'</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"false\"\n [class]=\"'mat-autocomplete-panel-full-size'\"\n [matAutocompletePosition]=\"'above'\"\n [logPrefix]=\"'[combo-desktop-full-size]'\"\n [debug]=\"true\"\n [showAllOnFocus]=\"true\"\n [showPanelOnFocus]=\"true\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Disabled control\n </ion-label>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"disableEntity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"false\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-disable]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Readonly control\n </ion-label>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"false\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-readonly]'\"\n [readonly]=\"true\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n\n <ion-row>\n <ion-col size=\"9\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Full size combo\n </ion-label>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>class=\"mat-autocomplete-panel-full-size\"</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-large')\"\n [mobile]=\"false\"\n class=\"mat-autocomplete-panel-full-size\"\n panelWidth=\"100vw\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-full-size]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col size=\"3\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Large combo\n </ion-label>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>panelWidth: string</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-large')\"\n [mobile]=\"false\"\n panelWidth=\"400px\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-large]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col size=\"6\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Readonly toggle\n </ion-text>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <ion-grid>\n <ion-row>\n <ion-col size=\"3\">\n <mat-checkbox (change)=\"readonlyField.readonly=$event.checked\" [checked]=\"readonlyField.readonly\">\n Readonly ?\n </mat-checkbox>\n </ion-col>\n <ion-col size=\"3\">\n <mat-checkbox #showIcons [checked]=\"false\">\n Icons ?\n </mat-checkbox>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col>\n <mat-autocomplete-field #readonlyField formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-filter')\"\n [placeholder]=\"'Entity'\"\n [mobile]=\"false\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-readonly]'\"\n [readonly]=\"true\"\n [clearable]=\"!readonlyField.readonly\">\n <mat-icon matPrefix *ngIf=\"showIcons.checked\">keyboard_arrow_right</mat-icon>\n <mat-icon matSuffix *ngIf=\"showIcons.checked\">keyboard_arrow_left</mat-icon>\n </mat-autocomplete-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n\n </ion-grid>\n\n </form>\n\n</ion-content>\n"
26388
26604
  },] }
26389
26605
  ];
26390
26606
  AutocompleteTestPage.ctorParameters = () => [
26391
26607
  { type: FormBuilder }
26392
26608
  ];
26609
+ AutocompleteTestPage.propDecorators = {
26610
+ farEntityField: [{ type: ViewChild, args: ['farEntityField',] }]
26611
+ };
26393
26612
 
26394
26613
  class LatLongTestPage {
26395
26614
  constructor(formBuilder) {
@@ -26450,20 +26669,20 @@ class LatLongTestPage {
26450
26669
  LatLongTestPage.decorators = [
26451
26670
  { type: Component, args: [{
26452
26671
  selector: 'app-latlong-test',
26453
- template: "<ion-header>\n <ion-toolbar color=\"primary\">\n\n <ion-buttons slot=\"start\">\n <ion-back-button></ion-back-button>\n </ion-buttons>\n\n <ion-title>Lat/Long field test page</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n\n <form class=\"form-container\" [formGroup]=\"form\" (ngSubmit)=\"doSubmit($event)\">\n\n <!-- Empty value -->\n <form *ngIf=\"form.get('empty') as formGroup\" [formGroup]=\"formGroup\">\n <ion-grid>\n <ion-row><ion-col><ion-text><h4>Empty, with defaults</h4></ion-text></ion-col></ion-row>\n\n <ion-row >\n\n <!-- DD Default sign -->\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Format DD, defaultSign '-'\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>{{stringify(formGroup.value)}}</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col>\n <mat-latlong-field formControlName=\"latitude\"\n type=\"latitude\"\n latLongPattern=\"DD\"\n defaultSign=\"-\"\n placeholder=\"Latitude\">\n <mat-icon matPrefix>room</mat-icon>\n <button matSuffix mat-icon-button (click)=\"geoPosition($event)\">\n <mat-icon>gps_fixed</mat-icon>\n </button>\n </mat-latlong-field>\n </ion-col>\n <ion-col>\n <mat-latlong-field formControlName=\"longitude\"\n type=\"longitude\"\n latLongPattern=\"DD\"\n defaultSign=\"-\"\n placeholder=\"Longitude\">\n <mat-icon matPrefix>room</mat-icon>\n <button type=\"button\" mat-icon-button matSuffix\n (click)=\"geoPosition($event)\">\n <mat-icon>gps_fixed</mat-icon>\n </button>\n </mat-latlong-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <!-- DDMM Default sign -->\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Format DDMM, defaultSign '+'\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>{{stringify(formGroup.value)}}</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col>\n <mat-latlong-field formControlName=\"latitude\"\n type=\"latitude\"\n latLongPattern=\"DDMM\"\n defaultSign=\"+\"\n placeholder=\"Latitude\">\n\n <mat-icon matPrefix >room</mat-icon>\n <button type=\"button\" mat-icon-button matSuffix\n (click)=\"geoPosition($event)\">\n <mat-icon>gps_fixed</mat-icon>\n </button>\n </mat-latlong-field>\n </ion-col>\n <ion-col>\n <mat-latlong-field formControlName=\"longitude\"\n type=\"longitude\"\n latLongPattern=\"DDMM\"\n defaultSign=\"+\"\n placeholder=\"Longitude\">\n <mat-icon matPrefix>room</mat-icon>\n <button type=\"button\" mat-icon-button matSuffix\n (click)=\"geoPosition($event)\">\n <mat-icon>gps_fixed</mat-icon>\n </button>\n </mat-latlong-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <!-- Mobile + DDMM -->\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Mobile=true\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>{{stringify(formGroup.value)}}</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col>\n <mat-latlong-field formControlName=\"latitude\"\n type=\"latitude\"\n latLongPattern=\"DDMM\"\n defaultSign=\"+\"\n placeholder=\"Latitude\"\n mobile=\"true\">\n </mat-latlong-field>\n </ion-col>\n <ion-col>\n <mat-latlong-field formControlName=\"longitude\"\n type=\"longitude\"\n latLongPattern=\"DDMM\"\n defaultSign=\"+\"\n placeholder=\"Longitude\">\n </mat-latlong-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n </ion-grid>\n </form>\n\n <form *ngIf=\"form.get('enable') as formGroup\" [formGroup]=\"formGroup\">\n <ion-grid>\n <!-- With value -->\n <ion-row><ion-col><ion-text><h4>With value</h4></ion-text></ion-col></ion-row>\n\n <ion-row>\n\n <!-- DD -->\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Format DD\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>{{stringify(formGroup.value)}}</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col>\n <mat-latlong-field formControlName=\"latitude\"\n type=\"latitude\"\n latLongPattern=\"DD\"\n placeholder=\"Latitude\">\n </mat-latlong-field>\n </ion-col>\n <ion-col>\n <mat-latlong-field formControlName=\"longitude\"\n type=\"longitude\"\n latLongPattern=\"DD\"\n placeholder=\"Longitude\">\n </mat-latlong-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <!-- DDMMSS -->\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Format DDMM\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>{{stringify(formGroup.value)}}</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col>\n <mat-latlong-field formControlName=\"latitude\"\n type=\"latitude\"\n latLongPattern=\"DDMM\"\n placeholder=\"Latitude\">\n </mat-latlong-field>\n </ion-col>\n <ion-col>\n <mat-latlong-field formControlName=\"longitude\"\n type=\"longitude\"\n latLongPattern=\"DDMM\"\n placeholder=\"Longitude\">\n </mat-latlong-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n\n <!-- DDMMSS -->\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Format DDMMSS\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>{{stringify(formGroup.value)}}</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col>\n <mat-latlong-field formControlName=\"latitude\"\n type=\"latitude\"\n latLongPattern=\"DDMMSS\"\n placeholder=\"Latitude\">\n </mat-latlong-field>\n </ion-col>\n <ion-col>\n <mat-latlong-field formControlName=\"longitude\"\n type=\"longitude\"\n latLongPattern=\"DDMMSS\"\n placeholder=\"Longitude\">\n </mat-latlong-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n </ion-grid>\n </form>\n\n <!-- Disable -->\n <form *ngIf=\"form.get('disable') as formGroup\" [formGroup]=\"formGroup\">\n <ion-grid>\n <ion-row><ion-col><ion-text><h4>Disabled control</h4></ion-text></ion-col></ion-row>\n <ion-row>\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Disabled control\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>{{stringify(formGroup.value)}}</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col>\n <mat-latlong-field formControlName=\"latitude\"\n type=\"latitude\"\n latLongPattern=\"DDMM\"\n placeholder=\"Latitude\">\n <mat-icon matPrefix>room</mat-icon>\n <mat-icon matSuffix>gps_fixed</mat-icon>\n </mat-latlong-field>\n </ion-col>\n <ion-col>\n <mat-latlong-field formControlName=\"longitude\"\n type=\"longitude\"\n latLongPattern=\"DDMM\"\n placeholder=\"Longitude\">\n <mat-icon matPrefix>room</mat-icon>\n <button type=\"button\" mat-icon-button matSuffix\n (click)=\"geoPosition($event)\">\n <mat-icon>gps_fixed</mat-icon>\n </button>\n </mat-latlong-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Readonly (control is enable)\n </ion-label>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n\n <mat-checkbox (change)=\"readonlyField.readonly=$event.checked\" [checked]=\"readonlyField.readonly\">\n </mat-checkbox>\n\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col>\n <mat-latlong-field #readonlyField [formControl]=\"form.get('enable.latitude')\"\n type=\"latitude\"\n latLongPattern=\"DDMM\"\n placeholder=\"Latitude\"\n [readonly]=\"true\">\n <mat-icon matPrefix>room</mat-icon>\n <mat-icon matSuffix>gps_fixed</mat-icon>\n </mat-latlong-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n </ion-grid>\n </form>\n\n </form>\n\n</ion-content>\n"
26672
+ template: "<ion-header>\n <ion-toolbar color=\"primary\">\n\n <ion-buttons slot=\"start\">\n <ion-back-button></ion-back-button>\n </ion-buttons>\n\n <ion-title>Lat/Long field test page</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n\n <form class=\"form-container\" [formGroup]=\"form\" (ngSubmit)=\"doSubmit($event)\">\n\n <!-- Empty value -->\n <form *ngIf=\"form.get('empty') as formGroup\" [formGroup]=\"formGroup\">\n <ion-grid>\n <ion-row><ion-col><ion-text><h4>Empty, with defaults</h4></ion-text></ion-col></ion-row>\n\n <ion-row >\n\n <!-- DD Default sign -->\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Format DD, defaultSign '-'\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>{{stringify(formGroup.value)}}</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col>\n <mat-latlong-field formControlName=\"latitude\"\n type=\"latitude\"\n latLongPattern=\"DD\"\n defaultSign=\"-\"\n placeholder=\"Latitude\">\n <mat-icon matPrefix>room</mat-icon>\n <button matSuffix mat-icon-button (click)=\"geoPosition($event)\">\n <mat-icon>gps_fixed</mat-icon>\n </button>\n </mat-latlong-field>\n </ion-col>\n <ion-col>\n <mat-latlong-field formControlName=\"longitude\"\n type=\"longitude\"\n latLongPattern=\"DD\"\n defaultSign=\"-\"\n placeholder=\"Longitude\">\n <mat-icon matPrefix>room</mat-icon>\n <button type=\"button\" mat-icon-button matSuffix\n (click)=\"geoPosition($event)\">\n <mat-icon>gps_fixed</mat-icon>\n </button>\n </mat-latlong-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <!-- DDMM Default sign -->\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Format DDMM, defaultSign '+'\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>{{stringify(formGroup.value)}}</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col>\n <mat-latlong-field formControlName=\"latitude\"\n type=\"latitude\"\n latLongPattern=\"DDMM\"\n defaultSign=\"+\"\n placeholder=\"Latitude\">\n\n <mat-icon matPrefix >room</mat-icon>\n <button type=\"button\" mat-icon-button matSuffix\n (click)=\"geoPosition($event)\">\n <mat-icon>gps_fixed</mat-icon>\n </button>\n </mat-latlong-field>\n </ion-col>\n <ion-col>\n <mat-latlong-field formControlName=\"longitude\"\n type=\"longitude\"\n latLongPattern=\"DDMM\"\n defaultSign=\"+\"\n placeholder=\"Longitude\">\n <mat-icon matPrefix>room</mat-icon>\n <button type=\"button\" mat-icon-button matSuffix\n (click)=\"geoPosition($event)\">\n <mat-icon>gps_fixed</mat-icon>\n </button>\n </mat-latlong-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n </ion-row>\n </ion-grid>\n </form>\n\n <form *ngIf=\"form.get('enable') as formGroup\" [formGroup]=\"formGroup\">\n <ion-grid>\n <!-- With value -->\n <ion-row><ion-col><ion-text><h4>With value</h4></ion-text></ion-col></ion-row>\n\n <ion-row>\n\n <!-- DD -->\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Format DD\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>{{stringify(formGroup.value)}}</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col>\n <mat-latlong-field formControlName=\"latitude\"\n type=\"latitude\"\n latLongPattern=\"DD\"\n placeholder=\"Latitude\">\n </mat-latlong-field>\n </ion-col>\n <ion-col>\n <mat-latlong-field formControlName=\"longitude\"\n type=\"longitude\"\n latLongPattern=\"DD\"\n placeholder=\"Longitude\">\n </mat-latlong-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <!-- DDMMSS -->\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Format DDMM\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>{{stringify(formGroup.value)}}</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col>\n <mat-latlong-field formControlName=\"latitude\"\n type=\"latitude\"\n latLongPattern=\"DDMM\"\n placeholder=\"Latitude\">\n </mat-latlong-field>\n </ion-col>\n <ion-col>\n <mat-latlong-field formControlName=\"longitude\"\n type=\"longitude\"\n latLongPattern=\"DDMM\"\n placeholder=\"Longitude\">\n </mat-latlong-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n\n <!-- DDMMSS -->\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Format DDMMSS\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>{{stringify(formGroup.value)}}</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col>\n <mat-latlong-field formControlName=\"latitude\"\n type=\"latitude\"\n latLongPattern=\"DDMMSS\"\n placeholder=\"Latitude\">\n </mat-latlong-field>\n </ion-col>\n <ion-col>\n <mat-latlong-field formControlName=\"longitude\"\n type=\"longitude\"\n latLongPattern=\"DDMMSS\"\n placeholder=\"Longitude\">\n </mat-latlong-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n </ion-grid>\n </form>\n\n <!-- Disable -->\n <form *ngIf=\"form.get('disable') as formGroup\" [formGroup]=\"formGroup\">\n <ion-grid>\n <ion-row><ion-col><ion-text><h4>Disabled control</h4></ion-text></ion-col></ion-row>\n <ion-row>\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Disabled control\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>{{stringify(formGroup.value)}}</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col>\n <mat-latlong-field formControlName=\"latitude\"\n type=\"latitude\"\n latLongPattern=\"DDMM\"\n placeholder=\"Latitude\">\n <mat-icon matPrefix>room</mat-icon>\n <mat-icon matSuffix>gps_fixed</mat-icon>\n </mat-latlong-field>\n </ion-col>\n <ion-col>\n <mat-latlong-field formControlName=\"longitude\"\n type=\"longitude\"\n latLongPattern=\"DDMM\"\n placeholder=\"Longitude\">\n <mat-icon matPrefix>room</mat-icon>\n <button type=\"button\" mat-icon-button matSuffix\n (click)=\"geoPosition($event)\">\n <mat-icon>gps_fixed</mat-icon>\n </button>\n </mat-latlong-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Readonly (control is enable)\n </ion-label>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n\n <mat-checkbox (change)=\"readonlyField.readonly=$event.checked\" [checked]=\"readonlyField.readonly\">\n </mat-checkbox>\n\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col>\n <mat-latlong-field #readonlyField [formControl]=\"form.get('enable.latitude')\"\n type=\"latitude\"\n latLongPattern=\"DDMM\"\n placeholder=\"Latitude\"\n [readonly]=\"true\">\n <mat-icon matPrefix>room</mat-icon>\n <mat-icon matSuffix>gps_fixed</mat-icon>\n </mat-latlong-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n </ion-grid>\n </form>\n\n </form>\n\n</ion-content>\n"
26454
26673
  },] }
26455
26674
  ];
26456
26675
  LatLongTestPage.ctorParameters = () => [
26457
26676
  { type: FormBuilder }
26458
26677
  ];
26459
26678
 
26460
- const moment$5 = momentImported;
26679
+ const moment$6 = momentImported;
26461
26680
  class SwipeTestPage {
26462
26681
  constructor(formBuilder, dateFormatPipe) {
26463
26682
  this.formBuilder = formBuilder;
26464
26683
  this.dateFormatPipe = dateFormatPipe;
26465
26684
  this.$dates = new BehaviorSubject(undefined);
26466
- this._today = moment$5().startOf('day');
26685
+ this._today = moment$6().startOf('day');
26467
26686
  this.form = formBuilder.group({
26468
26687
  empty: [null, Validators.required],
26469
26688
  date: [null, Validators.compose([Validators.required, SharedValidators.validDate])],
@@ -26476,7 +26695,7 @@ class SwipeTestPage {
26476
26695
  ngOnInit() {
26477
26696
  const dates = [];
26478
26697
  for (let d = 0; d < 7; d++) {
26479
- dates[d] = moment$5(this._today).add(d - 3, 'day');
26698
+ dates[d] = moment$6(this._today).add(d - 3, 'day');
26480
26699
  }
26481
26700
  this.$dates.next(dates);
26482
26701
  this.loadData();
@@ -26517,7 +26736,7 @@ SwipeTestPage.ctorParameters = () => [
26517
26736
  { type: DateFormatPipe }
26518
26737
  ];
26519
26738
 
26520
- const moment$6 = momentImported;
26739
+ const moment$7 = momentImported;
26521
26740
  class DateTimeTestPage {
26522
26741
  constructor(platform, formBuilder, cd) {
26523
26742
  this.platform = platform;
@@ -26529,7 +26748,8 @@ class DateTimeTestPage {
26529
26748
  this.memoryMobile = true;
26530
26749
  this.stringify = JSON.stringify;
26531
26750
  this.form = formBuilder.group({
26532
- empty: [null, Validators.required],
26751
+ empty: [null],
26752
+ emptyRequired: [null, Validators.required],
26533
26753
  enable: [null, Validators.required],
26534
26754
  disable: [null, Validators.required],
26535
26755
  readonly: [null]
@@ -26548,9 +26768,10 @@ class DateTimeTestPage {
26548
26768
  // Load the form with data
26549
26769
  loadData() {
26550
26770
  return __awaiter(this, void 0, void 0, function* () {
26551
- const now = moment$6();
26771
+ const now = moment$7();
26552
26772
  const data = {
26553
- empty: toDateISOString(now.clone().add(2, 'hours')),
26773
+ empty: null,
26774
+ emptyRequired: null,
26554
26775
  enable: toDateISOString(now),
26555
26776
  disable: now,
26556
26777
  readonly: now
@@ -26600,7 +26821,7 @@ class DateTimeTestPage {
26600
26821
  DateTimeTestPage.decorators = [
26601
26822
  { type: Component, args: [{
26602
26823
  selector: 'app-data-time-test',
26603
- template: "<ion-header>\n <ion-toolbar color=\"primary\">\n\n <ion-buttons slot=\"start\">\n <ion-back-button></ion-back-button>\n </ion-buttons>\n\n <ion-title>Date/Time field test page</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n\n <form class=\"form-container\" [formGroup]=\"form\" (ngSubmit)=\"doSubmit($event)\">\n\n <ion-grid>\n\n <!-- debugging memory leak -->\n <ion-row><ion-col><ion-text><h4>Debug memory leak</h4></ion-text></ion-col></ion-row>\n <ion-row>\n <ion-col size=\"2\">\n <ion-button *ngIf=\"!memoryTimer\" (click)=\"startMemoryTimer()\">Start timer</ion-button>\n <ion-button *ngIf=\"memoryTimer\" (click)=\"stopMemoryTimer()\">Stop timer</ion-button>\n </ion-col>\n <ion-col size=\"2\">\n <mat-form-field floatLabel=\"never\">\n <input matInput type=\"text\" hidden>\n <mat-checkbox (change)=\"memoryMobile=$event.checked\" [value]=\"memoryMobile\">\n Mobile ?\n </mat-checkbox>\n </mat-form-field>\n </ion-col>\n <ion-col>\n <mat-date-time-field formControlName=\"empty\"\n *ngIf=\"!memoryHide\"\n placeholder=\"Date/Time\"\n [required]=\"true\"\n [mobile]=\"memoryMobile\"\n [clearable]=\"true\">\n <ion-icon matPrefix name=\"calendar-outline\"></ion-icon>\n </mat-date-time-field>\n </ion-col>\n </ion-row>\n\n <!-- Mobile mode -->\n <ion-row><ion-col><ion-text><h4>Mobile mode</h4></ion-text></ion-col></ion-row>\n <ion-row>\n <ion-col>\n\n <!-- Empty value -->\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Empty value\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>{{stringify(form.controls.empty.value)}}</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-date-time-field formControlName=\"empty\"\n placeholder=\"Date/Time\"\n [required]=\"true\"\n [mobile]=\"true\"\n [clearable]=\"true\">\n <ion-icon matPrefix name=\"calendar-outline\"></ion-icon>\n </mat-date-time-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <!-- Enable -->\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n With value\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>{{stringify(form.controls.enable.value)}}</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-date-time-field formControlName=\"enable\"\n placeholder=\"Date/Time\"\n [required]=\"true\"\n [mobile]=\"true\"\n [clearable]=\"true\">\n <ion-icon matPrefix name=\"calendar-outline\"></ion-icon>\n </mat-date-time-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <!-- Disable -->\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Disable\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>{{stringify(form.controls.disable.value)}}</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-date-time-field formControlName=\"disable\"\n placeholder=\"Date/Time\"\n [required]=\"true\"\n [mobile]=\"true\">\n <ion-icon matPrefix name=\"calendar-outline\"></ion-icon>\n </mat-date-time-field>\n\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n <!-- debug console -->\n <ion-row>\n <!-- buttons -->\n <ion-col size=\"2\">\n <!-- submit form -->\n <ion-button (click)=\"doSubmit($event)\"\n fill=\"outline\">\n <ion-icon name=\"checkmark\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n\n <!-- clear log -->\n <ion-button (click)=\"clearLogPanel()\"\n fill=\"outline\">\n <ion-icon name=\"trash\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n </ion-col>\n <ion-col size=\"10\" *ngIf=\"showLogPanel\">\n <ion-text color=\"primary\">Log:<br/></ion-text>\n <div class=\"ion-padding-start\">\n <ion-text color=\"medium\">\n <small [innerHTML]=\"logContent\"></small>\n </ion-text>\n </div>\n </ion-col>\n </ion-row>\n\n <!-- Desktop mode -->\n <ion-row><ion-col><ion-text><h4>Desktop mode</h4></ion-text></ion-col></ion-row>\n <ion-row>\n <ion-col>\n\n <!-- Empty value -->\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Empty value\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>{{stringify(form.controls.empty.value)}}</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-date-time-field formControlName=\"empty\"\n placeholder=\"Date/Time\"\n [required]=\"true\"\n [mobile]=\"false\"\n [clearable]=\"true\">\n <ion-icon matPrefix name=\"calendar-outline\"></ion-icon>\n </mat-date-time-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <!-- Enable -->\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n With value\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>{{stringify(form.controls.enable.value)}}</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-date-time-field formControlName=\"enable\"\n placeholder=\"Date/Time\"\n [required]=\"true\"\n [mobile]=\"false\"\n [clearable]=\"true\">\n <ion-icon matPrefix name=\"calendar-outline\"></ion-icon>\n </mat-date-time-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <!-- Disable -->\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Disable\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>{{stringify(form.controls.disable.value)}}</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-date-time-field formControlName=\"disable\"\n placeholder=\"Date/Time\"\n [required]=\"true\"\n [mobile]=\"false\">\n <ion-icon matPrefix name=\"calendar-outline\"></ion-icon>\n </mat-date-time-field>\n\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <!-- Readonly -->\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Readonly toggle\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>{{stringify(form.controls.readonly.value)}}</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-checkbox (change)=\"readonlyField.readonly=$event.checked\" [checked]=\"readonlyField.readonly\">\n </mat-checkbox>\n\n <mat-date-time-field #readonlyField formControlName=\"readonly\"\n placeholder=\"Date/Time\"\n [readonly]=\"true\"\n [mobile]=\"false\"\n [clearable]=\"true\">\n <ion-icon matPrefix name=\"calendar-outline\"></ion-icon>\n </mat-date-time-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n </ion-grid>\n </form>\n\n</ion-content>\n"
26824
+ template: "<ion-header>\n <ion-toolbar color=\"primary\">\n\n <ion-buttons slot=\"start\">\n <ion-back-button></ion-back-button>\n </ion-buttons>\n\n <ion-title>Date/Time field test page</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n\n <form class=\"form-container\" [formGroup]=\"form\" (ngSubmit)=\"doSubmit($event)\">\n\n <ion-grid>\n\n <!-- debugging memory leak -->\n <ion-row><ion-col><ion-text><h4>Debug memory leak</h4></ion-text></ion-col></ion-row>\n <ion-row>\n <ion-col size=\"2\">\n <ion-button *ngIf=\"!memoryTimer\" (click)=\"startMemoryTimer()\">Start timer</ion-button>\n <ion-button *ngIf=\"memoryTimer\" (click)=\"stopMemoryTimer()\">Stop timer</ion-button>\n </ion-col>\n <ion-col size=\"2\">\n <mat-form-field floatLabel=\"never\">\n <input matInput type=\"text\" hidden>\n <mat-checkbox (change)=\"memoryMobile=$event.checked\" [value]=\"memoryMobile\">\n Mobile ?\n </mat-checkbox>\n </mat-form-field>\n </ion-col>\n <ion-col>\n <mat-date-time-field formControlName=\"emptyRequired\"\n *ngIf=\"!memoryHide\"\n placeholder=\"Date/Time\"\n [required]=\"true\"\n [mobile]=\"memoryMobile\"\n [clearable]=\"true\">\n <ion-icon matPrefix name=\"calendar-outline\"></ion-icon>\n </mat-date-time-field>\n </ion-col>\n </ion-row>\n\n <!-- Mobile mode -->\n <ion-row><ion-col><ion-text><h4>Mobile mode</h4></ion-text></ion-col></ion-row>\n <ion-row>\n <ion-col>\n\n <!-- Empty value -->\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Empty value\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>{{stringify(form.controls.empty.value)}}</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-date-time-field formControlName=\"empty\"\n placeholder=\"Date/Time\"\n [mobile]=\"true\"\n [clearable]=\"true\">\n <ion-icon matPrefix name=\"calendar-outline\"></ion-icon>\n </mat-date-time-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <!-- Empty value + required -->\n <ion-col>\n\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Empty value + required\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>{{stringify(form.controls.empty.value)}}</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-date-time-field formControlName=\"emptyRequired\"\n placeholder=\"Date/Time\"\n [required]=\"true\"\n [mobile]=\"true\"\n [clearable]=\"true\">\n <ion-icon matPrefix name=\"calendar-outline\"></ion-icon>\n </mat-date-time-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <!-- Enable -->\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n With value\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>{{stringify(form.controls.enable.value)}}</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-date-time-field formControlName=\"enable\"\n placeholder=\"Date/Time\"\n [required]=\"true\"\n [mobile]=\"true\"\n [clearable]=\"true\">\n <ion-icon matPrefix name=\"calendar-outline\"></ion-icon>\n </mat-date-time-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <!-- Disable -->\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Disable\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>{{stringify(form.controls.disable.value)}}</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-date-time-field formControlName=\"disable\"\n placeholder=\"Date/Time\"\n [required]=\"true\"\n [mobile]=\"true\">\n <ion-icon matPrefix name=\"calendar-outline\"></ion-icon>\n </mat-date-time-field>\n\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n <!-- debug console -->\n <ion-row>\n <!-- buttons -->\n <ion-col size=\"2\">\n <!-- submit form -->\n <ion-button (click)=\"doSubmit($event)\"\n fill=\"outline\">\n <ion-icon name=\"checkmark\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n\n <!-- clear log -->\n <ion-button (click)=\"clearLogPanel()\"\n fill=\"outline\">\n <ion-icon name=\"trash\" slot=\"icon-only\"></ion-icon>\n </ion-button>\n </ion-col>\n <ion-col size=\"10\" *ngIf=\"showLogPanel\">\n <ion-text color=\"primary\">Log:<br/></ion-text>\n <div class=\"ion-padding-start\">\n <ion-text color=\"medium\">\n <small [innerHTML]=\"logContent\"></small>\n </ion-text>\n </div>\n </ion-col>\n </ion-row>\n\n <!-- Desktop mode -->\n <ion-row><ion-col><ion-text><h4>Desktop mode</h4></ion-text></ion-col></ion-row>\n <ion-row>\n <ion-col>\n\n <!-- Empty value -->\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Empty value\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>{{stringify(form.controls.empty.value)}}</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-date-time-field formControlName=\"empty\"\n placeholder=\"Date/Time\"\n [mobile]=\"false\"\n [clearable]=\"true\">\n <ion-icon matPrefix name=\"calendar-outline\"></ion-icon>\n </mat-date-time-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <!-- Empty required value -->\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Empty value + required\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>{{stringify(form.controls.empty.value)}}</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-date-time-field formControlName=\"emptyRequired\"\n placeholder=\"Date/Time\"\n required\n [mobile]=\"false\"\n [clearable]=\"true\">\n <ion-icon matPrefix name=\"calendar-outline\"></ion-icon>\n </mat-date-time-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <!-- Enable -->\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n With value\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>{{stringify(form.controls.enable.value)}}</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-date-time-field formControlName=\"enable\"\n placeholder=\"Date/Time\"\n [required]=\"true\"\n [mobile]=\"false\"\n [clearable]=\"true\">\n <ion-icon matPrefix name=\"calendar-outline\"></ion-icon>\n </mat-date-time-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <!-- Disable -->\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Disable\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>{{stringify(form.controls.disable.value)}}</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-date-time-field formControlName=\"disable\"\n placeholder=\"Date/Time\"\n [required]=\"true\"\n [mobile]=\"false\">\n <ion-icon matPrefix name=\"calendar-outline\"></ion-icon>\n </mat-date-time-field>\n\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <!-- Readonly -->\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Readonly toggle\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>{{stringify(form.controls.readonly.value)}}</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-checkbox (change)=\"readonlyField.readonly=$event.checked\" [checked]=\"readonlyField.readonly\">\n </mat-checkbox>\n\n <mat-date-time-field #readonlyField formControlName=\"readonly\"\n placeholder=\"Date/Time\"\n [readonly]=\"true\"\n [mobile]=\"false\"\n [clearable]=\"true\">\n <ion-icon matPrefix name=\"calendar-outline\"></ion-icon>\n </mat-date-time-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n </ion-grid>\n </form>\n\n</ion-content>\n"
26604
26825
  },] }
26605
26826
  ];
26606
26827
  DateTimeTestPage.ctorParameters = () => [
@@ -26792,7 +27013,7 @@ NumpadTestPage.ctorParameters = () => [
26792
27013
  { type: FormBuilder }
26793
27014
  ];
26794
27015
 
26795
- const moment$7 = momentImported;
27016
+ const moment$8 = momentImported;
26796
27017
  class DateTestPage {
26797
27018
  constructor(formBuilder, cd) {
26798
27019
  this.formBuilder = formBuilder;
@@ -26825,7 +27046,7 @@ class DateTestPage {
26825
27046
  // Load the form with data
26826
27047
  loadData() {
26827
27048
  return __awaiter(this, void 0, void 0, function* () {
26828
- const now = moment$7();
27049
+ const now = moment$8();
26829
27050
  const nowAtMahe = now.clone().tz(this.timezone).startOf('day');
26830
27051
  const data = {
26831
27052
  empty: null,
@@ -27571,5 +27792,5 @@ CoreTestingModule.decorators = [
27571
27792
  * Generated bundle index. Do not edit.
27572
27793
  */
27573
27794
 
27574
- export { APP_ABOUT_DEVELOPERS, APP_ABOUT_PARTNERS, APP_CONFIG_OPTIONS, APP_FORM_ERROR_I18N_KEYS, APP_GRAPHQL_TYPE_POLICIES, APP_HOME_BUTTONS, APP_LOCALES, APP_LOCAL_SETTINGS, APP_LOCAL_SETTINGS_OPTIONS, APP_LOCAL_STORAGE_TYPE_POLICIES, APP_MENU_ITEMS, APP_TESTING_PAGES, AboutModal, Account, AccountPage, AccountService, AccountToStringPipe, ActionsColumnComponent, AdminModule, AdminRoutingModule, Alerts, AndroidOsEnvironment, AnimationState, AppEditor, AppEditorOptions, AppEntityEditor, AppForm, AppFormField, AppFormProvider, AppFormUtils, AppGestureConfig, AppGraphQLModule, AppHelpModal, AppInMemoryTable, AppInstallUpgradeCard, AppListForm, AppLoadingSpinner, AppMenuModule, AppNullForm, AppPropertiesForm, AppTabEditor, AppTabEditorOptions, AppTable, AppTableUtils, AppValidatorService, AppendToInputDirective, ArrayFilterPipe, ArrayFirstPipe, ArrayIncludesPipe, ArrayLengthPipe, ArrayPluckPipe, AudioProvider, AuthForm, AuthGuardService, AuthModal, AutocompleteTestPage, AutofocusDirective, Base58, BaseEntityService, BaseGraphqlService, BaseGraphqlServiceOptions, BaseReferential, Beans, CORE_CONFIG_OPTIONS, CORE_TESTING_PAGES, CellValueChangeListener, ChipsTestPage, Color, ColorScale, ComponentDirtyGuard, ConfigService, Configuration, CoreModule, CoreTestingModule, CryptoService, DATE_ISO_PATTERN, DATE_UNIX_MS_TIMESTAMP, DATE_UNIX_TIMESTAMP, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLACEHOLDER_CHAR, DEFAULT_REQUIRED_COLUMNS, DateDiffDurationPipe, DateFormatPipe, DateFromNowPipe, DateTimeTestPage, DateUtils, Department, DepartmentToStringPipe, DragAndDropDirective, DurationPipe, ENTITIES_STORAGE_KEY_PREFIX, ENVIRONMENT, EmptyArrayPipe, EntitiesService, EntitiesStorage, EntitiesTableDataSource, Entity, EntityClass, EntityClasses, EntityFilter, EntityFilterUtils, EntityMetadataComponent, EntityStore, EntityUtils, Environment, EvenPipe, FileResponse, FileService, FileSizePipe, FilesUtils, FormArrayHelper, FormButtonsBarComponent, FormButtonsBarToken, FormErrorTranslatePipe, FormErrorTranslator, FormFieldValuesHolder, FormGetArrayPipe, FormGetControlPipe, FormGetGroupPipe, FormGetPipe, Fragments$1 as Fragments, GraphqlService, HAMMER_PRESS_TIME, HAMMER_TAP_TIME, HighlightPipe, HomePage, Hotkeys, HotkeysDialogComponent, InMemoryEntitiesService, IsLoginAccountPipe, IsNilOrBlankPipe, IsNotNilOrBlankPipe, IsNotOnFieldModePipe, IsOnFieldModePipe, JobUtils, KEYBOARD_HIDE_DELAY_MS, LAT_LONG_DEFAULT_MAX_DECIMALS, LAT_LONG_PATTERNS, LatLongFormatPipe, LatLongTestPage, LatitudeFormatPipe, LocalSettingsService, LongitudeFormatPipe, MINIFY_ENTITY_FOR_LOCAL_STORAGE, MINIFY_ENTITY_FOR_POD, MapGetPipe, MapKeysPipe, MapValuesPipe, MatAutocompleteConfigHolder, MatAutocompleteField, MatBadgeIconDirective, MatBooleanField, MatChipsField, MatDate, MatDateShort, MatDateTime, MatDuration, MatLatLongField, MatNumpadComponent, MatNumpadContainerComponent, MatNumpadContent, MatPaginatorI18n, MatStepperI18n, MatSwipeField, MaterialTestingModule, MathAbsPipe, MenuComponent, MenuItems, MenuService, Message, MessageFilter, MessageForm, MessageModal, MessageService, MessageTypeList, MessageTypes, ModalToolbarComponent, NetworkService, NgInitDirective, NgVarDirective, NotEmptyArrayPipe, NumberFormatPipe, NumpadDirective, OddPipe, PRIORITIZED_AUTHORITIES, PUBKEY_REGEXP, Peer, Person, PersonFilter, PersonFragments, PersonService, PersonToStringPipe, PersonUtils, PersonValidatorService, PlatformService, ProgressBarService, ProgressInterceptor, PropertyGetPipe, RESERVED_END_COLUMNS, RESERVED_START_COLUMNS, Referential, ReferentialFilter, ReferentialRef, ReferentialUtils, ReferentialValidatorService, RegisterConfirmPage, SETTINGS_DISPLAY_COLUMNS, SETTINGS_FILTER, SETTINGS_PAGE_SIZE, SETTINGS_SORTED_COLUMN, SETTINGS_STORAGE_KEY, SETTINGS_TRANSIENT_PROPERTIES, SHARED_MATERIAL_TESTING_PAGES, SHARED_TESTING_PAGES, SPACE_PLACEHOLDER_CHAR, SelectPeerModal, SettingsPage, SharedAsyncValidators, SharedDirectivesModule, SharedFormArrayValidators, SharedFormGroupValidators, SharedHotkeysModule, SharedMatAutocompleteModule, SharedMatBadgeIconModule, SharedMatBooleanModule, SharedMatChipsModule, SharedMatDateTimeModule, SharedMatDurationModule, SharedMatLatLongModule, SharedMatNumpadModule, SharedMatSwipeModule, SharedMaterialModule, SharedModule, SharedPipesModule, SharedRoutingModule, SharedTestingModule, SharedTestsPage, SharedValidators, SocialErrorCodes, SocialModule, Software, StartableService, StatusById, StatusIds, StatusList, StrIncludesPipe, StrLengthPipe, SwipeTestPage, TableSelectColumnsComponent, TableTestPage, TableTestingModule, TextForm, TextPopover, TextPopoverTestingModule, TextPopoverTestingPage, ToStringPipe, Toasts, ToolbarComponent, ToolbarToken, TranslatablePipe, TranslateContextPipe, TranslateContextService, UploadFile, UploadFileComponent, UploadFilePopover, UploadFileTestingModule, UploadFileTestingPage, UserEvent, UserEventFilter, UserEventFragments, UserEventService, UserEventTypes, UserEventsTable, UserSettings, UsersPage, accountToString, adaptValueToControl, addValueInArray, arrayDistinct, arrayGroupBy, arraySize, asInputElement, canHaveFocus, capitalizeFirstLetter, chainPromises, changeCaseToUnderscore, clearValueInArray, copyEntity2Form, createPromiseEvent, createPromiseEventEmitter, departmentToString, departmentsToString, disableControls, emitPromiseEvent, entityToString, equals, equalsOrNil, fadeInAnimation, fadeInOutAnimation, filterFalse, filterNotNil, filterNumberInput, filterTrue, firstArrayValue, firstFalse, firstFalsePromise, firstNotNil, firstNotNilPromise, firstTrue, firstTruePromise, focusInput, focusNextInput, focusPreviousInput, formatLatitude, formatLongitude, fromDateISOString, fromScrollEndEvent, fromUnixMsTimestamp, fromUnixTimestamp, getCaretPosition, getColorContrast, getColorShade, getColorTint, getConnectionType, getControlFromPath, getFocusableInputElements, getFormErrors, getFormValueFromEntity, getProperty, getPropertyByPath, getPropertyByPathAsString, getRandomImage, hexToRgb, hexToRgbArray, isAndroid, isBlankString, isControlHasInput, isCordova, isEmptyArray, isIOS, isInputElement, isInstanceOf, isInt, isMobile, isNil, isNilOrBlank, isNilOrNaN, isNotEmptyArray, isNotNil, isNotNilBoolean, isNotNilOrBlank, isNotNilOrNaN, isNotNilString, isNumber, isNumberRange, isProgressEvent, isResponseEvent, isTouchUi, isWindows, joinProperties, joinPropertiesPath, logFormErrors, markAllAsTouched, markAsUntouched, markControlAsTouched, markFormGroupAsTouched, matchMedia, matchUpperCase, mergeLoadResult, mixHex, moveInputCaretToSeparator, noTrailingSlash, notNilOrDefault, nullIfUndefined, parseLatitudeOrLongitude, propertiesPathComparator, propertyComparator, propertyPathComparator, referentialToString, referentialsToString, remove, removeAll, removeDiacritics, removeDuplicatesFromArray, removeEnd, removeValueInArray, replaceAll, resetCalculatedValue, resizeArray, rgbArrayToHex, rgbToHex, round, scrollFactory, selectInputContent, selectInputRange, setCalculatedValue, setTabIndex, sleep, slideInOutAnimation, slideUpDownAnimation, sort, splitById, splitByProperty, startsWithUpperCase, suggestFromArray, suggestFromStringArray, tabindexComparator, testUserAgent, toBoolean, toDateISOString, toDuration, toFloat, toInt, toNotNil, toNumber, trimEmptyToNull, uncapitalizeFirstLetter, updateValueAndValidity, waitFor, waitForTrue, waitIdle, waitWhilePending, ɵ0, CustomReuseStrategy as ɵa, MatNumpadDomService as ɵb, isFocusableElement as ɵc, RegisterForm as ɵd, AccountValidatorService as ɵe, RegisterModal as ɵf, UserSettingsValidatorService as ɵg, LocalSettingsValidatorService as ɵh, AppIconComponent as ɵi, DateTestPage as ɵj, NumpadTestPage as ɵk, MatBadgeIconTestPage as ɵl, ToastTestingModule as ɵm, ToastTestingPage as ɵn };
27795
+ export { APP_ABOUT_DEVELOPERS, APP_ABOUT_PARTNERS, APP_CONFIG_OPTIONS, APP_FORM_ERROR_I18N_KEYS, APP_GRAPHQL_TYPE_POLICIES, APP_HOME_BUTTONS, APP_LOCALES, APP_LOCAL_SETTINGS, APP_LOCAL_SETTINGS_OPTIONS, APP_LOCAL_STORAGE_TYPE_POLICIES, APP_MENU_ITEMS, APP_TESTING_PAGES, AboutModal, Account, AccountPage, AccountService, AccountToStringPipe, ActionsColumnComponent, AdminModule, AdminRoutingModule, Alerts, AndroidOsEnvironment, AnimationState, AppEditor, AppEditorOptions, AppEntityEditor, AppForm, AppFormField, AppFormProvider, AppFormUtils, AppGestureConfig, AppGraphQLModule, AppHelpModal, AppInMemoryTable, AppInstallUpgradeCard, AppListForm, AppLoadingSpinner, AppMenuModule, AppNullForm, AppPropertiesForm, AppTabEditor, AppTabEditorOptions, AppTable, AppTableDataSourceOptions, AppTableUtils, AppValidatorService, AppendToInputDirective, ArrayFilterPipe, ArrayFirstPipe, ArrayIncludesPipe, ArrayLengthPipe, ArrayPluckPipe, AudioProvider, AuthForm, AuthGuardService, AuthModal, AutocompleteTestPage, AutofocusDirective, Base58, BaseEntityService, BaseGraphqlService, BaseGraphqlServiceOptions, BaseReferential, Beans, CORE_CONFIG_OPTIONS, CORE_TESTING_PAGES, CellValueChangeListener, ChipsTestPage, Color, ColorScale, ComponentDirtyGuard, ConfigService, Configuration, CoreModule, CoreTestingModule, CryptoService, DATE_ISO_PATTERN, DATE_UNIX_MS_TIMESTAMP, DATE_UNIX_TIMESTAMP, DEFAULT_MENU_SHOW_WHEN, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLACEHOLDER_CHAR, DEFAULT_REQUIRED_COLUMNS, DateDiffDurationPipe, DateFormatPipe, DateFromNowPipe, DateTimeTestPage, DateUtils, Department, DepartmentToStringPipe, DragAndDropDirective, DurationPipe, ENTITIES_STORAGE_KEY_PREFIX, ENVIRONMENT, EmptyArrayPipe, EntitiesService, EntitiesStorage, EntitiesTableDataSource, Entity, EntityClass, EntityClasses, EntityFilter, EntityFilterUtils, EntityMetadataComponent, EntityStore, EntityUtils, Environment, EvenPipe, FileResponse, FileService, FileSizePipe, FilesUtils, FormArrayHelper, FormButtonsBarComponent, FormButtonsBarToken, FormErrorTranslatePipe, FormErrorTranslator, FormFieldValuesHolder, FormGetArrayPipe, FormGetControlPipe, FormGetGroupPipe, FormGetPipe, FormGetValuePipe, Fragments$1 as Fragments, GraphqlService, HAMMER_PRESS_TIME, HAMMER_TAP_TIME, HighlightPipe, HomePage, Hotkeys, HotkeysDialogComponent, InMemoryEntitiesService, IsLoginAccountPipe, IsNilOrBlankPipe, IsNotNilOrBlankPipe, IsNotOnFieldModePipe, IsOnFieldModePipe, JobUtils, KEYBOARD_HIDE_DELAY_MS, LAT_LONG_DEFAULT_MAX_DECIMALS, LAT_LONG_PATTERNS, LatLongFormatPipe, LatLongTestPage, LatitudeFormatPipe, LocalSettingsService, LongitudeFormatPipe, MINIFY_ENTITY_FOR_LOCAL_STORAGE, MINIFY_ENTITY_FOR_POD, MapGetPipe, MapKeysPipe, MapValuesPipe, MatAutocompleteConfigHolder, MatAutocompleteField, MatBadgeIconDirective, MatBooleanField, MatChipsField, MatDate, MatDateShort, MatDateTime, MatDuration, MatLatLongField, MatNumpadComponent, MatNumpadContainerComponent, MatNumpadContent, MatPaginatorI18n, MatStepperI18n, MatSwipeField, MaterialTestingModule, MathAbsPipe, MenuComponent, MenuItems, MenuService, Message, MessageFilter, MessageForm, MessageModal, MessageService, MessageTypeList, MessageTypes, MimeTypes, ModalToolbarComponent, NetworkService, NgInitDirective, NgVarDirective, NotEmptyArrayPipe, NumberFormatPipe, NumpadDirective, OddPipe, PRIORITIZED_AUTHORITIES, PUBKEY_REGEXP, Peer, Person, PersonFilter, PersonFragments, PersonService, PersonToStringPipe, PersonUtils, PersonValidatorService, PlatformService, ProgressBarService, ProgressInterceptor, PropertyGetPipe, RESERVED_END_COLUMNS, RESERVED_START_COLUMNS, Referential, ReferentialFilter, ReferentialRef, ReferentialUtils, ReferentialValidatorService, RegisterConfirmPage, SETTINGS_DISPLAY_COLUMNS, SETTINGS_FILTER, SETTINGS_PAGE_SIZE, SETTINGS_SORTED_COLUMN, SETTINGS_STORAGE_KEY, SETTINGS_TRANSIENT_PROPERTIES, SHARED_MATERIAL_TESTING_PAGES, SHARED_TESTING_PAGES, SPACE_PLACEHOLDER_CHAR, SelectPeerModal, SettingsPage, SharedAsyncValidators, SharedDirectivesModule, SharedFormArrayValidators, SharedFormGroupValidators, SharedHotkeysModule, SharedMatAutocompleteModule, SharedMatBadgeIconModule, SharedMatBooleanModule, SharedMatChipsModule, SharedMatDateTimeModule, SharedMatDurationModule, SharedMatLatLongModule, SharedMatNumpadModule, SharedMatSwipeModule, SharedMaterialModule, SharedModule, SharedPipesModule, SharedRoutingModule, SharedTestingModule, SharedTestsPage, SharedValidators, SocialErrorCodes, SocialModule, Software, StartableService, StatusById, StatusIds, StatusList, StrIncludesPipe, StrLengthPipe, SwipeTestPage, TableSelectColumnsComponent, TableTestPage, TableTestingModule, TextForm, TextPopover, TextPopoverTestingModule, TextPopoverTestingPage, ToStringPipe, Toasts, ToolbarComponent, ToolbarToken, TranslatablePipe, TranslateContextPipe, TranslateContextService, UploadFile, UploadFileComponent, UploadFilePopover, UploadFileTestingModule, UploadFileTestingPage, UriUtils, UserEvent, UserEventFilter, UserEventFragments, UserEventService, UserEventTypes, UserEventsTable, UserSettings, UsersPage, accountToString, adaptValueToControl, addValueInArray, arrayDistinct, arrayGroupBy, arraySize, asInputElement, canHaveFocus, capitalizeFirstLetter, chainPromises, changeCaseToUnderscore, clearValueInArray, copyEntity2Form, createPromiseEvent, createPromiseEventEmitter, departmentToString, departmentsToString, disableControls, emitPromiseEvent, entityToString, equals, equalsOrNil, escapeRegExp, fadeInAnimation, fadeInOutAnimation, filterFalse, filterNotNil, filterNumberInput, filterTrue, firstArrayValue, firstFalse, firstFalsePromise, firstNotNil, firstNotNilPromise, firstTrue, firstTruePromise, focusInput, focusNextInput, focusPreviousInput, formatLatitude, formatLongitude, fromDateISOString, fromScrollEndEvent, fromUnixMsTimestamp, fromUnixTimestamp, getCaretPosition, getColorContrast, getColorShade, getColorTint, getConnectionType, getControlFromPath, getFocusableInputElements, getFormErrors, getFormValueFromEntity, getProperty, getPropertyByPath, getPropertyByPathAsString, getRandomImage, hexToRgb, hexToRgbArray, isAndroid, isBlankString, isControlHasInput, isCordova, isEmptyArray, isIOS, isInputElement, isInstanceOf, isInt, isMobile, isNil, isNilOrBlank, isNilOrNaN, isNotEmptyArray, isNotNil, isNotNilBoolean, isNotNilOrBlank, isNotNilOrNaN, isNotNilString, isNumber, isNumberRange, isProgressEvent, isResponseEvent, isTouchUi, isWindows, joinProperties, joinPropertiesPath, logFormErrors, markAllAsTouched, markAsUntouched, markControlAsTouched, markFormGroupAsTouched, matchMedia, matchUpperCase, mergeLoadResult, mixHex, moment$5 as moment, moveInputCaretToSeparator, noTrailingSlash, notNilOrDefault, nullIfUndefined, parseLatitudeOrLongitude, propertiesPathComparator, propertyComparator, propertyPathComparator, referentialToString, referentialsToString, remove, removeAll, removeDiacritics, removeDuplicatesFromArray, removeEnd, removeValueInArray, replaceAll, resetCalculatedValue, resizeArray, rgbArrayToHex, rgbToHex, round, scrollFactory, selectInputContent, selectInputRange, setCalculatedValue, setTabIndex, sleep, slideInOutAnimation, slideUpDownAnimation, sort, splitById, splitByProperty, startsWithUpperCase, suggestFromArray, suggestFromStringArray, tabindexComparator, testUserAgent, toBoolean, toDateISOString, toDuration, toFloat, toInt, toNotNil, toNumber, trimEmptyToNull, tz, uncapitalizeFirstLetter, updateValueAndValidity, waitFor, waitForTrue, waitIdle, waitWhilePending, ɵ0, CustomReuseStrategy as ɵa, MatNumpadDomService as ɵb, isFocusableElement as ɵc, RegisterForm as ɵd, AccountValidatorService as ɵe, RegisterModal as ɵf, UserSettingsValidatorService as ɵg, LocalSettingsValidatorService as ɵh, AppUpdateOfflineModeCard as ɵi, AppIconComponent as ɵj, DateTestPage as ɵk, NumpadTestPage as ɵl, MatBadgeIconTestPage as ɵm, ToastTestingModule as ɵn, ToastTestingPage as ɵo };
27575
27796
  //# sourceMappingURL=sumaris-net.ngx-components.js.map