@flusys/ng-shared 9.1.1 → 9.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -379,7 +379,7 @@ function getAcceptString(contentTypes) {
379
379
  return contentTypes.length ? contentTypes.join(",") : "*/*";
380
380
  }
381
381
  function isFileTypeAllowed(file, allowedTypes) {
382
- if (!allowedTypes.length) return true;
382
+ if (!allowedTypes.length || allowedTypes.includes("*/*")) return true;
383
383
  return allowedTypes.some((type) => type.endsWith("/*") ? file.type.startsWith(type.replace("/*", "/")) : file.type === type);
384
384
  }
385
385
  function getFileIconClass(contentType) {
@@ -584,13 +584,16 @@ var TranslateService = class TranslateService {
584
584
  registry = inject(FALLBACK_MESSAGES_REGISTRY, { optional: false });
585
585
  translate(key, params) {
586
586
  if (this.localizationState === null) {
587
- const value = this.registry[key] ?? key;
587
+ const value = this.lookup(this.registry, key) ?? key;
588
588
  return this.interpolate(value, params);
589
589
  }
590
- let value = this.localizationState.translations()[key];
591
- if (!value) value = this.localizationState.fallbackMessages()[key] ?? key;
590
+ let value = this.lookup(this.localizationState.translations(), key);
591
+ if (!value) value = this.lookup(this.localizationState.fallbackMessages(), key) ?? key;
592
592
  return this.interpolate(value, params);
593
593
  }
594
+ lookup(messages, key) {
595
+ return Object.hasOwn(messages, key) ? messages[key] : void 0;
596
+ }
594
597
  translateRef(ref) {
595
598
  const params = {};
596
599
  for (const [name, value] of Object.entries(ref.messageVariables ?? {})) params[name] = this.isMessageRef(value) ? this.translateRef(value) : value;
@@ -607,7 +610,7 @@ var TranslateService = class TranslateService {
607
610
  interpolate(template, params) {
608
611
  if (!params || !template) return template;
609
612
  return template.replace(/\{\{(\w+)\}\}/g, (match, paramKey) => {
610
- const value = params[paramKey];
613
+ const value = Object.hasOwn(params, paramKey) ? params[paramKey] : void 0;
611
614
  return value !== void 0 ? String(value) : match;
612
615
  });
613
616
  }
@@ -644,9 +647,12 @@ var FaviconService = class FaviconService {
644
647
  this.render(this.svgTemplate);
645
648
  return;
646
649
  }
647
- this.http.get("/favicon.svg", { responseType: "text" }).subscribe((text) => {
648
- this.svgTemplate = text;
649
- this.render(text);
650
+ this.http.get("/favicon.svg", { responseType: "text" }).subscribe({
651
+ next: (text) => {
652
+ this.svgTemplate = text;
653
+ this.render(text);
654
+ },
655
+ error: () => void 0
650
656
  });
651
657
  }
652
658
  render(svgText) {
@@ -1086,7 +1092,7 @@ function extractErrorInfo(error, translateAdapter) {
1086
1092
  messageVariables
1087
1093
  });
1088
1094
  else if (typeof error.error?.message === "string") detail = error.error.message;
1089
- else if (Array.isArray(error.error?.message)) detail = error.error.message[0];
1095
+ else if (Array.isArray(error.error?.message) && error.error.message.length) detail = error.error.message[0];
1090
1096
  else if (error.message) detail = error.message;
1091
1097
  else detail = translateAdapter ? translateAdapter.translate("shared.unexpected.error") : "shared.unexpected.error";
1092
1098
  let summary;
@@ -1304,22 +1310,21 @@ var BaseFormControl = class BaseFormControl {
1304
1310
  touched = model(false, ...ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []);
1305
1311
  onChange = () => {};
1306
1312
  onTouched = () => {};
1307
- writeDepth = 0;
1313
+ pendingWrite = null;
1308
1314
  initializeFormControl() {
1309
1315
  runInInjectionContext(this.injector, () => {
1310
1316
  effect(() => {
1311
1317
  const next = this.value();
1312
- if (this.writeDepth === 0) untracked(() => this.onChange(next));
1318
+ const written = this.pendingWrite;
1319
+ this.pendingWrite = null;
1320
+ if (written && Object.is(written.value, next)) return;
1321
+ untracked(() => this.onChange(next));
1313
1322
  });
1314
1323
  });
1315
1324
  }
1316
1325
  writeValue(value) {
1317
- this.writeDepth++;
1318
- try {
1319
- this.value.set(value);
1320
- } finally {
1321
- this.writeDepth--;
1322
- }
1326
+ this.pendingWrite = Object.is(untracked(this.value), value) ? null : { value };
1327
+ this.value.set(value);
1323
1328
  }
1324
1329
  registerOnChange(fn) {
1325
1330
  this.onChange = fn;
@@ -2123,6 +2128,7 @@ var BaseFormComponent = class BaseFormComponent {
2123
2128
  _submitted = signal(false, ...ngDevMode ? [{ debugName: "_submitted" }] : /* istanbul ignore next */ []);
2124
2129
  _isLoadingInternal = signal(false, ...ngDevMode ? [{ debugName: "_isLoadingInternal" }] : /* istanbul ignore next */ []);
2125
2130
  _touchedFields = signal(/* @__PURE__ */ new Set(), ...ngDevMode ? [{ debugName: "_touchedFields" }] : /* istanbul ignore next */ []);
2131
+ loadSeq = 0;
2126
2132
  formData = this._formData.asReadonly();
2127
2133
  submitted = this._submitted.asReadonly();
2128
2134
  effectiveIsEditMode = computed(() => this.isEditMode() || !!this.editId(), ...ngDevMode ? [{ debugName: "effectiveIsEditMode" }] : /* istanbul ignore next */ []);
@@ -2160,9 +2166,11 @@ var BaseFormComponent = class BaseFormComponent {
2160
2166
  });
2161
2167
  }
2162
2168
  async loadEntity(id, service, mapFn) {
2169
+ const seq = ++this.loadSeq;
2163
2170
  this._isLoadingInternal.set(true);
2164
2171
  try {
2165
2172
  const response = await service.findById(id);
2173
+ if (seq !== this.loadSeq) return;
2166
2174
  if (response.success && response.data) {
2167
2175
  this._formData.update((prev) => ({
2168
2176
  ...prev,
@@ -2171,9 +2179,9 @@ var BaseFormComponent = class BaseFormComponent {
2171
2179
  this._submitted.set(false);
2172
2180
  }
2173
2181
  } catch {
2174
- this.navigateTo(this.cancelRoute());
2182
+ if (seq === this.loadSeq) this.navigateTo(this.cancelRoute());
2175
2183
  } finally {
2176
- this._isLoadingInternal.set(false);
2184
+ if (seq === this.loadSeq) this._isLoadingInternal.set(false);
2177
2185
  }
2178
2186
  }
2179
2187
  getFieldValue(key) {
@@ -2200,6 +2208,7 @@ var BaseFormComponent = class BaseFormComponent {
2200
2208
  return this.fieldTemplates().find((t) => t.key() === key)?.templateRef ?? null;
2201
2209
  }
2202
2210
  async handleSubmit() {
2211
+ if (this._isLoadingInternal()) return;
2203
2212
  this._submitted.set(true);
2204
2213
  if (!this.isFormValid()) return;
2205
2214
  const service = this.service();
@@ -3025,6 +3034,16 @@ i0.ɵɵngDeclareClassMetadata({
3025
3034
  }]
3026
3035
  }] }
3027
3036
  });
3037
+ const HTML_ESCAPES = {
3038
+ "&": "&",
3039
+ "<": "&lt;",
3040
+ ">": "&gt;",
3041
+ "\"": "&quot;",
3042
+ "'": "&#39;"
3043
+ };
3044
+ function escapeHtml(value) {
3045
+ return value.replace(/[&<>"']/g, (ch) => HTML_ESCAPES[ch]);
3046
+ }
3028
3047
  var BaseListComponent = class BaseListComponent {
3029
3048
  translateAdapter = inject(TranslateService, { optional: true });
3030
3049
  router = inject(Router, { optional: true });
@@ -3136,7 +3155,7 @@ var BaseListComponent = class BaseListComponent {
3136
3155
  const name = this.getFieldValueAsString(item, nameField);
3137
3156
  const id = this.getFieldValueAsString(item, this.idField());
3138
3157
  this.confirmationService?.confirm({
3139
- message: this.t("shared.confirm.delete.item", { name }),
3158
+ message: this.t("shared.confirm.delete.item", { name: escapeHtml(name) }),
3140
3159
  header: this.t("shared.confirm.delete"),
3141
3160
  icon: "exclamation-triangle",
3142
3161
  acceptButtonStyleClass: "p-button-danger",
@@ -4500,7 +4519,7 @@ var FileSelectorDialogComponent = class FileSelectorDialogComponent {
4500
4519
  destroyRef = inject(DestroyRef);
4501
4520
  translateAdapter = inject(TranslateService, { optional: true });
4502
4521
  fileProvider = inject(FILE_PROVIDER, { optional: true });
4503
- abortController = null;
4522
+ requestSeq = 0;
4504
4523
  searchDebounceTimer = null;
4505
4524
  header = input(...ngDevMode ? [void 0, { debugName: "header" }] : /* istanbul ignore next */ []);
4506
4525
  acceptTypes = input([], ...ngDevMode ? [{ debugName: "acceptTypes" }] : /* istanbul ignore next */ []);
@@ -4558,8 +4577,8 @@ var FileSelectorDialogComponent = class FileSelectorDialogComponent {
4558
4577
  uploadMultipleFilesFn = this.fileProvider?.uploadMultipleFiles ? (files, options) => this.fileProvider.uploadMultipleFiles(files, options) : void 0;
4559
4578
  constructor() {
4560
4579
  this.destroyRef.onDestroy(() => {
4561
- this.abortController?.abort();
4562
- if (this.searchDebounceTimer) clearTimeout(this.searchDebounceTimer);
4580
+ this.requestSeq++;
4581
+ this.clearSearchTimer();
4563
4582
  });
4564
4583
  effect(() => {
4565
4584
  if (this.visible()) untracked(() => {
@@ -4579,8 +4598,9 @@ var FileSelectorDialogComponent = class FileSelectorDialogComponent {
4579
4598
  });
4580
4599
  }
4581
4600
  onSearchChange(value) {
4582
- if (this.searchDebounceTimer) clearTimeout(this.searchDebounceTimer);
4601
+ this.clearSearchTimer();
4583
4602
  this.searchDebounceTimer = setTimeout(() => {
4603
+ this.searchDebounceTimer = null;
4584
4604
  this.searchTerm.set(value);
4585
4605
  this.pagination.update((p) => ({
4586
4606
  ...p,
@@ -4614,7 +4634,7 @@ var FileSelectorDialogComponent = class FileSelectorDialogComponent {
4614
4634
  }
4615
4635
  isFileAllowed(file) {
4616
4636
  const allowedTypes = this.acceptTypes();
4617
- if (!allowedTypes.length) return true;
4637
+ if (!allowedTypes.length || allowedTypes.includes("*/*")) return true;
4618
4638
  return allowedTypes.some((type) => {
4619
4639
  if (type.endsWith("/*")) return file.contentType.startsWith(type.replace("/*", "/"));
4620
4640
  return file.contentType === type;
@@ -4733,9 +4753,13 @@ var FileSelectorDialogComponent = class FileSelectorDialogComponent {
4733
4753
  } else this.selectedFiles.set([fileInfo]);
4734
4754
  this.fetchFiles();
4735
4755
  }
4756
+ clearSearchTimer() {
4757
+ if (this.searchDebounceTimer) clearTimeout(this.searchDebounceTimer);
4758
+ this.searchDebounceTimer = null;
4759
+ }
4736
4760
  resetState() {
4737
- this.abortController?.abort();
4738
- this.abortController = null;
4761
+ this.requestSeq++;
4762
+ this.clearSearchTimer();
4739
4763
  this.isLoading.set(false);
4740
4764
  this.files.set([]);
4741
4765
  this.selectedFiles.set([]);
@@ -4763,9 +4787,8 @@ var FileSelectorDialogComponent = class FileSelectorDialogComponent {
4763
4787
  this.storageConfigsTotal.set(void 0);
4764
4788
  }
4765
4789
  async fetchFiles(append = false) {
4766
- if (!this.fileProvider || this.isLoading()) return;
4767
- this.abortController?.abort();
4768
- this.abortController = new AbortController();
4790
+ if (!this.fileProvider || append && this.isLoading()) return;
4791
+ const seq = ++this.requestSeq;
4769
4792
  this.isLoading.set(true);
4770
4793
  try {
4771
4794
  const pag = this.pagination();
@@ -4778,11 +4801,13 @@ var FileSelectorDialogComponent = class FileSelectorDialogComponent {
4778
4801
  storageConfigId: this.selectedStorageConfigId() || void 0
4779
4802
  };
4780
4803
  const response = await firstValueFrom(this.fileProvider.loadFiles(filter));
4804
+ if (seq !== this.requestSeq) return;
4781
4805
  if (response.success && response.data) {
4782
4806
  let files = response.data;
4783
4807
  if (this.fileProvider.getFileUrls && files.length > 0) {
4784
4808
  const fileIds = files.map((f) => f.id);
4785
4809
  const urlResponse = await firstValueFrom(this.fileProvider.getFileUrls(fileIds));
4810
+ if (seq !== this.requestSeq) return;
4786
4811
  if (urlResponse.success && urlResponse.data) {
4787
4812
  const urlMap = new Map(urlResponse.data.map((f) => [f.id, f.url]));
4788
4813
  files = files.map((f) => ({
@@ -4796,9 +4821,9 @@ var FileSelectorDialogComponent = class FileSelectorDialogComponent {
4796
4821
  this.total.set(response.meta?.total);
4797
4822
  }
4798
4823
  } catch (error) {
4799
- if (error.name !== "AbortError") this.onError.emit(error);
4824
+ if (seq === this.requestSeq) this.onError.emit(error);
4800
4825
  } finally {
4801
- this.isLoading.set(false);
4826
+ if (seq === this.requestSeq) this.isLoading.set(false);
4802
4827
  }
4803
4828
  }
4804
4829
  t(key, variables) {
@@ -5824,9 +5849,9 @@ function sanitizeSvgMarkup(svgText) {
5824
5849
  const element = node;
5825
5850
  Array.from(element.attributes).forEach((attr) => {
5826
5851
  const name = attr.name.toLowerCase();
5827
- const value = attr.value.trim().toLowerCase();
5852
+ const value = attr.value.replace(/[\u0000-\u0020]/g, "").toLowerCase();
5828
5853
  const isEventHandler = name.startsWith("on");
5829
- const isScriptUri = (name === "href" || name === "xlink:href") && value.startsWith("javascript:");
5854
+ const isScriptUri = value.includes("javascript:");
5830
5855
  if (isEventHandler || isScriptUri) element.removeAttribute(attr.name);
5831
5856
  });
5832
5857
  node = walker.nextNode();
@@ -6023,6 +6048,7 @@ var LazyMultiSelectComponent = class LazyMultiSelectComponent extends BaseFormCo
6023
6048
  event.stopPropagation();
6024
6049
  }
6025
6050
  handleDocumentClick(event) {
6051
+ if (!this.openOptions()) return;
6026
6052
  const target = event.target;
6027
6053
  const clickedInSelect = this.pSelectRef()?.nativeElement.contains(target);
6028
6054
  const clickedInOverlay = this.overlayViewRef?.rootNodes[0]?.contains(target);
@@ -7285,7 +7311,7 @@ var BaseUserSelectComponent = class BaseUserSelectComponent {
7285
7311
  injector = inject(Injector);
7286
7312
  userProvider = inject(USER_PROVIDER);
7287
7313
  translateAdapter = inject(TranslateService);
7288
- abortController = null;
7314
+ requestSeq = 0;
7289
7315
  loadUsers = input(...ngDevMode ? [void 0, { debugName: "loadUsers" }] : /* istanbul ignore next */ []);
7290
7316
  placeHolder = input("", ...ngDevMode ? [{ debugName: "placeHolder" }] : /* istanbul ignore next */ []);
7291
7317
  isEditMode = input.required(...ngDevMode ? [{ debugName: "isEditMode" }] : /* istanbul ignore next */ []);
@@ -7344,7 +7370,7 @@ var BaseUserSelectComponent = class BaseUserSelectComponent {
7344
7370
  }, ...ngDevMode ? [{ debugName: "displayPlaceholder" }] : /* istanbul ignore next */ []);
7345
7371
  constructor() {
7346
7372
  this.destroyRef.onDestroy(() => {
7347
- this.abortController?.abort();
7373
+ this.requestSeq++;
7348
7374
  });
7349
7375
  effect(() => {
7350
7376
  const size = this.pageSize();
@@ -7382,9 +7408,8 @@ var BaseUserSelectComponent = class BaseUserSelectComponent {
7382
7408
  this.fetchUsers();
7383
7409
  }
7384
7410
  async fetchUsers(append = false) {
7385
- if (this.isLoading()) return;
7386
- this.abortController?.abort();
7387
- this.abortController = new AbortController();
7411
+ if (append && this.isLoading()) return;
7412
+ const seq = ++this.requestSeq;
7388
7413
  this.isLoading.set(true);
7389
7414
  try {
7390
7415
  const pag = this.pagination();
@@ -7396,6 +7421,7 @@ var BaseUserSelectComponent = class BaseUserSelectComponent {
7396
7421
  };
7397
7422
  const customLoadUsers = this.loadUsers();
7398
7423
  const response = await firstValueFrom(customLoadUsers ? customLoadUsers(filter) : this.loadUsersFromProvider(filter));
7424
+ if (seq !== this.requestSeq) return;
7399
7425
  if (response.success && response.data) {
7400
7426
  if (append) this.users.update((current) => [...current, ...response.data]);
7401
7427
  else this.users.set(response.data);
@@ -7403,9 +7429,9 @@ var BaseUserSelectComponent = class BaseUserSelectComponent {
7403
7429
  this.rememberUsers(response.data);
7404
7430
  }
7405
7431
  } catch (error) {
7406
- if (error.name !== "AbortError") this.onError.emit(error);
7432
+ if (seq === this.requestSeq) this.onError.emit(error);
7407
7433
  } finally {
7408
- this.isLoading.set(false);
7434
+ if (seq === this.requestSeq) this.isLoading.set(false);
7409
7435
  }
7410
7436
  }
7411
7437
  loadUsersFromProvider(filter) {
@@ -7955,16 +7981,6 @@ function errorToMessageRef(error, fallbackKey = "shared.unexpected.error") {
7955
7981
  } : { messageKey: body.messageKey };
7956
7982
  return { messageKey: fallbackKey };
7957
7983
  }
7958
- const HTML_ESCAPES = {
7959
- "&": "&amp;",
7960
- "<": "&lt;",
7961
- ">": "&gt;",
7962
- "\"": "&quot;",
7963
- "'": "&#39;"
7964
- };
7965
- function escapeHtml(value) {
7966
- return value.replace(/[&<>"']/g, (ch) => HTML_ESCAPES[ch]);
7967
- }
7968
7984
  function compareBranches(a, b) {
7969
7985
  return (a.serial ?? 0) - (b.serial ?? 0) || (a.name ?? "").localeCompare(b.name ?? "");
7970
7986
  }