@dsivd/prestations-ng 19.2.0-beta.8 → 19.2.0-beta.9

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.
package/CHANGELOG.md CHANGED
@@ -10,6 +10,12 @@
10
10
 
11
11
  ### Added
12
12
 
13
+ - [eslint/configs/spec-ts-recommended.mjs](projects/prestations-ng/src/eslint/configs/spec-ts-recommended.mjs)
14
+ - New `prestationsNg.configs.specTsRecommended` ESLint config for the Vitest specs, to use in the `extends` of a
15
+ `**/*.spec.ts` block: the `recommended` config of `@vitest/eslint-plugin`, which also counts the assertions of
16
+ the `HttpTestingController` and of the helpers of the specs. It needs `@vitest/eslint-plugin` in the
17
+ devDependencies of your project, only when you use it. See
18
+ [UPGRADING_V19.md](UPGRADING_V19.md#migrate-to-eslint-flat-configuration).
13
19
  - [foehn-bo-multi-upload.component.ts](projects/prestations-ng/src/foehn-upload/foehn-bo-multi-upload/foehn-bo-multi-upload.component.ts)
14
20
  - New `localUpload` input. When set to `true`, selected files are kept purely local
15
21
  (as `DocumentReferenceWithFile`, no `uploadUrl()` call) so they can be cancelled before
@@ -22,11 +28,9 @@
22
28
  - The default (direct upload) mode is unchanged.
23
29
  - New `maxFileSizeInMo` input: a client-side, per-file size limit that doesn't depend on
24
30
  the `fileMaxSizeInBytesByFormKey` server configuration (useful for consumers that don't
25
- expose that config yet). Uses the new dictionary key
26
- `foehn-uploader.file-too-big-error-message` (params: `filename`, `maxSizeInMo`), which
27
- ships with a French default in
28
- [default-dictionary.ts](projects/prestations-ng/src/sdk-dictionary/default-dictionary.ts)
29
- like the rest of `foehn-uploader.*` — override it project-side only if you need to.
31
+ expose that config yet). When both are set, the smallest limit applies. A file too big gets
32
+ the usual `413` error, which can be customized with `customErrors` (`{fileName}`,
33
+ `{fileMaxSize}`).
30
34
  - New `chooseButtonStyleModifier` input, appended to the choose button's CSS classes
31
35
  (e.g. `btn-sm`), mirroring the same input already available on other uploaders.
32
36
 
@@ -63,7 +67,7 @@ Please check the [UPGRADING_V19.md](UPGRADING_V19.md) guide for more information
63
67
  (_read-only_), `timeslotRemaining` (_read-only_), `canClose` (_read-only_)
64
68
  - `foehn-autocomplete`: `searchValue`, `inputHasFocus`, `hideSuggestions`, `displayedElements`,
65
69
  `notificationMessageUpdated`, `VIEW_PORT_HEIGHT` (_read-only_)
66
- - `foehn-multiselect-autocomplete`: `autocompleteInputValue`, `showEmptyListMessage`, `srAnnouncements`
70
+ - `foehn-multiselect-autocomplete`: `autocompleteInputValue`, `showEmptyListMessage` (_read-only_), `srAnnouncements`
67
71
  - `foehn-select`, `foehn-radio`, `foehn-checkbox` and the autocompletes (`FoehnCheckableGroupComponent`):
68
72
  `groupedElements`
69
73
  - `foehn-radio`: `inputValue`
@@ -120,6 +124,11 @@ Please check the [UPGRADING_V19.md](UPGRADING_V19.md) guide for more information
120
124
 
121
125
  ### Fixed
122
126
 
127
+ - `foehn-multiselect-autocomplete`: the suggestions and the message displayed once every element is selected follow a
128
+ model changed by the project, not only by the user. Once every element was selected, a model set back to some of
129
+ them kept the message and the hidden input; a model selecting every element did not display the message; the
130
+ suggestions still contained the elements selected by the project. An empty list of elements no longer displays
131
+ the message (every element of the list has already been added).
123
132
  - `providePrestationsNgCore()` registers again the HTTP interceptors lost with `FoehnPageModule` in 19.0.0: the
124
133
  `foehn-transmit-waiting-modal` is displayed again during a long transmission, the warning before the expiration of
125
134
  the session receives its date again, and a new version of the application deployed meanwhile is detected again.
package/UPGRADING_V19.md CHANGED
@@ -1064,6 +1064,8 @@ npm install --save-dev eslint-plugin-simple-import-sort@13
1064
1064
  npm install --save-dev eslint-config-prettier@10
1065
1065
  npm install --save-dev eslint-plugin-import-x@4
1066
1066
  npm install --save-dev prettier
1067
+ # only if your project has Vitest specs (see "Migrate to vitest")
1068
+ npm install --save-dev @vitest/eslint-plugin@1
1067
1069
  ```
1068
1070
 
1069
1071
  In your `front` folder, add a `.prettierrc` file with the following content:
@@ -1140,6 +1142,12 @@ export default tseslint.config(
1140
1142
  extends: [...prestationsNg.configs.templateRecommended],
1141
1143
  },
1142
1144
 
1145
+ // ─── Vitest specs (only if your project has Vitest specs) ─────────────────
1146
+ {
1147
+ files: ['**/*.spec.ts'],
1148
+ extends: [...prestationsNg.configs.specTsRecommended],
1149
+ },
1150
+
1143
1151
  // ─── Prettier (must be last — disables formatting rules) ───────────────────
1144
1152
  prettier,
1145
1153
  );
@@ -1150,6 +1158,11 @@ everything: the `eslint`, `typescript-eslint` and `angular-eslint` baselines, th
1150
1158
  accessibility set, the naming and import conventions, and the rules written by the library
1151
1159
  itself. You declare no `plugins` and list no rule.
1152
1160
 
1161
+ `specTsRecommended` is the `recommended` config of `@vitest/eslint-plugin`, which also counts the assertions
1162
+ made by the `HttpTestingController` (`expectOne`, `expectNone`, `verify`) and by helpers named `expect*`,
1163
+ `assert*` or `test*`. The `**/*.spec.ts` pattern does not reach the Cypress `*.spec.cy.ts` files. It needs
1164
+ `@vitest/eslint-plugin` only when you use it: the other configs work without it.
1165
+
1153
1166
  The packages installed above are still required — the library's config imports them from your
1154
1167
  `node_modules` rather than shipping its own copies. For the same reason, do **not** spread
1155
1168
  `angular.configs.tsRecommended` or `angular.configs.templateRecommended` yourself: they are
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The house preset of the Vitest specs: the recommended rules of `@vitest/eslint-plugin`, with the
3
+ * assertions also made by the helpers of the specs and by the `HttpTestingController`.
4
+ *
5
+ * Meant for the `extends` of a `**\/*.spec.ts` block (the Cypress `*.spec.cy.ts` files are not
6
+ * Vitest specs).
7
+ *
8
+ * @param {import('@vitest/eslint-plugin').default} vitest the `@vitest/eslint-plugin` module
9
+ * @returns {import('typescript-eslint').ConfigArray}
10
+ */
11
+ const specTsRecommended = (vitest) => [
12
+ vitest.configs.recommended,
13
+ {
14
+ name: 'prestations-ng/spec-ts-recommended',
15
+ rules: {
16
+ 'vitest/expect-expect': [
17
+ 'error',
18
+ {
19
+ assertFunctionNames: [
20
+ 'expect',
21
+ 'expect*',
22
+ 'assert*',
23
+ 'test*',
24
+ '*.expectOne',
25
+ '*.expectNone',
26
+ '*.verify',
27
+ ],
28
+ },
29
+ ],
30
+ },
31
+ },
32
+ ];
33
+
34
+ export default specTsRecommended;
package/eslint/index.mjs CHANGED
@@ -1,9 +1,17 @@
1
+ import specTsRecommended from './configs/spec-ts-recommended.mjs';
1
2
  import templateRecommended from './configs/template-recommended.mjs';
