@sumaris-net/ngx-components 2.0.0-rc3 → 2.0.0-rc5

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 (39) hide show
  1. package/doc/changelog.md +13 -0
  2. package/esm2020/src/app/core/form/editor.class.mjs +4 -1
  3. package/esm2020/src/app/core/form/entity-editor-modal.class.mjs +19 -5
  4. package/esm2020/src/app/core/form/form.class.mjs +4 -1
  5. package/esm2020/src/app/core/form/form.utils.mjs +3 -3
  6. package/esm2020/src/app/core/icon/icon.component.mjs +2 -2
  7. package/esm2020/src/app/core/services/model/peer.model.mjs +3 -3
  8. package/esm2020/src/app/core/services/model/referential.model.mjs +11 -3
  9. package/esm2020/src/app/core/table/entities-table-datasource.class.mjs +1 -1
  10. package/esm2020/src/app/core/table/table.class.mjs +162 -159
  11. package/esm2020/src/app/shared/events.mjs +2 -2
  12. package/esm2020/src/app/shared/graph/graph-colors.mjs +17 -63
  13. package/esm2020/src/app/shared/material/autocomplete/material.autocomplete.mjs +3 -5
  14. package/esm2020/src/app/shared/material/chips/material.chips.mjs +2 -3
  15. package/esm2020/src/app/shared/material/material.animations.mjs +1 -1
  16. package/esm2020/src/app/shared/material/material.testing.module.mjs +18 -6
  17. package/esm2020/src/app/shared/observables.mjs +29 -22
  18. package/esm2020/src/app/shared/services/memory-entity-service.class.mjs +12 -6
  19. package/esm2020/src/app/shared/testing/observable.test.mjs +96 -0
  20. package/fesm2015/sumaris-net.ngx-components.mjs +489 -422
  21. package/fesm2015/sumaris-net.ngx-components.mjs.map +1 -1
  22. package/fesm2020/sumaris-net.ngx-components.mjs +359 -266
  23. package/fesm2020/sumaris-net.ngx-components.mjs.map +1 -1
  24. package/package.json +2 -2
  25. package/src/app/core/form/editor.class.d.ts +1 -0
  26. package/src/app/core/form/entity-editor-modal.class.d.ts +18 -9
  27. package/src/app/core/form/form.class.d.ts +1 -0
  28. package/src/app/core/form/form.utils.d.ts +2 -2
  29. package/src/app/core/services/model/referential.model.d.ts +6 -1
  30. package/src/app/core/table/table.class.d.ts +34 -47
  31. package/src/app/shared/events.d.ts +4 -4
  32. package/src/app/shared/graph/graph-colors.d.ts +8 -8
  33. package/src/app/shared/material/material.testing.module.d.ts +9 -8
  34. package/src/app/shared/observables.d.ts +1 -1
  35. package/src/app/shared/services/memory-entity-service.class.d.ts +5 -0
  36. package/src/app/shared/testing/observable.test.d.ts +25 -0
  37. package/src/theme/_ionic.globals.scss +4 -0
  38. package/src/theme/_material.globals.scss +3 -1
  39. package/src/theme/_mixins.scss +3 -3
@@ -56,8 +56,8 @@ import * as i6 from '@angular/material/autocomplete';
56
56
  import { MatAutocomplete, MatAutocompleteTrigger, MatAutocompleteModule, MAT_AUTOCOMPLETE_SCROLL_STRATEGY, MAT_AUTOCOMPLETE_DEFAULT_OPTIONS } from '@angular/material/autocomplete';
57
57
  import * as i1$2 from '@angular/forms';
58
58
  import { Validators, NG_VALUE_ACCESSOR, AbstractControl, UntypedFormGroup, UntypedFormArray, UntypedFormControl, ReactiveFormsModule } from '@angular/forms';
59
- import { timer, merge, fromEvent, BehaviorSubject, Subscription, Subject, isObservable, from, of, noop as noop$9, Observable, forkJoin, defer, interval, combineLatest, EMPTY } from 'rxjs';
60
- import { filter, takeUntil, map, first, switchMap, tap, debounceTime, startWith, distinctUntilChanged, mergeMap, catchError, throttleTime, skip, bufferWhen, take } from 'rxjs/operators';
59
+ import { timer, merge, fromEvent, BehaviorSubject, Subscription, Subject, isObservable, from, of, noop as noop$9, Observable, forkJoin, defer, interval, EMPTY } from 'rxjs';
60
+ import { filter, map, takeUntil, first, switchMap, tap, debounceTime, startWith, distinctUntilChanged, mergeMap, catchError, throttleTime, skip, bufferWhen, take } from 'rxjs/operators';
61
61
  import { trigger, transition, style, animate, state } from '@angular/animations';
62
62
  import * as i2 from '@ionic/angular';
63
63
  import { IonicModule, Platform, ToastController, IonicSafeString, createAnimation, IonicRouteStrategy, ModalController, AlertController, IonContent, IonItem, IonInfiniteScroll } from '@ionic/angular';
