@sumaris-net/ngx-components 1.20.0-rc4 → 1.20.1

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 (91) hide show
  1. package/README.md +7 -7
  2. package/bundles/sumaris-net.ngx-components.umd.js +1497 -1314
  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 +46 -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 +81 -58
  36. package/esm2015/src/app/shared/material/autocomplete/testing/autocomplete.test.js +24 -9
  37. package/esm2015/src/app/shared/material/datetime/material.date.js +2 -2
  38. package/esm2015/src/app/shared/material/datetime/material.dateshort.js +2 -2
  39. package/esm2015/src/app/shared/material/datetime/material.datetime.js +7 -8
  40. package/esm2015/src/app/shared/material/datetime/testing/mat-date-time.test.js +6 -4
  41. package/esm2015/src/app/shared/material/latlong/latlong.utils.js +1 -1
  42. package/esm2015/src/app/shared/material/latlong/material.latlong.js +57 -51
  43. package/esm2015/src/app/shared/material/latlong/testing/latlong.test.js +2 -2
  44. package/esm2015/src/app/shared/material/swipe/material.swipe.js +5 -5
  45. package/esm2015/src/app/shared/pipes/form.pipes.js +86 -23
  46. package/esm2015/src/app/shared/pipes/latlong-format.pipe.js +17 -24
  47. package/esm2015/src/app/shared/pipes/number-format.pipe.js +6 -3
  48. package/esm2015/src/app/shared/pipes/pipes.module.js +6 -4
  49. package/esm2015/src/app/shared/platforms.js +2 -1
  50. package/esm2015/src/app/shared/services/job.utils.js +4 -3
  51. package/esm2015/src/app/shared/toolbar/toolbar.js +25 -9
  52. package/esm2015/src/app/shared/validator/validators.js +9 -4
  53. package/esm2015/src/app/vendor.js +7 -1
  54. package/esm2015/sumaris-net.ngx-components.js +8 -7
  55. package/fesm2015/sumaris-net.ngx-components.js +1049 -831
  56. package/fesm2015/sumaris-net.ngx-components.js.map +1 -1
  57. package/package.json +2 -2
  58. package/src/app/core/form/entity-editor.class.d.ts +2 -1
  59. package/src/app/core/form/form-buttons-bar.component.d.ts +3 -1
  60. package/src/app/core/install/install-upgrade-card.component.d.ts +1 -6
  61. package/src/app/core/menu/menu.component.d.ts +12 -7
  62. package/src/app/core/menu/menu.service.d.ts +15 -6
  63. package/src/app/core/offline/update-offline-mode-card.component.d.ts +8 -0
  64. package/src/app/core/services/model/entity.model.d.ts +0 -1
  65. package/src/app/core/services/model/filter.model.d.ts +1 -1
  66. package/src/app/core/services/model/referential.model.d.ts +3 -4
  67. package/src/app/core/services/platform.service.d.ts +1 -0
  68. package/src/app/core/services/storage/entities-storage.service.d.ts +10 -6
  69. package/src/app/core/services/storage/entity-store.class.d.ts +2 -2
  70. package/src/app/core/table/entities-table-datasource.class.d.ts +28 -26
  71. package/src/app/core/table/table.class.d.ts +2 -2
  72. package/src/app/shared/dates.d.ts +3 -3
  73. package/src/app/shared/files.d.ts +10 -0
  74. package/src/app/shared/functions.d.ts +1 -0
  75. package/src/app/shared/material/autocomplete/testing/autocomplete.test.d.ts +4 -2
  76. package/src/app/shared/material/latlong/latlong.utils.d.ts +2 -2
  77. package/src/app/shared/material/latlong/material.latlong.d.ts +15 -10
  78. package/src/app/shared/material/swipe/material.swipe.d.ts +2 -2
  79. package/src/app/shared/pipes/form.pipes.d.ts +20 -4
  80. package/src/app/shared/pipes/latlong-format.pipe.d.ts +6 -4
  81. package/src/app/shared/pipes/number-format.pipe.d.ts +3 -1
  82. package/src/app/shared/toolbar/toolbar.d.ts +5 -2
  83. package/src/app/shared/validator/validators.d.ts +6 -1
  84. package/src/app/vendor.d.ts +5 -0
  85. package/src/assets/i18n/en-US.json +2 -1
  86. package/src/assets/i18n/en.json +2 -1
  87. package/src/assets/i18n/fr.json +4 -1
  88. package/src/theme/_ngx-components.globals.scss +1 -0
  89. package/src/theme/_ngx-components.scss +32 -20
  90. package/sumaris-net.ngx-components.d.ts +7 -6
  91. 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 [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,
@@ -2222,52 +2249,49 @@ function roundFloat(input, maxDecimals) {
2222
2249
  return input;
2223
2250
  }
2224
2251
 
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
2252
  class LatitudeFormatPipe {
2241
2253
  constructor() {
2242
2254
  this.transform = formatLatitude;
2243
2255
  }
2244
2256
  }
2245
- LatitudeFormatPipe.ɵprov = ɵɵdefineInjectable({ factory: function LatitudeFormatPipe_Factory() { return new LatitudeFormatPipe(); }, token: LatitudeFormatPipe, providedIn: "root" });
2246
2257
  LatitudeFormatPipe.decorators = [
2247
2258
  { type: Pipe, args: [{
2248
2259
  name: 'latitudeFormat'
2249
- },] },
2250
- { type: Injectable, args: [{ providedIn: 'root' },] }
2260
+ },] }
2251
2261
  ];
2252
2262
  class LongitudeFormatPipe {
2253
2263
  constructor() {
2254
2264
  this.transform = formatLongitude;
2255
2265
  }
2256
2266
  }
2257
- LongitudeFormatPipe.ɵprov = ɵɵdefineInjectable({ factory: function LongitudeFormatPipe_Factory() { return new LongitudeFormatPipe(); }, token: LongitudeFormatPipe, providedIn: "root" });
2258
2267
  LongitudeFormatPipe.decorators = [
2259
2268
  { type: Pipe, args: [{
2260
2269
  name: 'longitudeFormat'
2261
- },] },
2262
- { type: Injectable, args: [{ providedIn: 'root' },] }
2270
+ },] }
2271
+ ];
2272
+ /**
2273
+ * @deprecated use 'latitudeFormat' or 'longitudeFormat' instead
2274
+ */
2275
+ class LatLongFormatPipe {
2276
+ transform(value, opts) {
2277
+ return ((!opts || (opts === null || opts === void 0 ? void 0 : opts.type) !== 'latitude') ? formatLongitude : formatLatitude)(value, opts);
2278
+ }
2279
+ }
2280
+ LatLongFormatPipe.decorators = [
2281
+ { type: Pipe, args: [{
2282
+ name: 'latLongFormat'
2283
+ },] }
2263
2284
  ];
2264
2285
 