2
3
  import templateRules from './configs/template-rules.mjs';
3
4
  import tsRecommended from './configs/ts-recommended.mjs';
4
5
  import tsRules from './configs/ts-rules.mjs';
5
6
  import rules from './rules/index.mjs';
6
7
 
8
+ // Only needed by the projects linting Vitest specs: the other configs can be used without it.
9
+ const vitest = await import('@vitest/eslint-plugin').then(
10
+ (module) => module.default,
11
+ () => null,
12
+ );
13
+ const specTsRecommendedConfig = vitest ? specTsRecommended(vitest) : null;
14
+
7
15
  const plugin = {
8
16
  meta: { name: '@dsivd/prestations-ng' },
9
17
  rules,
@@ -16,6 +24,15 @@ const configs = {
16
24
  // Only this library's own rules, for a project that supplies its own style.
17
25
  tsRules: tsRules(plugin),
18
26
  templateRules: templateRules(plugin),
27
+ // The preset of the Vitest specs (needs @vitest/eslint-plugin)
28
+ get specTsRecommended() {
29
+ if (!specTsRecommendedConfig) {
30
+ throw new Error(
31
+ 'prestationsNg.configs.specTsRecommended needs @vitest/eslint-plugin: npm install --save-dev @vitest/eslint-plugin@1',
32
+ );
33
+ }
34
+ return specTsRecommendedConfig;
35
+ },
19
36
  };
20
37
 
21
38
  export { configs, plugin, rules };
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, inject, InjectionToken, model, input, viewChildren, viewChild, forwardRef, output, computed, linkedSignal, DestroyRef, signal, effect, untracked, PendingTasks, Directive, ChangeDetectorRef, Pipe, Component, contentChildren, EnvironmentInjector, runInInjectionContext, afterNextRender, ElementRef, Injector, makeEnvironmentProviders, ErrorHandler, provideBrowserGlobalErrorListeners, provideAppInitializer, importProvidersFrom, NgZone, contentChild, TemplateRef } from '@angular/core';
2
+ import { Injectable, inject, InjectionToken, model, input, viewChildren, viewChild, forwardRef, output, computed, linkedSignal, DestroyRef, signal, effect, untracked, PendingTasks, Directive, ChangeDetectorRef, Pipe, Component, contentChildren, EnvironmentInjector, runInInjectionContext, afterNextRender, ElementRef, Injector, makeEnvironmentProviders, ErrorHandler, provideBrowserGlobalErrorListeners, provideAppInitializer, importProvidersFrom, NgZone, afterRenderEffect, contentChild, TemplateRef } from '@angular/core';
3
3
  import { takeUntilDestroyed, toSignal, toObservable, outputToObservable } from '@angular/core/rxjs-interop';
4
4
  import { Router, ActivatedRoute, RouterLink, NavigationStart, NavigationEnd } from '@angular/router';
5
5
  import { Subject, BehaviorSubject, combineLatest, of, filter as filter$1, tap as tap$1, throwError, switchMap as switchMap$1, forkJoin, from, concatMap, toArray, EMPTY, startWith, merge, noop, interval, withLatestFrom, map as map$1, concat } from 'rxjs';
@@ -1341,7 +1341,6 @@ const DEFAULT_DICTIONARY = {
1341
1341
  'foehn-uploader.delete-readonly-error-message': 'Un document en lecture seule ne peut pas être supprimé',
1342
1342
  'foehn-uploader.unknown-error-message': 'Impossible de transmettre le fichier',
1343
1343
  'foehn-uploader.pending-label': "en attente d'envoi",
1344
- 'foehn-uploader.file-too-big-error-message': 'Votre pièce jointe {filename} a une taille supérieure ou égale à la limite autorisée : {maxSizeInMo} Mo',
1345
1344
  'foehn-picture-upload.choose-button-label': 'Choisissez une photo',
1346
1345
  'foehn-picture-upload.loading-label': 'Chargement...',
1347
1346
  'foehn-picture-upload.invalid-file-type': 'Le type de votre pièce jointe {filename} ne figure pas parmi ceux qui sont autorisés : "{acceptFormats}"',
@@ -9718,12 +9717,35 @@ class FoehnMultiselectAutocompleteComponent extends FoehnAutocompleteComponent {
9718
9717
  return [...defaultValues, defaultValue];
9719
9718
  }, ...(ngDevMode ? [{ debugName: "defaultElementValuesComputed" }] : /* istanbul ignore next */ []));
9720
9719
  autocompleteInputValue = signal(null, ...(ngDevMode ? [{ debugName: "autocompleteInputValue" }] : /* istanbul ignore next */ []));
9721
- showEmptyListMessage = signal(false, ...(ngDevMode ? [{ debugName: "showEmptyListMessage" }] : /* istanbul ignore next */ []));
9720
+ /**
9721
+ * Every element is selected: a message replaces the autocomplete (not for an empty list of elements).
9722
+ */
9723
+ showEmptyListMessage = computed(() => !this.disabled() &&
9724
+ !!this.elements()?.length &&
9725
+ !this.suggestions().length, ...(ngDevMode ? [{ debugName: "showEmptyListMessage" }] : /* istanbul ignore next */ []));
9722
9726
  srAnnouncements = signal('', ...(ngDevMode ? [{ debugName: "srAnnouncements" }] : /* istanbul ignore next */ []));
9727
+ // The elements which are not selected, proposed by the autocomplete
9728
+ suggestions = computed(() => this.filterAutocompleteElements(this.elements() ?? []), ...(ngDevMode ? [{ debugName: "suggestions" }] : /* istanbul ignore next */ []));
9723
9729
  dictionaryService = inject(SdkDictionaryService);
9724
9730
  srAnnouncementClearTimer = null;
9725
9731
  SR_ANNOUNCEMENT_CLEAR_TIMEOUT_INTERVAL_MS = 2000;
9726
9732
  pendingRemovedIndexForFocus = null;