@@ -824,32 +824,40 @@ function filterTrue(obs) {
824
824
  function filterFalse(obs, opts) {
825
825
  return obs.pipe(filter((v) => v === false));
826
826
  }
827
- function decorateForFirst(obs, opts) {
827
+ function decorateWithTakeUntil(obs, opts) {
828
828
  // Set take until notifier
829
- const takeUntil$ = opts?.stop || (opts?.timeout && timer(opts.timeout));
830
- if (takeUntil$) {
831
- // When stop, throw an error (useful when used with a toPromise())
832
- if (opts.stopError) {
833
- return merge(obs.pipe(takeUntil(takeUntil$)), takeUntil$
834
- .pipe(map(() => {
835
- throw new Error(opts.timeout ? `Timeout ${opts.timeout}ms` : `Stop`);
836
- })));
829
+ let takeUntil$ = opts?.stop || (opts?.timeout && timer(opts.timeout));
830
+ if (!takeUntil$)
831
+ return obs; // Skip
832
+ // When stop, throw an error (useful when used with a toPromise())
833
+ if (opts.stopError) {
834
+ let error;
835
+ if (opts.stopError === true) {
836
+ error = new Error(opts.timeout ? `Timeout ${opts.timeout}ms` : `stop`);
837
+ }
838
+ else if (typeof opts.stopError === 'string') {
839
+ error = new Error(opts.stopError);
840
+ }
841
+ else {
842
+ error = opts.stopError;
837
843
  }
838
- // Add a takeUntil
839
- return obs.pipe(takeUntil(takeUntil$));
844
+ takeUntil$ = takeUntil$.pipe(map(() => {
845
+ throw error;
846
+ }));
840
847
  }
841
- return obs;
848
+ return obs
849
+ .pipe(takeUntil(takeUntil$));
842
850
  }
843
851
  function firstNotNil(obs, opts) {
844
- return decorateForFirst(obs, opts).pipe(first(isNotNil));
852
+ return decorateWithTakeUntil(obs.pipe(first(isNotNil)), opts);
845
853
  }
846
854
  function firstTrue(obs, opts) {
847
- return decorateForFirst(obs, opts).pipe(first((v) => v === true), map(_ => { }) // Convert to void
848
- );
855
+ return decorateWithTakeUntil(obs.pipe(first((v) => v === true), map(_ => { }) // Convert to void
856
+ ), opts);
849
857
  }
850
858
  function firstFalse(obs, opts) {
851
- return decorateForFirst(obs, opts).pipe(first((v) => v === false), map(_ => { }) // Convert to void
852
- );
859
+ return decorateWithTakeUntil(obs.pipe(first((v) => v === false), map(_ => { }) // Convert to void
860
+ ), opts);
853
861
  }
854
862
  function firstTruePromise(obs, opts) {
855
863
  return firstTrue(obs, { stopError: true, ...opts }).toPromise();
@@ -896,9 +904,8 @@ function waitForTrue(observable, opts) {
896
904
  opts = { stopError: true, ...opts };
897
905
  // dueTime (without timeout)
898
906
  if (opts && opts.dueTime > 0) {
899
- return timer(opts.dueTime)
900
- .pipe(switchMap(() => firstTrue(observable, opts)))
901
- .toPromise();
907
+ observable = timer(opts.dueTime)
908
+ .pipe(switchMap(() => observable));
902
909
  }
903
910
  return firstTrue(observable, opts).toPromise();
904
911
  }
@@ -913,7 +920,7 @@ function createPromiseEventEmitter() {
913
920
  }
914
921
  function createPromiseEvent(eventType, promise, initArg) {
915
922
  const detail = {
916
- ...(initArg && initArg.detail || {}),
923
+ ...(initArg?.detail || {}),
917
924
  ...promise
918
925
  };
919
926
  return new CustomEvent(eventType, { detail });
@@ -2265,8 +2272,7 @@ class MatAutocompleteField {
2265
2272
  ? timer(0)
2266
2273
  : this.autocomplete.opened.pipe(debounceTime(200));
2267
2274
  const scrollEnd$ = opened$
2268
- .pipe(map((_) => this.getAutocompletePanel()), filter(isNotNil), switchMap((panel) => fromScrollEndEvent(panel, threshold)
2269
- .pipe(takeUntil(this.autocomplete.closed))));
2275
+ .pipe(map((_) => this.getAutocompletePanel()), filter(isNotNil), switchMap((panel) => fromScrollEndEvent(panel, threshold)), takeUntil(this.autocomplete.closed));
2270
2276
  // Trigger fetch more, end end scroll reached
2271
2277
  this._openedSubscription = scrollEnd$
2272
2278
  .subscribe(() => this._fetchMore$.emit());
@@ -2286,8 +2292,7 @@ class MatAutocompleteField {
2286
2292
  ? timer(0)
2287
2293
  : this.matSelect._openedStream.pipe(debounceTime(200));
2288
2294
  const scrollEnd$ = opened$
2289
- .pipe(map((_) => this.getMatSelectPanel()), filter(isNotNil), switchMap((panel) => fromScrollEndEvent(panel, threshold)
2290
- .pipe(takeUntil(this.matSelect._closedStream))));
2295
+ .pipe(map((_) => this.getMatSelectPanel()), filter(isNotNil), switchMap((panel) => fromScrollEndEvent(panel, threshold)), takeUntil(this.matSelect._closedStream));
2291
2296
  // Trigger fetch more, end end scroll reached
2292
2297
  this._openedSubscription = scrollEnd$
2293
2298
  .pipe(filter(_ => this._moreItemsCount > 0))
@@ -4551,7 +4556,7 @@ AppFormUtils.waitIdle = waitIdle;
4551
4556
  AppFormUtils.updateValueAndValidity = updateValueAndValidity;
4552
4557
  AppFormUtils.getFormErrors = getFormErrors;
4553
4558
  // ArrayForm
4554
- AppFormUtils.addValueInArray = pushValueInArray;
4559
+ AppFormUtils.pushValueInArray = pushValueInArray;
4555
4560
  AppFormUtils.removeValueInArray = removeValueInArray;
4556
4561
  AppFormUtils.resizeArray = resizeArray;
4557
4562
  AppFormUtils.clearValueInArray = clearValueInArray;
@@ -8863,8 +8868,7 @@ class MatChipsField {
8863
8868
  ? timer(0)
8864
8869
  : this.autocomplete.opened.pipe(debounceTime(200));
8865
8870
  const scrollEnd$ = opened$
8866
- .pipe(map((_) => this.getAutocompletePanel()), filter(isNotNil), switchMap((panel) => fromScrollEndEvent(panel, threshold)
8867
- .pipe(takeUntil(this.autocomplete.closed))));
8871
+ .pipe(map((_) => this.getAutocompletePanel()), filter(isNotNil), switchMap((panel) => fromScrollEndEvent(panel, threshold)), takeUntil(this.autocomplete.closed));
8868
8872
  // Trigger fetch more, end end scroll reached
8869
8873
  this._openedSubscription = scrollEnd$
8870
8874
  .subscribe(() => this._fetchMore$.emit());
@@ -12926,7 +12930,7 @@ let Peer = Peer_1 = class Peer extends Entity {
12926
12930
  }
12927
12931
  static parseUrl(peerUrl) {
12928
12932
  const url = new URL(peerUrl);
12929
- return this.fromObject({
12933
+ return Peer_1.fromObject({
12930
12934
  dns: url.hostname,
12931
12935
  port: isNilOrBlank(url.port) ? undefined : url.port,
12932
12936
  useSsl: url.protocol && (url.protocol.startsWith('https') || url.protocol.startsWith('wss')),
@@ -12944,7 +12948,7 @@ let Peer = Peer_1 = class Peer extends Entity {
12944
12948
  return path.substring(1);
12945
12949
  return path;
12946
12950
  }).filter(isNotNilOrBlank);
12947
- return [noTrailingSlash(this.fromObject(peer).url)].concat(...paths).join('/');
12951
+ return [noTrailingSlash(Peer_1.fromObject(peer).url)].concat(...paths).join('/');
12948
12952
  }
12949
12953
  asObject(options) {
12950
12954
  return super.asObject(options);
@@ -14528,6 +14532,8 @@ class BaseReferential extends Entity {
14528
14532
  target.creationDate = toDateISOString(this.creationDate);
14529
14533
  if (opts && opts.keepTypename === false)
14530
14534
  delete target.entityName;
14535
+ if (opts && opts.keepIcon === false)
14536
+ delete target.icon;
14531
14537
  return target;
14532
14538
  }
14533
14539
  fromObject(source, opts) {
@@ -14542,6 +14548,7 @@ class BaseReferential extends Entity {
14542
14548
  this.parentId = source.parentId;
14543
14549
  this.creationDate = fromDateISOString(source.creationDate);
14544
14550
  this.entityName = source.entityName;
14551
+ this.icon = source.icon;
14545
14552
  }
14546
14553
  equals(other) {
14547
14554
  return super.equals(other) && this.entityName === other.entityName;
@@ -14580,7 +14587,8 @@ const MINIFY_ENTITY_FOR_LOCAL_STORAGE = Object.freeze({
14580
14587
  minify: true,
14581
14588
  keepTypename: true,
14582
14589
  keepEntityName: true,
14583
- keepLocalId: true
14590
+ keepLocalId: true,
14591
+ keepIcon: true
14584
14592
  });
14585
14593
  /**
14586
14594
  * Used to send an entity to a pod
@@ -14589,7 +14597,8 @@ const MINIFY_ENTITY_FOR_POD = Object.freeze({
14589
14597
  minify: true,
14590
14598
  keepTypename: false,
14591
14599
  keepEntityName: false,
14592
- keepLocalId: false
14600
+ keepLocalId: false,
14601
+ keepIcon: false
14593
14602
  });
14594
14603
  // @dynamic
14595
14604
  let ReferentialRef = ReferentialRef_1 = class ReferentialRef extends Entity {
@@ -14607,6 +14616,8 @@ let ReferentialRef = ReferentialRef_1 = class ReferentialRef extends Entity {
14607
14616
  const target = super.asObject(opts);
14608
14617
  if (opts && opts.keepEntityName === false)
14609
14618
  delete target.entityName;
14619
+ if (opts && opts.keepIcon === false)
14620
+ delete target.icon;
14610
14621
  return target;
14611
14622
  }
14612
14623
  fromObject(source, opts) {
@@ -14617,6 +14628,7 @@ let ReferentialRef = ReferentialRef_1 = class ReferentialRef extends Entity {
14617
14628
  this.description = source.description;
14618
14629
  this.statusId = source.statusId;
14619
14630
  this.levelId = source.levelId;
14631
+ this.icon = source.icon;
14620
14632
  }
14621
14633
  };
14622
14634
  ReferentialRef = ReferentialRef_1 = __decorate([
@@ -19403,8 +19415,6 @@ class InMemoryEntitiesService extends StartableObservableService {
19403
19415
  async ngOnStop() {
19404
19416
  if (this.debug)
19405
19417
  console.debug('[memory-data-service] Stopping...');
19406
- this.dataSubject.complete();
19407
- this.dataSubject.unsubscribe();
19408
19418
  this._hiddenData = null;
19409
19419
  this.markAsSaved();
19410
19420
  this.markAsPristine();
@@ -19530,10 +19540,10 @@ class InMemoryEntitiesService extends StartableObservableService {
19530
19540
  offset = offset >= 0 ? offset : 0;
19531
19541
  size = size >= 0 ? size : -1;
19532
19542
  return this.dataSubject
19533
- .pipe(takeUntil(this.stopSubject),
19543
+ .pipe(
19534
19544
  // Warn if waiting value to be set
19535
- tap((data) => this.debug && !data && console.debug('[memory-data-service] Waiting value to be set...')), filter(isNotNil), mergeMap(_ => this.loadAll(offset, size, sortBy, sortDirection, filterData, options)), filter(isNotNil) // Skip no result (e.g. when stopped)
19536
- );
19545
+ tap((data) => this.debug && !data && console.debug('[memory-data-service] Waiting value to be set...')), filter(isNotNil), mergeMap(_ => this.loadAll(offset, size, sortBy, sortDirection, filterData, options)), filter(isNotNil), // Skip no result (e.g. when stopped)
19546
+ takeUntil(this.stopSubject));
19537
19547
  }
19538
19548
  async saveAll(data, options) {
19539
19549
  if (!this.dataSubject.value)
@@ -19600,6 +19610,14 @@ class InMemoryEntitiesService extends StartableObservableService {
19600
19610
  * Count data stored by the service (without hidden data).
19601
19611
  * @param opts
19602
19612
  */
19613
+ // get visibleCount(): number {
19614
+ // return (this.dataSubject.value?.length || 0);
19615
+ // }
19616
+ /**
19617
+ * Count data stored by the service (without hidden data).
19618
+ * @param opts
19619
+ * @deprecated TODO Please rename this getter into visibleCount (and create count = hiddenCount + visibleCount)
19620
+ */
19603
19621
  get count() {
19604
19622
  return (this.dataSubject.value?.length || 0);
19605
19623
  }
@@ -19908,14 +19926,13 @@ Color.transparent = function () {
19908
19926
  // @dynamic
19909
19927
  class ColorScale {
19910
19928
  constructor(colorArray, options) {
19911
- this.colorArray = colorArray;
19912
19929
  options = options || {};
19913
19930
  // reserved last colors for value > max
19914
19931
  const nbIntervalBeforeUpper = !options.upperMax ? colorArray.length : (colorArray.length - 1);
19915
19932
  this._min = options.min || 0;
19916
19933
  this._max = options.max || nbIntervalBeforeUpper;
19917
19934
  this._rangeSize = Math.round((this._max - this._min) / nbIntervalBeforeUpper);
19918
- this._legendItems = this.computeLegend();
19935
+ this._legendItems = this.computeLegendItems(colorArray);
19919
19936
  }
19920
19937
  static default() {
19921
19938
  return ColorScale.custom(25);
@@ -19939,15 +19956,21 @@ class ColorScale {
19939
19956
  };
19940
19957
  }
19941
19958
  getValueColor(value) {
19942
- const index = Math.floor(value * (this.colorArray.length - 1) / this._max);
19943
- return this.colorArray[index];
19959
+ return this.getLegendForValue(value).color;
19960
+ }
19961
+ getLegendForValue(value) {
19962
+ const index = Math.floor(value * (this._legendItems.length - 1) / this._max);
19963
+ return this._legendItems[index];
19964
+ }
19965
+ getLegendAtIndex(index) {
19966
+ return this._legendItems[index];
19944
19967
  }
19945
- computeLegend() {
19946
- return this.colorArray.map((color, index) => {
19968
+ computeLegendItems(colorArray) {
19969
+ return colorArray.map((color, index) => {
19947
19970
  const start = index * this._rangeSize;
19948
19971
  const end = start + this._rangeSize;
19949
19972
  return {
19950
- color,
19973
+ color: new Color(color),
19951
19974
  label: (end < this._max) ? `${start.toLocaleString()} - ${end.toLocaleString()}` : ` >= ${start}`
19952
19975
  };
19953
19976
  });
@@ -19956,12 +19979,10 @@ class ColorScale {
19956
19979
  ColorScale.custom = (count, options) => {
19957
19980
  options = options || {};
19958
19981
  return new ColorScale(linearColorGradientWithIntermediate(count, {
19959
- opacity: options.opacity,
19960
19982
  startColor: options.startColor || undefined,
19961
19983
  mainColor: options.mainColor || undefined,
19962
19984
  mainColorIndex: isNotNil(options.mainColorIndex) ? options.mainColorIndex : undefined,
19963
- endColor: options.endColor || undefined,
19964
- format: options.format
19985
+ endColor: options.endColor || undefined
19965
19986
  }), options);
19966
19987
  };
19967
19988
  // Fill colorsMap
@@ -19969,28 +19990,6 @@ Object.getOwnPropertyNames(rgbArrayMap)
19969
19990
  .forEach((key) => {
19970
19991
  colorsMap[key] = new Color(rgbArrayMap[key], 1, key);
19971
19992
  });
19972
- // Internal function
19973
- function state2side(state) {
19974
- switch (state) {
19975
- case 0:
19976
- return 0;
19977
- case 1:
19978
- return -1;
19979
- case 2:
19980
- return 0;
19981
- case 3:
19982
- return 1;
19983
- }
19984
- }
19985
- const SCALE_OPTIONS_DEFAULT = {
19986
- startColor: rgbArrayMap.red,
19987
- startStates: [0, 2, 3],
19988
- startStepsFn: (defaultStateSize) => [
19989
- Math.round((rgbArrayMap.red[0] - 50) / defaultStateSize),
19990
- Math.round((255 - rgbArrayMap.red[1]) / defaultStateSize),
19991
- Math.round((255 - rgbArrayMap.red[2]) / defaultStateSize)
19992
- ]
19993
- };
19994
19993
  /**
19995
19994
  * Internal function, that create a colors scale, using iteration
19996
19995
  *
@@ -20003,34 +20002,26 @@ const SCALE_OPTIONS_DEFAULT = {
20003
20002
  function linearColorGradientWithIntermediate(count, options) {
20004
20003
  options = options || {};
20005
20004
  // From [0,1]
20006
- options.opacity = (options.opacity > 0 && options.opacity < 1) ? options.opacity : 1;
20007
20005
  options.startColor = options.startColor || [255, 255, 190]; // default start = creme
20008
20006
  options.mainColorIndex = options.mainColorIndex && options.mainColorIndex < count - 1 ? options.mainColorIndex : count - 1;
20009
20007
  options.endColor = options.endColor || [255, 0, 0]; // default main = red
20010
- options.format = options.format || 'rgb';
20011
20008
  if (!options.mainColor) {
20012
20009
  return linearColorGradient(count, {
20013
- opacity: options.opacity,
20014
20010
  startColor: options.startColor,
20015
- endColor: options.endColor,
20016
- format: options.format
20011
+ endColor: options.endColor
20017
20012
  });
20018
20013
  }
20019
20014
  else {
20020
20015
  // Step 1: startColor -> mainColor
20021
20016
  const result = linearColorGradient(options.mainColorIndex + 1, {
20022
- opacity: options.opacity,
20023
20017
  startColor: options.startColor,
20024
- endColor: options.mainColor,
20025
- format: options.format
20018
+ endColor: options.mainColor
20026
20019
  });
20027
20020
  // Step 2: mainColor -> endColor
20028
20021
  if (options.mainColorIndex < count - 1) {
20029
20022
  return result.concat(linearColorGradient(count - options.mainColorIndex, {
20030
- opacity: options.opacity,
20031
20023
  startColor: options.mainColor,
20032
- endColor: options.endColor,
20033
- format: options.format
20024
+ endColor: options.endColor
20034
20025
  }));
20035
20026
  }
20036
20027
  else {
@@ -20041,10 +20032,8 @@ function linearColorGradientWithIntermediate(count, options) {
20041
20032
  function linearColorGradient(count, options) {
20042
20033
  options = options || {};
20043
20034
  // From [0,1]
20044
- options.opacity = (options.opacity > 0 && options.opacity < 1) ? options.opacity : 1;
20045
20035
  options.startColor = options.startColor || [255, 255, 255]; // default start = white
20046
20036
  options.endColor = options.endColor || [255, 0, 0]; // default end = red
20047
- options.format = options.format || 'rgb';
20048
20037
  const result = [];
20049
20038
  const color = options.startColor.slice(); // copy the start color
20050
20039
  const delta = [
@@ -20061,23 +20050,6 @@ function linearColorGradient(count, options) {
20061
20050
  // Force last color = end color
20062
20051
  result.push(options.endColor.slice());
20063
20052
  // Output as array
20064
- if (options.format === 'array') {
20065
- return result;
20066
- }
20067
- // Output as rgb(r,g,b) string
20068
- if (options.format === 'rgb') {
20069
- if (options.opacity >= 1) {
20070
- return result.map(color => 'rgb(' + color.join(',') + ')');
20071
- }
20072
- else {
20073
- return result.map(color => 'rgba(' + color.concat(options.opacity).join(',') + ')');
20074
- }
20075
- }
20076
- // Output as hex
20077
- // TODO
20078
- // return result.map(color => {
20079
- // return "rgb(" + color.join(',') + ")";
20080
- // });
20081
20053
  return result;
20082
20054
  }
20083
20055
 
@@ -20542,6 +20514,9 @@ class AppForm {
20542
20514
  this.markForCheck();
20543
20515
  }
20544
20516
  }
20517
+ get destroyed() {
20518
+ return this.destroySubject?.closed !== false;
20519
+ }
20545
20520
  set form(value) {
20546
20521
  this.setForm(value);
20547
20522
  }
@@ -23329,7 +23304,7 @@ class AppIconComponent {
23329
23304
  this.icon = value.icon;
23330
23305
  this.matIcon = value.matIcon;
23331
23306
  this.matSvgIcon = value.matSvgIcon;
23332
- this.color = value.color;
23307
+ this.color = value.color || this.color; // Keep existing, if not defined in the reference
23333
23308
  }
23334
23309
  ;
23335
23310
  }
@@ -24646,6 +24621,7 @@ class AppTable {
24646
24621
  this.defaultPageSize = DEFAULT_PAGE_SIZE;
24647
24622
  this.defaultPageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS;
24648
24623
  this.onRefresh = new EventEmitter();
24624
+ this.onOpenIdAndRow = new EventEmitter();
24649
24625
  this.onOpenRow = new EventEmitter();
24650
24626
  this.onNewRow = new EventEmitter();
24651
24627
  this.onStartEditingRow = new EventEmitter();
@@ -24788,7 +24764,7 @@ class AppTable {
24788
24764
  if (this.dirty || (this.editedRow && this.editedRow.id !== this.previouslyEditedRowId)) {
24789
24765
  // Cancel the current editing row only if editing and if it was not previously saved
24790
24766
  if (this.editedRow?.editing) {
24791
- this.editedRow.cancelOrDelete();
24767
+ this.dataSource.cancelOrDelete(this.editedRow);
24792
24768
  // Mark row as pristine
24793
24769
  this.checkIfRowPristine(this.editedRow, { emitEvent: false });
24794
24770
  }
@@ -24890,6 +24866,9 @@ class AppTable {
24890
24866
  get paginator() {
24891
24867
  return this._paginator || this.childPaginator;
24892
24868
  }
24869
+ get destroyed() {
24870
+ return this.destroySubject?.closed !== false;
24871
+ }
24893
24872
  ngOnInit() {
24894
24873
  if (this._initialized)
24895
24874
  return; // Init only once
@@ -24924,17 +24903,11 @@ class AppTable {
24924
24903
  // Propagate row dirty state to table
24925
24904
  this.registerSubscription(this.onStartEditingRow
24926
24905
  .pipe(filter(row => row?.validator && true), mergeMap(row => row.validator.valueChanges
24927
- .pipe(takeUntil(
24928
- // Stop if next another row, or destroying
24929
- combineLatest([
24930
- this.onStartEditingRow,
24931
- this.destroySubject
24932
- ])
24906
+ .pipe(filter(row => row.dirty), first(),
24933
24907
  // DEBUG
24934
- //.pipe(
24935
- // tap(() => console.debug('TODO stopping previous row listener'))
24936
- //)
24937
- ), map(() => row.dirty), filter(dirty => dirty === true), first())))
24908
+ //tap(() => console.debug("Propagate row's dirty to table..."))
24909
+ // Stop if next another row, or destroying
24910
+ takeUntil(this.onStartEditingRow), takeUntil(this.destroySubject))))
24938
24911
  .subscribe(() => this.markAsDirty()));
24939
24912
  // Call datasource refresh, on each refresh events
24940
24913
  this.registerSubscription(this.onRefresh
@@ -25063,34 +25036,38 @@ class AppTable {
25063
25036
  this.applyFilter(filter, opts);
25064
25037
  }
25065
25038
  }
25066
- async confirmAndAdd(event, row) {
25067
- const confirmed = await this.confirmEditCreate(event, row);
25068
- if (!confirmed)
25039
+ confirmAndAdd(event, row) {
25040
+ if (row && !this.confirmEditCreate(event, row)) {
25069
25041
  return false;
25042
+ }
25070
25043
  // Add row
25071
25044
  return this.addRow(event);
25072
25045
  }
25073
- async confirmAndBackward(event, row) {
25046
+ confirmAndBackward(event, row) {
25074
25047
  // Deleting edited row, if empty and not dirty
25075
25048
  const previousRow = this.editedRow;
25076
- if (previousRow && (previousRow.id === -1) && previousRow.validator?.invalid && !(previousRow.dirty)) {
25077
- const deleted = this.deleteNew(event, previousRow);
25078
- if (!deleted)
25079
- return false;
25049
+ if (previousRow && (previousRow.id === -1) && previousRow.validator?.invalid && !(previousRow.validator?.dirty)) {
25050
+ this.deleteNewRow(event, previousRow);
25080
25051
  // Wait deletion is done, then edit previous row (by id, because of reloading)
25081
- await this.waitIdle();
25052
+ this.waitIdle()
25053
+ .then(() => this.editRowById(event, row.id, { focusColumn: this.lastUserColumn }));
25054
+ return true;
25082
25055
  }
25083
25056
  // Edit previous row
25084
- return this.startEditRow(event, row, { focusColumn: this.lastUserColumn });
25057
+ this.editRow(event, row, { focusColumn: this.lastUserColumn });
25058
+ return true;
25085
25059
  }
25086
- async confirmAndForward(event, row) {
25060
+ confirmAndForward(event, row) {
25087
25061
  row = row || this.editedRow;
25088
- const confirmed = await this.confirmEditCreate(event, row);
25089
- // Cannot confirm previous row: skip
25090
- if (!confirmed)
25062
+ if (!this.confirmEditCreate(event, row)) {
25063
+ // If pending: Wait end of validation, then loop
25064
+ if (row.validator?.pending) {
25065
+ return AppFormUtils.waitWhilePending(row.validator).then(() => this.confirmAndForward(event, row));
25066
+ }
25091
25067
  return false;
25068
+ }
25092
25069
  // Edit next row
25093
- this.startEditRowById(null, row.id + 1, { focusColumn: this.firstUserColumn });
25070
+ this.editRowById(event, row.id + 1, { focusColumn: this.firstUserColumn });
25094
25071
  return true;
25095
25072
  }
25096
25073
  /**
@@ -25099,15 +25076,15 @@ class AppTable {
25099
25076
  * @param event
25100
25077
  * @param row
25101
25078
  */
25102
- async confirmEditCreate(event, row) {
25079
+ confirmEditCreate(event, row) {
25103
25080
  row = row || this.editedRow;
25104
25081
  if (!row || !row.editing)
25105
25082
  return true; // no row to confirm
25106
25083
  // Stop event
25107
- event?.preventDefault();
25108
25084
  event?.stopPropagation();
25109
25085
  // Confirmation edition or creation
25110
- const confirmed = await row.confirmEditCreate();
25086
+ // TODO: add await ?
25087
+ const confirmed = row.confirmEditCreate();
25111
25088
  if (confirmed) {
25112
25089
  // If edit finished, forget edited row
25113
25090
  if (row === this.editedRow) {
@@ -25123,8 +25100,13 @@ class AppTable {
25123
25100
  this.onConfirmEditCreateRow.next(row);
25124
25101
  return true; // Continue
25125
25102
  }
25126
- // Not conformed + validator
25127
- else if (row.validator) {
25103
+ if (row.validator) {
25104
+ // If pending: Wait end of validation, then loop
25105
+ // TODO: remove when using async isValid() function
25106
+ if (row.validator.pending) {
25107
+ AppFormUtils.waitWhilePending(row.validator).then(() => this.confirmEditCreate(event, row));
25108
+ return false;
25109
+ }
25128
25110
  // NOT confirmed = row has error
25129
25111
  if (this.debug) {
25130
25112
  console.warn('[table] Cannot confirm row, because invalid');
@@ -25141,18 +25123,18 @@ class AppTable {
25141
25123
  // Not confirmed
25142
25124
  return false;
25143
25125
  }
25144
- async cancelOrDelete(event, row, opts) {
25126
+ cancelOrDelete(event, row, opts) {
25145
25127
  // Delete new row
25146
25128
  if (row.id === -1) {
25147
- return this.deleteNew(event, row);
25129
+ this.deleteNewRow(event, row);
25148
25130
  }
25149
- // Delete existing (but not editing) row
25131
+ // Delete existing (bu not editing) row
25150
25132
  else if (!row.editing) {
25151
- return this.deleteExisting(event, row, opts);
25133
+ this.deleteExistingRow(event, row, opts);
25152
25134
  }
25153
25135
  // Cancel existing (and editing) row
25154
25136
  else {
25155
- return this.cancelExisting(event, row, opts);
25137
+ this.cancelExistingRow(event, row, opts);
25156
25138
  }
25157
25139
  }
25158
25140
  async addRow(event, insertAt, opts) {
@@ -25166,11 +25148,11 @@ class AppTable {
25166
25148
  return false;
25167
25149
  }
25168
25150
  // Try to finish edited row first
25169
- const confirmed = await this.confirmEditCreate();
25170
- if (!confirmed)
25151
+ if (!this.confirmEditCreate()) {
25171
25152
  return false;
25172
- // Create a new row
25173
- const row = await this.createNewRow(insertAt, opts);
25153
+ }
25154
+ // Add new row
25155
+ const row = await this.addRowToTable(insertAt, opts);
25174
25156
  return !!row;
25175
25157
  }
25176
25158
  async save(opts) {
@@ -25211,27 +25193,28 @@ class AppTable {
25211
25193
  }
25212
25194
  }
25213
25195
  }
25214
- async cancel(event, opts) {
25196
+ cancel(event, opts) {
25215
25197
  // Check confirmation
25216
25198
  if ((!opts || opts.interactive !== false) && this.dirty && (this.confirmBeforeCancel || this.onBeforeCancelRows.observers.length > 0)) {
25217
25199
  event?.stopPropagation();
25218
- const canCancel = await this.canCancelRows();
25219
- if (!canCancel)
25220
- return false;
25200
+ this.canCancelRows().then(confirm => {
25201
+ // If confirmed, loop
25202
+ if (confirm)
25203
+ this.cancel(null, { interactive: false });
25204
+ });
25205
+ return;
25221
25206
  }
25222
25207
  this.onRefresh.emit();
25223
- return true;
25224
25208
  }
25225
25209
  async duplicateRow(event, row, opts) {
25226
25210
  event?.stopPropagation();
25227
25211
  row = row || this.singleSelectedRow;
25228
- if (!row)
25229
- return false; // No row, or too many row
25230
- // Confirm the source row
25231
- const confirmed = await this.confirmEditCreate(event, row);
25232
- if (!confirmed)
25212
+ if (!row || !this.confirmEditCreate(event, row)) {
25233
25213
  return false;
25214
+ }
25234
25215
  const newRow = await this.addRowToTable(row.id + 1);
25216
+ if (!newRow)
25217
+ throw new Error('Cannot add new row to table');
25235
25218
  const json = { ...row.currentData, id: null };
25236
25219
  // Reset some properties (e.g. rankOrder, etc)
25237
25220
  if (opts && opts.skipProperties) {
@@ -25295,8 +25278,9 @@ class AppTable {
25295
25278
  * And to force deletion even if table is busy
25296
25279
  */
25297
25280
  async deleteRows(event, rows, opts) {
25298
- if (this.readOnly)
25281
+ if (this.readOnly) {
25299
25282
  throw { code: ErrorCodes.TABLE_READ_ONLY, message: 'ERROR.TABLE_READ_ONLY' };
25283
+ }
25300
25284
  if (event?.defaultPrevented)
25301
25285
  return 0; // SKip
25302
25286
  event?.preventDefault();
@@ -25332,7 +25316,7 @@ class AppTable {
25332
25316
  if (row.id === -1 || (!row.editing && row.invalid /*do not use !valid because if row is disabled, it will be always !valid */)) {
25333
25317
  if (this.debug)
25334
25318
  console.debug(`[table] Delete row #${row.id}`);
25335
- row.delete(); // TODO: add await ?
25319
+ row.delete();
25336
25320
  this.visibleRowCount--;
25337
25321
  this.totalRowCount--;
25338
25322
  deletedRows.push(row);
@@ -25342,7 +25326,7 @@ class AppTable {
25342
25326
  }
25343
25327
  // Cancel the row (and mark as pristine), when not valid but in edition
25344
25328
  else if (row.editing && !row.valid) {
25345
- row.cancel(); // TODO: add await ?
25329
+ row.cancel();
25346
25330
  // Mark row as pristine (if possible)
25347
25331
  this.checkIfRowPristine(row, { emitEvent: false, onlySelf: true /*avoid propagation to table*/ });
25348
25332
  return true; // Keep the row. the row will be deleted after the save
@@ -25354,8 +25338,10 @@ class AppTable {
25354
25338
  if (deletedRows.length || tempRows.length) {
25355
25339
  // Save data (e.g. when using memory service)
25356
25340
  const saved = await this.saveBeforeAction('delete');
25357
- if (!saved)
25358
- return 0; // Stop if save cancelled or save failed
25341
+ if (!saved) {
25342
+ // Stop if save cancelled or save failed
25343
+ return;
25344
+ }
25359
25345
  }
25360
25346
  }
25361
25347
  try {
@@ -25369,7 +25355,7 @@ class AppTable {
25369
25355
  // DO not update manually, because watchALl().subscribe() will update this count
25370
25356
  //this.totalRowCount -= deleteCount;
25371
25357
  //this.visibleRowCount -= deleteCount;
25372
- this.selection.deselect(...rows);
25358
+ this.selection.clear();
25373
25359
  this.editedRow = undefined;
25374
25360
  this.markAsDirty({ emitEvent: false /*markForCheck() is called just after*/ });
25375
25361
  this.markForCheck();
@@ -25404,12 +25390,13 @@ class AppTable {
25404
25390
  return false;
25405
25391
  return this.clickRow(null, row);
25406
25392
  }
25407
- async clickRow(event, row) {
25408
- // Wait while loading
25393
+ clickRow(event, row) {
25409
25394
  if (this.loading) {
25395
+ // Wait while loading, and loop
25410
25396
  if (this.debug)
25411
25397
  console.debug("[table] Waiting before apply clickRow() (datasource is busy)...");
25412
- await this.waitIdle();
25398
+ this.waitIdle().then(() => this.clickRow(event, row));
25399
+ return false;
25413
25400
  }
25414
25401
  // DEBUG
25415
25402
  //console.debug("[table] Detect click on row");
@@ -25417,27 +25404,24 @@ class AppTable {
25417
25404
  return true; // Already in edition
25418
25405
  if (event?.defaultPrevented)
25419
25406
  return false; // Cancelled by event
25420
- // Start editing row
25421
- if (this.inlineEdition) {
25422
- return this.editRow(event, row, { focusColumn: undefined /*force to use the click target*/ });
25423
- }
25424
- // Open the detail page
25425
- else {
25407
+ // Open the detail page (if not inline editing)
25408
+ if (!this.inlineEdition) {
25426
25409
  if (event) {
25427
25410
  event.stopPropagation();
25428
25411
  event.preventDefault();
25429
25412
  }
25430
25413
  this.markAsLoading();
25431
- try {
25432
- return this.openRowDetail(row);
25433
- }
25434
- finally {
25435
- this.markAsLoaded();
25436
- }
25414
+ this.selection.clear();
25415
+ this.openRow(row.currentData.id, row)
25416
+ .then(() => this.markAsLoaded())
25417
+ .catch(() => this.markAsLoaded());
25418
+ return true;
25437
25419
  }
25420
+ // Start editing row
25421
+ return this.editRow(event, row, { focusColumn: undefined /*force to use the click target*/ });
25438
25422
  }
25439
- async moveRow(id, direction) {
25440
- return this.dataSource.move(id, direction);
25423
+ moveRow(id, direction) {
25424
+ this.dataSource.move(id, direction);
25441
25425
  }
25442
25426
  ready(opts) {
25443
25427
  return waitForTrue(this.readySubject, opts);
@@ -25494,7 +25478,7 @@ class AppTable {
25494
25478
  canHide: this.getRequiredColumns().indexOf(name) === -1
25495
25479
  }));
25496
25480
  }
25497
- async escapeEditingRow(event, row) {
25481
+ escapeEditingRow(event, row) {
25498
25482
  row = row || this.editedRow;
25499
25483
  if (!row || !row.editing)
25500
25484
  return;
@@ -25507,21 +25491,29 @@ class AppTable {
25507
25491
  }
25508
25492
  // If new row (no id)
25509
25493
  if (row.id === -1) {
25510
- const valid = await row.isValid();
25511
- // Row is invalid: delete the row
25512
- if (!valid) {
25513
- return this.deleteNew(event, row);
25494
+ if (row.validator) {
25495
+ // If pending: Wait end of validation, then loop
25496
+ if (row.validator.pending) {
25497
+ AppFormUtils.waitWhilePending(row.validator).then(() => this.escapeEditingRow(event, row));
25498
+ return;
25499
+ }
25500
+ // Row is invalid: delete the row
25501
+ if (row.validator.invalid) {
25502
+ this.deleteNewRow(event, row);
25503
+ return;
25504
+ }
25514
25505
  }
25515
25506
  }
25516
25507
  // If the row exists (has an id)
25517
25508
  else {
25518
25509
  // need to call cancel function (if confirmation will be call before before)
25519
25510
  if (this.confirmBeforeCancel) {
25520
- return this.cancelExisting(event, row, { keepEditing: false });
25511
+ this.cancelExistingRow(event, row, { keepEditing: false });
25512
+ return;
25521
25513
  }
25522
25514
  }
25523
25515
  // By default, try to confirm the row
25524
- return this.confirmEditCreate(event, row);
25516
+ this.confirmEditCreate(event, row);
25525
25517
  }
25526
25518
  translateControlPath(path) {
25527
25519
  // Can be override by subclasses, to resolve all field name
@@ -25530,21 +25522,19 @@ class AppTable {
25530
25522
  return this.translate?.instant(i18nColumnKey) || i18nColumnKey;
25531
25523
  }
25532
25524
  /* -- protected method -- */
25533
- async startEditRow(event, row, opts) {
25525
+ editRow(event, row, opts) {
25534
25526
  if (!this._enabled)
25535
25527
  return false;
25536
25528
  if (this.editedRow === row)
25537
25529
  return true; // Already the edited row
25538
25530
  if (event?.defaultPrevented)
25539
25531
  return false;
25540
- const confirmed = await this.confirmEditCreate();
25541
- if (!confirmed)
25532
+ if (!this.confirmEditCreate()) {
25542
25533
  return false;
25534
+ }
25543
25535
  if (!row.editing && !this.loading) {
25544
25536
  this.focusColumn = opts && opts.focusColumn || this.focusColumn;
25545
- const started = await row.startEdit();
25546
- if (!started)
25547
- return false;
25537
+ this._dataSource.startEdit(row);
25548
25538
  }
25549
25539
  this.editedRow = row;
25550
25540
  this.onStartEditingRow.emit(row);
@@ -25612,7 +25602,7 @@ class AppTable {
25612
25602
  }
25613
25603
  async canCancelRows(rows, opts) {
25614
25604
  // Get dirty rows
25615
- rows = rows || this.dataSource.getRows().filter(row => row.dirty);
25605
+ rows = rows || this.dataSource.getRows().filter(row => row.validator?.dirty);
25616
25606
  if (isEmptyArray(rows))
25617
25607
  return true; // No dirty: OK
25618
25608
  // Check using emitter
@@ -25694,26 +25684,31 @@ class AppTable {
25694
25684
  * @param row
25695
25685
  * @protected
25696
25686
  */
25697
- async openRowDetail(row) {
25698
- if (!this.allowRowDetail)
25699
- return false;
25700
- if (this.debug && this.dirty) {
25701
- console.warn('[table] Opening row details, but table has unsaved changes!');
25702
- }
25703
- if (this.onOpenRow.observers.length > 0) {
25704
- this.onOpenRow.emit(row);
25705
- return true;
25706
- }
25707
- // No ID defined: unable to open details
25708
- const id = row.currentData.id;
25709
- if (isNil(id)) {
25710
- console.warn('[table] Opening row details, but data has no id!');
25711
- return false;
25687
+ async openRow(id, row) {
25688
+ if (this.allowRowDetail) {
25689
+ if (this.debug && this.dirty) {
25690
+ console.warn('[table] Opening row details, but table has unsaved changes!');
25691
+ }
25692
+ if (this.onOpenIdAndRow.observers.length) {
25693
+ console.warn('[table] Detected deprecated use of (onOpenIdAndRow). Please use (onOpenRow) instead');
25694
+ this.onOpenIdAndRow.emit({ id, row });
25695
+ return true;
25696
+ }
25697
+ if (this.onOpenRow.observers.length) {
25698
+ this.onOpenRow.emit(row);
25699
+ return true;
25700
+ }
25701
+ // No ID defined: unable to open details
25702
+ if (isNil(id)) {
25703
+ console.warn('[table] Opening row details, but data has no id!');
25704
+ return false;
25705
+ }
25706
+ return this.router.navigate(['.', id.toString()], {
25707
+ relativeTo: this.route,
25708
+ queryParams: {}
25709
+ });
25712
25710
  }
25713
- return this.router.navigate(['.', id.toString()], {
25714
- relativeTo: this.route,
25715
- queryParams: {}
25716
- });
25711
+ return false;
25717
25712
  }
25718
25713
  async openNewRowDetail(event) {
25719
25714
  if (!this.allowRowDetail)
@@ -25801,8 +25796,13 @@ class AppTable {
25801
25796
  //if (this.debug) console.debug("[table] id = " + id);
25802
25797
  return id;
25803
25798
  }
25804
- async createNewRow(insertAt, opts) {
25805
- const row = await this._dataSource.createNew(insertAt, opts && { editing: opts.editing });
25799
+ async addRowToTable(insertAt, opts) {
25800
+ // Try to finish edited row first
25801
+ if (!this.confirmEditCreate()) {
25802
+ console.warn('[table] Cannot add new row, because the previous edited row cannot be confirmed');
25803
+ return undefined;
25804
+ }
25805
+ const row = await this._dataSource.createNew(insertAt, { editing: opts?.editing });
25806
25806
  if (!row)
25807
25807
  return undefined;
25808
25808
  if (row.editing) {
@@ -25926,24 +25926,6 @@ class AppTable {
25926
25926
  row.validator?.markAsDirty(opts);
25927
25927
  this.markAsDirty(opts);
25928
25928
  }
25929
- /**
25930
- * @deprecated Use startEditRow instead
25931
- */
25932
- editRow(event, row, opts) {
25933
- return this.startEditRow(event, row, opts);
25934
- }
25935
- /**
25936
- * @deprecated use openRowDetail() instead)
25937
- */
25938
- async openRow(id, row) {
25939
- return this.openRowDetail(row);
25940
- }
25941
- /**
25942
- * @deprecated use createNewRow() instead
25943
- */
25944
- async addRowToTable(insertAt, opts) {
25945
- return this.createNewRow(insertAt, opts);
25946
- }
25947
25929
  /* -- private method -- */
25948
25930
  async listenSortAndPaginationEvents() {
25949
25931
  if (!this.table) {
@@ -25981,17 +25963,15 @@ class AppTable {
25981
25963
  .subscribe(() => this.paginator.pageIndex = 0));
25982
25964
  }
25983
25965
  }
25984
- async startEditRowById(event, id, opts) {
25966
+ async editRowById(event, id, opts) {
25985
25967
  if (id < 0)
25986
- return false; // Invalid row id
25968
+ return;
25987
25969
  if (id >= this.visibleRowCount) {
25988
- return this.addRow(event, undefined, { ...opts, editing: true });
25970
+ this.addRow(event, undefined, { ...opts, editing: true });
25989
25971
  }
25990
25972
  else {
25991
- const row = this.dataSource.getRow(id);
25992
- if (!row)
25993
- return false;
25994
- return this.startEditRow(event, row, opts);
25973
+ const row = await this.dataSource.getRow(id);
25974
+ this.editRow(event, row, opts);
25995
25975
  }
25996
25976
  }
25997
25977
  setLoading(value, opts) {
@@ -26002,57 +25982,53 @@ class AppTable {
26002
25982
  }
26003
25983
  }
26004
25984
  }
26005
- async deleteNew(event, row) {
26006
- // Make sure row will be cancelled, and NOT deleted
25985
+ deleteNewRow(event, row) {
26007
25986
  if (row.id !== -1)
26008
25987
  throw new Error('Row must have id = -1');
26009
- event?.preventDefault();
26010
25988
  event?.stopPropagation();
26011
- const deleted = await this._dataSource.cancelOrDelete(row);
26012
- if (!deleted)
26013
- return false;
26014
25989
  this.selection.clear();
26015
25990
  this.editedRow = undefined; // unselect row
25991
+ this._dataSource.cancelOrDelete(row);
26016
25992
  this.onCancelOrDeleteRow.next(row);
26017
25993
  this.resetError();
26018
25994
  this.totalRowCount--;
26019
25995
  this.visibleRowCount--;
26020
- return true;
26021
25996
  }
26022
- async deleteExisting(event, row, opts) {
25997
+ deleteExistingRow(event, row, opts) {
26023
25998
  // Make sure row will be cancelled, and NOT deleted
26024
25999
  if (row.id === -1)
26025
26000
  throw new Error('Row must have an id');
26001
+ if (event?.defaultPrevented)
26002
+ return 0; // SKip
26026
26003
  event?.preventDefault();
26027
- event?.stopPropagation();
26028
- return this.deleteRow(null, row, opts);
26004
+ this.deleteRow(null, row, opts);
26029
26005
  }
26030
- async cancelExisting(event, row, opts) {
26006
+ cancelExistingRow(event, row, opts) {
26031
26007
  // Make sure row will be cancelled, and NOT deleted
26032
26008
  if (row.id === -1 || !row.editing)
26033
26009
  throw new Error('Row cannot be canceling, but only deleting');
26034
26010
  const confirmed = (!opts || opts.interactive !== false);
26035
26011
  // Ask user confirmation, if cancel
26036
26012
  if (!confirmed && row.dirty && (this.confirmBeforeCancel || this.onBeforeCancelRows.observers.length > 0)) {
26037
- event?.preventDefault();
26038
- event?.stopPropagation();
26039
- const canCancel = await this.canCancelRows([row], opts);
26040
- if (!canCancel)
26041
- return false;
26013
+ event.stopPropagation();
26014
+ this.canCancelRows([row], opts)
26015
+ .then(confirm => {
26016
+ // If confirmed, loop
26017
+ if (confirm)
26018
+ this.cancelExistingRow(event, row, { interactive: false });
26019
+ });
26020
+ return;
26042
26021
  }
26043
26022
  const keepEditing = row.editing && (!opts || opts.keepEditing !== false);
26044
26023
  this.editedRow = undefined; // unselect row
26045
- const cancelled = await row.cancel(); // TODO check this
26046
- if (!cancelled)
26047
- return false;
26024
+ this._dataSource.cancelOrDelete(row);
26048
26025
  this.onCancelOrDeleteRow.next(row);
26049
26026
  // Mark row as pristine
26050
- await this.checkIfRowPristine(row);
26027
+ this.checkIfRowPristine(row);
26051
26028
  // Restore editing state
26052
26029
  if (keepEditing) {
26053
26030
  this.editRow(undefined, row);
26054
26031
  }
26055
- return true;
26056
26032
  }
26057
26033
  async checkIfRowPristine(row, opts) {
26058
26034
  // Mark row as pristine
@@ -26153,7 +26129,7 @@ class AppTable {
26153
26129
  }
26154
26130
  }
26155
26131
  AppTable.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: AppTable, deps: "invalid", target: i0.ɵɵFactoryTarget.Directive });
26156
- AppTable.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.6", type: AppTable, inputs: { settingsId: "settingsId", debug: "debug", i18nColumnPrefix: "i18nColumnPrefix", i18nColumnSuffix: "i18nColumnSuffix", autoLoad: "autoLoad", readOnly: "readOnly", inlineEdition: "inlineEdition", focusFirstColumn: "focusFirstColumn", confirmBeforeDelete: "confirmBeforeDelete", confirmBeforeCancel: "confirmBeforeCancel", undoableDeletion: "undoableDeletion", saveBeforeDelete: "saveBeforeDelete", keepEditedRowOnSave: "keepEditedRowOnSave", saveBeforeSort: "saveBeforeSort", saveBeforeFilter: "saveBeforeFilter", propagateRowError: "propagateRowError", defaultSortBy: "defaultSortBy", defaultSortDirection: "defaultSortDirection", defaultPageSize: "defaultPageSize", defaultPageSizeOptions: "defaultPageSizeOptions", focusColumn: "focusColumn", dataSource: "dataSource", filter: "filter", disabled: "disabled", paginator: "paginator" }, outputs: { onRefresh: "onRefresh", onOpenRow: "onOpenRow", onNewRow: "onNewRow", onStartEditingRow: "onStartEditingRow", onConfirmEditCreateRow: "onConfirmEditCreateRow", onCancelOrDeleteRow: "onCancelOrDeleteRow", onBeforeDeleteRows: "onBeforeDeleteRows", onBeforeCancelRows: "onBeforeCancelRows", onBeforeSave: "onBeforeSave", onAfterDeletedRows: "onAfterDeletedRows", onSort: "onSort", onDirty: "onDirty", onError: "onError" }, viewQueries: [{ propertyName: "table", first: true, predicate: MatTable, descendants: true }, { propertyName: "childPaginator", first: true, predicate: MatPaginator, descendants: true }, { propertyName: "sort", first: true, predicate: MatSort, descendants: true }], ngImport: i0 });
26132
+ AppTable.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.6", type: AppTable, inputs: { settingsId: "settingsId", debug: "debug", i18nColumnPrefix: "i18nColumnPrefix", i18nColumnSuffix: "i18nColumnSuffix", autoLoad: "autoLoad", readOnly: "readOnly", inlineEdition: "inlineEdition", focusFirstColumn: "focusFirstColumn", confirmBeforeDelete: "confirmBeforeDelete", confirmBeforeCancel: "confirmBeforeCancel", undoableDeletion: "undoableDeletion", saveBeforeDelete: "saveBeforeDelete", keepEditedRowOnSave: "keepEditedRowOnSave", saveBeforeSort: "saveBeforeSort", saveBeforeFilter: "saveBeforeFilter", propagateRowError: "propagateRowError", defaultSortBy: "defaultSortBy", defaultSortDirection: "defaultSortDirection", defaultPageSize: "defaultPageSize", defaultPageSizeOptions: "defaultPageSizeOptions", focusColumn: "focusColumn", dataSource: "dataSource", filter: "filter", disabled: "disabled", paginator: "paginator" }, outputs: { onRefresh: "onRefresh", onOpenIdAndRow: "onOpenIdAndRow", onOpenRow: "onOpenRow", onNewRow: "onNewRow", onStartEditingRow: "onStartEditingRow", onConfirmEditCreateRow: "onConfirmEditCreateRow", onCancelOrDeleteRow: "onCancelOrDeleteRow", onBeforeDeleteRows: "onBeforeDeleteRows", onBeforeCancelRows: "onBeforeCancelRows", onBeforeSave: "onBeforeSave", onAfterDeletedRows: "onAfterDeletedRows", onSort: "onSort", onDirty: "onDirty", onError: "onError" }, viewQueries: [{ propertyName: "table", first: true, predicate: MatTable, descendants: true }, { propertyName: "childPaginator", first: true, predicate: MatPaginator, descendants: true }, { propertyName: "sort", first: true, predicate: MatSort, descendants: true }], ngImport: i0 });
26157
26133
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: AppTable, decorators: [{
26158
26134
  type: Directive
26159
26135
  }], ctorParameters: function () { return [{ type: i0.Injector }, { type: undefined }, { type: EntitiesTableDataSource }, { type: undefined }]; }, propDecorators: { settingsId: [{
@@ -26204,6 +26180,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.6", ngImpor
26204
26180
  type: Input
26205
26181
  }], onRefresh: [{
26206
26182
  type: Output
26183
+ }], onOpenIdAndRow: [{
26184
+ type: Output
26207
26185
  }], onOpenRow: [{
26208
26186
  type: Output
26209
26187
  }], onNewRow: [{
@@ -26299,6 +26277,9 @@ class AppEditor {
26299
26277
  get saving() {
26300
26278
  return this.savingSubject.value || this.editors?.some(e => e.saving);
26301
26279
  }
26280
+ get destroyed() {
26281
+ return this.destroySubject?.closed !== false;
26282
+ }
26302
26283
  get tables() {
26303
26284
  return this._children && this._children.filter(c => c instanceof AppTable);
26304
26285
  }
@@ -27557,11 +27538,23 @@ class AppEntityEditorModal extends AppTabEditor {
27557
27538
  // FOR DEV ONLY ----
27558
27539
  //this.debug = !environment.production;
27559
27540
  }
27560
- set isNewData(value) {
27561
- this._isNewData = value;
27541
+ set isNew(value) {
27542
+ this._isNew = value;
27543
+ }
27544
+ get isNew() {
27545
+ return isNotNil(this._isNew) ? this._isNew : isNil(this.data?.id);
27562
27546
  }
27547
+ /**
27548
+ * @deprecated use isNew instead
27549
+ */
27563
27550
  get isNewData() {
27564
- return isNotNil(this._isNewData) ? this._isNewData : isNil(this.data?.id);
27551
+ return this.isNew;
27552
+ }
27553
+ /**
27554
+ * @deprecated use isNew instead
27555
+ */
27556
+ set isNewData(value) {
27557
+ this.isNew = value;
27565
27558
  }
27566
27559
  set disabled(value) {
27567
27560
  this._enabled = !value;
@@ -27893,7 +27886,7 @@ class AppEntityEditorModal extends AppTabEditor {
27893
27886
  }
27894
27887
  }
27895
27888
  AppEntityEditorModal.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: AppEntityEditorModal, deps: "invalid", target: i0.ɵɵFactoryTarget.Directive });
27896
- AppEntityEditorModal.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.6", type: AppEntityEditorModal, inputs: { data: "data", mobile: "mobile", usageMode: "usageMode", onAfterModalInit: "onAfterModalInit", onDelete: "onDelete", i18nSuffix: "i18nSuffix", isNewData: "isNewData", disabled: "disabled" }, usesInheritance: true, ngImport: i0 });
27889
+ AppEntityEditorModal.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.6", type: AppEntityEditorModal, inputs: { data: "data", mobile: "mobile", usageMode: "usageMode", onAfterModalInit: "onAfterModalInit", onDelete: "onDelete", i18nSuffix: "i18nSuffix", isNew: "isNew", isNewData: "isNewData", disabled: "disabled" }, usesInheritance: true, ngImport: i0 });
27897
27890
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: AppEntityEditorModal, decorators: [{
27898
27891
  type: Directive
27899
27892
  }], ctorParameters: function () { return [{ type: i0.Injector }, { type: undefined }, { type: undefined, decorators: [{
@@ -27910,6 +27903,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.6", ngImpor
27910
27903
  type: Input
27911
27904
  }], i18nSuffix: [{
27912
27905
  type: Input
27906
+ }], isNew: [{
27907
+ type: Input
27913
27908
  }], isNewData: [{
27914
27909
  type: Input
27915
27910
  }], disabled: [{
@@ -30606,6 +30601,93 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.6", ngImpor
30606
30601
  args: [{ selector: 'app-data-test', 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 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\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=\"auto\">\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=\"4\">\n <mat-form-field floatLabel=\"never\">\n <input matInput type=\"text\" hidden>\n <mat-checkbox (change)=\"memoryMobile=$event.checked\" [checked]=\"memoryMobile\">\n Mobile ?\n </mat-checkbox>\n </mat-form-field>\n </ion-col>\n <ion-col>\n <mat-date-field formControlName=\"empty\"\n *ngIf=\"!memoryHide\"\n placeholder=\"Date\"\n [required]=\"true\"\n [mobile]=\"memoryMobile\"\n [clearable]=\"true\">\n <ion-icon matPrefix name=\"calendar-outline\"></ion-icon>\n </mat-date-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-field formControlName=\"empty\"\n placeholder=\"Date\"\n [mobile]=\"true\"\n [clearable]=\"true\">\n <ion-icon matPrefix name=\"calendar-outline\"></ion-icon>\n </mat-date-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\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 (required)\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>{{stringify(form.controls.emptyRequired.value)}}</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-date-field formControlName=\"emptyRequired\"\n placeholder=\"Date\"\n [required]=\"true\"\n [mobile]=\"true\"\n [clearable]=\"true\">\n <ion-icon matPrefix name=\"calendar-outline\"></ion-icon>\n </mat-date-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-field formControlName=\"enable\"\n placeholder=\"Date\"\n [required]=\"true\"\n [mobile]=\"true\"\n [clearable]=\"true\">\n <ion-icon matPrefix name=\"calendar-outline\"></ion-icon>\n </mat-date-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-field formControlName=\"disable\"\n placeholder=\"Date\"\n [required]=\"true\"\n [mobile]=\"true\">\n <ion-icon matPrefix name=\"calendar-outline\"></ion-icon>\n </mat-date-field>\n\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <!-- TimeZone -->\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n TimeZone\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>timezone=\"{{timezone}}\" value=\"{{stringify(form.controls.timezone.value)}}\"</pre></small>\n <pre>Should display using the browser TZ,<br/>but serialize/deserialize for the given TZ</pre>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-date-field formControlName=\"timezone\"\n placeholder=\"Date\"\n [mobile]=\"true\"\n [clearable]=\"true\"\n [timezone]=\"timezone\">\n <ion-icon matPrefix name=\"calendar-outline\"></ion-icon>\n </mat-date-field>\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)=\"mobileReadonlyField.readonly=$event.checked\" [checked]=\"mobileReadonlyField.readonly\">\n </mat-checkbox>\n\n <mat-date-field #mobileReadonlyField formControlName=\"readonly\"\n placeholder=\"Date\"\n [readonly]=\"true\"\n [mobile]=\"true\"\n [clearable]=\"true\">\n <ion-icon matPrefix name=\"calendar-outline\"></ion-icon>\n </mat-date-field>\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 <hr/>\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-field formControlName=\"empty\"\n placeholder=\"Date\"\n [mobile]=\"false\"\n [clearable]=\"true\">\n <ion-icon matPrefix name=\"calendar-outline\"></ion-icon>\n </mat-date-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\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 (required)\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>{{stringify(form.controls.emptyRequired.value)}}</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-date-field formControlName=\"emptyRequired\"\n placeholder=\"Date\"\n [required]=\"true\"\n [mobile]=\"false\"\n [clearable]=\"true\">\n <ion-icon matPrefix name=\"calendar-outline\"></ion-icon>\n </mat-date-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-field formControlName=\"enable\"\n placeholder=\"Date\"\n [required]=\"true\"\n [mobile]=\"false\"\n [clearable]=\"true\">\n <ion-icon matPrefix name=\"calendar-outline\"></ion-icon>\n </mat-date-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-field formControlName=\"disable\"\n placeholder=\"Date\"\n [required]=\"true\"\n [mobile]=\"false\">\n <ion-icon matPrefix name=\"calendar-outline\"></ion-icon>\n </mat-date-field>\n\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <!-- TimeZone -->\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n TimeZone\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>timezone=\"{{timezone}}\" value={{stringify(form.controls.timezone.value)}}</pre></small>\n <pre>Should display using the browser TZ,<br/>but serialize/deserialize for the given TZ</pre>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-date-field formControlName=\"timezone\"\n placeholder=\"Date\"\n [mobile]=\"false\"\n [clearable]=\"true\"\n [timezone]=\"timezone\">\n <ion-icon matPrefix name=\"calendar-outline\"></ion-icon>\n </mat-date-field>\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)=\"desktopReadonlyField.readonly=$event.checked\" [checked]=\"desktopReadonlyField.readonly\">\n </mat-checkbox>\n\n <mat-date-field #desktopReadonlyField formControlName=\"readonly\"\n placeholder=\"Date\"\n [readonly]=\"true\"\n [mobile]=\"false\"\n [clearable]=\"true\">\n <ion-icon matPrefix name=\"calendar-outline\"></ion-icon>\n </mat-date-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n </ion-row>\n </ion-grid>\n\n </form>\n</ion-content>\n" }]
30607
30602
  }], ctorParameters: function () { return [{ type: i1$2.UntypedFormBuilder }, { type: i0.ChangeDetectorRef }]; } });
30608
30603
 
30604
+ class ObservableTestPage {
30605
+ constructor() {
30606
+ this._stopSubject = new Subject();
30607
+ this.$value = new Subject();
30608
+ this.runningTaskCount = 0;
30609
+ this.taskId = 0;
30610
+ this.options = {
30611
+ stop: this._stopSubject
30612
+ };
30613
+ }
30614
+ ngOnDestroy() {
30615
+ this._stopSubject.next();
30616
+ }
30617
+ setOptions(opts) {
30618
+ this.options = {
30619
+ ...this.options,
30620
+ ...opts
30621
+ };
30622
+ this.log('Options changes to: ', { ...this.options, stop: this.options?.stop ? '$stop' : 'null' });
30623
+ }
30624
+ setOptionsError(errorMessage) {
30625
+ this.setOptions({ stopError: new Error(errorMessage) });
30626
+ }
30627
+ async firstNotNilPromise(opts) {
30628
+ const logPrefix = `[firstNotNilPromise() #${++this.taskId}] `;
30629
+ await this.execute(firstNotNilPromise, opts, logPrefix);
30630
+ }
30631
+ async waitForTrue(opts) {
30632
+ const logPrefix = `[waitForTrue() #${++this.taskId}] `;
30633
+ await this.execute(waitForTrue, opts, logPrefix);
30634
+ }
30635
+ async execute(fn, opts, logPrefix) {
30636
+ opts = {
30637
+ ...this.options,
30638
+ stop: this._stopSubject,
30639
+ ...opts
30640
+ };
30641
+ logPrefix = logPrefix || `[#${++this.taskId}] `;
30642
+ const value$ = this.$value.pipe(tap(value => this.log(logPrefix + `Received value: ${value}`)));
30643
+ try {
30644
+ this.runningTaskCount++;
30645
+ this.log(logPrefix + 'Waiting...');
30646
+ // Execute the async function
30647
+ const result = await fn(value$, opts);
30648
+ // Dump result
30649
+ if (isNotNil(result))
30650
+ this.log(logPrefix + '[OK] with result:', result);
30651
+ else
30652
+ this.log(logPrefix + '[OK]');
30653
+ }
30654
+ catch (err) {
30655
+ const errorMsg = logPrefix + 'Error: ' + (err?.message || '');
30656
+ console.error(errorMsg, err);
30657
+ this.log(errorMsg
30658
+ + (err?.originalStack ? `<pre>${err.originalStack}</pre>` : ''));
30659
+ }
30660
+ finally {
30661
+ this.runningTaskCount--;
30662
+ }
30663
+ }
30664
+ emit(value) {
30665
+ this.$value.next(value);
30666
+ }
30667
+ stop() {
30668
+ this._stopSubject.next();
30669
+ }
30670
+ log(message, ...args) {
30671
+ let fullMessage = `${toDateISOString(moment$7())} - ${message}`;
30672
+ if (args?.length) {
30673
+ fullMessage += ' - <small>' + args.map(JSON.stringify).join(', ') + '</small>';
30674
+ }
30675
+ this.appendLog(fullMessage);
30676
+ }
30677
+ appendLog(message) {
30678
+ this.resultDiv.nativeElement.innerHTML += message + '<br/>';
30679
+ }
30680
+ }
30681
+ ObservableTestPage.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: ObservableTestPage, deps: [], target: i0.ɵɵFactoryTarget.Component });
30682
+ ObservableTestPage.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.2.6", type: ObservableTestPage, selector: "app-observable-test", viewQueries: [{ propertyName: "resultDiv", first: true, predicate: ["result"], descendants: true }], ngImport: i0, template: "\n<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>Observables test page</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n\n <ion-card>\n <ion-card-header>\n <ion-card-title>waitFor, waitForTrue, waitForFalse</ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <div style=\"display: inline-block\">\n <p>Set options: </p>\n <ion-button color=\"medium\" fill=\"outline\" (click)=\"setOptions({stopError: false})\">stopError: false</ion-button>\n <ion-button color=\"medium\" fill=\"outline\" (click)=\"setOptions({stopError: true})\">stopError: true</ion-button>\n <ion-button color=\"medium\" fill=\"outline\" (click)=\"setOptions({stopError: 'my error'})\">stopError: 'my error'</ion-button>\n\n <ion-button color=\"medium\" fill=\"outline\" (click)=\"setOptions({timeout: 2000})\">timeout: 2000</ion-button>\n <ion-button color=\"medium\" fill=\"outline\" (click)=\"setOptions({timeout: null})\">timeout: null</ion-button>\n <br/>\n <p>Call await function: </p>\n <ion-button (click)=\"waitForTrue()\">waitForTrue</ion-button>\n <ion-button (click)=\"firstNotNilPromise()\">firstNotNilPromise</ion-button>\n <br/>\n <p>Send event to source: </p>\n <ion-button (click)=\"emit(true)\">Emit 'true'</ion-button>\n <ion-button (click)=\"emit(false)\">Emit 'false'</ion-button>\n <br/>\n <p>Stop: </p>\n <ion-button color=\"danger\" (click)=\"stop()\">Stop</ion-button>\n </div>\n </ion-card-content>\n </ion-card>\n\n <ion-card>\n <ion-toolbar>\n <ion-title>Running job count: {{runningTaskCount}}</ion-title>\n <ion-button fill=\"outline\" slot=\"end\" (click)=\"result.innerHTML=''\">Clear log</ion-button>\n </ion-toolbar>\n <ion-card-content>\n <div #result style=\"color: var(--ion-color-medium)\">\n </div>\n </ion-card-content>\n </ion-card>\n</ion-content>\n", styles: ["ion-button{text-transform:none!important}\n"], dependencies: [{ kind: "component", type: i2.IonBackButton, selector: "ion-back-button", inputs: ["color", "defaultHref", "disabled", "icon", "mode", "routerAnimation", "text", "type"] }, { kind: "component", type: i2.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2.IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: i2.IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: i2.IonCardHeader, selector: "ion-card-header", inputs: ["color", "mode", "translucent"] }, { kind: "component", type: i2.IonCardTitle, selector: "ion-card-title", inputs: ["color", "mode"] }, { kind: "component", type: i2.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2.IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2.IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: i2.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "directive", type: i2.IonBackButtonDelegate, selector: "ion-back-button", inputs: ["defaultHref", "routerAnimation"] }] });
30683
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: ObservableTestPage, decorators: [{
30684
+ type: Component,
30685
+ args: [{ selector: 'app-observable-test', template: "\n<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>Observables test page</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n\n <ion-card>\n <ion-card-header>\n <ion-card-title>waitFor, waitForTrue, waitForFalse</ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <div style=\"display: inline-block\">\n <p>Set options: </p>\n <ion-button color=\"medium\" fill=\"outline\" (click)=\"setOptions({stopError: false})\">stopError: false</ion-button>\n <ion-button color=\"medium\" fill=\"outline\" (click)=\"setOptions({stopError: true})\">stopError: true</ion-button>\n <ion-button color=\"medium\" fill=\"outline\" (click)=\"setOptions({stopError: 'my error'})\">stopError: 'my error'</ion-button>\n\n <ion-button color=\"medium\" fill=\"outline\" (click)=\"setOptions({timeout: 2000})\">timeout: 2000</ion-button>\n <ion-button color=\"medium\" fill=\"outline\" (click)=\"setOptions({timeout: null})\">timeout: null</ion-button>\n <br/>\n <p>Call await function: </p>\n <ion-button (click)=\"waitForTrue()\">waitForTrue</ion-button>\n <ion-button (click)=\"firstNotNilPromise()\">firstNotNilPromise</ion-button>\n <br/>\n <p>Send event to source: </p>\n <ion-button (click)=\"emit(true)\">Emit 'true'</ion-button>\n <ion-button (click)=\"emit(false)\">Emit 'false'</ion-button>\n <br/>\n <p>Stop: </p>\n <ion-button color=\"danger\" (click)=\"stop()\">Stop</ion-button>\n </div>\n </ion-card-content>\n </ion-card>\n\n <ion-card>\n <ion-toolbar>\n <ion-title>Running job count: {{runningTaskCount}}</ion-title>\n <ion-button fill=\"outline\" slot=\"end\" (click)=\"result.innerHTML=''\">Clear log</ion-button>\n </ion-toolbar>\n <ion-card-content>\n <div #result style=\"color: var(--ion-color-medium)\">\n </div>\n </ion-card-content>\n </ion-card>\n</ion-content>\n", styles: ["ion-button{text-transform:none!important}\n"] }]
30686
+ }], ctorParameters: function () { return []; }, propDecorators: { resultDiv: [{
30687
+ type: ViewChild,
30688
+ args: ['result']
30689
+ }] } });
30690
+
30609
30691
  const SHARED_MATERIAL_TESTING_PAGES = [
30610
30692
  { label: 'Shared Material components', divider: true },
30611
30693
  { label: 'Date/Time field', page: '/testing/shared/datetime' },
@@ -30616,7 +30698,9 @@ const SHARED_MATERIAL_TESTING_PAGES = [
30616
30698
  { label: 'Swipe component', page: '/testing/shared/swipe' },
30617
30699
  { label: 'Chips component', page: '/testing/shared/chips' },
30618
30700
  { label: 'Shared directives', divider: true },
30619
- { label: 'Badge icon directive', page: '/testing/shared/badge-icon' }
30701
+ { label: 'Badge directive', page: '/testing/shared/badge' },
30702
+ { label: 'Shared utils', divider: true },
30703
+ { label: 'Observable', page: '/testing/shared/observable' }
30620
30704
  ];
30621
30705
  const routes$7 = [
30622
30706
  {
@@ -30624,6 +30708,11 @@ const routes$7 = [
30624
30708
  pathMatch: 'full',
30625
30709
  component: SharedTestsPage
30626
30710
  },
30711
+ {
30712
+ path: 'observable',
30713
+ pathMatch: 'full',
30714
+ component: ObservableTestPage
30715
+ },
30627
30716
  {
30628
30717
  path: 'autocomplete',
30629
30718
  pathMatch: 'full',
@@ -30675,7 +30764,8 @@ MaterialTestingModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", v
30675
30764
  NumpadTestPage,
30676
30765
  SwipeTestPage,
30677
30766
  ChipsTestPage,
30678
- MatBadgeTestPage], imports: [CommonModule,
30767
+ MatBadgeTestPage,
30768
+ ObservableTestPage], imports: [CommonModule,
30679
30769
  IonicModule,
30680
30770
  ReactiveFormsModule,
30681
30771
  SharedMaterialModule, i1$1.TranslateModule, i1$5.RouterModule, SharedPipesModule], exports: [SharedMaterialModule,
@@ -30686,7 +30776,8 @@ MaterialTestingModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", v
30686
30776
  LatLongTestPage,
30687
30777
  NumpadTestPage,
30688
30778
  SwipeTestPage,
30689
- ChipsTestPage] });
30779
+ ChipsTestPage,
30780
+ ObservableTestPage] });
30690
30781
  MaterialTestingModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.2.6", ngImport: i0, type: MaterialTestingModule, imports: [CommonModule,
30691
30782
  IonicModule,
30692
30783
  ReactiveFormsModule,
@@ -30715,7 +30806,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.6", ngImpor
30715
30806
  NumpadTestPage,
30716
30807
  SwipeTestPage,
30717
30808
  ChipsTestPage,
30718
- MatBadgeTestPage
30809
+ MatBadgeTestPage,
30810
+ ObservableTestPage
30719
30811
  ],
30720
30812
  exports: [
30721
30813
  SharedMaterialModule,
@@ -30726,7 +30818,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.6", ngImpor
30726
30818
  LatLongTestPage,
30727
30819
  NumpadTestPage,
30728
30820
  SwipeTestPage,
30729
- ChipsTestPage
30821
+ ChipsTestPage,
30822
+ ObservableTestPage
30730
30823
  ]
30731
30824
  }]
30732
30825
  }] });