2265
2286
  class NumberFormatPipe {
2266
- transform(val, numberFormatOptions) {
2287
+ transform(val, opts) {
2267
2288
  // Format the output to display any way you want here.
2268
2289
  // For instance:
2269
2290
  if (val !== undefined && val !== null) {
2270
- return val.toLocaleString(undefined, numberFormatOptions);
2291
+ if (opts === null || opts === void 0 ? void 0 : opts.fixedDecimals) {
2292
+ return val.toFixed(opts.fixedDecimals);
2293
+ }
2294
+ return val.toLocaleString(undefined, opts);
2271
2295
  }
2272
2296
  else {
2273
2297
  return '';
@@ -2380,15 +2404,9 @@ FileSizePipe.decorators = [
2380
2404
  ];
2381
2405
 
2382
2406
  const moment$1 = momentImported;
2383
- const tz = momentTZImported;
2384
2407
  const DATE_UNIX_TIMESTAMP = 'X';
2385
2408
  const DATE_UNIX_MS_TIMESTAMP = 'x';
2386
2409
  class DateUtils {
2387
- constructor() {
2388
- this.toDateISOString = toDateISOString;
2389
- this.fromDateISOString = fromDateISOString;
2390
- this.toDuration = toDuration;
2391
- }
2392
2410
  static min(date1, date2) {
2393
2411
  return date1 && (!date2 || date1.isSameOrBefore(date2)) ? date1 : date2;
2394
2412
  }
@@ -2438,6 +2456,9 @@ class DateUtils {
2438
2456
  return date.day() === weekday;
2439
2457
  }
2440
2458
  }
2459
+ DateUtils.toDateISOString = toDateISOString;
2460
+ DateUtils.fromDateISOString = fromDateISOString;
2461
+ DateUtils.toDuration = toDuration;
2441
2462
  function toDateISOString(value) {
2442
2463
  if (!value)
2443
2464
  return undefined;
@@ -2908,7 +2929,7 @@ const moment$2 = momentImported;
2908
2929
  // @dynamic
2909
2930
  class SharedValidators {
2910
2931
  static getDoubleRegexp(maxDecimals) {
2911
- if (isNil(maxDecimals) || maxDecimals === -1)
2932
+ if (isNil(maxDecimals))
2912
2933
  return this._REGEXP_CACHE.DOUBLE_BY_MAX_DECIMALS.NO_LIMIT;
2913
2934
  if (maxDecimals < 0)
2914
2935
  throw new Error(`Invalid maxDecimals value: ${maxDecimals}`);
@@ -2969,7 +2990,7 @@ class SharedValidators {
2969
2990
  }
2970
2991
  return null;
2971
2992
  }
2972
- static double(opts) {
2993
+ static decimal(opts) {
2973
2994
  const regexp = this.getDoubleRegexp(opts === null || opts === void 0 ? void 0 : opts.maxDecimals);
2974
2995
  return (control) => {
2975
2996
  let value = control.value;
@@ -2978,7 +2999,7 @@ class SharedValidators {
2978
2999
  return null;
2979
3000
  }
2980
3001
  if (isNotNil(value) && value !== '' && !regexp.test(value)) {
2981
- return { maxDecimals: { maxDecimals: opts === null || opts === void 0 ? void 0 : opts.maxDecimals } };
3002
+ return isNotNil(opts === null || opts === void 0 ? void 0 : opts.maxDecimals) ? { maxDecimals: { maxDecimals: opts.maxDecimals } } : { decimal: true };
2982
3003
  }
2983
3004
  return null;
2984
3005
  };
@@ -3110,6 +3131,7 @@ SharedValidators.I18N_ERROR_KEYS = {
3110
3131
  dateMinDuration: 'ERROR.FIELD_NOT_VALID_DATE_MIN_DURATION',
3111
3132
  dateMaxDuration: 'ERROR.FIELD_NOT_VALID_DATE_MAX_DURATION',
3112
3133
  maxDecimals: 'ERROR.FIELD_MAXIMUM_DECIMALS',
3134
+ double: 'ERROR.FIELD_NOT_VALID_DOUBLE',
3113
3135
  integer: 'ERROR.FIELD_NOT_VALID_INTEGER',
3114
3136
  email: 'ERROR.FIELD_NOT_VALID_EMAIL',
3115
3137
  latitude: 'ERROR.FIELD_NOT_VALID_LATITUDE',
@@ -3118,6 +3140,10 @@ SharedValidators.I18N_ERROR_KEYS = {
3118
3140
  unique: 'ERROR.FIELD_NOT_UNIQUE',
3119
3141
  entity: 'ERROR.FIELD_INVALID'
3120
3142
  };
3143
+ /**
3144
+ * @deprecated use decimal
3145
+ */
3146
+ SharedValidators.double = SharedValidators.decimal;
3121
3147
  // @dynamic
3122
3148
  class SharedFormGroupValidators {
3123
3149
  static dateRange(startDateField, endDateField, msg) {
@@ -4132,8 +4158,8 @@ class FormErrorTranslatePipe {
4132
4158
  // if there is a subscription to onLangChange, clean it
4133
4159
  this._dispose();
4134
4160
  // subscribe to onTranslationChange event, in case the translations change
4135
- if (!this._onFormStatusChange) {
4136
- this._onFormStatusChange = form.statusChanges.subscribe((status) => {
4161
+ if (!this._onFormStatusChanges) {
4162
+ this._onFormStatusChanges = form.statusChanges.subscribe((status) => {
4137
4163
  this._updateValue(form, opts, status);
4138
4164
  });
4139
4165
  }
@@ -4156,9 +4182,9 @@ class FormErrorTranslatePipe {
4156
4182
  * Clean any existing subscription to change events
4157
4183
  */
4158
4184
  _dispose() {
4159
- if (typeof this._onFormStatusChange !== 'undefined') {
4160
- this._onFormStatusChange.unsubscribe();
4161
- this._onFormStatusChange = undefined;
4185
+ if (typeof this._onFormStatusChanges !== 'undefined') {
4186
+ this._onFormStatusChanges.unsubscribe();
4187
+ this._onFormStatusChanges = undefined;
4162
4188
  }
4163
4189
  }
4164
4190
  }
@@ -4177,48 +4203,112 @@ class FormGetPipe {
4177
4203
  return form.get(path);
4178
4204
  }
4179
4205
  }
4180
- FormGetPipe.ɵprov = ɵɵdefineInjectable({ factory: function FormGetPipe_Factory() { return new FormGetPipe(); }, token: FormGetPipe, providedIn: "root" });
4181
4206
  FormGetPipe.decorators = [
4182
4207
  { type: Pipe, args: [{
4183
4208
  name: 'formGet'
4184
- },] },
4185
- { type: Injectable, args: [{ providedIn: 'root' },] }
4209
+ },] }
4186
4210
  ];
4187
4211
  class FormGetControlPipe {
4188
4212
  transform(form, path) {
4189
- return form.get(path);
4213
+ return (form && path && form.get(path) || form);
4190
4214
  }
4191
4215
  }
4192
- FormGetControlPipe.ɵprov = ɵɵdefineInjectable({ factory: function FormGetControlPipe_Factory() { return new FormGetControlPipe(); }, token: FormGetControlPipe, providedIn: "root" });
4193
4216
  FormGetControlPipe.decorators = [
4194
4217
  { type: Pipe, args: [{
4195
4218
  name: 'formGetControl'
4196
- },] },
4197
- { type: Injectable, args: [{ providedIn: 'root' },] }
4219
+ },] }
4198
4220
  ];
4199
4221
  class FormGetArrayPipe {
4200
4222
  transform(form, path) {
4201
- return form.get(path);
4223
+ return (form && path && form.get(path) || form);
4202
4224
  }
4203
4225
  }
4204
- FormGetArrayPipe.ɵprov = ɵɵdefineInjectable({ factory: function FormGetArrayPipe_Factory() { return new FormGetArrayPipe(); }, token: FormGetArrayPipe, providedIn: "root" });
4205
4226
  FormGetArrayPipe.decorators = [
4206
4227
  { type: Pipe, args: [{
4207
4228
  name: 'formGetArray'
4208
- },] },
4209
- { type: Injectable, args: [{ providedIn: 'root' },] }
4229
+ },] }
4210
4230
  ];
4211
4231
  class FormGetGroupPipe {
4212
4232
  transform(form, path) {
4213
- return form.get(path);
4233
+ return (form && path && form.get(path) || form);
4214
4234
  }
4215
4235
  }
4216
- FormGetGroupPipe.ɵprov = ɵɵdefineInjectable({ factory: function FormGetGroupPipe_Factory() { return new FormGetGroupPipe(); }, token: FormGetGroupPipe, providedIn: "root" });
4217
4236
  FormGetGroupPipe.decorators = [
4218
4237
  { type: Pipe, args: [{
4219
4238
  name: 'formGetGroup'
4220
- },] },
4221
- { type: Injectable, args: [{ providedIn: 'root' },] }
4239
+ },] }
4240
+ ];
4241
+ class FormGetValuePipe {
4242
+ constructor(adapter, _ref) {
4243
+ this.adapter = adapter;
4244
+ this._ref = _ref;
4245
+ this.value = undefined;
4246
+ this._lastControl = null;
4247
+ }
4248
+ transform(form, path, listenStatusChanges = false) {
4249
+ if (!form) {
4250
+ this._dispose();
4251
+ return undefined;
4252
+ }
4253
+ // if we ask another time for the same form and opts, return the last value
4254
+ if (form === this._lastControl && equals(path, this._lastPath)) {
4255
+ return this.value;
4256
+ }
4257
+ // store the query, in case it changes
4258
+ this._lastControl = form;
4259
+ // store the params, in case they change
4260
+ this._lastPath = path;
4261
+ // set the value
4262
+ const control = path ? form.get(path) : form;
4263
+ this.value = control === null || control === void 0 ? void 0 : control.value;
4264
+ // if there is a subscription to onLangChange, clean it
4265
+ this._dispose();
4266
+ // subscribe to valueChanges event, in case control value change
4267
+ if (!this._onControlValueChanges && control) {
4268
+ this._onControlValueChanges = control.valueChanges.subscribe((value) => {
4269
+ if (value !== this.value) {
4270
+ this.value = value;
4271
+ this._ref.markForCheck();
4272
+ }
4273
+ });
4274
+ }
4275
+ if (listenStatusChanges && !this._onControlStatusChanges && control) {
4276
+ this._onControlStatusChanges = control.statusChanges.subscribe((status) => {
4277
+ const value = control.value;
4278
+ if (value !== this.value) {
4279
+ this.value = value;
4280
+ this._ref.markForCheck();
4281
+ }
4282
+ });
4283
+ }
4284
+ return this.value;
4285
+ }
4286
+ ngOnDestroy() {
4287
+ this._dispose();
4288
+ }
4289
+ /**
4290
+ * Clean any existing subscription to change events
4291
+ */
4292
+ _dispose() {
4293
+ if (typeof this._onControlValueChanges !== 'undefined') {
4294
+ this._onControlValueChanges.unsubscribe();
4295
+ this._onControlValueChanges = undefined;
4296
+ }
4297
+ if (typeof this._onControlStatusChanges !== 'undefined') {
4298
+ this._onControlStatusChanges.unsubscribe();
4299
+ this._onControlStatusChanges = undefined;
4300
+ }
4301
+ }
4302
+ }
4303
+ FormGetValuePipe.decorators = [
4304
+ { type: Pipe, args: [{
4305
+ name: 'formGetValue',
4306
+ pure: false
4307
+ },] }
4308
+ ];
4309
+ FormGetValuePipe.ctorParameters = () => [
4310
+ { type: FormErrorTranslator },
4311
+ { type: ChangeDetectorRef }
4222
4312
  ];
4223
4313
 
4224
4314
  class SharedPipesModule {
@@ -4267,7 +4357,8 @@ SharedPipesModule.decorators = [
4267
4357
  FormGetPipe,
4268
4358
  FormGetControlPipe,
4269
4359
  FormGetArrayPipe,
4270
- FormGetGroupPipe
4360
+ FormGetGroupPipe,
4361
+ FormGetValuePipe
4271
4362
  ],
4272
4363
  exports: [
4273
4364
  PropertyGetPipe,
@@ -4306,7 +4397,8 @@ SharedPipesModule.decorators = [
4306
4397
  FormGetPipe,
4307
4398
  FormGetControlPipe,
4308
4399
  FormGetArrayPipe,
4309
- FormGetGroupPipe
4400
+ FormGetGroupPipe,
4401
+ FormGetValuePipe
4310
4402
  ]
4311
4403
  },] }
4312
4404
  ];
@@ -4583,10 +4675,10 @@ class MatLatLongField {
4583
4675
  this.disabling = false;
4584
4676
  this.writing = false;
4585
4677
  this._readonly = false;
4678
+ this.required = false;
4679
+ this.floatLabel = 'auto';
4586
4680
  this.maxDecimals = LAT_LONG_DEFAULT_MAX_DECIMALS;
4587
4681
  this.placeholderChar = DEFAULT_PLACEHOLDER_CHAR;
4588
- this.floatLabel = 'auto';
4589
- this.required = false;
4590
4682
  this.onBlur = new EventEmitter();
4591
4683
  this.onFocus = new EventEmitter();
4592
4684
  }
@@ -4601,7 +4693,6 @@ class MatLatLongField {
4601
4693
  return this._readonly;
4602
4694
  }
4603
4695
  ngOnInit() {
4604
- this.mobile = isNotNil(this.mobile) ? this.mobile : isMobile(window);
4605
4696
  this.type = this.type || 'latitude';
4606
4697
  this.pattern = this.pattern || 'DDMM';
4607
4698
  this.mask = MASKS[this.type] && MASKS[this.type][this.pattern];
@@ -4632,8 +4723,8 @@ class MatLatLongField {
4632
4723
  this.maxDecimals = DEFAULT_MAX_DECIMALS;
4633
4724
  }
4634
4725
  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 };
4726
+ this._formatFn = this.type === 'latitude' ? formatLatitude : formatLongitude;
4727
+ this._formatOptions = { pattern: this.pattern, maxDecimals: this.maxDecimals, hideSign: this.showSignControl };
4637
4728
  this.inputPlaceholder = 'COMMON.LAT_LONG.' + (this.type === 'longitude' && 'D' || '') + this.pattern + '_PLACEHOLDER';
4638
4729
  this.textFormControl = this.formBuilder.control(this.required ? [null, Validators.required] : [null]);
4639
4730
  this.textMaskConfig = { mask: this.mask, keepCharPositions: true, placeholderChar: this.placeholderChar };
@@ -4667,7 +4758,7 @@ class MatLatLongField {
4667
4758
  return;
4668
4759
  this.value = (typeof obj === 'string') ? parseFloat(obj.replace(/,/g, '.')) : obj;
4669
4760
  this.writing = true;
4670
- const strValue = this.formatFn(this.value, Object.assign(Object.assign({}, this.formatFnOptions), { placeholderChar: this.placeholderChar }));
4761
+ const strValue = this._formatFn(this.value, Object.assign(Object.assign({}, this._formatOptions), { placeholderChar: this.placeholderChar }));
4671
4762
  const sign = isNotNil(this.value) ? (this.value < 0 ? -1 : 1) :
4672
4763
  // Use default sign, if any
4673
4764
  (this.defaultSign ? (this.defaultSign === '-' ? -1 : 1) : null);
@@ -4700,41 +4791,13 @@ class MatLatLongField {
4700
4791
  this.disabling = false;
4701
4792
  this.markForCheck();
4702
4793
  }
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;
4794
+ /**
4795
+ * Format value to display as string (no placeholder char, and show sign)
4796
+ * @param value
4797
+ * @param opts
4798
+ */
4799
+ _format(value, opts) {
4800
+ return this._formatFn(value, Object.assign(Object.assign({}, this._formatOptions), { hideSign: false, placeholderChar: '' }));
4738
4801
  }
4739
4802
  _onFocus(event) {
4740
4803
  // Apply the default sign, when pattern is DD and field is empty
@@ -4743,7 +4806,8 @@ class MatLatLongField {
4743
4806
  // Compute the text value, using the default sign
4744
4807
  const defaultSign = this.defaultSign === '-' ? -1 : 1;
4745
4808
  if (this.pattern === 'DD') {
4746
- let valueStr = this.formatFn(defaultSign, Object.assign(Object.assign({}, this.formatFnOptions), { placeholderChar: this.placeholderChar }));
4809
+ let valueStr = this._formatFn(defaultSign, Object.assign(Object.assign({}, this._formatOptions), { placeholderChar: this.placeholderChar }));
4810
+ // Keep only the sign ('+' or '-') and placeholder chars, but NOT the '1'
4747
4811
  valueStr = valueStr && valueStr.replace('1', this.placeholderChar);
4748
4812
  // Wait end of focus animation (label should move to top)
4749
4813
  setTimeout(() => {
@@ -4843,6 +4907,42 @@ class MatLatLongField {
4843
4907
  }
4844
4908
  }
4845
4909
  }
4910
+ onFormChange(strValue) {
4911
+ if (this.writing)
4912
+ return; // Skip if call by self
4913
+ this.writing = true;
4914
+ if (this.textFormControl.invalid || this.signFormControl.invalid) {
4915
+ this.formControl.markAsPending();
4916
+ this.formControl.setErrors(Object.assign(Object.assign(Object.assign({}, this.formControl.errors), this.textFormControl.errors), this.signFormControl.errors));
4917
+ this.writing = false;
4918
+ return;
4919
+ }
4920
+ const parsedValue = isNotNilOrBlank(strValue) ? parseLatitudeOrLongitude(strValue, this.pattern, 7 /*=precision of the converted double value */, this.placeholderChar) : null;
4921
+ // DEBUG
4922
+ //console.debug('parsedValue=', parsedValue);
4923
+ if (isNaN(parsedValue)) {
4924
+ this.formControl.markAsPending();
4925
+ this.formControl.setErrors(this.type === 'latitude' ? { latitude: true } : { longitude: true });
4926
+ this.writing = false;
4927
+ return;
4928
+ }
4929
+ const sign = (this.pattern === 'DD') ? 1 /*ignore sign*/ : (this.signFormControl.value || 1);
4930
+ this.value = isNotNil(parsedValue) ? sign * parsedValue : null;
4931
+ // Get the model value
4932
+ //console.debug("[mat-latlon] Setting value {" + this.value + "} parsed from {" + strValue + "}");
4933
+ this.formControl.patchValue(this.value, { emitEvent: false });
4934
+ this.writing = false;
4935
+ this.markForCheck();
4936
+ this._onChangeCallback(this.value);
4937
+ }
4938
+ checkIfTouched() {
4939
+ if (this.formControl.touched || this.textFormControl.touched) {
4940
+ this.markForCheck();
4941
+ this._onTouchedCallback();
4942
+ return true;
4943
+ }
4944
+ return false;
4945
+ }
4846
4946
  markForCheck() {
4847
4947
  this.cd.markForCheck();
4848
4948
  }
@@ -4850,7 +4950,7 @@ class MatLatLongField {
4850
4950
  MatLatLongField.decorators = [
4851
4951
  { type: Component, args: [{
4852
4952
  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",
4953
+ 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
4954
  providers: [
4855
4955
  {
4856
4956
  provide: NG_VALUE_ACCESSOR,
@@ -4859,7 +4959,7 @@ MatLatLongField.decorators = [
4859
4959
  }
4860
4960
  ],
4861
4961
  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%}"]
4962
+ 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
4963
  },] }
4864
4964
  ];
4865
4965
  MatLatLongField.ctorParameters = () => [
@@ -4869,19 +4969,18 @@ MatLatLongField.ctorParameters = () => [
4869
4969
  { type: FormGroupDirective, decorators: [{ type: Optional }] }
4870
4970
  ];
4871
4971
  MatLatLongField.propDecorators = {
4872
- mobile: [{ type: Input }],
4873
4972
  formControl: [{ type: Input }],
4874
4973
  formControlName: [{ type: Input }],
4875
- placeholder: [{ type: Input }],
4876
4974
  type: [{ type: Input }],
4877
4975
  pattern: [{ type: Input, args: ['latLongPattern',] }],
4976
+ required: [{ type: Input }],
4977
+ floatLabel: [{ type: Input }],
4978
+ placeholder: [{ type: Input }],
4979
+ tabindex: [{ type: Input }],
4878
4980
  defaultSign: [{ type: Input }],
4879
4981
  maxDecimals: [{ type: Input }],
4880
4982
  placeholderChar: [{ type: Input }],
4881
- floatLabel: [{ type: Input }],
4882
4983
  readonly: [{ type: Input }],
4883
- required: [{ type: Input }],
4884
- tabindex: [{ type: Input }],
4885
4984
  onBlur: [{ type: Output }],
4886
4985
  onFocus: [{ type: Output }],
4887
4986
  inputElement: [{ type: ViewChild, args: ['inputElement',] }]
@@ -5234,7 +5333,7 @@ class MatDate {
5234
5333
  MatDate.decorators = [
5235
5334
  { type: Component, args: [{
5236
5335
  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",
5336
+ 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
5337
  providers: [
5239
5338
  DEFAULT_VALUE_ACCESSOR,
5240
5339
  ],
@@ -5331,7 +5430,7 @@ class MatDateTime {
5331
5430
  // Workaround because ion-datetime has issue (do not returned a ISO date)
5332
5431
  this._keyboardHideDelay = this.mobile && KEYBOARD_HIDE_DELAY_MS || 0;
5333
5432
  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)
5433
+ // Add 'validDate' validator (when existing validator are null or required, to be sure to keep)
5335
5434
  if (!this.formControl.validator || this.formControl.validator === Validators.required) {
5336
5435
  this.formControl.setValidators(this.required ? [Validators.required, SharedValidators.validDate] : SharedValidators.validDate);
5337
5436
  }
@@ -5340,12 +5439,9 @@ class MatDateTime {
5340
5439
  [this.formControl.validator, SharedValidators.validDate]);
5341
5440
  }
5342
5441
  // Create the day control
5343
- this.dayControl = this.formBuilder.control(null, this.required ? Validators.required : null);
5442
+ this.dayControl = this.formBuilder.control(null);
5344
5443
  // 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);
5444
+ this.hourControl = this.formBuilder.control(null, this.mobile ? null : Validators.pattern(HOUR_REGEXP));
5349
5445
  // Get patterns to display date and date+time
5350
5446
  this._subscription.add(this.translate.get(['COMMON.DATE_PATTERN', 'COMMON.DATE_TIME_PATTERN'])
5351
5447
  .subscribe((patterns) => this.updatePattern(patterns)));
@@ -5528,7 +5624,9 @@ class MatDateTime {
5528
5624
  if (incompleteValue || this.dayControl.invalid || this.hourControl.invalid) {
5529
5625
  this.formControl.markAsPending({ onlySelf: true });
5530
5626
  this.formControl.setErrors(Object.assign(Object.assign(Object.assign({ validDate: incompleteValue }, this.formControl.errors), this.dayControl.errors), this.hourControl.errors));
5531
- this.formControl.markAsDirty();
5627
+ if (this.dayControl.dirty || this.hourControl.dirty) {
5628
+ this.formControl.markAsDirty();
5629
+ }
5532
5630
  this._writing = false;
5533
5631
  return;
5534
5632
  }
@@ -5907,7 +6005,7 @@ class MatDateShort {
5907
6005
  MatDateShort.decorators = [
5908
6006
  { type: Component, args: [{
5909
6007
  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",
6008
+ 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
6009
  providers: [
5912
6010
  DEFAULT_VALUE_ACCESSOR$2,
5913
6011
  ],
@@ -7045,8 +7143,8 @@ class MatSwipeField {
7045
7143
  this.required = false;
7046
7144
  this.clearable = false;
7047
7145
  this.onClick = new EventEmitter();
7048
- this.onBlur = new EventEmitter();
7049
- this.onFocus = new EventEmitter();
7146
+ this.blurred = new EventEmitter();
7147
+ this.focused = new EventEmitter();
7050
7148
  this.selectInputContent = selectInputContent;
7051
7149
  }
7052
7150
  set tabindex(value) {
@@ -7314,8 +7412,8 @@ MatSwipeField.propDecorators = {
7314
7412
  tabindex: [{ type: Input }],
7315
7413
  items: [{ type: Input }],
7316
7414
  onClick: [{ type: Output, args: ['click',] }],
7317
- onBlur: [{ type: Output, args: ['blur',] }],
7318
- onFocus: [{ type: Output, args: ['focus',] }],
7415
+ blurred: [{ type: Output, args: ['blur',] }],
7416
+ focused: [{ type: Output, args: ['focus',] }],
7319
7417
  fakeInput: [{ type: ViewChild, args: ['fakeInput',] }],
7320
7418
  slides: [{ type: ViewChild, args: ['slides', { static: true },] }],
7321
7419
  prevButton: [{ type: ViewChild, args: ['prevButton',] }],
@@ -8276,7 +8374,8 @@ AppGestureConfig.decorators = [
8276
8374
  class ToolbarToken {
8277
8375
  }
8278
8376
  class ToolbarComponent {
8279
- constructor(router, routerOutlet, cd, progressBarService) {
8377
+ constructor(route, router, routerOutlet, cd, progressBarService) {
8378
+ this.route = route;
8280
8379
  this.router = router;
8281
8380
  this.routerOutlet = routerOutlet;
8282
8381
  this.cd = cd;
@@ -8362,15 +8461,28 @@ class ToolbarComponent {
8362
8461
  return __awaiter(this, void 0, void 0, function* () {
8363
8462
  console.debug('[toolbar] calling goBack()');
8364
8463
  if (this._backHref) {
8365
- yield this.router.navigateByUrl(this._backHref);
8464
+ const replaceUrl = !this.routerOutlet.canGoBack();
8465
+ // Relative href
8466
+ if (this._backHref.startsWith('.')) {
8467
+ yield this.router.navigate([this._backHref], { relativeTo: this.route, replaceUrl });
8468
+ }
8469
+ else {
8470
+ yield this.router.navigateByUrl(this._backHref, { replaceUrl });
8471
+ }
8366
8472
  }
8367
8473
  else if (this.routerOutlet.canGoBack()) {
8368
8474
  yield this.routerOutlet.pop();
8369
8475
  }
8370
8476
  else if (this._defaultBackHref) {
8371
- yield this.router.navigateByUrl(this._defaultBackHref, {
8372
- replaceUrl: true
8373
- });
8477
+ // Relative href
8478
+ if (this._defaultBackHref.startsWith('.')) {
8479
+ yield this.router.navigate([this._defaultBackHref], { relativeTo: this.route, replaceUrl: true });
8480
+ }
8481
+ else {
8482
+ yield this.router.navigateByUrl(this._defaultBackHref, {
8483
+ replaceUrl: true
8484
+ });
8485
+ }
8374
8486
  }
8375
8487
  else {
8376
8488
  console.error('[toolbar] Cannot go back. Missing attribute \'defaultBackHref\' or \'backHref\'');
@@ -8429,7 +8541,7 @@ class ToolbarComponent {
8429
8541
  ToolbarComponent.decorators = [
8430
8542
  { type: Component, args: [{
8431
8543
  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",
8544
+ 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
8545
  providers: [
8434
8546
  { provide: ToolbarToken, useExisting: ToolbarComponent }
8435
8547
  ],
@@ -8438,6 +8550,7 @@ ToolbarComponent.decorators = [
8438
8550
  },] }
8439
8551
  ];
8440
8552
  ToolbarComponent.ctorParameters = () => [
8553
+ { type: ActivatedRoute },
8441
8554
  { type: Router },
8442
8555
  { type: IonRouterOutlet },
8443
8556
  { type: ChangeDetectorRef },
@@ -8459,7 +8572,8 @@ ToolbarComponent.propDecorators = {
8459
8572
  onValidateAndClose: [{ type: Output }],
8460
8573
  onBackClick: [{ type: Output }],
8461
8574
  onSearch: [{ type: Output }],
8462
- searchbar: [{ type: ViewChild, args: ['searchbar', { static: true },] }]
8575
+ searchbar: [{ type: ViewChild, args: ['searchbar', { static: true },] }],
8576
+ menuToggle: [{ type: ViewChild, args: ['menuToggle', { static: true },] }]
8463
8577
  };
8464
8578
 
8465
8579
  class MatPaginatorI18n extends MatPaginatorIntl {
@@ -8746,7 +8860,7 @@ class AppFormField {
8746
8860
  AppFormField.decorators = [
8747
8861
  { type: Component, args: [{
8748
8862
  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",
8863
+ 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
8864
  providers: [
8751
8865
  {
8752
8866
  provide: NG_VALUE_ACCESSOR,
@@ -9730,10 +9844,6 @@ class EntityUtils {
9730
9844
  return isNotNil(obj.__typename) && (typeof obj.fromObject === 'function') && (typeof obj.asObject === 'function');
9731
9845
  }
9732
9846
  // 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
9847
  static isNotEmpty(obj, checkedAttribute) {
9738
9848
  return !!obj && obj[checkedAttribute] !== null && obj[checkedAttribute] !== undefined;
9739
9849
  }
@@ -9813,6 +9923,8 @@ class EntityUtils {
9813
9923
  (items || []).forEach(EntityUtils.cleanIdAndUpdateDate);
9814
9924
  }
9815
9925
  static sort(data, sortBy, sortDirection) {
9926
+ if (!data || data.length <= 1 || !sortBy)
9927
+ return data;
9816
9928
  return data.sort(this.sortComparator(sortBy, sortDirection));
9817
9929
  }
9818
9930
  static sortComparator(sortBy, sortDirection) {
@@ -11837,11 +11949,10 @@ class EntityStore {
11837
11949
  /**
11838
11950
  *
11839
11951
  * @param opts
11840
- * opts.force : force all entities to be persist, and not only if dirty
11952
+ * opts.skipIfPristine = false persist all entities, even if not dirty
11841
11953
  */
11842
- persist(opts) {
11954
+ persist(opts = { skipIfPristine: true }) {
11843
11955
  return __awaiter(this, void 0, void 0, function* () {
11844
- opts = Object.assign({ skipIfPristine: this.policy.skipNonLocalEntities }, opts);
11845
11956
  // Skip is not dirty
11846
11957
  if (opts.skipIfPristine && !this._dirty) {
11847
11958
  console.debug(`[entity-storage] Persisting ${this.name} not need. Skip`);
@@ -12051,7 +12162,7 @@ class EntityStore {
12051
12162
  if (variables.size === 0)
12052
12163
  return { data: [], total };
12053
12164
  // Sort by
12054
- if (data.length && variables.sortBy) {
12165
+ if (variables.sortBy) {
12055
12166
  EntityUtils.sort(data, variables.sortBy, variables.sortDirection);
12056
12167
  }
12057
12168
  // Slice in a page (using offset and size)
@@ -12124,6 +12235,33 @@ class EntitiesStorage extends StartableService {
12124
12235
  get dirty() {
12125
12236
  return this._dirty || Object.values(this._data).some(store => store.dirty);
12126
12237
  }
12238
+ ngOnStart() {
12239
+ return __awaiter(this, void 0, void 0, function* () {
12240
+ const now = Date.now();
12241
+ console.info(`[entities-storage] Starting entity storage...`);
12242
+ // Restore stores
12243
+ yield this.restoreLocally();
12244
+ // Start a save timer
12245
+ this.registerSubscription(merge(this._$save, timer(this._saveTimerPeriod, this._saveTimerPeriod))
12246
+ .pipe(
12247
+ // Avoid to many call (e.g. when $save AND timer are triggered
12248
+ throttleTime(this._saveTimerPeriod))
12249
+ .subscribe(() => this.storeLocally({ skipIfPristine: true })));
12250
+ // Save, when platform is in pause
12251
+ this.registerSubscription(this.platform.pause.subscribe(() => __awaiter(this, void 0, void 0, function* () {
12252
+ console.warn(`[entities-storage] Platform will pause: saving...`);
12253
+ yield this.storeLocally({ skipIfPristine: true });
12254
+ console.warn(`[entities-storage] Platform will pause: saving [OK]`);
12255
+ })));
12256
+ console.info(`[entities-storage] Starting [OK] in ${Date.now() - now}ms`);
12257
+ return this._data;
12258
+ });
12259
+ }
12260
+ ngOnStop() {
12261
+ return __awaiter(this, void 0, void 0, function* () {
12262
+ yield this.storeLocally({ skipIfPristine: true });
12263
+ });
12264
+ }
12127
12265
  watchAll(entityName, variables, opts) {
12128
12266
  // Make sure store is ready
12129
12267
  if (!this.started) {
@@ -12389,34 +12527,8 @@ class EntitiesStorage extends StartableService {
12389
12527
  this._$save.emit();
12390
12528
  });
12391
12529
  }
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
- });
12530
+ persist(opts = { skipIfPristine: true }) {
12531
+ return this.storeLocally(opts);
12420
12532
  }
12421
12533
  /* -- protected methods -- */
12422
12534
  getEntityStore(name, opts) {
@@ -12455,9 +12567,9 @@ class EntitiesStorage extends StartableService {
12455
12567
  console.debug(`[entities-storage] Restoring entities [OK] ${entitiesCount} entities found in ${Date.now() - now}ms`);
12456
12568
  });
12457
12569
  }
12458
- storeLocally() {
12570
+ storeLocally(opts = { skipIfPristine: true }) {
12459
12571
  return __awaiter(this, void 0, void 0, function* () {
12460
- if (!this.dirty)
12572
+ if (opts.skipIfPristine && !this.dirty)
12461
12573
  return; // skip
12462
12574
  // Saving progress: report this save later
12463
12575
  if (this._saving) {
@@ -12481,11 +12593,11 @@ class EntitiesStorage extends StartableService {
12481
12593
  console.warn(`[entities-storage] Persisting ${entityName}: store not found!`);
12482
12594
  return;
12483
12595
  }
12484
- // SKip store if not dirty
12485
- if (!entityStore.dirty)
12486
- return Promise.resolve(); // Skip
12596
+ // SKip store if pristine
12597
+ if (opts.skipIfPristine && !entityStore.dirty)
12598
+ return Promise.resolve();
12487
12599
  // Persist store
12488
- return (entityStore.dirty ? entityStore.persist() : Promise.resolve(entityStore.length))
12600
+ return entityStore.persist(opts)
12489
12601
  .then(count => {
12490
12602
  // If no entity found, remove from the entity names array
12491
12603
  if (!count) {
@@ -12734,9 +12846,6 @@ class ReferentialUtils {
12734
12846
  // A referential entity should always have a 'id' filled (can be negative is local and temporary)
12735
12847
  return EntityUtils.isNotEmpty(obj, 'id');
12736
12848
  }
12737
- static isNotEmptyReferential(obj) {
12738
- return EntityUtils.isNotEmpty(obj, 'id');
12739
- }
12740
12849
  static isEmpty(obj) {
12741
12850
  return EntityUtils.isEmpty(obj, 'id');
12742
12851
  }
@@ -14637,7 +14746,7 @@ class AccountService extends BaseGraphqlService {
14637
14746
  return this._$additionalFields.getValue();
14638
14747
  }
14639
14748
  get $additionalFields() {
14640
- return this._$additionalFields;
14749
+ return this._$additionalFields.asObservable();
14641
14750
  }
14642
14751
  getAdditionalField(key) {
14643
14752
  return this._$additionalFields.getValue().find(f => f.key === key);
@@ -15684,6 +15793,196 @@ ConfigService.ctorParameters = () => [
15684
15793
  { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [APP_CONFIG_OPTIONS,] }] }
15685
15794
  ];
15686
15795
 
15796
+ class UploadFile extends File {
15797
+ }
15798
+ class FileResponse {
15799
+ constructor(init) {
15800
+ this.type = HttpEventType.Response;
15801
+ this.body = isNotNil(init === null || init === void 0 ? void 0 : init.body) ? init.body : null;
15802
+ this.status = (init === null || init === void 0 ? void 0 : init.status) || 0;
15803
+ this.statusText = (init === null || init === void 0 ? void 0 : init.statusText) || 'OK';
15804
+ }
15805
+ }
15806
+ function isProgressEvent(event) {
15807
+ return event && (event.type === HttpEventType.UploadProgress
15808
+ || event.type === HttpEventType.DownloadProgress);
15809
+ }
15810
+ function isResponseEvent(event) {
15811
+ return event && event.type === HttpEventType.Response;
15812
+ }
15813
+
15814
+ class UploadFilePopover {
15815
+ constructor(popoverController, translate, cd) {
15816
+ this.popoverController = popoverController;
15817
+ this.translate = translate;
15818
+ this.cd = cd;
15819
+ this.uniqueFile = false;
15820
+ this.instantUpload = false;
15821
+ this.importing = false;
15822
+ }
15823
+ get files() {
15824
+ return this.uploader.files;
15825
+ }
15826
+ get disabled() {
15827
+ return this.importing || isEmptyArray(this.files);
15828
+ }
15829
+ get valid() {
15830
+ return isNotEmptyArray(this.files);
15831
+ }
15832
+ onValidate(event) {
15833
+ return __awaiter(this, void 0, void 0, function* () {
15834
+ event === null || event === void 0 ? void 0 : event.stopPropagation();
15835
+ // Avoid multiple call
15836
+ if (this.importing)
15837
+ return;
15838
+ console.debug('[upload-file-popover] Validate form: will upload all files...');
15839
+ try {
15840
+ this.importing = true;
15841
+ this.resetError();
15842
+ try {
15843
+ // Wait processing files finished
15844
+ yield this.uploader.waitIdle();
15845
+ // upload all files
15846
+ yield this.uploader.uploadFiles();
15847
+ const files = (this.uploader.processedFiles || [])
15848
+ .filter(file => !file.deleting); // Remove deleting file (deletion in progress)
15849
+ // Nothing to upload: close popover
15850
+ if (isEmptyArray(files))
15851
+ return this.cancel();
15852
+ // check errors
15853
+ const errors = files.map(file => file.error).filter(isNotNilOrBlank);
15854
+ // If error: stop
15855
+ if (errors.length) {
15856
+ errors.forEach((error) => console.error(error));
15857
+ throw { message: 'FILE.UPLOAD.ERROR' };
15858
+ }
15859
+ // return files
15860
+ yield this.popoverController.dismiss(files);
15861
+ }
15862
+ catch (err) {
15863
+ this.error = (err && err.message) || err;
15864
+ }
15865
+ }
15866
+ finally {
15867
+ this.importing = false;
15868
+ this.cd.markForCheck();
15869
+ }
15870
+ });
15871
+ }
15872
+ cancel() {
15873
+ return this.popoverController.dismiss();
15874
+ }
15875
+ resetError() {
15876
+ if (this.error) {
15877
+ this.error = null;
15878
+ this.cd.markForCheck();
15879
+ }
15880
+ }
15881
+ }
15882
+ UploadFilePopover.decorators = [
15883
+ { type: Component, args: [{
15884
+ selector: 'app-upload-file-popover',
15885
+ 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",
15886
+ encapsulation: ViewEncapsulation.None,
15887
+ changeDetection: ChangeDetectionStrategy.OnPush,
15888
+ styles: [".importing-info{-webkit-padding-start:var(--ion-padding);padding-inline-start:var(--ion-padding);display:inline-flex;flex-wrap:nowrap;padding-top:8px}"]
15889
+ },] }
15890
+ ];
15891
+ UploadFilePopover.ctorParameters = () => [
15892
+ { type: PopoverController },
15893
+ { type: TranslateService },
15894
+ { type: ChangeDetectorRef }
15895
+ ];
15896
+ UploadFilePopover.propDecorators = {
15897
+ uploader: [{ type: ViewChild, args: ['uploader', { static: true },] }],
15898
+ fileExtension: [{ type: Input }],
15899
+ title: [{ type: Input }],
15900
+ uniqueFile: [{ type: Input }],
15901
+ instantUpload: [{ type: Input }],
15902
+ uploadFn: [{ type: Input }],
15903
+ deleteFn: [{ type: Input }],
15904
+ maxParallelUpload: [{ type: Input }]
15905
+ };
15906
+
15907
+ const MimeTypes = Object.freeze({
15908
+ ANDROID_APK: 'application/vnd.android.package-archive'
15909
+ });
15910
+ class FilesUtils {
15911
+ static showUploadPopover(popoverController, event, opts) {
15912
+ return __awaiter(this, void 0, void 0, function* () {
15913
+ const modal = yield popoverController.create({
15914
+ component: UploadFilePopover,
15915
+ componentProps: opts,
15916
+ backdropDismiss: false,
15917
+ keyboardClose: false,
15918
+ event,
15919
+ translucent: true,
15920
+ cssClass: 'popover-large',
15921
+ });
15922
+ yield modal.present();
15923
+ const { data, role } = yield modal.onDidDismiss();
15924
+ return {
15925
+ role,
15926
+ data: data ? data : undefined
15927
+ };
15928
+ });
15929
+ }
15930
+ static readAsText(file, encoding) {
15931
+ const $progress = new Subject();
15932
+ const reader = new FileReader();
15933
+ encoding = (encoding || 'UTF-8').toLowerCase();
15934
+ // Listen progress
15935
+ reader.onprogress = (e) => {
15936
+ const total = e.total || 1;
15937
+ const loaded = e.total || 0;
15938
+ if (loaded < total)
15939
+ $progress.next({ type: HttpEventType.UploadProgress, loaded, total });
15940
+ };
15941
+ // Listen end
15942
+ reader.onloadend = (e) => {
15943
+ const body = reader.result;
15944
+ if (typeof body === 'string') {
15945
+ $progress.next(new FileResponse({ body }));
15946
+ $progress.complete();
15947
+ }
15948
+ else {
15949
+ console.error(`Cannot read file '${file.name}' as text`);
15950
+ $progress.error(`Cannot read file as text`);
15951
+ }
15952
+ };
15953
+ // Listen error
15954
+ reader.onerror = (event) => {
15955
+ console.error('[files] Error while reading file:', event);
15956
+ $progress.error(event);
15957
+ };
15958
+ // Start reading the file
15959
+ reader.readAsText(file, encoding);
15960
+ return $progress.asObservable();
15961
+ }
15962
+ }
15963
+ class UriUtils {
15964
+ /**
15965
+ * Extract the filename, from an uri (using the last slash)
15966
+ * @param uri
15967
+ */
15968
+ static getFilename(uri) {
15969
+ if (!uri)
15970
+ return uri;
15971
+ let filename = uri.trim();
15972
+ // Get last part (or all string, if no '/')
15973
+ const lastSlashIndex = filename.lastIndexOf('/');
15974
+ if (lastSlashIndex !== -1 && lastSlashIndex !== uri.length - 1) {
15975
+ filename = filename.substring(lastSlashIndex + 1);
15976
+ }
15977
+ // Remove query params
15978
+ const queryParamIndex = filename.indexOf('?');
15979
+ if (queryParamIndex !== -1) {
15980
+ filename = filename.substring(0, queryParamIndex);
15981
+ }
15982
+ return filename;
15983
+ }
15984
+ }
15985
+
15687
15986
  const moment$4 = momentImported;
15688
15987
  const AndroidOsEnvironment = Object.freeze({
15689
15988
  DIRECTORY_DOWNLOADS: 'Download',
@@ -15720,7 +16019,7 @@ class PlatformService extends StartableService {
15720
16019
  switch (platformName) {
15721
16020
  case 'mobile':
15722
16021
  case 'mobileweb':
15723
- // Exclude Windows touch screens - see SUMARIS issue #323
16022
+ // Exclude Windows touch screens - see sumaris-app issues #323 and #346
15724
16023
  return this.platform.is(platformName) && !isWindows(window);
15725
16024
  default:
15726
16025
  return this.platform.is(platformName);
@@ -15774,6 +16073,7 @@ class PlatformService extends StartableService {
15774
16073
  this._cordova = this.is('cordova');
15775
16074
  this._android = this.is('android');
15776
16075
  this._ios = this.is('ios');
16076
+ this.configureClassList(this._mobile);
15777
16077
  this.configureCordovaPlugins();
15778
16078
  // Force some settings
15779
16079
  this.settings.mobile = this._mobile;
@@ -15808,7 +16108,7 @@ class PlatformService extends StartableService {
15808
16108
  this.accountService.tokenType = tokenType;
15809
16109
  });
15810
16110
  // Hide the splashscreen (if mobile) - after 1s - and play start sound
15811
- if (this.mobile) {
16111
+ if (this._mobile) {
15812
16112
  setTimeout(() => {
15813
16113
  var _a;
15814
16114
  (_a = this.splashScreen) === null || _a === void 0 ? void 0 : _a.hide();
@@ -15844,34 +16144,48 @@ class PlatformService extends StartableService {
15844
16144
  return this._downloadingJobs.get(uri);
15845
16145
  }
15846
16146
  download(request) {
16147
+ var _a, _b;
15847
16148
  if (!request || !request.uri)
15848
16149
  throw new Error('Missing argument \'request\' or \'request.uri\'');
15849
16150
  if (this._android && this.downloader) {
15850
16151
  // 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));
16152
+ let job$ = this._downloadingJobs.get(request.uri);
16153
+ if (job$)
16154
+ return job$;
16155
+ // Compute subPath (final filename)
16156
+ let subPath = ((_a = request.destinationInExternalPublicDir) === null || _a === void 0 ? void 0 : _a.subPath) || ((_b = request.destinationInExternalFilesDir) === null || _b === void 0 ? void 0 : _b.subPath)
16157
+ || request.filename || UriUtils.getFilename(request.uri)
16158
+ || request.title || 'download';
16159
+ subPath = subPath.toLowerCase()
16160
+ // Remove spaces
16161
+ .replace(/\s+/g, '-');
16162
+ // Force APK to finish with '.apk'
16163
+ if (request.mimeType === MimeTypes.ANDROID_APK && !subPath.toLowerCase().endsWith('.apk')) {
16164
+ subPath += '.apk';
16165
+ }
16166
+ const downloadRequest = Object.assign(Object.assign({ visibleInDownloadsUi: true, notificationVisibility: NotificationVisibility.VisibleNotifyCompleted }, request), { filename: undefined, destinationInExternalPublicDir: !request.destinationInExternalFilesDir
16167
+ ? Object.assign(Object.assign({ dirType: AndroidOsEnvironment.DIRECTORY_DOWNLOADS }, request.destinationInExternalPublicDir), { subPath }) : undefined, destinationInExternalFilesDir: request.destinationInExternalFilesDir
16168
+ ? Object.assign(Object.assign({ dirType: AndroidOsEnvironment.DIRECTORY_DOWNLOADS }, request.destinationInExternalFilesDir), { subPath }) : undefined });
15856
16169
  // Start download
15857
- location$ = from(this.downloader.download(request));
16170
+ console.debug('[platform] Downloading, using request: ' + JSON.stringify(downloadRequest));
16171
+ job$ = from(this.downloader.download(downloadRequest));
15858
16172
  // Remember this request uri
15859
- this._downloadingJobs.set(request.uri, location$);
15860
- return location$
16173
+ this._downloadingJobs.set(downloadRequest.uri, job$);
16174
+ return job$
15861
16175
  .pipe(tap(location => {
15862
16176
  console.info('[platform] File successfully downloaded at:' + location);
15863
- // Forget the request
15864
- this._downloadingJobs.delete(request.uri);
16177
+ // Forget job
16178
+ this._downloadingJobs.delete(downloadRequest.uri);
15865
16179
  return location;
15866
16180
  }), catchError(err => {
15867
16181
  var _a;
15868
16182
  console.error('[platform] Unable to download: ' + (err && err.message || err));
15869
16183
  // Forget the request
15870
- this._downloadingJobs.delete(request.uri);
16184
+ this._downloadingJobs.delete(downloadRequest.uri);
15871
16185
  // 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);
16186
+ if (((_a = downloadRequest.destinationInExternalPublicDir) === null || _a === void 0 ? void 0 : _a.dirType) === AndroidOsEnvironment.DIRECTORY_DOWNLOADS) {
16187
+ downloadRequest.destinationInExternalPublicDir.dirType = AndroidOsEnvironment.DIRECTORY_DOWNLOADS_OLD;
16188
+ return this.download(downloadRequest);
15875
16189
  }
15876
16190
  throw err;
15877
16191
  }));
@@ -15896,6 +16210,17 @@ class PlatformService extends StartableService {
15896
16210
  //}
15897
16211
  }
15898
16212
  /* -- protected methods -- */
16213
+ configureClassList(mobile, win = window) {
16214
+ var _a, _b;
16215
+ if (mobile)
16216
+ return; // Nothing to do
16217
+ // Workaround, for MS Windows touch screen (detected as mobile, but forced as desktop)
16218
+ 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')))) {
16219
+ console.warn('[platform] Force class list to: <html class="plt-desktop">. Workaround for Windows touch screen.');
16220
+ win.document.documentElement.classList.remove('plt-mobile', 'plt-mobileweb');
16221
+ win.document.documentElement.classList.add('plt-desktop');
16222
+ }
16223
+ }
15899
16224
  configureCordovaPlugins() {
15900
16225
  console.info('[platform] Configuring Cordova plugins...');
15901
16226
  if (this.statusBar) {
@@ -16223,24 +16548,6 @@ MatStepperI18n.ctorParameters = () => [
16223
16548
  { type: TranslateService }
16224
16549
  ];
16225
16550
 
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
16551
  class UploadFileComponent {
16245
16552
  constructor(cd, translate) {
16246
16553
  this.cd = cd;
@@ -16435,99 +16742,6 @@ UploadFileComponent.propDecorators = {
16435
16742
  maxParallelUpload: [{ type: Input }]
16436
16743
  };
16437
16744
 
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
16745
  const APP_TESTING_PAGES = new InjectionToken('testingPages');
16532
16746
  class SharedTestsPage {
16533
16747
  constructor(route, router, translate, pages) {
@@ -16910,8 +17124,8 @@ class ComponentDirtyGuard {
16910
17124
  }
16911
17125
  canDeactivate(component, currentRoute, currentState, nextState) {
16912
17126
  return __awaiter(this, void 0, void 0, function* () {
16913
- const dirty = component.dirty;
16914
- const valid = component.valid;
17127
+ const dirty = component === null || component === void 0 ? void 0 : component.dirty;
17128
+ const valid = component === null || component === void 0 ? void 0 : component.valid;
16915
17129
  let can = {
16916
17130
  confirmed: !dirty,
16917
17131
  save: false,
@@ -17791,6 +18005,7 @@ class JobUtils {
17791
18005
  return progression;
17792
18006
  }
17793
18007
  static fetchAllPages(loadPageFn, opts) {
18008
+ var _a, _b;
17794
18009
  return __awaiter(this, void 0, void 0, function* () {
17795
18010
  const maxProgression = toNumber(opts === null || opts === void 0 ? void 0 : opts.maxProgression, 100);
17796
18011
  const progression = opts === null || opts === void 0 ? void 0 : opts.progression;
@@ -17823,7 +18038,7 @@ class JobUtils {
17823
18038
  progressionStep = maxProgression / pageCount;
17824
18039
  // DEBUG
17825
18040
  if (opts && opts.logPrefix)
17826
- console.debug(`${opts.logPrefix} {pageCount: ${pageCount}, progression: ${progression.value}, pageProgressionStep: ${progressionStep}}`);
18041
+ 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
18042
  }
17828
18043
  }
17829
18044
  // Increment progression on each iteration (if possible)
@@ -17850,7 +18065,7 @@ class JobUtils {
17850
18065
  }
17851
18066
  // DEBUG
17852
18067
  if (opts && opts.logPrefix)
17853
- console.debug(`${opts.logPrefix} All pages fetched - {progression: ${progression.value}, lastProgressionStep: ${progressionStep}`);
18068
+ 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
18069
  // Final increment
17855
18070
  if (progression && progressionStep > 0) {
17856
18071
  progression.next(progression.value + progressionStep);
@@ -17863,60 +18078,6 @@ class JobUtils {
17863
18078
  }
17864
18079
  }
17865
18080
 
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
18081
  /**
17921
18082
  * Define here theme colors
17922
18083
  */
@@ -18723,7 +18884,7 @@ HomePage.decorators = [
18723
18884
  { type: Component, args: [{
18724
18885
  moduleId: module.id.toString(),
18725
18886
  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",
18887
+ 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
18888
  changeDetection: ChangeDetectionStrategy.OnPush,
18728
18889
  animations: [fadeInAnimation, slideUpDownAnimation],
18729
18890
  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 +19131,9 @@ class AccountPage extends AppForm {
18970
19131
  this.setValue(this.accountService.account);
18971
19132
  this.markAsPristine();
18972
19133
  }));
18973
- this.registerSubscription(this.accountService.$additionalFields.subscribe((additionalFields) => {
18974
- this.additionalFields = additionalFields.slice();
19134
+ this.registerSubscription(this.accountService.$additionalFields
19135
+ .subscribe((additionalFields) => {
19136
+ this.additionalFields = additionalFields.slice(); // Copy
18975
19137
  if (accountService.isLogin()) {
18976
19138
  this.onLogin(this.accountService.account);
18977
19139
  }
@@ -19105,9 +19267,21 @@ class AccountPage extends AppForm {
19105
19267
  this.form.controls.pubkey.disable();
19106
19268
  // Always disable some additional fields
19107
19269
  this.additionalFields
19108
- .filter(field => field.extra && field.extra.account && field.extra.account.disabled || false)
19109
19270
  .forEach(field => {
19110
- this.form.controls[field.key].disable();
19271
+ var _a, _b;
19272
+ const control = this.form.controls[field.key];
19273
+ if (!control)
19274
+ return; // Skip
19275
+ const disabled = ((_a = field.extra) === null || _a === void 0 ? void 0 : _a.account) && field.extra.account.disabled || false;
19276
+ if (disabled && control.enabled) {
19277
+ control.disable();
19278
+ }
19279
+ const required = ((_b = field.extra) === null || _b === void 0 ? void 0 : _b.account) && field.extra.account.required || false;
19280
+ if (!field.required && required) {
19281
+ control.setValidators(control.validator
19282
+ ? Validators.compose([control.validator, Validators.required])
19283
+ : Validators.required);
19284
+ }
19111
19285
  });
19112
19286
  this.markForCheck();
19113
19287
  }
@@ -19164,11 +19338,62 @@ EntityMetadataComponent.propDecorators = {
19164
19338
  showRecorder: [{ type: Input }]
19165
19339
  };
19166
19340
 
19341
+ const DEFAULT_MENU_SHOW_WHEN = 'lg';
19342
+ class MenuService {
19343
+ constructor() {
19344
+ this._$toggled = new Subject();
19345
+ this._$opened = new Subject();
19346
+ this._$splitPaneWhen = new BehaviorSubject(DEFAULT_MENU_SHOW_WHEN);
19347
+ this._$enabled = new BehaviorSubject(true);
19348
+ }
19349
+ get opened() {
19350
+ return this._$opened.asObservable();
19351
+ }
19352
+ get splitPaneWhen() {
19353
+ return this._$splitPaneWhen.asObservable();
19354
+ }
19355
+ get enabled() {
19356
+ return this._$enabled.asObservable();
19357
+ }
19358
+ toggle() {
19359
+ this._$toggled.next();
19360
+ }
19361
+ enable(value) {
19362
+ this._$enabled.next(value);
19363
+ }
19364
+ setSplitPaneShowWhen(value) {
19365
+ if (this._$splitPaneWhen.value !== value) {
19366
+ this._$splitPaneWhen.next(value);
19367
+ }
19368
+ }
19369
+ toggleSplitPaneWhen() {
19370
+ if (this._$splitPaneWhen.value !== false) {
19371
+ this._$splitPaneWhen.next(false);
19372
+ }
19373
+ else {
19374
+ this._$splitPaneWhen.next(DEFAULT_MENU_SHOW_WHEN);
19375
+ }
19376
+ }
19377
+ _markAsOpened() {
19378
+ this._$opened.next(true);
19379
+ }
19380
+ _markAsClosed() {
19381
+ this._$opened.next(false);
19382
+ }
19383
+ }
19384
+ MenuService.ɵprov = ɵɵdefineInjectable({ factory: function MenuService_Factory() { return new MenuService(); }, token: MenuService, providedIn: "root" });
19385
+ MenuService.decorators = [
19386
+ { type: Injectable, args: [{
19387
+ providedIn: 'root'
19388
+ },] }
19389
+ ];
19390
+
19167
19391
  class FormButtonsBarToken {
19168
19392
  }
19169
19393
  class FormButtonsBarComponent {
19170
- constructor(hotkeys) {
19394
+ constructor(hotkeys, menu) {
19171
19395
  this.hotkeys = hotkeys;
19396
+ this.menu = menu;
19172
19397
  this._subscription = new Subscription();
19173
19398
  this.disabled = false;
19174
19399
  this.disabledCancel = false;
@@ -19200,15 +19425,16 @@ class FormButtonsBarComponent {
19200
19425
  FormButtonsBarComponent.decorators = [
19201
19426
  { type: Component, args: [{
19202
19427
  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",
19428
+ 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
19429
  providers: [
19205
19430
  { provide: FormButtonsBarToken, useExisting: FormButtonsBarComponent }
19206
19431
  ],
19207
- styles: [":host{width:100%}:host ion-toolbar{height:var(--app-form-buttons-bar-height);padding:0 6px}:host ion-col{padding:0}"]
19432
+ 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
19433
  },] }
19209
19434
  ];
19210
19435
  FormButtonsBarComponent.ctorParameters = () => [
19211
- { type: Hotkeys }
19436
+ { type: Hotkeys },
19437
+ { type: MenuService }
19212
19438
  ];
19213
19439
  FormButtonsBarComponent.propDecorators = {
19214
19440
  disabled: [{ type: Input }],
@@ -20084,7 +20310,7 @@ AppListForm.decorators = [
20084
20310
  selector: 'app-list-form',
20085
20311
  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
20312
  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}"]
20313
+ 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
20314
  },] }
20089
20315
  ];
20090
20316
  AppListForm.ctorParameters = () => [
@@ -20145,9 +20371,6 @@ PersonToStringPipe.decorators = [
20145
20371
  { type: Injectable, args: [{ providedIn: 'root' },] }
20146
20372
  ];
20147
20373
 
20148
- const MimeTypes = Object.freeze({
20149
- ANDROID_APK: 'application/vnd.android.package-archive'
20150
- });
20151
20374
  class AppInstallUpgradeCard {
20152
20375
  constructor(modalCtrl, configService, toastController, alertController, translate, cd, platform, network, environment) {
20153
20376
  this.modalCtrl = modalCtrl;
@@ -20160,7 +20383,6 @@ class AppInstallUpgradeCard {
20160
20383
  this.network = network;
20161
20384
  this.environment = environment;
20162
20385
  this._subscription = new Subscription();
20163
- this._showUpdateOfflineFeature = false;
20164
20386
  this.loading = true;
20165
20387
  this.hasDownloader = false;
20166
20388
  this.downloading = false;
@@ -20168,16 +20390,6 @@ class AppInstallUpgradeCard {
20168
20390
  this.showUpgradeWarning = true;
20169
20391
  this.showOfflineWarning = true;
20170
20392
  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
20393
  }
20182
20394
  ngOnInit() {
20183
20395
  this.offline = this.network.offline;
@@ -20201,7 +20413,6 @@ class AppInstallUpgradeCard {
20201
20413
  ngOnDestroy() {
20202
20414
  this._subscription.unsubscribe();
20203
20415
  this._subscription = null;
20204
- this.onUpdateOfflineModeClick.complete();
20205
20416
  this.installLinks = null;
20206
20417
  this.upgradeLinks = null;
20207
20418
  }
@@ -20352,10 +20563,10 @@ class AppInstallUpgradeCard {
20352
20563
  let title;
20353
20564
  let mimeType;
20354
20565
  // Get file name
20355
- let filename = this.getFilename(url);
20566
+ let filename = UriUtils.getFilename(url);
20356
20567
  let version = minVersion || 'latest';
20357
20568
  // 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')) {
20569
+ if (filename === null || filename === void 0 ? void 0 : filename.toLowerCase().endsWith('.apk')) {
20359
20570
  // Define mime type
20360
20571
  mimeType = MimeTypes.ANDROID_APK;
20361
20572
  // Get the file version (if any)
@@ -20366,11 +20577,9 @@ class AppInstallUpgradeCard {
20366
20577
  filename = `${name}-v${version}.apk`.toLowerCase();
20367
20578
  title = `${name} v${version}`;
20368
20579
  }
20369
- else {
20370
- // Replace 'latest' with the app version, if present
20371
- if (filename.indexOf('latest')) {
20372
- filename = filename.replace('latest', version);
20373
- }
20580
+ // Replace 'latest' with the app version, if present in the filename
20581
+ else if (filename.indexOf('latest') && version !== 'latest') {
20582
+ filename = filename.replace('latest', version);
20374
20583
  }
20375
20584
  }
20376
20585
  result.push({ name, url, platform: 'android', version, filename, title, mimeType });
@@ -20382,18 +20591,6 @@ class AppInstallUpgradeCard {
20382
20591
  //}
20383
20592
  return result;
20384
20593
  }
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
20594
  listenDownloadJobs() {
20398
20595
  // Listening downloading promise, if exists
20399
20596
  (this.upgradeLinks || []).forEach(link => {
@@ -20491,7 +20688,7 @@ class AppInstallUpgradeCard {
20491
20688
  AppInstallUpgradeCard.decorators = [
20492
20689
  { type: Component, args: [{
20493
20690
  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",
20691
+ 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
20692
  changeDetection: ChangeDetectionStrategy.OnPush,
20496
20693
  animations: [slideUpDownAnimation],
20497
20694
  styles: ["ion-text small{font-size:85%}"]
@@ -20512,9 +20709,7 @@ AppInstallUpgradeCard.propDecorators = {
20512
20709
  isLogin: [{ type: Input }],
20513
20710
  showUpgradeWarning: [{ type: Input }],
20514
20711
  showOfflineWarning: [{ type: Input }],
20515
- showInstallButton: [{ type: Input }],
20516
- showUpdateOfflineFeature: [{ type: Input }],
20517
- onUpdateOfflineModeClick: [{ type: Output }]
20712
+ showInstallButton: [{ type: Input }]
20518
20713
  };
20519
20714
 
20520
20715
  class IsLoginAccountPipe {
@@ -20627,33 +20822,7 @@ ActionsColumnComponent.propDecorators = {
20627
20822
  forward: [{ type: Output }]
20628
20823
  };
20629
20824
 
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
20825
  const APP_MENU_ITEMS = new InjectionToken('menuItems');
20656
- const SPLIT_PANE_SHOW_WHEN = 'lg';
20657
20826
  class MenuComponent {
20658
20827
  constructor(platformService, accountService, router, menu, modalCtrl, alertController, translate, configService, cd, menuService, environment, items) {
20659
20828
  this.platformService = platformService;
@@ -20672,28 +20841,35 @@ class MenuComponent {
20672
20841
  this.loading = true;
20673
20842
  this.isLogin = false;
20674
20843
  this.$items = new BehaviorSubject(undefined);
20675
- this.appVersion = this.environment.version;
20844
+ this.id = 'menu';
20845
+ this.menuId = 'left';
20676
20846
  this.side = 'left';
20847
+ this.contentId = 'menu-content';
20848
+ this.appVersion = this.environment.version;
20677
20849
  this._debug = !environment.production;
20678
20850
  }
20679
20851
  ngOnInit() {
20680
20852
  return __awaiter(this, void 0, void 0, function* () {
20681
- this.splitPaneOpened = true;
20682
- this.splitPane.when = SPLIT_PANE_SHOW_WHEN;
20853
+ this.splitPane.when = yield firstNotNilPromise(this.menuService.splitPaneWhen);
20683
20854
  // Wait platform started
20684
20855
  yield this.platformService.ready();
20685
- this.splitPaneOpened = true;
20686
20856
  // Listen to menu service event
20687
- this.menuService.menuToggled$
20688
- .subscribe(() => this.toggleSplitPane());
20857
+ this.menuService.splitPaneWhen
20858
+ .subscribe((value) => this.setSplitPaneWhen(value));
20859
+ this.menuService.enabled
20860
+ .subscribe((value) => this.enable(value));
20861
+ this.menuService.opened
20862
+ // Avoid duplicated events
20863
+ .pipe(distinctUntilChanged())
20864
+ .subscribe((value) => value ? this.open() : this.close());
20689
20865
  // Update component when refresh is need (=login events or config changed)
20690
20866
  this._subscription.add(merge(this.accountService.onLogin, this.accountService.onLogout, this.configService.config
20691
20867
  .pipe(tap(config => this._config = config)))
20692
20868
  .pipe(
20693
20869
  // Wait account service ready (can be restarted)
20694
- mergeMap(() => this.accountService.ready()))
20695
- .subscribe(account => {
20696
- if (this.accountService.isLogin()) {
20870
+ mergeMap(() => this.accountService.ready()), map(() => this.accountService.isLogin()), distinctUntilChanged())
20871
+ .subscribe(login => {
20872
+ if (login) {
20697
20873
  this.onLogin(this.accountService.account);
20698
20874
  }
20699
20875
  else {
@@ -20721,8 +20897,6 @@ class MenuComponent {
20721
20897
  if (!skipRedirect)
20722
20898
  console.debug('[menu] logout');
20723
20899
  this.isLogin = false;
20724
- //this.splitPaneOpened = false;
20725
- //this.splitPane.when = false;
20726
20900
  this.accountAvatar = null;
20727
20901
  this.accountEmail = null;
20728
20902
  this.accountName = null;
@@ -20751,7 +20925,8 @@ class MenuComponent {
20751
20925
  text: translations['COMMON.BTN_CANCEL'],
20752
20926
  role: 'cancel',
20753
20927
  cssClass: 'secondary'
20754
- }, {
20928
+ },
20929
+ {
20755
20930
  text: translations['AUTH.LOGOUT.BTN_CONFIRM'],
20756
20931
  cssClass: 'ion-color-primary',
20757
20932
  handler: () => {
@@ -20766,9 +20941,40 @@ class MenuComponent {
20766
20941
  yield alert.present();
20767
20942
  });
20768
20943
  }
20769
- menuClose() {
20944
+ enable(value) {
20945
+ return __awaiter(this, void 0, void 0, function* () {
20946
+ const enabled = yield this.menu.isEnabled(this.menuId);
20947
+ if (enabled !== value) {
20948
+ yield this.menu.enable(value, this.menuId);
20949
+ if (!value)
20950
+ yield this.close();
20951
+ }
20952
+ });
20953
+ }
20954
+ open() {
20955
+ return __awaiter(this, void 0, void 0, function* () {
20956
+ console.debug('[menu] Checking open event...');
20957
+ const opened = yield this.menu.isOpen(this.menuId);
20958
+ if (!opened) {
20959
+ console.debug('[menu] Opening menu');
20960
+ yield this.menu.open(this.menuId);
20961
+ // Propage to service
20962
+ this.menuService._markAsOpened();
20963
+ }
20964
+ return true;
20965
+ });
20966
+ }
20967
+ close() {
20770
20968
  return __awaiter(this, void 0, void 0, function* () {
20771
- return this.menu.close('left');
20969
+ console.debug('[menu] Checking close event...');
20970
+ const opened = yield this.menu.isOpen(this.menuId);
20971
+ if (opened) {
20972
+ console.debug('[menu] Closing menu');
20973
+ yield this.menu.close(this.menuId);
20974
+ // Propage to service
20975
+ this.menuService._markAsClosed();
20976
+ }
20977
+ return true;
20772
20978
  });
20773
20979
  }
20774
20980
  openAboutModal(event) {
@@ -20777,24 +20983,33 @@ class MenuComponent {
20777
20983
  return modal.present();
20778
20984
  });
20779
20985
  }
20780
- toggleSplitPane(event) {
20986
+ setSplitPaneWhen(value) {
20987
+ return __awaiter(this, void 0, void 0, function* () {
20988
+ console.debug('[menu] Checking splitPane when...');
20989
+ if (this.splitPane.when !== value) {
20990
+ console.debug('[menu] Set splitPane when to ' + value);
20991
+ this.splitPane.when = value;
20992
+ this.detectChanges();
20993
+ // Propagate to the menu service
20994
+ setTimeout(() => this.menuService.setSplitPaneShowWhen(value), 200);
20995
+ }
20996
+ });
20997
+ }
20998
+ toggleSplitPaneShow(event) {
20781
20999
  if (event && event.defaultPrevented)
20782
21000
  return;
20783
21001
  if (event)
20784
21002
  event.preventDefault();
20785
- this.splitPaneOpened = !this.splitPaneOpened;
20786
- if (!this.splitPaneOpened) {
20787
- this.splitPane.when = false;
21003
+ if (this.splitPane.when === false) {
21004
+ return this.setSplitPaneWhen(DEFAULT_MENU_SHOW_WHEN);
20788
21005
  }
20789
21006
  else {
20790
- this.splitPane.when = SPLIT_PANE_SHOW_WHEN;
21007
+ return this.setSplitPaneWhen(false);
20791
21008
  }
20792
- this.menuService.menuVisible(this.splitPaneOpened);
20793
- this.detectChanges();
20794
21009
  }
20795
21010
  doAction(action, event) {
20796
21011
  return __awaiter(this, void 0, void 0, function* () {
20797
- this.menuClose();
21012
+ this.close();
20798
21013
  switch (action) {
20799
21014
  case 'logout':
20800
21015
  yield this.logout();
@@ -20880,7 +21095,7 @@ class MenuComponent {
20880
21095
  MenuComponent.decorators = [
20881
21096
  { type: Component, args: [{
20882
21097
  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",
21098
+ 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
21099
  animations: [fadeInAnimation],
20885
21100
  changeDetection: ChangeDetectionStrategy.OnPush,
20886
21101
  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 +21116,13 @@ MenuComponent.ctorParameters = () => [
20901
21116
  { type: Array, decorators: [{ type: Optional }, { type: Inject, args: [APP_MENU_ITEMS,] }] }
20902
21117
  ];
20903
21118
  MenuComponent.propDecorators = {
21119
+ id: [{ type: Input }],
21120
+ menuId: [{ type: Input }],
21121
+ side: [{ type: Input }],
21122
+ contentId: [{ type: Input }],
20904
21123
  logo: [{ type: Input }],
20905
21124
  appName: [{ type: Input }],
20906
21125
  appVersion: [{ type: Input }],
20907
- side: [{ type: Input }],
20908
21126
  splitPane: [{ type: ViewChild, args: ['splitPane', { static: true },] }]
20909
21127
  };
20910
21128
 
@@ -20958,6 +21176,30 @@ AppIconComponent.propDecorators = {
20958
21176
  ref: [{ type: Input }]
20959
21177
  };
20960
21178
 
21179
+ class AppUpdateOfflineModeCard {
21180
+ constructor() {
21181
+ this.onUpdateClick = new EventEmitter();
21182
+ }
21183
+ ngOnDestroy() {
21184
+ this.onUpdateClick.complete();
21185
+ }
21186
+ }
21187
+ AppUpdateOfflineModeCard.decorators = [
21188
+ { type: Component, args: [{
21189
+ selector: 'app-update-offline-mode-card',
21190
+ 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",
21191
+ changeDetection: ChangeDetectionStrategy.OnPush,
21192
+ animations: [slideUpDownAnimation],
21193
+ styles: ["ion-text small{font-size:85%}"]
21194
+ },] }
21195
+ ];
21196
+ AppUpdateOfflineModeCard.ctorParameters = () => [];
21197
+ AppUpdateOfflineModeCard.propDecorators = {
21198
+ progressionMessage: [{ type: Input }],
21199
+ progressionValue: [{ type: Input }],
21200
+ onUpdateClick: [{ type: Output }]
21201
+ };
21202
+
20961
21203
  class CoreModule {
20962
21204
  static forRoot() {
20963
21205
  console.info('[core] Creating module (root)');
@@ -20978,7 +21220,6 @@ CoreModule.decorators = [
20978
21220
  CacheModule,
20979
21221
  IonicStorageModule,
20980
21222
  NgxJdenticonModule,
20981
- TranslateModule.forChild(),
20982
21223
  // Sub modules
20983
21224
  AppGraphQLModule,
20984
21225
  AppMenuModule
@@ -21005,6 +21246,7 @@ CoreModule.decorators = [
21005
21246
  // Network
21006
21247
  SelectPeerModal,
21007
21248
  AppInstallUpgradeCard,
21249
+ AppUpdateOfflineModeCard,
21008
21250
  // Other components
21009
21251
  TableSelectColumnsComponent,
21010
21252
  ActionsColumnComponent,
@@ -21040,6 +21282,7 @@ CoreModule.decorators = [
21040
21282
  AppPropertiesForm,
21041
21283
  AppListForm,
21042
21284
  AppInstallUpgradeCard,
21285
+ AppUpdateOfflineModeCard,
21043
21286
  DepartmentToStringPipe,
21044
21287
  AppIconComponent,
21045
21288
  TextPopover,
@@ -21048,6 +21291,9 @@ CoreModule.decorators = [
21048
21291
  },] }
21049
21292
  ];
21050
21293
 
21294
+ const moment$5 = momentImported;
21295
+ const tz = momentTZImported;
21296
+
21051
21297
  class AuthGuardService {
21052
21298
  constructor(accountService, modalCtrl, router, environment) {
21053
21299
  this.accountService = accountService;
@@ -21567,6 +21813,8 @@ class AppTableUtils {
21567
21813
  }
21568
21814
  }
21569
21815
 
21816
+ class AppTableDataSourceOptions {
21817
+ }
21570
21818
  // @dynamic
21571
21819
  // eslint-disable-next-line @angular-eslint/directive-class-suffix
21572
21820
  class EntitiesTableDataSource extends TableDataSource {
@@ -21575,27 +21823,34 @@ class EntitiesTableDataSource extends TableDataSource {
21575
21823
  *
21576
21824
  * @param dataService A service to load and save data
21577
21825
  * @param dataType Type of data contained by the Table. If not specified, then `data` with at least one element must be specified.
21826
+ * @param environment
21578
21827
  * @param validatorService Service that create instances of the FormGroup used to validate row fields.
21579
21828
  * @param config Additional configuration for table.
21580
21829
  */
21581
21830
  constructor(dataType, dataService, validatorService, config) {
21582
- super([], dataType, validatorService, Object.assign({ dataServiceOptions: {}, debug: config && config.suppressErrors === false, keepOriginalDataAfterConfirm: false }, config));
21831
+ super([], dataType, validatorService, config);
21583
21832
  this.dataService = dataService;
21584
21833
  this._creating = false;
21585
21834
  this._saving = false;
21835
+ this._useValidator = false;
21586
21836
  this._stopWatching$ = new Subject();
21587
21837
  this._editingRowCount = 0;
21588
21838
  this._fetchMoreFn = null;
21589
21839
  this.loadingSubject = new BehaviorSubject(undefined);
21840
+ this._options = Object.assign({ dataServiceOptions: {}, debug: config && config.suppressErrors === false, keepOriginalDataAfterConfirm: false }, config);
21841
+ this._useValidator = isNotNil(validatorService);
21590
21842
  this._logTypeName = removeEnd((new dataType()).__typename || 'UnknownVO', 'VO');
21591
21843
  // For DEV ONLY
21592
- this._debug = this.config.debug === true;
21844
+ this._debug = this._options.debug === true;
21593
21845
  }
21594
21846
  get serviceOptions() {
21595
- return this.config.dataServiceOptions;
21847
+ return this._options.dataServiceOptions;
21596
21848
  }
21597
21849
  set serviceOptions(value) {
21598
- this.config.dataServiceOptions = value;
21850
+ this._options.dataServiceOptions = value;
21851
+ }
21852
+ get options() {
21853
+ return this._options;
21599
21854
  }
21600
21855
  get loaded() {
21601
21856
  return this.loadingSubject.value === false; // Should be false when undefined (initial state)
@@ -21603,15 +21858,12 @@ class EntitiesTableDataSource extends TableDataSource {
21603
21858
  get loading() {
21604
21859
  return this.loadingSubject.value !== false; // Should be true when undefined (initial state)
21605
21860
  }
21606
- connect(collectionViewer) {
21607
- // DEBUG console.debug("[entities-datasource] connect");
21608
- return super.connect(collectionViewer);
21609
- }
21610
- disconnect(collectionViewer) {
21611
- super.disconnect(collectionViewer);
21861
+ ngOnDestroy() {
21612
21862
  this._stopWatching$.next();
21613
21863
  this._stopWatching$.complete();
21864
+ this._stopWatching$.unsubscribe();
21614
21865
  this.loadingSubject.complete();
21866
+ this.loadingSubject.unsubscribe();
21615
21867
  }
21616
21868
  watchAll(offset, size, sortBy, sortDirection, filter) {
21617
21869
  this._stopWatching$.next();
@@ -21655,26 +21907,27 @@ class EntitiesTableDataSource extends TableDataSource {
21655
21907
  // Get all rows
21656
21908
  const rows = yield this.getRows();
21657
21909
  // Finish editing all rows
21658
- const confirmed = yield this.confirmRows(rows);
21910
+ const invalidRows = rows.filter(row => row.editing && !row.confirmEditCreate());
21659
21911
  // Cannot finish some rows: error
21660
- if (!confirmed) {
21912
+ if (invalidRows.length) {
21661
21913
  // log errors
21662
21914
  if (this._debug)
21663
- this.getEditingRows(rows).forEach(row => AppTableUtils.logRowErrors(row, `[table-datasource] ${this._logTypeName} row #${row.id}`));
21915
+ invalidRows.forEach(row => AppTableUtils.logRowErrors(row, `[table-datasource] ${this._logTypeName} row #${row.id}`));
21664
21916
  // Stop with an error
21665
21917
  throw { code: ErrorCodes.TABLE_INVALID_ROW_ERROR, message: 'ERROR.TABLE_INVALID_ROW_ERROR' };
21666
21918
  }
21667
21919
  this._editingRowCount = 0;
21668
21920
  let data;
21669
21921
  let dataToSave;
21670
- if (!!this.validatorService) {
21922
+ if (this._useValidator) {
21671
21923
  dataToSave = [];
21672
21924
  data = rows.map(row => {
21673
- const entity = this.toEntity(row.currentData);
21925
+ const currentData = new this.dataConstructor();
21926
+ currentData.fromObject(row.currentData);
21674
21927
  // Filter to keep only dirty row
21675
21928
  if (onlyDirtyRows && row.validator.dirty)
21676
- dataToSave.push(entity);
21677
- return entity;
21929
+ dataToSave.push(currentData);
21930
+ return currentData;
21678
21931
  });
21679
21932
  if (!onlyDirtyRows)
21680
21933
  dataToSave = data;
@@ -21711,33 +21964,25 @@ class EntitiesTableDataSource extends TableDataSource {
21711
21964
  }
21712
21965
  });
21713
21966
  }
21714
- getDataFromRows(rows, options = { originalData: false, toEntity: false }) {
21715
- if (options.toEntity) {
21716
- return super.getDataFromRows(rows, options)
21717
- .map(data => this.toEntity(data));
21967
+ updateDatasource(data, opts) {
21968
+ if (this._debug)
21969
+ console.debug(`[table-datasource] Updating datasource with data:`, data);
21970
+ super.updateDatasource(data, opts);
21971
+ if (!opts || opts.emitEvent !== false) {
21972
+ this.markAsLoaded();
21718
21973
  }
21719
- return super.getDataFromRows(rows, options);
21720
21974
  }
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;
21975
+ // Overwrite default signature
21976
+ createNew(insertAt) {
21977
+ this.asyncCreateNew(insertAt);
21728
21978
  }
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
- });
21979
+ connect(collectionViewer) {
21980
+ // DEBUG console.debug("[entities-datasource] connect");
21981
+ return super.connect(collectionViewer);
21982
+ }
21983
+ disconnect(collectionViewer) {
21984
+ super.disconnect(collectionViewer);
21985
+ this._stopWatching$.next();
21741
21986
  }
21742
21987
  waitIdle(debounceTimeMs) {
21743
21988
  return firstFalsePromise(this.loadingSubject
@@ -21746,60 +21991,34 @@ class EntitiesTableDataSource extends TableDataSource {
21746
21991
  ));
21747
21992
  }
21748
21993
  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
- });
21994
+ if (!super.confirmCreate(row))
21995
+ return false;
21996
+ if (row.editing && row.validator) {
21997
+ console.warn('[table-datasource] Row still has {editing: true} after confirmCreate()! Force editing to false');
21998
+ row.validator.disable({ onlySelf: true, emitEvent: false });
21999
+ }
22000
+ return true;
21761
22001
  }
21762
22002
  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
- });
22003
+ if (!super.confirmEdit(row))
22004
+ return false;
22005
+ if (row.editing && row.validator) {
22006
+ console.warn('[table-datasource] Row still has {editing: true} after confirmCreate()! Force editing to false');
22007
+ row.validator.disable({ onlySelf: true, emitEvent: false });
22008
+ }
22009
+ return true;
21775
22010
  }
21776
22011
  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
- });
22012
+ if (this._debug)
22013
+ console.debug('[table-datasource] Start to edit row', row);
22014
+ row.startEdit();
22015
+ this._editingRowCount++;
21790
22016
  }
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
- });
22017
+ cancelOrDelete(row) {
22018
+ if (this._debug)
22019
+ console.debug('[table-datasource] Cancelling or deleting row', row);
22020
+ row.cancelOrDelete();
22021
+ this._editingRowCount--;
21803
22022
  }
21804
22023
  handleError(error, message) {
21805
22024
  const errorMsg = error && error.message || error;
@@ -21814,77 +22033,59 @@ class EntitiesTableDataSource extends TableDataSource {
21814
22033
  throw error;
21815
22034
  }
21816
22035
  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);
22036
+ // If new row: not need to propagate to the dataService
22037
+ if (id === -1) {
22038
+ return super.delete(id);
22039
+ }
22040
+ const row = this.getRow(id);
22041
+ if (!row) {
22042
+ console.error(`[table-datasource] Row to delete with id=${id} not found`);
22043
+ return;
22044
+ }
22045
+ this.loadingSubject.next(true);
22046
+ this.dataService.deleteAll([row.currentData], this.serviceOptions)
22047
+ .catch(err => this.handleErrorPromise(err))
22048
+ .then(() => {
22049
+ setTimeout(() => {
22050
+ // make sure row has been deleted (because GrapQHl cache remove can failed)
21835
22051
  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
- }
22052
+ if (present)
22053
+ super.delete(id);
22054
+ this.loadingSubject.next(false);
22055
+ }, 300);
21847
22056
  });
21848
22057
  }
21849
22058
  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();
22059
+ this.markAsLoading();
22060
+ const data = rows.map(r => r.currentData);
22061
+ const rowsById = rows.reduce((res, row) => {
22062
+ res[row.id] = row;
22063
+ return res;
22064
+ }, {});
22065
+ const self = this;
22066
+ const selfDelete = super.delete;
22067
+ return this.dataService.deleteAll(data, this.serviceOptions)
22068
+ .catch(err => this.handleErrorPromise(err))
22069
+ .then(() => {
22070
+ // Workaround, to be sure all rows has been deleted
22071
+ // Sometimes, the service miss deletion, or GrapQHl cache remove failed
22072
+ const rowNotDeleted = Object.getOwnPropertyNames(rowsById).reduce((res, id) => {
22073
+ const row = rowsById[id];
22074
+ const present = self.getRow(+id) === row;
22075
+ return present ? res.concat(row) : res;
22076
+ }, []).sort((a, b) => a.id > b.id ? -1 : 1);
22077
+ // Apply missing deletion
22078
+ if (isNotEmptyArray(rowNotDeleted)) {
22079
+ console.warn(`[table-datasource] Force deletion of ${rowNotDeleted.length} rows (Is service applying deletion to observable ?)`);
22080
+ rowNotDeleted.forEach(r => selfDelete.call(self, r.id));
21881
22081
  }
22082
+ this.loadingSubject.next(false);
21882
22083
  });
21883
22084
  }
21884
22085
  getRows() {
21885
- return this.rowsSubject.toPromise();
22086
+ return firstNotNilPromise(this.connect(null));
21886
22087
  }
21887
- createNew(insertAt) {
22088
+ asyncCreateNew(insertAt) {
21888
22089
  const _super = Object.create(null, {
21889
22090
  createNew: { get: () => super.createNew }
21890
22091
  });
@@ -21892,39 +22093,22 @@ class EntitiesTableDataSource extends TableDataSource {
21892
22093
  if (this._creating)
21893
22094
  return; // Avoid multiple call (should never occur)
21894
22095
  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
22096
+ _super.createNew.call(this, insertAt);
22097
+ const row = this.getRow(-1);
22098
+ if (row && this._options && this._options.onRowCreated) {
22099
+ const res = this._options.onRowCreated(row);
22100
+ // If async function, wait the end before ending
22101
+ if (res instanceof Promise) {
22102
+ try {
22103
+ yield res;
22104
+ }
22105
+ catch (err) {
22106
+ console.error(err && err.message || err, err);
22107
+ }
21904
22108
  }
21905
22109
  }
21906
- if (done)
21907
- this._editingRowCount++;
22110
+ this._editingRowCount++;
21908
22111
  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
22112
  });
21929
22113
  }
21930
22114
  getData() {
@@ -21939,7 +22123,7 @@ class EntitiesTableDataSource extends TableDataSource {
21939
22123
  });
21940
22124
  return __awaiter(this, void 0, void 0, function* () {
21941
22125
  if (!this._fetchMoreFn)
21942
- return false;
22126
+ return false; // Avoid multiple call
21943
22127
  if (this._editingRowCount > 0) {
21944
22128
  console.warn('Cannot fetch more because still editing row');
21945
22129
  return;
@@ -21953,10 +22137,9 @@ class EntitiesTableDataSource extends TableDataSource {
21953
22137
  if (isNotEmptyArray(res === null || res === void 0 ? void 0 : res.data))
21954
22138
  return false;
21955
22139
  // Update the data source
21956
- // TODO review this, to keep existing rows, instead of creating new TableElement + validator !
21957
22140
  const existingData = yield this.getData();
21958
- yield _super.updateDatasource.call(this, existingData.concat(...res.data), opts);
21959
- // Store next fetchMore function
22141
+ _super.updateDatasource.call(this, existingData.concat(...res.data), opts);
22142
+ // Remember fetchMore
21960
22143
  this._fetchMoreFn = res.fetchMore;
21961
22144
  return true;
21962
22145
  });
@@ -21979,8 +22162,8 @@ EntitiesTableDataSource.decorators = [
21979
22162
  EntitiesTableDataSource.ctorParameters = () => [
21980
22163
  { type: Function },
21981
22164
  { type: undefined },
21982
- { type: undefined },
21983
- { type: undefined }
22165
+ { type: ValidatorService },
22166
+ { type: AppTableDataSourceOptions }
21984
22167
  ];
21985
22168
 
21986
22169
  const SETTINGS_DISPLAY_COLUMNS = 'displayColumns';
@@ -22372,6 +22555,7 @@ class AppTable {
22372
22555
  }
22373
22556
  }
22374
22557
  ngOnDestroy() {
22558
+ var _a;
22375
22559
  this._subscription.unsubscribe();
22376
22560
  // Unsubscribe column value changes
22377
22561
  Object.keys(this._cellValueChangesDefs).forEach(col => this.stopCellValueChanges(col, true));
@@ -22396,6 +22580,7 @@ class AppTable {
22396
22580
  this.onError.unsubscribe();
22397
22581
  this._destroy$.next();
22398
22582
  this._destroy$.unsubscribe();
22583
+ (_a = this._dataSource) === null || _a === void 0 ? void 0 : _a.ngOnDestroy();
22399
22584
  }
22400
22585
  updateView(res, opts) {
22401
22586
  return __awaiter(this, void 0, void 0, function* () {
@@ -22437,7 +22622,7 @@ class AppTable {
22437
22622
  this._dataSourceLoadingSubscription.unsubscribe();
22438
22623
  this._subscription.remove(this._dataSourceLoadingSubscription);
22439
22624
  }
22440
- (_a = this._dataSource) === null || _a === void 0 ? void 0 : _a.disconnect();
22625
+ (_a = this._dataSource) === null || _a === void 0 ? void 0 : _a.ngOnDestroy();
22441
22626
  this._dataSource = null;
22442
22627
  }
22443
22628
  addColumnDef(column) {
@@ -22492,15 +22677,18 @@ class AppTable {
22492
22677
  return true;
22493
22678
  }
22494
22679
  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 });
22680
+ var _a;
22681
+ row = row || this.editedRow;
22682
+ if (!this.confirmEditCreate(event, row)) {
22683
+ // If pending: Wait end of validation, then loop
22684
+ if ((_a = row.validator) === null || _a === void 0 ? void 0 : _a.pending) {
22685
+ return AppFormUtils.waitWhilePending(row.validator).then(() => this.confirmAndForward(event, row));
22501
22686
  }
22502
- return done;
22503
- });
22687
+ return false;
22688
+ }
22689
+ // Edit next row
22690
+ this.editRowById(event, row.id + 1, { focusColumn: this.firstUserColumn });
22691
+ return true;
22504
22692
  }
22505
22693
  editRowById(event, id, opts) {
22506
22694
  return __awaiter(this, void 0, void 0, function* () {
@@ -22524,44 +22712,48 @@ class AppTable {
22524
22712
  */
22525
22713
  confirmEditCreate(event, row) {
22526
22714
  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
- }
22715
+ row = row || this.editedRow;
22716
+ if (!row || !row.editing)
22717
+ return true; // no row to confirm
22718
+ // Stop event
22719
+ event === null || event === void 0 ? void 0 : event.stopPropagation();
22720
+ // Confirmation edition or creation
22721
+ const confirmed = row.confirmEditCreate();
22722
+ if (confirmed) {
22723
+ // If edit finished, forget edited row
22724
+ if (row === this.editedRow) {
22725
+ this.editedRow = undefined;
22562
22726
  }
22563
- return done;
22564
- });
22727
+ // Mark table as dirty (if row is dirty)
22728
+ if ((_a = row.validator) === null || _a === void 0 ? void 0 : _a.dirty) {
22729
+ this.markAsDirty({ emitEvent: false /* because of resetError() */ });
22730
+ }
22731
+ // Clear error
22732
+ this.resetError();
22733
+ // Emit the confirm event
22734
+ this.onConfirmEditCreateRow.next(row);
22735
+ return true; // Continue
22736
+ }
22737
+ if (row.validator) {
22738
+ // If pending: Wait end of validation, then loop
22739
+ if (row.validator.pending) {
22740
+ AppFormUtils.waitWhilePending(row.validator).then(() => this.confirmEditCreate(event, row));
22741
+ return false;
22742
+ }
22743
+ // NOT confirmed = row has error
22744
+ if (this.debug) {
22745
+ console.warn('[table] Cannot confirm row, because invalid');
22746
+ AppFormUtils.logFormErrors(row.validator, '[table] ');
22747
+ }
22748
+ // fix: mark all controls as touched to show errors
22749
+ row.validator.markAllAsTouched();
22750
+ // Compute row error, and propagate to table's error
22751
+ if (this.propagateRowError) {
22752
+ const error = this.getRowError(row);
22753
+ this.setError(error);
22754
+ }
22755
+ }
22756
+ return false;
22565
22757
  }
22566
22758
  cancelOrDelete(event, row, opts) {
22567
22759
  // Delete new row
@@ -22606,7 +22798,6 @@ class AppTable {
22606
22798
  // Keep edited row id (should be done BEFORE confirmEditCreate() )
22607
22799
  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
22800
  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
22801
  if (!this.confirmEditCreate()) {
22611
22802
  throw { code: ErrorCodes.TABLE_INVALID_ROW_ERROR, message: 'ERROR.TABLE_INVALID_ROW_ERROR' };
22612
22803
  }
@@ -23164,7 +23355,7 @@ class AppTable {
23164
23355
  console.warn('[table] Opening row details, but data has no id!');
23165
23356
  return false;
23166
23357
  }
23167
- return yield this.router.navigate([id], {
23358
+ return this.router.navigate(['.', id.toString()], {
23168
23359
  relativeTo: this.route,
23169
23360
  queryParams: {}
23170
23361
  });
@@ -23261,9 +23452,7 @@ class AppTable {
23261
23452
  addRowToTable(insertAt) {
23262
23453
  return __awaiter(this, void 0, void 0, function* () {
23263
23454
  this.focusFirstColumn = true;
23264
- const done = yield this._dataSource.createNew(insertAt);
23265
- if (!done)
23266
- return undefined;
23455
+ yield this._dataSource.asyncCreateNew(insertAt);
23267
23456
  this.editedRow = this._dataSource.getRow(-1);
23268
23457
  // Emit start editing event
23269
23458
  this.onStartEditingRow.emit(this.editedRow);
@@ -23394,64 +23583,59 @@ class AppTable {
23394
23583
  }
23395
23584
  }
23396
23585
  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
- });
23586
+ if (row.id !== -1)
23587
+ throw new Error('Row must have id = -1');
23588
+ event === null || event === void 0 ? void 0 : event.stopPropagation();
23589
+ this.selection.clear();
23590
+ this.editedRow = undefined; // unselect row
23591
+ this._dataSource.cancelOrDelete(row);
23592
+ this.onCancelOrDeleteRow.next(row);
23593
+ this.resetError();
23594
+ this.totalRowCount--;
23595
+ this.visibleRowCount--;
23412
23596
  }
23413
23597
  deleteExistingRow(event, row, opts) {
23414
23598
  // Make sure row will be cancelled, and NOT deleted
23415
23599
  if (row.id === -1)
23416
23600
  throw new Error('Row must have an id');
23417
23601
  if (event === null || event === void 0 ? void 0 : event.defaultPrevented)
23418
- return Promise.resolve(false); // SKip
23602
+ return 0; // SKip
23419
23603
  event === null || event === void 0 ? void 0 : event.preventDefault();
23420
- return this.deleteRow(null, row, opts);
23604
+ this.deleteRow(null, row, opts);
23421
23605
  }
23422
23606
  cancelExistingRow(event, row, opts) {
23423
23607
  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
- });
23608
+ // Make sure row will be cancelled, and NOT deleted
23609
+ if (row.id === -1 || !row.editing)
23610
+ throw new Error('Row cannot be canceling, but only deleting');
23611
+ const confirmed = (!opts || opts.interactive !== false);
23612
+ // Ask user confirmation, if cancel
23613
+ if (!confirmed && ((_a = row.validator) === null || _a === void 0 ? void 0 : _a.dirty) && (this.confirmBeforeCancel || this.onBeforeCancelRows.observers.length > 0)) {
23614
+ event.stopPropagation();
23615
+ this.canCancelRows([row], opts)
23616
+ .then(confirm => {
23617
+ // If confirmed, loop
23618
+ if (confirm)
23619
+ this.cancelExistingRow(event, row, { interactive: false });
23620
+ });
23621
+ return;
23622
+ }
23623
+ const keepEditing = row.editing && (!opts || opts.keepEditing !== false);
23624
+ this.editedRow = undefined; // unselect row
23625
+ this._dataSource.cancelOrDelete(row);
23626
+ this.onCancelOrDeleteRow.next(row);
23627
+ // Mark row as pristine
23628
+ this.checkIfRowPristine(row);
23629
+ // Restore editing state
23630
+ if (keepEditing) {
23631
+ this.editRow(undefined, row);
23632
+ }
23449
23633
  }
23450
23634
  checkIfRowPristine(row, opts) {
23451
23635
  var _a, _b;
23452
23636
  return __awaiter(this, void 0, void 0, function* () {
23453
23637
  // Mark row as pristine
23454
- const markRowAsPristine = ((_a = this.dataSource) === null || _a === void 0 ? void 0 : _a.config.keepOriginalDataAfterConfirm) === true;
23638
+ const markRowAsPristine = ((_a = this.dataSource) === null || _a === void 0 ? void 0 : _a.options.keepOriginalDataAfterConfirm) === true;
23455
23639
  if (markRowAsPristine) {
23456
23640
  (_b = row.validator) === null || _b === void 0 ? void 0 : _b.markAsPristine();
23457
23641
  // Check if table is now pristine
@@ -24259,6 +24443,12 @@ class AppEntityEditor extends AppTabEditor {
24259
24443
  this.onUpdateView.complete();
24260
24444
  this.onUpdateView.unsubscribe();
24261
24445
  }
24446
+ waitIdle(opts) {
24447
+ // Wait end of saving
24448
+ if (this.saving)
24449
+ return waitFor(() => this.saving !== true);
24450
+ return super.waitIdle(opts);
24451
+ }
24262
24452
  /**
24263
24453
  * Load data from the snapshot route
24264
24454
  *
@@ -24377,7 +24567,7 @@ class AppEntityEditor extends AppTabEditor {
24377
24567
  const idChanged = isNotNil(data.id)
24378
24568
  && this.previousDataId !== undefined // Ignore if first loading (=undefined)
24379
24569
  && this.previousDataId !== data.id;
24380
- opts = Object.assign({ updateRoute: this._autoUpdateRoute && idChanged && !this.loading, openTabIndex: this._autoOpenNextTab && idChanged && isNil(this.previousDataId) && this.computeNextTabIndex() }, opts);
24570
+ opts = Object.assign({ updateRoute: this._autoUpdateRoute && idChanged && !this.loading }, opts);
24381
24571
  this.data = data;
24382
24572
  this.previousDataId = data.id || null;
24383
24573
  yield this.ready();
@@ -24428,18 +24618,25 @@ class AppEntityEditor extends AppTabEditor {
24428
24618
  // Save the opened tab into the queryParams
24429
24619
  this.queryParams.tab = this.selectedTabIndex;
24430
24620
  // Update route location
24431
- const forcedQueryParams = {};
24432
- forcedQueryParams[this._pathIdAttribute] = data && isNotNil(data.id) ? data.id : 'new';
24433
- if (data && isNotNil(data.id)) {
24621
+ if (isNotNil(data === null || data === void 0 ? void 0 : data.id)) {
24622
+ const queryParams = Object.assign({}, this.queryParams);
24623
+ queryParams[this._pathIdAttribute] = data.id;
24624
+ if (this.debug)
24625
+ console.debug('[entity-editor] Updating route using params: ', queryParams);
24434
24626
  yield this.router.navigate(['.'], {
24435
24627
  relativeTo: this.route,
24436
- queryParams: Object.assign(Object.assign({}, this.queryParams), forcedQueryParams)
24628
+ queryParams
24437
24629
  });
24438
24630
  }
24631
+ // Move to ../new
24439
24632
  else {
24633
+ const queryParams = Object.assign({}, this.queryParams);
24634
+ queryParams[this._pathIdAttribute] = 'new';
24635
+ if (this.debug)
24636
+ console.debug('[entity-editor] Updating route to \'../new\' using params: ', queryParams);
24440
24637
  yield this.router.navigate(['..', 'new'], {
24441
24638
  relativeTo: this.route,
24442
- queryParams: Object.assign(Object.assign({}, this.queryParams), forcedQueryParams)
24639
+ queryParams
24443
24640
  });
24444
24641
  }
24445
24642
  yield this.updateRoute(data, this.queryParams);
@@ -24449,7 +24646,7 @@ class AppEntityEditor extends AppTabEditor {
24449
24646
  * Update the route location, and open the next tab
24450
24647
  */
24451
24648
  updateTabIndex(tabIndex) {
24452
- if (isNotNil(tabIndex) && tabIndex > this.selectedTabIndex) {
24649
+ if (isNotNil(tabIndex) && tabIndex !== this.selectedTabIndex) {
24453
24650
  this.selectedTabIndex = tabIndex;
24454
24651
  this.markForCheck();
24455
24652
  }
@@ -24505,6 +24702,8 @@ class AppEntityEditor extends AppTabEditor {
24505
24702
  if (this.debug)
24506
24703
  console.debug('[data-editor] Saving data...');
24507
24704
  try {
24705
+ // Compute next tab index (if enable)
24706
+ const nextTabIndex = this._autoOpenNextTab && isNil(this.previousDataId) && this.computeNextTabIndex();
24508
24707
  // Get data
24509
24708
  const data = yield this.getValue();
24510
24709
  this.disable();
@@ -24512,7 +24711,9 @@ class AppEntityEditor extends AppTabEditor {
24512
24711
  const updatedData = yield this.dataService.save(data, opts);
24513
24712
  yield this.onEntitySaved(updatedData);
24514
24713
  // Update the view (e.g metadata)
24515
- yield this.updateView(updatedData, opts);
24714
+ yield this.updateView(updatedData, Object.assign({
24715
+ // Open next tab only if user has not changed it
24716
+ openTabIndex: nextTabIndex > this._selectedTabIndex ? nextTabIndex : undefined }, opts));
24516
24717
  // Subscribe to remote changes
24517
24718
  if (!this._listenChangesSubscription)
24518
24719
  this.startListenRemoteChanges();
@@ -24732,7 +24933,8 @@ class AppEntityEditor extends AppTabEditor {
24732
24933
  // If NOT data, then add to page history
24733
24934
  if (!this.isNewData) {
24734
24935
  const page = yield this.computePageHistory(title);
24735
- return this.addToPageHistory(page);
24936
+ if (page)
24937
+ return this.addToPageHistory(page);
24736
24938
  }
24737
24939
  });
24738
24940
  }
@@ -24745,6 +24947,8 @@ class AppEntityEditor extends AppTabEditor {
24745
24947
  }
24746
24948
  computePageHistory(title) {
24747
24949
  return __awaiter(this, void 0, void 0, function* () {
24950
+ if (this.debug)
24951
+ console.debug('[entity-editor] Computing page history, using url: ' + this.router.url);
24748
24952
  return {
24749
24953
  title,
24750
24954
  path: this.router.url
@@ -24775,7 +24979,8 @@ class AppEntityEditor extends AppTabEditor {
24775
24979
  fetchPolicy: 'no-cache' }, opts));
24776
24980
  }
24777
24981
  markForCheck() {
24778
- this.cd.markForCheck();
24982
+ var _a;
24983
+ (_a = this.cd) === null || _a === void 0 ? void 0 : _a.markForCheck();
24779
24984
  }
24780
24985
  /* -- private functions -- */
24781
24986
  /**
@@ -24860,7 +25065,7 @@ AppInMemoryTable.ctorParameters = () => [
24860
25065
  { type: Function },
24861
25066
  { type: InMemoryEntitiesService },
24862
25067
  { type: ValidatorService },
24863
- { type: undefined },
25068
+ { type: AppTableDataSourceOptions },
24864
25069
  { type: undefined }
24865
25070
  ];
24866
25071
  AppInMemoryTable.propDecorators = {
@@ -25884,7 +26089,7 @@ class PersonService extends BaseEntityService {
25884
26089
  }
25885
26090
  suggest(value, filter, sortBy, sortDirection) {
25886
26091
  return __awaiter(this, void 0, void 0, function* () {
25887
- if (ReferentialUtils.isNotEmpty(value))
26092
+ if (EntityUtils.isNotEmpty(value, 'id'))
25888
26093
  return { data: [value] };
25889
26094
  value = (typeof value === 'string' && value !== '*') && value || undefined;
25890
26095
  sortBy = sortBy || filter.searchAttribute || (filter.searchAttributes && filter.searchAttributes[0]);
@@ -26036,11 +26241,7 @@ class UsersPage extends AppTable {
26036
26241
  switch (authTokenType) {
26037
26242
  case "basic":
26038
26243
  this.setShowColumn('pubkey', false, { emitEvent: false });
26039
- this.setShowColumn('username', true, { emitEvent: false });
26040
- this.setShowColumn('usernameExtranet', true, { emitEvent: false });
26041
- break;
26042
26244
  case "basic-and-token":
26043
- this.setShowColumn('pubkey', true, { emitEvent: false });
26044
26245
  this.setShowColumn('username', true, { emitEvent: false });
26045
26246
  this.setShowColumn('usernameExtranet', true, { emitEvent: false });
26046
26247
  break;
@@ -26252,6 +26453,7 @@ class AutocompleteTestPage {
26252
26453
  missingEntity: [null, SharedValidators.entity],
26253
26454
  disableEntity: [null, SharedValidators.entity],
26254
26455
  entities: [null, SharedFormArrayValidators.requiredArrayMinLength(1)],
26456
+ farEntity: [null, SharedValidators.entity]
26255
26457
  });
26256
26458
  this.form.get('disableEntity').disable();
26257
26459
  }
@@ -26260,8 +26462,7 @@ class AutocompleteTestPage {
26260
26462
  this.autocompleteFields.add('entity-suggestFn', {
26261
26463
  suggestFn: (value, filter) => this.suggest(value, filter),
26262
26464
  attributes: ['label', 'name'],
26263
- displayWith: this.entityToString,
26264
- showAllOnFocus: false
26465
+ displayWith: this.entityToString
26265
26466
  });
26266
26467
  // From items array
26267
26468
  this.autocompleteFields.add('entity-items', {
@@ -26289,22 +26490,34 @@ class AutocompleteTestPage {
26289
26490
  attributes: ['label', 'name', 'description', 'comments'],
26290
26491
  displayWith: this.entityToString
26291
26492
  });
26292
- // For mutliple value
26493
+ // Far entity, in the list
26494
+ this.autocompleteFields.add('entity-far', {
26495
+ suggestFn: (value, filter) => this.suggest(value, filter),
26496
+ attributes: ['label', 'name'],
26497
+ displayWith: this.entityToString
26498
+ });
26499
+ // For multiple value
26293
26500
  this.autocompleteFields.add('entities-items-filter', {
26294
26501
  service: {
26295
26502
  suggest: (value, filter) => this.suggest(value, filter)
26296
26503
  },
26297
26504
  attributes: ['label', 'name'],
26298
26505
  multiple: true
26299
- //displayWith: this.entitiesToString
26300
26506
  });
26301
- this.loadData();
26507
+ }
26508
+ ngAfterViewInit() {
26509
+ setTimeout(() => {
26510
+ this.farEntityField.suggestLengthThreshold = 2;
26511
+ this.farEntityField.reloadItems();
26512
+ }, 2000);
26513
+ setTimeout(() => this.loadData(), 1000);
26302
26514
  }
26303
26515
  // Load the form with data
26304
26516
  loadData() {
26305
26517
  return __awaiter(this, void 0, void 0, function* () {
26306
26518
  const data = {
26307
26519
  entity: deepCopy(FAKE_ENTITIES)[0],
26520
+ farEntity: deepCopy(FAKE_ENTITIES)[2],
26308
26521
  // This item is NOT in the items list => i should be displayed anyway
26309
26522
  missingEntity: {
26310
26523
  id: -1, label: '??', name: 'Missing item'
@@ -26341,7 +26554,7 @@ class AutocompleteTestPage {
26341
26554
  total,
26342
26555
  data: res.data,
26343
26556
  fetchMore: (variables) => __awaiter(this, void 0, void 0, function* () {
26344
- yield sleep(1000);
26557
+ yield sleep(1000); // Simulate access to server
26345
26558
  return this.suggest(value, Object.assign(Object.assign({}, filter), variables));
26346
26559
  })
26347
26560
  };
@@ -26384,12 +26597,15 @@ class AutocompleteTestPage {
26384
26597
  AutocompleteTestPage.decorators = [
26385
26598
  { type: Component, args: [{
26386
26599
  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"
26600
+ 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
26601
  },] }
26389
26602
  ];
26390
26603
  AutocompleteTestPage.ctorParameters = () => [
26391
26604
  { type: FormBuilder }
26392
26605
  ];
26606
+ AutocompleteTestPage.propDecorators = {
26607
+ farEntityField: [{ type: ViewChild, args: ['farEntityField',] }]
26608
+ };
26393
26609
 
26394
26610
  class LatLongTestPage {
26395
26611
  constructor(formBuilder) {
@@ -26450,20 +26666,20 @@ class LatLongTestPage {
26450
26666
  LatLongTestPage.decorators = [
26451
26667
  { type: Component, args: [{
26452
26668
  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"
26669
+ 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
26670
  },] }
26455
26671
  ];
26456
26672
  LatLongTestPage.ctorParameters = () => [
26457
26673
  { type: FormBuilder }
26458
26674
  ];
26459
26675
 
26460
- const moment$5 = momentImported;
26676
+ const moment$6 = momentImported;
26461
26677
  class SwipeTestPage {
26462
26678
  constructor(formBuilder, dateFormatPipe) {
26463
26679
  this.formBuilder = formBuilder;
26464
26680
  this.dateFormatPipe = dateFormatPipe;
26465
26681
  this.$dates = new BehaviorSubject(undefined);
26466
- this._today = moment$5().startOf('day');
26682
+ this._today = moment$6().startOf('day');
26467
26683
  this.form = formBuilder.group({
26468
26684
  empty: [null, Validators.required],
26469
26685
  date: [null, Validators.compose([Validators.required, SharedValidators.validDate])],
@@ -26476,7 +26692,7 @@ class SwipeTestPage {
26476
26692
  ngOnInit() {
26477
26693
  const dates = [];
26478
26694
  for (let d = 0; d < 7; d++) {
26479
- dates[d] = moment$5(this._today).add(d - 3, 'day');
26695
+ dates[d] = moment$6(this._today).add(d - 3, 'day');
26480
26696
  }
26481
26697
  this.$dates.next(dates);
26482
26698
  this.loadData();
@@ -26517,7 +26733,7 @@ SwipeTestPage.ctorParameters = () => [
26517
26733
  { type: DateFormatPipe }
26518
26734
  ];
26519
26735
 
26520
- const moment$6 = momentImported;
26736
+ const moment$7 = momentImported;
26521
26737
  class DateTimeTestPage {
26522
26738
  constructor(platform, formBuilder, cd) {
26523
26739
  this.platform = platform;
@@ -26529,7 +26745,8 @@ class DateTimeTestPage {
26529
26745
  this.memoryMobile = true;
26530
26746
  this.stringify = JSON.stringify;
26531
26747
  this.form = formBuilder.group({
26532
- empty: [null, Validators.required],
26748
+ empty: [null],
26749
+ emptyRequired: [null, Validators.required],
26533
26750
  enable: [null, Validators.required],
26534
26751
  disable: [null, Validators.required],
26535
26752
  readonly: [null]
@@ -26548,9 +26765,10 @@ class DateTimeTestPage {
26548
26765
  // Load the form with data
26549
26766
  loadData() {
26550
26767
  return __awaiter(this, void 0, void 0, function* () {
26551
- const now = moment$6();
26768
+ const now = moment$7();
26552
26769
  const data = {
26553
- empty: toDateISOString(now.clone().add(2, 'hours')),
26770
+ empty: null,
26771
+ emptyRequired: null,
26554
26772
  enable: toDateISOString(now),
26555
26773
  disable: now,
26556
26774
  readonly: now
@@ -26600,7 +26818,7 @@ class DateTimeTestPage {
26600
26818
  DateTimeTestPage.decorators = [
26601
26819
  { type: Component, args: [{
26602
26820
  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"
26821
+ 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
26822
  },] }
26605
26823
  ];
26606
26824
  DateTimeTestPage.ctorParameters = () => [
@@ -26792,7 +27010,7 @@ NumpadTestPage.ctorParameters = () => [
26792
27010
  { type: FormBuilder }
26793
27011
  ];
26794
27012
 
26795
- const moment$7 = momentImported;
27013
+ const moment$8 = momentImported;
26796
27014
  class DateTestPage {
26797
27015
  constructor(formBuilder, cd) {
26798
27016
  this.formBuilder = formBuilder;
@@ -26825,7 +27043,7 @@ class DateTestPage {
26825
27043
  // Load the form with data
26826
27044
  loadData() {
26827
27045
  return __awaiter(this, void 0, void 0, function* () {
26828
- const now = moment$7();
27046
+ const now = moment$8();
26829
27047
  const nowAtMahe = now.clone().tz(this.timezone).startOf('day');
26830
27048
  const data = {
26831
27049
  empty: null,
@@ -27571,5 +27789,5 @@ CoreTestingModule.decorators = [
27571
27789
  * Generated bundle index. Do not edit.
27572
27790
  */
27573
27791
 
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 };
27792
+ 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
27793
  //# sourceMappingURL=sumaris-net.ngx-components.js.map