9733
+ // The suggestions follow the elements and the model, whoever changes them (the user or the project)
9734
+ suggestionsEffect = effect(() => {
9735
+ this.suggestions();
9736
+ this.autocompleteComponent();
9737
+ untracked(() => this.applySuggestionsToAutocomplete());
9738
+ }, ...(ngDevMode ? [{ debugName: "suggestionsEffect" }] : /* istanbul ignore next */ []));
9739
+ // The input is hidden while the message replaces the autocomplete
9740
+ autocompleteInputVisibilityEffect = afterRenderEffect({
9741
+ write: () => {
9742
+ const isHidden = this.showEmptyListMessage();
9743
+ const inputElement = this.autocompleteComponent()?.inputElement()?.nativeElement;
9744
+ if (inputElement) {
9745
+ inputElement.hidden = isHidden;
9746
+ }
9747
+ },
9748
+ });
9727
9749
  selectedElemRefEffect = effect(() => {
9728
9750
  this.selectedElemRef();
9729
9751
  if (this.pendingRemovedIndexForFocus !== null) {
@@ -9738,12 +9760,6 @@ class FoehnMultiselectAutocompleteComponent extends FoehnAutocompleteComponent {
9738
9760
  this.hostId.set(this.buildId('ParentContainer'));
9739
9761
  this.hostName = this.hostId();
9740
9762
  }
9741
- onModelChange(value) {
9742
- super.onModelChange(value);
9743
- if (!value || ObjectHelper.isEmpty(value)) {
9744
- this.manageSuggestionListAndFocusAfterRemovingAnItem(null);
9745
- }
9746
- }
9747
9763
  hasErrorsToDisplay() {
9748
9764
  return this.autocompleteComponent()?.hasErrorsToDisplay() ?? false;
9749
9765
  }
@@ -9770,7 +9786,7 @@ class FoehnMultiselectAutocompleteComponent extends FoehnAutocompleteComponent {
9770
9786
  current.push(item);
9771
9787
  this.updateMultiselectAutocomplete(current);
9772
9788
  this.handleUserInput(current);
9773
- this.manageSuggestionListAndFocusAfterAddingAnItem();
9789
+ this.focusAfterAddingAnItem();
9774
9790
  this.manageScreenReaderAnnouncement(item, false);
9775
9791
  }
9776
9792
  }
@@ -9779,7 +9795,8 @@ class FoehnMultiselectAutocompleteComponent extends FoehnAutocompleteComponent {
9779
9795
  const current = (this.model() ?? []).filter((_item, itemIndex) => itemIndex !== index);
9780
9796
  this.updateMultiselectAutocomplete(current);
9781
9797
  this.handleUserInput(current);
9782
- this.manageSuggestionListAndFocusAfterRemovingAnItem(index);
9798
+ // When deleting a selected item, check were to set focus back when no more items to delete
9799
+ this.pendingRemovedIndexForFocus = index;
9783
9800
  this.manageScreenReaderAnnouncement(item, true);
9784
9801
  this.autocompleteComponent()?.markAsDirty();
9785
9802
  }
@@ -9790,14 +9807,14 @@ class FoehnMultiselectAutocompleteComponent extends FoehnAutocompleteComponent {
9790
9807
  return (!this.disabled() &&
9791
9808
  !this.defaultElementValuesComputed()?.includes(this.model()?.[index]));
9792
9809
  }
9793
- onAutocompleteElementsLoaded(elements) {
9794
- this.manageListWhenElementsAreLoaded(elements);
9810
+ onAutocompleteElementsLoaded() {
9811
+ // The autocomplete has (re)loaded its elements: it proposes the elements which are not selected
9812
+ this.applySuggestionsToAutocomplete();
9795
9813
  if (!this.model()?.length &&
9796
9814
  this.defaultElementValuesComputed()?.length) {
9797
9815
  // The default values are selected once the current rendering is done.
9798
9816
  afterNextRender(() => {
9799
9817
  this.updateMultiselectAutocomplete(this.defaultElementValuesComputed());
9800
- this.manageListWhenElementsAreLoaded(elements);
9801
9818
  }, { injector: this.componentInjector });
9802
9819
  }
9803
9820
  }
@@ -9818,15 +9835,6 @@ class FoehnMultiselectAutocompleteComponent extends FoehnAutocompleteComponent {
9818
9835
  }
9819
9836
  return elementBadgeLabel(element);
9820
9837
  }
9821
- manageListWhenElementsAreLoaded(elements) {
9822
- const autocompleteComponent = this.autocompleteComponent();
9823
- if (autocompleteComponent) {
9824
- autocompleteComponent.elements.set(this.filterAutocompleteElements(elements));
9825
- if (!this.disabled() && !autocompleteComponent.elements().length) {
9826
- this.showAlertMessageAndHideAutocomplete();
9827
- }
9828
- }
9829
- }
9830
9838
  findElementByModelItem(item) {
9831
9839
  const elements = this.elements();
9832
9840
  if (elements?.length) {
@@ -9881,24 +9889,14 @@ class FoehnMultiselectAutocompleteComponent extends FoehnAutocompleteComponent {
9881
9889
  }
9882
9890
  return filteredElements;
9883
9891
  }
9884
- manageSuggestionListAndFocusAfterAddingAnItem() {
9892
+ focusAfterAddingAnItem() {
9885
9893
  // Empty autocomplete input
9886
9894
  if (!this.isEmpty(this.autocompleteInputValue())) {
9887
9895
  this.autocompleteInputValue.set(null);
9888
9896
  }
9889
- const autocompleteComponent = this.autocompleteComponent();
9890
- if (!autocompleteComponent) {
9891
- return;
9892
- }
9893
9897
  // Reset autocomplete input value signal to ensure the input is empty
9894
- autocompleteComponent.inputValue.set(null);
9895
- // Remove selected item from autocomplete suggestion list
9896
- autocompleteComponent.elements.set(this.filterAutocompleteElements(this.elements()));
9897
- // Force autocomplete to filter elements again because the input value is empty and the list of elements has changed
9898
- autocompleteComponent.filterElements(true);
9899
- // Check if we still have suggestions to display
9900
- if (!autocompleteComponent.elements().length) {
9901
- this.showAlertMessageAndHideAutocomplete();
9898
+ this.autocompleteComponent()?.inputValue.set(null);
9899
+ if (this.showEmptyListMessage()) {
9902
9900
  // The alert message can be focused once it is rendered
9903
9901
  afterNextRender(() => {
9904
9902
  const alertMessage = this.alertMessageContainer()?.nativeElement.querySelector('p');
@@ -9908,37 +9906,18 @@ class FoehnMultiselectAutocompleteComponent extends FoehnAutocompleteComponent {
9908
9906
  }, { injector: this.componentInjector });
9909
9907
  }
9910
9908
  }
9911
- manageSuggestionListAndFocusAfterRemovingAnItem(removedIndex) {
9912
- // Hide message saying no more suggestions to display
9913
- this.showEmptyListMessage.set(false);
9909
+ applySuggestionsToAutocomplete() {
9914
9910
  const autocompleteComponent = this.autocompleteComponent();
9915
- if (autocompleteComponent) {
9916
- // Manually show autocomplete because there is at least one suggestions to display
9917
- const inputElement = autocompleteComponent.inputElement();
9918
- if (inputElement?.nativeElement) {
9919
- inputElement.nativeElement.hidden = false;
9920
- }
9921
- // Add back selected item to autocomplete suggestion list
9922
- autocompleteComponent.elements.set(this.filterAutocompleteElements(this.elements()));
9923
- // Force autocomplete to filter elements again because the input value is empty and the list of elements has changed
9924
- autocompleteComponent.filterElements(true);
9925
- }
9926
- if (removedIndex !== null) {
9927
- // When deleting a selected item, check were to set focus back when no more items to delete
9928
- this.pendingRemovedIndexForFocus = removedIndex;
9929
- }
9930
- }
9931
- showAlertMessageAndHideAutocomplete() {
9932
- // Show a message to user indicating there is no more suggestions to display
9933
- this.showEmptyListMessage.set(true);
9934
- // Manually reset and hide autocomplete because there is no more suggestions to display
9935
- if (!this.isEmpty(this.autocompleteInputValue())) {
9936
- this.autocompleteInputValue.set(null);
9911
+ // Before the rendering or the loading of the elements
9912
+ if (!autocompleteComponent || !this.elements()) {
9913
+ return;
9937
9914
  }
9938
- const autocompleteComponent = this.autocompleteComponent();
9939
- const inputElement = autocompleteComponent?.inputElement();
9940
- if (inputElement?.nativeElement) {
9941
- inputElement.nativeElement.hidden = true;
9915
+ autocompleteComponent.elements.set(this.suggestions());
9916
+ // Force autocomplete to filter elements again because the list of elements has changed
9917
+ autocompleteComponent.filterElements(true);
9918
+ if (this.showEmptyListMessage() &&
9919
+ !this.isEmpty(this.autocompleteInputValue())) {
9920
+ this.autocompleteInputValue.set(null);
9942
9921
  }
9943
9922
  }
9944
9923
  manageScreenReaderAnnouncement(element, isDelete) {
@@ -9963,7 +9942,7 @@ class FoehnMultiselectAutocompleteComponent extends FoehnAutocompleteComponent {
9963
9942
  provide: FoehnAutocompleteComponent,
9964
9943
  useExisting: forwardRef(() => FoehnMultiselectAutocompleteComponent),
9965
9944
  },
9966
- ], viewQueries: [{ propertyName: "autocompleteComponent", first: true, predicate: ["autocompleteComponent"], descendants: true, isSignal: true }, { propertyName: "alertMessageContainer", first: true, predicate: ["alertMessageContainer"], descendants: true, isSignal: true }, { propertyName: "selectedElemRef", predicate: ["selectedElemRef"], descendants: true, isSignal: true }, { propertyName: "defaultElemRef", predicate: ["defaultElemRef"], descendants: true, isSignal: true }, { propertyName: "defaultDeleteFocusArea", first: true, predicate: ["defaultDeleteFocusArea"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div\n class=\"form-group container-box\"\n [class.has-danger]=\"hasErrorsToDisplay()\"\n [class.vd-form-group-danger]=\"hasErrorsToDisplay()\"\n>\n <foehn-autocomplete\n #autocompleteComponent\n [name]=\"name()\"\n [label]=\"label()\"\n [elements]=\"elements()\"\n [elementsUrl]=\"elementsUrl()\"\n [elementValue]=\"elementValue()\"\n [elementLabel]=\"elementLabel()\"\n [elementSuggestionLabel]=\"elementSuggestionLabel()\"\n [elementValueIdentity]=\"elementValueIdentity()\"\n [elementDisabled]=\"elementDisabled()\"\n [elementGroup]=\"elementGroup()\"\n [allowCustomValue]=\"false\"\n [searchValueMinCharsCount]=\"searchValueMinCharsCount()\"\n [customValueModelGenerator]=\"customValueModelGenerator()\"\n [required]=\"required()\"\n [disabled]=\"disabled()\"\n (userInput)=\"addItem($event)\"\n [(model)]=\"autocompleteInputValue\"\n (elementsLoaded)=\"onAutocompleteElementsLoaded($event)\"\n [showErrorWhenDisabled]=\"showErrorWhenDisabled()\"\n [hideNotRequiredExtraLabel]=\"hideNotRequiredExtraLabel()\"\n [itemHeightInSuggestionListInPx]=\"itemHeightInSuggestionListInPx()\"\n [caseSensitiveSearch]=\"caseSensitiveSearch()\"\n [isLabelSrOnly]=\"isLabelSrOnly()\"\n [isSuggestionListDynamic]=\"isSuggestionListDynamic()\"\n >\n @if (!showEmptyListMessage()) {\n <small\n [attr.id]=\"buildChildId('Help')\"\n class=\"form-text text-secondary\"\n [innerHTML]=\"\n helpText()\n ? helpText()\n : ('foehn-multiselect-autocomplete.autocomplete.screen-reader.label'\n | fromDictionary)\n \"\n ></small>\n }\n\n <ng-content />\n\n <output\n [id]=\"buildChildId('AlertMessageContainer')\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n #alertMessageContainer\n >\n @if (showEmptyListMessage()) {\n <p class=\"alert alert-info mb-0\" tabindex=\"-1\">\n {{\n 'foehn-multiselect-autocomplete.autocomplete.empty-message'\n | fromDictionary\n }}\n </p>\n }\n </output>\n </foehn-autocomplete>\n\n <div class=\"selected-list-container pt-2\" tabindex=\"-1\">\n @if (hasDefaultDeleteFocusArea()) {\n <small\n class=\"form-text text-secondary my-0\"\n [tabindex]=\"0\"\n [innerHTML]=\"noElementsSelectedLabel() | fromDictionary\"\n #defaultDeleteFocusArea\n ></small>\n }\n\n <p\n [id]=\"buildChildId('srOnlyAnnouncements')\"\n class=\"visually-hidden\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n >\n {{ srAnnouncements() }}\n </p>\n\n @if (!!model()?.length) {\n <fieldset\n class=\"mb-0\"\n [attr.aria-describedby]=\"buildChildId('Label')\"\n >\n <legend\n [id]=\"buildChildId('Legend')\"\n class=\"form-text text-secondary small mb-0\"\n [innerHTML]=\"selectedItemsListLegend() | fromDictionary\"\n ></legend>\n <div\n class=\"selected-list d-flex align-content-stretch flex-wrap\"\n >\n @for (\n element of model();\n track trackByFn(index);\n let index = $index\n ) {\n @if (!canBeDeleted(index)) {\n <span\n class=\"badge rounded-pill bg-secondary me-1 mb-1 py-2 px-3 d-flex align-items-center\"\n [tabindex]=\"disabled() ? -1 : 0\"\n [attr.aria-describedby]=\"buildChildId('Legend')\"\n #defaultElemRef\n >\n <span\n [innerHTML]=\"getBadgeLabel(element)\"\n ></span>\n <span class=\"visually-hidden\">\n {{\n 'foehn-multiselect-autocomplete.sr-default-selected-element'\n | fromDictionary\n }}\n </span>\n </span>\n }\n @if (canBeDeleted(index)) {\n <button\n type=\"button\"\n class=\"btn btn-primary badge rounded-pill me-1 mb-1 py-2 px-3 d-flex align-items-center\"\n [tabindex]=\"0\"\n [attr.aria-label]=\"\n getDeleteButtonAriaLabel(element)\n \"\n [attr.aria-describedby]=\"buildChildId('Legend')\"\n [title]=\"getDeleteButtonAriaLabel(element)\"\n (click)=\"removeItem(element, index)\"\n #selectedElemRef\n >\n <span\n [innerHTML]=\"getBadgeLabel(element)\"\n ></span>\n <foehn-icon-times\n aria-hidden=\"true\"\n class=\"ms-2\"\n [title]=\"getDeleteButtonAriaLabel(element)\"\n />\n </button>\n }\n }\n </div>\n </fieldset>\n }\n </div>\n\n @if (hasMissingModelValue()) {\n @for (missingValue of missingModelValues(); track $index) {\n <p\n class=\"alert alert-warning\"\n role=\"alert\"\n [attr.id]=\"buildId('MissingValue' + $index)\"\n >\n {{\n 'foehn-checkable-group.missing-value.warning'\n | fromDictionary\n : { value: getMissingValueLabel(missingValue) }\n }}\n </p>\n }\n }\n</div>\n", styles: ["foehn-autocomplete p.alert:focus{outline:0;box-shadow:inset 0 1px 1px #00000013,0 0 0 .2rem var(--vd-focus)}:host ::ng-deep foehn-autocomplete .form-group{margin-bottom:0!important}.selected-list-container .btn{text-transform:none}:host ::ng-deep .btn .svg-inline--fa,:host ::ng-deep .btn a{color:#fff!important}.selected-list-container span.rounded-pill:focus{outline:0;box-shadow:inset 0 1px 1px #00000013,0 0 0 .2rem var(--vd-focus)}.container-box{border:1px solid #ededed;background-color:#fbfbfb;padding:.5rem}.container-box.vd-form-group-danger{border-left:5px solid var(--vd-danger);padding-left:1rem}:host ::ng-deep foehn-autocomplete div.vd-form-group-danger{border-left:0;padding-left:0}\n"], dependencies: [{ kind: "component", type: FoehnAutocompleteComponent, selector: "foehn-autocomplete", inputs: ["searchValueMinCharsCount", "allowCustomValue", "isSuggestionListDynamic", "customValueModelGenerator", "itemHeightInSuggestionListInPx", "elementSuggestionLabel", "caseSensitiveSearch"], outputs: ["userSearchInput"] }, { kind: "component", type: FoehnIconTimesComponent, selector: "foehn-icon-times" }, { kind: "pipe", type: SdkDictionaryPipe, name: "fromDictionary" }] });
9945
+ ], viewQueries: [{ propertyName: "autocompleteComponent", first: true, predicate: ["autocompleteComponent"], descendants: true, isSignal: true }, { propertyName: "alertMessageContainer", first: true, predicate: ["alertMessageContainer"], descendants: true, isSignal: true }, { propertyName: "selectedElemRef", predicate: ["selectedElemRef"], descendants: true, isSignal: true }, { propertyName: "defaultElemRef", predicate: ["defaultElemRef"], descendants: true, isSignal: true }, { propertyName: "defaultDeleteFocusArea", first: true, predicate: ["defaultDeleteFocusArea"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div\n class=\"form-group container-box\"\n [class.has-danger]=\"hasErrorsToDisplay()\"\n [class.vd-form-group-danger]=\"hasErrorsToDisplay()\"\n>\n <foehn-autocomplete\n #autocompleteComponent\n [name]=\"name()\"\n [label]=\"label()\"\n [elements]=\"elements()\"\n [elementsUrl]=\"elementsUrl()\"\n [elementValue]=\"elementValue()\"\n [elementLabel]=\"elementLabel()\"\n [elementSuggestionLabel]=\"elementSuggestionLabel()\"\n [elementValueIdentity]=\"elementValueIdentity()\"\n [elementDisabled]=\"elementDisabled()\"\n [elementGroup]=\"elementGroup()\"\n [allowCustomValue]=\"false\"\n [searchValueMinCharsCount]=\"searchValueMinCharsCount()\"\n [customValueModelGenerator]=\"customValueModelGenerator()\"\n [required]=\"required()\"\n [disabled]=\"disabled()\"\n (userInput)=\"addItem($event)\"\n [(model)]=\"autocompleteInputValue\"\n (elementsLoaded)=\"onAutocompleteElementsLoaded()\"\n [showErrorWhenDisabled]=\"showErrorWhenDisabled()\"\n [hideNotRequiredExtraLabel]=\"hideNotRequiredExtraLabel()\"\n [itemHeightInSuggestionListInPx]=\"itemHeightInSuggestionListInPx()\"\n [caseSensitiveSearch]=\"caseSensitiveSearch()\"\n [isLabelSrOnly]=\"isLabelSrOnly()\"\n [isSuggestionListDynamic]=\"isSuggestionListDynamic()\"\n >\n @if (!showEmptyListMessage()) {\n <small\n [attr.id]=\"buildChildId('Help')\"\n class=\"form-text text-secondary\"\n [innerHTML]=\"\n helpText()\n ? helpText()\n : ('foehn-multiselect-autocomplete.autocomplete.screen-reader.label'\n | fromDictionary)\n \"\n ></small>\n }\n\n <ng-content />\n\n <output\n [id]=\"buildChildId('AlertMessageContainer')\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n #alertMessageContainer\n >\n @if (showEmptyListMessage()) {\n <p class=\"alert alert-info mb-0\" tabindex=\"-1\">\n {{\n 'foehn-multiselect-autocomplete.autocomplete.empty-message'\n | fromDictionary\n }}\n </p>\n }\n </output>\n </foehn-autocomplete>\n\n <div class=\"selected-list-container pt-2\" tabindex=\"-1\">\n @if (hasDefaultDeleteFocusArea()) {\n <small\n class=\"form-text text-secondary my-0\"\n [tabindex]=\"0\"\n [innerHTML]=\"noElementsSelectedLabel() | fromDictionary\"\n #defaultDeleteFocusArea\n ></small>\n }\n\n <p\n [id]=\"buildChildId('srOnlyAnnouncements')\"\n class=\"visually-hidden\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n >\n {{ srAnnouncements() }}\n </p>\n\n @if (!!model()?.length) {\n <fieldset\n class=\"mb-0\"\n [attr.aria-describedby]=\"buildChildId('Label')\"\n >\n <legend\n [id]=\"buildChildId('Legend')\"\n class=\"form-text text-secondary small mb-0\"\n [innerHTML]=\"selectedItemsListLegend() | fromDictionary\"\n ></legend>\n <div\n class=\"selected-list d-flex align-content-stretch flex-wrap\"\n >\n @for (\n element of model();\n track trackByFn(index);\n let index = $index\n ) {\n @if (!canBeDeleted(index)) {\n <span\n class=\"badge rounded-pill bg-secondary me-1 mb-1 py-2 px-3 d-flex align-items-center\"\n [tabindex]=\"disabled() ? -1 : 0\"\n [attr.aria-describedby]=\"buildChildId('Legend')\"\n #defaultElemRef\n >\n <span\n [innerHTML]=\"getBadgeLabel(element)\"\n ></span>\n <span class=\"visually-hidden\">\n {{\n 'foehn-multiselect-autocomplete.sr-default-selected-element'\n | fromDictionary\n }}\n </span>\n </span>\n }\n @if (canBeDeleted(index)) {\n <button\n type=\"button\"\n class=\"btn btn-primary badge rounded-pill me-1 mb-1 py-2 px-3 d-flex align-items-center\"\n [tabindex]=\"0\"\n [attr.aria-label]=\"\n getDeleteButtonAriaLabel(element)\n \"\n [attr.aria-describedby]=\"buildChildId('Legend')\"\n [title]=\"getDeleteButtonAriaLabel(element)\"\n (click)=\"removeItem(element, index)\"\n #selectedElemRef\n >\n <span\n [innerHTML]=\"getBadgeLabel(element)\"\n ></span>\n <foehn-icon-times\n aria-hidden=\"true\"\n class=\"ms-2\"\n [title]=\"getDeleteButtonAriaLabel(element)\"\n />\n </button>\n }\n }\n </div>\n </fieldset>\n }\n </div>\n\n @if (hasMissingModelValue()) {\n @for (missingValue of missingModelValues(); track $index) {\n <p\n class=\"alert alert-warning\"\n role=\"alert\"\n [attr.id]=\"buildId('MissingValue' + $index)\"\n >\n {{\n 'foehn-checkable-group.missing-value.warning'\n | fromDictionary\n : { value: getMissingValueLabel(missingValue) }\n }}\n </p>\n }\n }\n</div>\n", styles: ["foehn-autocomplete p.alert:focus{outline:0;box-shadow:inset 0 1px 1px #00000013,0 0 0 .2rem var(--vd-focus)}:host ::ng-deep foehn-autocomplete .form-group{margin-bottom:0!important}.selected-list-container .btn{text-transform:none}:host ::ng-deep .btn .svg-inline--fa,:host ::ng-deep .btn a{color:#fff!important}.selected-list-container span.rounded-pill:focus{outline:0;box-shadow:inset 0 1px 1px #00000013,0 0 0 .2rem var(--vd-focus)}.container-box{border:1px solid #ededed;background-color:#fbfbfb;padding:.5rem}.container-box.vd-form-group-danger{border-left:5px solid var(--vd-danger);padding-left:1rem}:host ::ng-deep foehn-autocomplete div.vd-form-group-danger{border-left:0;padding-left:0}\n"], dependencies: [{ kind: "component", type: FoehnAutocompleteComponent, selector: "foehn-autocomplete", inputs: ["searchValueMinCharsCount", "allowCustomValue", "isSuggestionListDynamic", "customValueModelGenerator", "itemHeightInSuggestionListInPx", "elementSuggestionLabel", "caseSensitiveSearch"], outputs: ["userSearchInput"] }, { kind: "component", type: FoehnIconTimesComponent, selector: "foehn-icon-times" }, { kind: "pipe", type: SdkDictionaryPipe, name: "fromDictionary" }] });
9967
9946
  }
9968
9947
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: FoehnMultiselectAutocompleteComponent, decorators: [{
9969
9948
  type: Component,
@@ -9982,7 +9961,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImpo
9982
9961
  SdkDictionaryPipe,
9983
9962
  ], host: {
9984
9963
  '[attr.name]': 'hostName',
9985
- }, template: "<div\n class=\"form-group container-box\"\n [class.has-danger]=\"hasErrorsToDisplay()\"\n [class.vd-form-group-danger]=\"hasErrorsToDisplay()\"\n>\n <foehn-autocomplete\n #autocompleteComponent\n [name]=\"name()\"\n [label]=\"label()\"\n [elements]=\"elements()\"\n [elementsUrl]=\"elementsUrl()\"\n [elementValue]=\"elementValue()\"\n [elementLabel]=\"elementLabel()\"\n [elementSuggestionLabel]=\"elementSuggestionLabel()\"\n [elementValueIdentity]=\"elementValueIdentity()\"\n [elementDisabled]=\"elementDisabled()\"\n [elementGroup]=\"elementGroup()\"\n [allowCustomValue]=\"false\"\n [searchValueMinCharsCount]=\"searchValueMinCharsCount()\"\n [customValueModelGenerator]=\"customValueModelGenerator()\"\n [required]=\"required()\"\n [disabled]=\"disabled()\"\n (userInput)=\"addItem($event)\"\n [(model)]=\"autocompleteInputValue\"\n (elementsLoaded)=\"onAutocompleteElementsLoaded($event)\"\n [showErrorWhenDisabled]=\"showErrorWhenDisabled()\"\n [hideNotRequiredExtraLabel]=\"hideNotRequiredExtraLabel()\"\n [itemHeightInSuggestionListInPx]=\"itemHeightInSuggestionListInPx()\"\n [caseSensitiveSearch]=\"caseSensitiveSearch()\"\n [isLabelSrOnly]=\"isLabelSrOnly()\"\n [isSuggestionListDynamic]=\"isSuggestionListDynamic()\"\n >\n @if (!showEmptyListMessage()) {\n <small\n [attr.id]=\"buildChildId('Help')\"\n class=\"form-text text-secondary\"\n [innerHTML]=\"\n helpText()\n ? helpText()\n : ('foehn-multiselect-autocomplete.autocomplete.screen-reader.label'\n | fromDictionary)\n \"\n ></small>\n }\n\n <ng-content />\n\n <output\n [id]=\"buildChildId('AlertMessageContainer')\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n #alertMessageContainer\n >\n @if (showEmptyListMessage()) {\n <p class=\"alert alert-info mb-0\" tabindex=\"-1\">\n {{\n 'foehn-multiselect-autocomplete.autocomplete.empty-message'\n | fromDictionary\n }}\n </p>\n }\n </output>\n </foehn-autocomplete>\n\n <div class=\"selected-list-container pt-2\" tabindex=\"-1\">\n @if (hasDefaultDeleteFocusArea()) {\n <small\n class=\"form-text text-secondary my-0\"\n [tabindex]=\"0\"\n [innerHTML]=\"noElementsSelectedLabel() | fromDictionary\"\n #defaultDeleteFocusArea\n ></small>\n }\n\n <p\n [id]=\"buildChildId('srOnlyAnnouncements')\"\n class=\"visually-hidden\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n >\n {{ srAnnouncements() }}\n </p>\n\n @if (!!model()?.length) {\n <fieldset\n class=\"mb-0\"\n [attr.aria-describedby]=\"buildChildId('Label')\"\n >\n <legend\n [id]=\"buildChildId('Legend')\"\n class=\"form-text text-secondary small mb-0\"\n [innerHTML]=\"selectedItemsListLegend() | fromDictionary\"\n ></legend>\n <div\n class=\"selected-list d-flex align-content-stretch flex-wrap\"\n >\n @for (\n element of model();\n track trackByFn(index);\n let index = $index\n ) {\n @if (!canBeDeleted(index)) {\n <span\n class=\"badge rounded-pill bg-secondary me-1 mb-1 py-2 px-3 d-flex align-items-center\"\n [tabindex]=\"disabled() ? -1 : 0\"\n [attr.aria-describedby]=\"buildChildId('Legend')\"\n #defaultElemRef\n >\n <span\n [innerHTML]=\"getBadgeLabel(element)\"\n ></span>\n <span class=\"visually-hidden\">\n {{\n 'foehn-multiselect-autocomplete.sr-default-selected-element'\n | fromDictionary\n }}\n </span>\n </span>\n }\n @if (canBeDeleted(index)) {\n <button\n type=\"button\"\n class=\"btn btn-primary badge rounded-pill me-1 mb-1 py-2 px-3 d-flex align-items-center\"\n [tabindex]=\"0\"\n [attr.aria-label]=\"\n getDeleteButtonAriaLabel(element)\n \"\n [attr.aria-describedby]=\"buildChildId('Legend')\"\n [title]=\"getDeleteButtonAriaLabel(element)\"\n (click)=\"removeItem(element, index)\"\n #selectedElemRef\n >\n <span\n [innerHTML]=\"getBadgeLabel(element)\"\n ></span>\n <foehn-icon-times\n aria-hidden=\"true\"\n class=\"ms-2\"\n [title]=\"getDeleteButtonAriaLabel(element)\"\n />\n </button>\n }\n }\n </div>\n </fieldset>\n }\n </div>\n\n @if (hasMissingModelValue()) {\n @for (missingValue of missingModelValues(); track $index) {\n <p\n class=\"alert alert-warning\"\n role=\"alert\"\n [attr.id]=\"buildId('MissingValue' + $index)\"\n >\n {{\n 'foehn-checkable-group.missing-value.warning'\n | fromDictionary\n : { value: getMissingValueLabel(missingValue) }\n }}\n </p>\n }\n }\n</div>\n", styles: ["foehn-autocomplete p.alert:focus{outline:0;box-shadow:inset 0 1px 1px #00000013,0 0 0 .2rem var(--vd-focus)}:host ::ng-deep foehn-autocomplete .form-group{margin-bottom:0!important}.selected-list-container .btn{text-transform:none}:host ::ng-deep .btn .svg-inline--fa,:host ::ng-deep .btn a{color:#fff!important}.selected-list-container span.rounded-pill:focus{outline:0;box-shadow:inset 0 1px 1px #00000013,0 0 0 .2rem var(--vd-focus)}.container-box{border:1px solid #ededed;background-color:#fbfbfb;padding:.5rem}.container-box.vd-form-group-danger{border-left:5px solid var(--vd-danger);padding-left:1rem}:host ::ng-deep foehn-autocomplete div.vd-form-group-danger{border-left:0;padding-left:0}\n"] }]
9964
+ }, template: "<div\n class=\"form-group container-box\"\n [class.has-danger]=\"hasErrorsToDisplay()\"\n [class.vd-form-group-danger]=\"hasErrorsToDisplay()\"\n>\n <foehn-autocomplete\n #autocompleteComponent\n [name]=\"name()\"\n [label]=\"label()\"\n [elements]=\"elements()\"\n [elementsUrl]=\"elementsUrl()\"\n [elementValue]=\"elementValue()\"\n [elementLabel]=\"elementLabel()\"\n [elementSuggestionLabel]=\"elementSuggestionLabel()\"\n [elementValueIdentity]=\"elementValueIdentity()\"\n [elementDisabled]=\"elementDisabled()\"\n [elementGroup]=\"elementGroup()\"\n [allowCustomValue]=\"false\"\n [searchValueMinCharsCount]=\"searchValueMinCharsCount()\"\n [customValueModelGenerator]=\"customValueModelGenerator()\"\n [required]=\"required()\"\n [disabled]=\"disabled()\"\n (userInput)=\"addItem($event)\"\n [(model)]=\"autocompleteInputValue\"\n (elementsLoaded)=\"onAutocompleteElementsLoaded()\"\n [showErrorWhenDisabled]=\"showErrorWhenDisabled()\"\n [hideNotRequiredExtraLabel]=\"hideNotRequiredExtraLabel()\"\n [itemHeightInSuggestionListInPx]=\"itemHeightInSuggestionListInPx()\"\n [caseSensitiveSearch]=\"caseSensitiveSearch()\"\n [isLabelSrOnly]=\"isLabelSrOnly()\"\n [isSuggestionListDynamic]=\"isSuggestionListDynamic()\"\n >\n @if (!showEmptyListMessage()) {\n <small\n [attr.id]=\"buildChildId('Help')\"\n class=\"form-text text-secondary\"\n [innerHTML]=\"\n helpText()\n ? helpText()\n : ('foehn-multiselect-autocomplete.autocomplete.screen-reader.label'\n | fromDictionary)\n \"\n ></small>\n }\n\n <ng-content />\n\n <output\n [id]=\"buildChildId('AlertMessageContainer')\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n #alertMessageContainer\n >\n @if (showEmptyListMessage()) {\n <p class=\"alert alert-info mb-0\" tabindex=\"-1\">\n {{\n 'foehn-multiselect-autocomplete.autocomplete.empty-message'\n | fromDictionary\n }}\n </p>\n }\n </output>\n </foehn-autocomplete>\n\n <div class=\"selected-list-container pt-2\" tabindex=\"-1\">\n @if (hasDefaultDeleteFocusArea()) {\n <small\n class=\"form-text text-secondary my-0\"\n [tabindex]=\"0\"\n [innerHTML]=\"noElementsSelectedLabel() | fromDictionary\"\n #defaultDeleteFocusArea\n ></small>\n }\n\n <p\n [id]=\"buildChildId('srOnlyAnnouncements')\"\n class=\"visually-hidden\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n >\n {{ srAnnouncements() }}\n </p>\n\n @if (!!model()?.length) {\n <fieldset\n class=\"mb-0\"\n [attr.aria-describedby]=\"buildChildId('Label')\"\n >\n <legend\n [id]=\"buildChildId('Legend')\"\n class=\"form-text text-secondary small mb-0\"\n [innerHTML]=\"selectedItemsListLegend() | fromDictionary\"\n ></legend>\n <div\n class=\"selected-list d-flex align-content-stretch flex-wrap\"\n >\n @for (\n element of model();\n track trackByFn(index);\n let index = $index\n ) {\n @if (!canBeDeleted(index)) {\n <span\n class=\"badge rounded-pill bg-secondary me-1 mb-1 py-2 px-3 d-flex align-items-center\"\n [tabindex]=\"disabled() ? -1 : 0\"\n [attr.aria-describedby]=\"buildChildId('Legend')\"\n #defaultElemRef\n >\n <span\n [innerHTML]=\"getBadgeLabel(element)\"\n ></span>\n <span class=\"visually-hidden\">\n {{\n 'foehn-multiselect-autocomplete.sr-default-selected-element'\n | fromDictionary\n }}\n </span>\n </span>\n }\n @if (canBeDeleted(index)) {\n <button\n type=\"button\"\n class=\"btn btn-primary badge rounded-pill me-1 mb-1 py-2 px-3 d-flex align-items-center\"\n [tabindex]=\"0\"\n [attr.aria-label]=\"\n getDeleteButtonAriaLabel(element)\n \"\n [attr.aria-describedby]=\"buildChildId('Legend')\"\n [title]=\"getDeleteButtonAriaLabel(element)\"\n (click)=\"removeItem(element, index)\"\n #selectedElemRef\n >\n <span\n [innerHTML]=\"getBadgeLabel(element)\"\n ></span>\n <foehn-icon-times\n aria-hidden=\"true\"\n class=\"ms-2\"\n [title]=\"getDeleteButtonAriaLabel(element)\"\n />\n </button>\n }\n }\n </div>\n </fieldset>\n }\n </div>\n\n @if (hasMissingModelValue()) {\n @for (missingValue of missingModelValues(); track $index) {\n <p\n class=\"alert alert-warning\"\n role=\"alert\"\n [attr.id]=\"buildId('MissingValue' + $index)\"\n >\n {{\n 'foehn-checkable-group.missing-value.warning'\n | fromDictionary\n : { value: getMissingValueLabel(missingValue) }\n }}\n </p>\n }\n }\n</div>\n", styles: ["foehn-autocomplete p.alert:focus{outline:0;box-shadow:inset 0 1px 1px #00000013,0 0 0 .2rem var(--vd-focus)}:host ::ng-deep foehn-autocomplete .form-group{margin-bottom:0!important}.selected-list-container .btn{text-transform:none}:host ::ng-deep .btn .svg-inline--fa,:host ::ng-deep .btn a{color:#fff!important}.selected-list-container span.rounded-pill:focus{outline:0;box-shadow:inset 0 1px 1px #00000013,0 0 0 .2rem var(--vd-focus)}.container-box{border:1px solid #ededed;background-color:#fbfbfb;padding:.5rem}.container-box.vd-form-group-danger{border-left:5px solid var(--vd-danger);padding-left:1rem}:host ::ng-deep foehn-autocomplete div.vd-form-group-danger{border-left:0;padding-left:0}\n"] }]
9986
9965
  }], propDecorators: { elementBadgeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "elementBadgeLabel", required: false }] }], selectedItemsListLegend: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedItemsListLegend", required: false }] }], noElementsSelectedLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "noElementsSelectedLabel", required: false }] }], excludeFromErrorSummary: [{ type: i0.Input, args: [{ isSignal: true, alias: "excludeFromErrorSummary", required: false }] }], defaultElementValues: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultElementValues", required: false }] }], autocompleteComponent: [{ type: i0.ViewChild, args: ['autocompleteComponent', { isSignal: true }] }], alertMessageContainer: [{ type: i0.ViewChild, args: ['alertMessageContainer', { isSignal: true }] }], selectedElemRef: [{ type: i0.ViewChildren, args: ['selectedElemRef', { isSignal: true }] }], defaultElemRef: [{ type: i0.ViewChildren, args: ['defaultElemRef', { isSignal: true }] }], defaultDeleteFocusArea: [{ type: i0.ViewChild, args: ['defaultDeleteFocusArea', { isSignal: true }] }] } });
