@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 +15 -6
- package/UPGRADING_V19.md +13 -0
- package/dsivd-prestations-ng-19.2.0-beta.9.tgz +0 -0
- package/eslint/configs/spec-ts-recommended.mjs +34 -0
- package/eslint/index.mjs +17 -0
- package/fesm2022/dsivd-prestations-ng.mjs +79 -102
- package/fesm2022/dsivd-prestations-ng.mjs.map +1 -1
- package/package.json +1 -1
- package/src/eslint/configs/__tests__/configs.test.mjs +46 -2
- package/src/eslint/configs/spec-ts-recommended.mjs +34 -0
- package/src/eslint/index.mjs +17 -0
- package/types/dsivd-prestations-ng.d.ts +23 -11
- package/dsivd-prestations-ng-19.2.0-beta.8.tgz +0 -0
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).
|
|
26
|
-
`
|
|
27
|
-
|
|
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
|
|
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
|
|
Binary file
|
|
@@ -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
|
-
|
|
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.
|
|
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
|
-
|
|
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(
|
|
9794
|
-
|
|
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
|
-
|
|
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
|
|
9895
|
-
|
|
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
|
-
|
|
9912
|
-
// Hide message saying no more suggestions to display
|
|
9913
|
-
this.showEmptyListMessage.set(false);
|
|
9909
|
+
applySuggestionsToAutocomplete() {
|
|
9914
9910
|
const autocompleteComponent = this.autocompleteComponent();
|
|
9915
|
-
|
|
9916
|
-
|
|
9917
|
-
|
|
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
|
-
|
|
9939
|
-
|
|
9940
|
-
|
|
9941
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
13126
|
-
|
|
13127
|
-
|
|
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,
|
|
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 '
|
|
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.
|
|
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) => [
|