9987
9966
 
9988
9967
  class FoehnInputEmailComponent extends FoehnInputStringComponent {
@@ -13122,12 +13101,23 @@ class UploaderHelper {
13122
13101
  ? this._fileMaxSizeInBytesByFormKey[keyAsRegex]
13123
13102
  : null;
13124
13103
  }
13125
- isFileTooBig(formKey, fileSizeInBytes) {
13126
- const maxFileSizeInBytesForFormKey = this.getMaxFileSizeInBytesForFormKey(formKey);
13127
- if (!maxFileSizeInBytesForFormKey) {
13104
+ /**
13105
+ * The maximum size of a file: the one of its form key (application info) and `maxFileSizeInMo` (a limit of the
13106
+ * component), the smallest of both.
13107
+ */
13108
+ getMaxFileSizeInBytes(formKey, maxFileSizeInMo) {
13109
+ const limits = [
13110
+ this.getMaxFileSizeInBytesForFormKey(formKey),
13111
+ maxFileSizeInMo ? maxFileSizeInMo * 1024 * 1024 : null,
13112
+ ].filter((limit) => !!limit);
13113
+ return limits.length ? Math.min(...limits) : null;
13114
+ }
13115
+ isFileTooBig(formKey, fileSizeInBytes, maxFileSizeInMo) {
13116
+ const maxFileSizeInBytes = this.getMaxFileSizeInBytes(formKey, maxFileSizeInMo);
13117
+ if (!maxFileSizeInBytes) {
13128
13118
  return false;
13129
13119
  }
13130
- return this.isTooBig(fileSizeInBytes, maxFileSizeInBytesForFormKey);
13120
+ return this.isTooBig(fileSizeInBytes, maxFileSizeInBytes);
13131
13121
  }
13132
13122
  isFileTooSmall(fileSizeInBytes) {
13133
13123
  if (!this.fileMinSizeInBytes) {
@@ -13164,12 +13154,12 @@ class UploaderHelper {
13164
13154
  .split(',')
13165
13155
  .includes(fileExtension.toUpperCase()));
13166
13156
  }
13167
- getUniqueErrorByCode(name, errorCode, fileName, customErrors, overrideAcceptedExtensions, overrideMaxFileNameLength, overrideIllegalCharacters) {
13157
+ getUniqueErrorByCode(name, errorCode, fileName, customErrors, overrideAcceptedExtensions, overrideMaxFileNameLength, overrideIllegalCharacters, maxFileSizeInMo) {
13168
13158
  const uniqSuffix = Date.now();
13169
13159
  return {
13170
13160
  name,
13171
13161
  code: `${errorCode}_${uniqSuffix}`,
13172
- message: this.replaceErrorMessageKeys(name, customErrors[errorCode], fileName, errorCode, overrideAcceptedExtensions, overrideMaxFileNameLength, overrideIllegalCharacters),
13162
+ message: this.replaceErrorMessageKeys(name, customErrors[errorCode], fileName, errorCode, overrideAcceptedExtensions, overrideMaxFileNameLength, overrideIllegalCharacters, maxFileSizeInMo),
13173
13163
  };
13174
13164
  }
13175
13165
  addCustomErrorOrDefault(name, destinationErrors, errorCode, fileName, customErrors, overrideAcceptedExtensions, overrideMaxFileNameLength, overrideIllegalCharacters) {
@@ -13231,7 +13221,7 @@ class UploaderHelper {
13231
13221
  createDefaultCustomErrors(customErrors) {
13232
13222
  Object.assign(customErrors, this.getDefaultCustomErrors());
13233
13223
  }
13234
- getLocalErrors(name, files, customErrors, overrideAcceptedExtensions, overrideMaxFileNameLength, overrideIllegalCharacters) {
13224
+ getLocalErrors(name, files, customErrors, overrideAcceptedExtensions, overrideMaxFileNameLength, overrideIllegalCharacters, maxFileSizeInMo) {
13235
13225
  const errors = [];
13236
13226
  files.forEach((file) => {
13237
13227
  // File bad extension
@@ -13248,10 +13238,10 @@ class UploaderHelper {
13248
13238
  errors.push(this.getUniqueErrorByCode(name, 'FILENAME_SHOULD_NOT_CONTAIN', file.name, customErrors, overrideAcceptedExtensions, overrideMaxFileNameLength, overrideIllegalCharacters));
13249
13239
  }
13250
13240
  // Files too big
13251
- if (this.isFileTooBig(name, file.size)) {
13252
- // Default custom error 413 exist (see function 'createDefaultCustomErrors')
13241
+ if (this.isFileTooBig(name, file.size, maxFileSizeInMo)) {
13242
+ // Default custom error 413 exist (see function 'getDefaultCustomErrors')
13253
13243
  // Need to create unique error to ensure ability to display multiple error messages
13254
- errors.push(this.getUniqueErrorByCode(name, '413', file.name, customErrors, overrideAcceptedExtensions, overrideMaxFileNameLength, overrideIllegalCharacters));
13244
+ errors.push(this.getUniqueErrorByCode(name, '413', file.name, customErrors, overrideAcceptedExtensions, overrideMaxFileNameLength, overrideIllegalCharacters, maxFileSizeInMo));
13255
13245
  }
13256
13246
  if (this.isFileTooSmall(file.size)) {
13257
13247
  // Default custom error FILE_TOO_SMALL exist (see function 'createDefaultCustomErrors')
@@ -13367,7 +13357,7 @@ class UploaderHelper {
13367
13357
  isTooBig(fileSizeInBytes, limitInBytes) {
13368
13358
  return fileSizeInBytes > limitInBytes;
13369
13359
  }
13370
- replaceErrorMessageKeys(name, errorMsg, fileName, errorCode, overrideAcceptedExtensions, overrideMaxFileNameLength, overrideIllegalCharacters) {
13360
+ replaceErrorMessageKeys(name, errorMsg, fileName, errorCode, overrideAcceptedExtensions, overrideMaxFileNameLength, overrideIllegalCharacters, maxFileSizeInMo) {
13371
13361
  const safeFileName = fileName?.length
13372
13362
  ? getSafeFileNameForErrorMessage(fileName)
13373
13363
  : '';
@@ -13381,7 +13371,7 @@ class UploaderHelper {
13381
13371
  // getDefaultCustomErrors function: customErrors['0'] = customErrors['413'];
13382
13372
  if (errorCode.startsWith('413') || errorCode.startsWith('0')) {
13383
13373
  // REQUEST_ENTITY_TOO_LARGE_ERROR_MSG
13384
- const fileMaxSize = ((this.getMaxFileSizeInBytesForFormKey(name) ?? 0) /
13374
+ const fileMaxSize = ((this.getMaxFileSizeInBytes(name, maxFileSizeInMo) ?? 0) /
13385
13375
  (1024 * 1024)).toString();
13386
13376
  return withValue('fileMaxSize', fileMaxSize);
13387
13377
  }
@@ -13540,8 +13530,14 @@ class AbstractFoehnUploaderComponent extends FoehnInputComponent {
13540
13530
  removeFileOnConfirmation(document) {
13541
13531
  this.deleteDocumentSubject.next(document);
13542
13532
  }
13533
+ /**
13534
+ * A maximum size of each file, in addition to the one of its form key in the application info.
13535
+ */
13536
+ getMaxFileSizeInMo() {
13537
+ return undefined;
13538
+ }
13543
13539
  displayLocalErrors(files) {
13544
- const errors = this.uploaderHelper.getLocalErrors(this.name(), files, this.uploaderCustomErrors(), this.overrideAcceptedExtensions(), this.overrideMaxFileNameLength(), this.overrideIllegalCharacters());
13540
+ const errors = this.uploaderHelper.getLocalErrors(this.name(), files, this.uploaderCustomErrors(), this.overrideAcceptedExtensions(), this.overrideMaxFileNameLength(), this.overrideIllegalCharacters(), this.getMaxFileSizeInMo());
13545
13541
  this.refreshErrors([errors, true]);
13546
13542
  return errors;
13547
13543
  }
@@ -14301,7 +14297,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImpo
14301
14297
  }]
14302
14298
  }], ctorParameters: () => [] });
14303
14299
 
14304
- const BYTES_IN_MO = 1024 * 1024;
14305
14300
  class FoehnBoMultiUploadComponent extends AbstractFoehnUploaderComponent {
14306
14301
  uploadUrl = input(...(ngDevMode ? [undefined, { debugName: "uploadUrl" }] : /* istanbul ignore next */ []));
14307
14302
  deleteUrl = input(...(ngDevMode ? [undefined, { debugName: "deleteUrl" }] : /* istanbul ignore next */ []));
@@ -14419,6 +14414,9 @@ class FoehnBoMultiUploadComponent extends AbstractFoehnUploaderComponent {
14419
14414
  }
14420
14415
  }
14421
14416
  }
14417
+ getMaxFileSizeInMo() {
14418
+ return this.maxFileSizeInMo();
14419
+ }
14422
14420
  removeFileOnConfirmation(document) {
14423
14421
  if (this.localUpload()) {
14424
14422
  this.removeDocumentLocally(document);
@@ -14442,11 +14440,6 @@ class FoehnBoMultiUploadComponent extends AbstractFoehnUploaderComponent {
14442
14440
  if (filesErrors.length) {
14443
14441
  return;
14444
14442
  }
14445
- const tooBigErrors = this.tooBigErrors(files);
14446
- if (tooBigErrors.length) {
14447
- this.refreshErrors([tooBigErrors, true]);
14448
- return;
14449
- }
14450
14443
  // Initialize model if empty
14451
14444
  if (!this.model()?.length) {
14452
14445
  this.model.set([]);
@@ -14460,22 +14453,6 @@ class FoehnBoMultiUploadComponent extends AbstractFoehnUploaderComponent {
14460
14453
  // we send files to server
14461
14454
  this.uploadFilesSubject.next(files);
14462
14455
  }
14463
- tooBigErrors(files) {
14464
- const maxFileSizeInMo = this.maxFileSizeInMo();
14465
- if (!maxFileSizeInMo) {
14466
- return [];
14467
- }
14468
- return files
14469
- .filter((file) => file.size > maxFileSizeInMo * BYTES_IN_MO)
14470
- .map((file, index) => ({
14471
- name: this.name(),
14472
- code: `FILE_TOO_BIG_${index}`,
14473
- message: this.dictionaryService.getKeySync('foehn-uploader.file-too-big-error-message', {
14474
- filename: getSafeFileNameForErrorMessage(file.name),
14475
- maxSizeInMo: `${maxFileSizeInMo}`,
14476
- }),
14477
- }));
14478
- }
14479
14456
  addFilesLocally(files) {
14480
14457
  const pendingDocuments = files.map((file) => this.toPendingDocument(file));
14481
14458
  this.model.update((current) => [