@edugouvfr/ngx-dsfr-ext
Version:
NgxDsfrExt est une extension au package @edugouvfr/ngx-dsfr (portage Angular des éléments DSFR)
343 lines • 94.3 kB
JavaScript
import { CdkVirtualScrollViewport, ScrollingModule } from '@angular/cdk/scrolling';
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, ContentChild, Input, ViewChild, ViewEncapsulation, forwardRef, signal, } from '@angular/core';
import { FormsModule, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms';
import { DsfrButtonModule, DsfrI18nPipe, DsfrTagModule } from '@edugouvfr/ngx-dsfr';
import { AbstractSelectComponent, DropdownContainerComponent, EduCheckboxComponent } from '../shared/components';
import { SelectInputLabelComponent } from '../shared/components/select-input-label/select-input-label.component';
import { EduMessageSeverityDirective } from '../shared/directives/message-severity.directive';
import { SelectFocusUtils } from './select-focus.utils';
import * as i0 from "@angular/core";
import * as i1 from "@angular/common";
import * as i2 from "@angular/forms";
import * as i3 from "@angular/cdk/scrolling";
import * as i4 from "@edugouvfr/ngx-dsfr";
export class DsfrMultiselectComponent extends AbstractSelectComponent {
constructor() {
super(...arguments);
/** @internal liste d'options filtrées */
this.optionsFilter = [];
/** Nombre de résultats après une recherche (pas de valeur initiale = a11y) */
this.nbFilterResults = signal(0);
this.containerListClass = SelectFocusUtils.CONTAINER_LIST_CLASS;
/** @internal */
this.trackByIndex = (index) => {
return index;
};
/** Fonction de recherche par défaut */
this._searchFn = function (o, searchText) {
return o.label?.toLowerCase().includes(searchText.toLowerCase());
};
}
/**@internal */
get options() {
return this._options;
}
/** Personnalisation de la fonction de recherche pour filtrer les options. Fonction à 2 arguments : option (DsfrSelectOption) et texte cherché (string) */
set searchFn(fn) {
if (typeof fn !== 'function') {
throw Error('`searchFn` must be a function.');
}
this._searchFn = fn;
this.searchText = '';
this.optionsFilter = this.options ?? [];
}
/** Liste des options disponibles. */
set options(val) {
this._options = val ? val : [];
this.optionsFlatten = this.flattenOptions(this._options);
this.optionsFilter = this.options ?? [];
if (this.value && this.value.length > 0) {
this.writeValue(this.value);
}
}
/**
* Mise a jour de la valeur de selectedOptions et value programmaticalement (ex. à l'init reactive form)
* @param value
* @internal
*/
writeValue(value) {
if (value) {
if (this.isSingleMode) {
this.selectedOptions =
this.optionsFlatten && this.optionsFlatten.length
? [this.optionsFlatten?.find((o) => this._compareWith(value, o.value))]
: [];
}
else if (!(value instanceof Array)) {
throw Error('`value` must be an array.');
}
else {
// Sélection des options en enlevant les doublons et valeurs nulles éventuelles
this.selectedOptions = Array.from(new Set(value
.map((val) => this.optionsFlatten?.find((o) => this._compareWith(val, o.value)))
.filter((selected) => !!selected)));
}
this.indexFirstSelected = this.getFirstIndexSelected(value);
}
else {
this.selectedOptions = [];
this.indexFirstSelected = null;
}
super.writeValue(value);
this.cd.markForCheck();
}
/**
* Sélection ou désélection d'une option
* @internal */
onOptionClick(e, option) {
if (this.isSingleMode) {
this.optionClickSingle(option);
}
else {
this.optionClickMultiple(option);
}
this.indexFirstSelected = this.getFirstIndexSelected(this.value);
this.selectionChange.emit(this.value);
e.preventDefault();
}
/** @internal */
isSelected(option) {
if (this.isSingleMode) {
return this._compareWith(this.value, option.value);
}
if (this.value && !(this.value instanceof Array))
throw Error('`value` must be an array.');
return !!this.value?.find((o) => this._compareWith(o, option.value));
}
/**
* Interaction au clavier dans la liste déroulante
* Les flêches permettent de naviguer d'une option à l'autre en modifiant le focus.
* A la fin ou au début de la liste on boucle sur la première ou dernière option
* @internal */
onOptionKeydown(event, option) {
const el = event.target;
// retour tab - gestion par défaut du focus sur la toolbar
if (event.shiftKey && event.key === 'Tab' && (this.showSelectAll || this.showSearch)) {
return;
}
switch (event.key) {
case 'ArrowDown':
event.preventDefault();
const nextItem = SelectFocusUtils.findNextListItem(el.nextElementSibling, el);
this.setFocusElement(nextItem);
break;
case 'ArrowUp':
event.preventDefault();
const prevItem = SelectFocusUtils.findPreviousListItem(el.previousElementSibling, el);
this.setFocusElement(prevItem);
break;
case 'End':
event.preventDefault();
const lastItem = SelectFocusUtils.setFocusElementOnLast(event);
this.setFocusElement(lastItem);
break;
case 'Home':
event.preventDefault();
const firstItem = SelectFocusUtils.setFocusElementOnFirst(event);
this.setFocusElement(firstItem);
break;
case 'Enter':
event.preventDefault();
this.onOptionClick(event, option);
this.hide();
break;
case ' ':
event.preventDefault();
this.onOptionClick(event, option);
break;
case 'Escape':
event.preventDefault();
this.hide();
break;
case 'Tab':
this.hide();
break;
}
}
/**
* Sélectionner ou déselectionner toutes les options
* Si maxSelectedOptions est défini, sélection uniquement des (max) premières options
@internal
*/
selectAll() {
if (this.optionsFilter?.length === this.value?.length) {
this.selectedOptions = [];
this.value = [];
}
else {
this.selectedOptions = [...this.flattenOptions(this.optionsFilter)];
this.value = this.selectedOptions.map((o) => o.value);
}
this.selectionChange.emit(this.value);
}
/**
* Filtrer les options selon la fonction de recherche et conserver la structure. Les labels des groupes sont ignorés.
* @internal */
onSearch() {
let countResults = 0;
if (!this.options || !this.options.length || !this._searchFn)
return;
const filtered = [];
for (const option of this.options) {
// Ignorer les groupes dans la recherche
if (option.options?.length) {
const optionsChildren = option.options.filter((o) => this._searchFn(o, this.searchText));
if (optionsChildren?.length) {
filtered.push({ ...option, options: optionsChildren });
countResults = countResults + optionsChildren.length;
}
}
else if (this._searchFn(option, this.searchText)) {
filtered.push(option);
countResults++;
}
}
this.optionsFilter = filtered;
this.nbFilterResults.set(countResults);
}
/**
* Filtrer les options selon la fonction de recherche et conserver la structure. Les labels des groupes sont ignorés.
* @internal */
onSearchFlatten() {
const groupIdsInserted = [];
let countResults = 0;
if (!this.optionsFlatten || !this.optionsFlatten.length || !this._searchFn)
return;
this.optionsFilter = [];
for (const option of this.optionsFlatten) {
// Ignorer les groupes dans la recherche
if (!option.isGroup && this._searchFn(option, this.searchText)) {
// Ajouter le groupe si il n'est pas déja présent
if (option.groupId && !groupIdsInserted.includes(option.groupId)) {
const group = this.optionsFlatten.find((o) => o.isGroup && o.groupId === option.groupId);
if (group) {
this.optionsFilter.push(group);
groupIdsInserted.push(option.groupId);
}
}
this.optionsFilter.push(option);
countResults++;
}
}
this.nbFilterResults.set(countResults);
}
/** @internal */
hasisAllSelected() {
return this.optionsFilter?.length > 0 && this.value?.length === this.optionsFilter.length;
}
/** @internal */
clearOption(event) {
event.nativeEvent.stopPropagation();
this.onOptionClick(event.nativeEvent, event.option);
}
/** @internal */
clearAllOptions() {
this.selectedOptions = [];
this.value = [];
this.selectionChange.emit(this.value);
}
handleListFocus(el) {
const itemToFocus = SelectFocusUtils.findNextListItem(el);
if (itemToFocus)
this.setFocusElement(itemToFocus);
if (this.virtualScroll)
this.scrollForVirtualScroll(el);
}
/** Index du premier element sélectionné pour positionner le focus et aria */
getFirstIndexSelected(value) {
if (this.isSingleMode) {
return this.optionsFilter?.findIndex((o) => this._compareWith(o.value, value));
}
if (value && value[0]) {
const firstOption = value ? value[0] : null;
return this.optionsFilter?.findIndex((o) => this._compareWith(o.value, firstOption));
}
return -1;
}
/** Sélection ou déselection d'une option en mode multiple/checkbox */
optionClickMultiple(option) {
if (!this.isSelected(option)) {
if (this.maxSelectedOptions && this.selectedOptions.length === this.maxSelectedOptions) {
// nothing here
}
else {
this.selectedOptions.push(option);
}
}
else {
// déselection
const i = this.selectedOptions.findIndex((o) => o === option);
this.selectedOptions.splice(i, 1);
}
this.selectedOptions = [...this.selectedOptions];
this.value = this.selectedOptions.map((o) => o.value);
}
/** Sélection ou déselection d'une option en mode single */
optionClickSingle(option) {
this.selectedOptions = this.isSelected(option) ? [] : [option];
this.value = this.selectedOptions[0] ? this.selectedOptions[0].value : undefined;
this.hide();
}
/** Set focus on element and add aria-activedescendant attribute to parent */
setFocusElement(optionEl) {
if (!optionEl)
return;
if (optionEl) {
optionEl.focus();
const optionId = optionEl.getAttribute('id');
if (optionId)
optionEl.closest(`.${this.containerListClass}`)?.setAttribute('aria-activedescendant', optionId);
if (this.virtualScroll) {
this.scrollForVirtualScroll(optionEl);
}
}
}
/* *Gestion manuelle du scroll dans la liste dans le cas de la navigation clavier avec virtualScroll activé */
scrollForVirtualScroll(optionEl) {
const index = optionEl.getAttribute('data-index') ?? '0';
this.viewport.scrollToIndex(Number(index), 'smooth');
}
/** Gestion à plat du tableau des options en cas de groupe */
flattenOptions(options) {
let indexGroup = 0;
const optionsViewModel = options.flatMap((opt) => opt.options?.length
? [
{ label: opt.label, value: opt.value, isGroup: true, groupId: 1 + indexGroup++ },
...opt.options.map((o) => {
return { ...o, groupId: indexGroup };
}),
]
: [opt]);
return optionsViewModel;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: DsfrMultiselectComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: DsfrMultiselectComponent, isStandalone: true, selector: "dsfr-ext-multiselect, dsfrx-multiselect,", inputs: { searchFn: "searchFn", options: "options" }, providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => DsfrMultiselectComponent), multi: true }], queries: [{ propertyName: "groupTemplate", first: true, predicate: ["groupTemplate"], descendants: true, static: true }], viewQueries: [{ propertyName: "viewport", first: true, predicate: CdkVirtualScrollViewport, descendants: true }], usesInheritance: true, ngImport: i0, template: "<div\n class=\"fr-select-group dsfrx-select\"\n [class]=\"message ? 'fr-select-group--' + messageSeverity : ''\"\n [ngClass]=\"{\n 'fr-select-group--disabled': disabled,\n 'fr-select-group--valid': message && messageSeverity === severityConst.SUCCESS,\n }\">\n <div class=\"fr-label\" [ngClass]=\"{ 'fr-sr-only': labelSrOnly }\" id=\"{{ selectId }}-label\">\n @if (label) {\n {{ label }}\n } @else {\n <ng-content select=\"[label]\"></ng-content>\n }\n @if (hint) {\n <span class=\"fr-hint-text\">{{ hint }}</span>\n }\n </div>\n\n <edu-dropdown-container\n [displayDropdown]=\"displayDropdown\"\n [appendTo]=\"appendTo\"\n [classToWrap]=\"'dsfrx-multiselect'\"\n [autoWidthDropdown]=\"autoWidthDropdown\"\n [autofocus]=\"true\"\n [hasToolBar]=\"showSearch || showSelectAll\"\n [zIndex]=\"zIndex\"\n [scrollHeight]=\"scrollHeight\"\n [indexFirstSelected]=\"indexFirstSelected\"\n (focusOnList)=\"handleListFocus($event)\"\n (hideList)=\"hide()\"\n [id]=\"selectId\"\n [virtualScroll]=\"virtualScroll\"\n [hasOptions]=\"optionsFilter.length > 0\">\n <!-- Control input -->\n <div\n ngProjectAs=\"control\"\n ngDefaultControl\n [(ngModel)]=\"value\"\n id=\"{{ selectId }}\"\n [attr.tabindex]=\"disabled ? -1 : 0\"\n role=\"combobox\"\n (click)=\"toggleDropdown($event)\"\n (keydown)=\"onButtonKeyDown($event)\"\n aria-haspopup=\"listbox\"\n [ngClass]=\"{ 'fr-select--disabled': disabled }\"\n [disabled]=\"disabled\"\n [attr.aria-controls]=\"selectId + '-listbox'\"\n [attr.aria-expanded]=\"displayDropdown\"\n [attr.aria-describedby]=\"message ? selectId + '-messages' : null\"\n [attr.aria-labelledby]=\"selectId + '-label'\"\n [attr.aria-required]=\"ariaRequired || null\"\n [attr.aria-invalid]=\"ariaInvalid || null\"\n class=\"fr-select\">\n <edu-select-input-label\n [placeHolder]=\"placeHolder\"\n [clearableOptions]=\"clearableOptions\"\n [maxDisplayOptions]=\"maxDisplayOptions\"\n [selectedOptionTemplate]=\"selectedOptionTemplate\"\n [selectedOptions]=\"selectedOptions\"\n [maxDisplayOptionsTemplate]=\"maxDisplayOptionsTemplate\"\n (clearOptionSelect)=\"clearOption($event)\"\n (clearAllOptionsSelect)=\"clearAllOptions()\">\n </edu-select-input-label>\n </div>\n\n <!-- If toolbar is shown -->\n <ng-container toolbar>\n @if (showSelectAll && !maxSelectedOptions) {\n <dsfr-button\n size=\"SM\"\n variant=\"tertiary\"\n customClass=\"fr-btn--select-all\"\n (click)=\"selectAll()\"\n [label]=\"hasisAllSelected() ? ('multiselect.unselectAll' | dsfrI18n) : ('multiselect.selectAll' | dsfrI18n)\">\n </dsfr-button>\n }\n\n @if (showSearch) {\n <label for=\"{{ selectId }}-searchbox\" class=\"fr-sr-only\">{{ 'multiselect.searchList' | dsfrI18n }}</label>\n <div\n class=\"fr-input-wrap\"\n [class.fr-icon-search-line]=\"!showClearSearch\"\n [class.fr-input-wrap--action]=\"showClearSearch\">\n <input\n #searchBox\n class=\"fr-input\"\n type=\"search\"\n aria-invalid=\"false\"\n id=\"{{ selectId }}-searchbox\"\n role=\"combobox\"\n [attr.aria-label]=\"searchMessage ?? ('multiselect.search' | dsfrI18n)\"\n [placeholder]=\"searchMessage ?? ('multiselect.search' | dsfrI18n)\"\n (input)=\"onSearch()\"\n aria-controls=\"listboxid\"\n aria-expanded=\"true\"\n aria-autocomplete=\"list\"\n autocomplete=\"off\"\n aria-haspopup=\"listbox\"\n [(ngModel)]=\"searchText\" />\n @if (showClearSearch) {\n <button\n type=\"button\"\n class=\"fr-btn fr-icon-close-line fr-btn--tertiary\"\n id=\"{{ selectId }}-reset\"\n (click)=\"resetSearchInput()\"\n [title]=\"'multiselect.resetSearch' | dsfrI18n\">\n {{ 'multiselect.resetSearch' | dsfrI18n }}\n </button>\n }\n </div>\n }\n <div role=\"status\" aria-live=\"polite\" class=\"fr-sr-only\">\n @if (nbFilterResults() > 0) {\n <p>{{ nbFilterResults() }} {{ 'multiselect.availableResults' | dsfrI18n }}</p>\n }\n </div>\n </ng-container>\n\n <!-- template de listbox (options) en virtual scroll -->\n @if (virtualScroll) {\n <div\n [ngStyle]=\"{ 'z-index': zIndex }\"\n [style.max-height]=\"scrollHeight\"\n #listbox\n class=\"dsfrx-multiselect__list dsfrx-multiselect__list--has-viewport\">\n <cdk-virtual-scroll-viewport\n #viewport\n minBufferPx=\"200\"\n maxBufferPx=\"600\"\n [itemSize]=\"virtualScrollItemHeight\"\n class=\"viewport\">\n @if (optionsFilter.length > 0) {\n <ul\n role=\"listbox\"\n [attr.aria-multiselectable]=\"!this.isSingleMode\"\n tabindex=\"-1\"\n id=\"{{ selectId }}-listbox\">\n <li\n *cdkVirtualFor=\"let option of optionsFilter; let i = index\"\n role=\"option\"\n class=\"dsfrx-multiselect__item\"\n [attr.data-dsfrx-option]=\"true\"\n id=\"{{ selectId }}-listbox-{{ i }}\"\n [ngClass]=\"{ selected: isSelected(option) }\"\n [attr.data-id]=\"option.value\"\n [attr.data-index]=\"i\"\n [attr.aria-selected]=\"this.isSingleMode ? isSelected(option) : null\"\n [attr.aria-checked]=\"this.isSingleMode ? null : isSelected(option)\"\n tabindex=\"-1\"\n (click)=\"onOptionClick($event, option)\"\n (keydown)=\"onOptionKeydown($event, option)\">\n <ng-container\n [ngTemplateOutlet]=\"insideItem\"\n [ngTemplateOutletContext]=\"{ option: option, index: i }\"></ng-container>\n </li>\n </ul>\n }\n </cdk-virtual-scroll-viewport>\n </div>\n } @else {\n <!-- template de listbox (options) sans virtual scroll -->\n <ul\n #listbox\n id=\"{{ selectId }}-listbox\"\n [ngStyle]=\"{ 'z-index': zIndex }\"\n role=\"listbox\"\n tabindex=\"-1\"\n [style.max-height]=\"scrollHeight\"\n [attr.aria-multiselectable]=\"!this.isSingleMode\"\n [class]=\"containerListClass\">\n @for (option of optionsFilter; track option; let i = $index) {\n @if (option.options) {\n <ul class=\"dsfrx-multiselect__group\" role=\"group\" [attr.aria-labelledby]=\"selectId + '-group-' + i\">\n <li id=\"{{ selectId }}-group-{{ i }}\" role=\"presentation\" class=\"dsfrx-multiselect__group--label\">\n @if (groupTemplate) {\n <ng-container *ngTemplateOutlet=\"groupTemplate; context: { $implicit: option }\"> </ng-container>\n } @else {\n <b>{{ option.label }}</b>\n }\n </li>\n @for (optChild of option.options; track optChild; let j = $index) {\n <!-- template option -->\n <ng-container\n [ngTemplateOutlet]=\"item\"\n [ngTemplateOutletContext]=\"{ option: optChild, index: i + '-' + j }\"></ng-container>\n }\n </ul>\n } @else {\n <!-- template option -->\n <ng-container\n [ngTemplateOutlet]=\"item\"\n [ngTemplateOutletContext]=\"{ option: option, index: i }\"></ng-container>\n }\n }\n </ul>\n }\n <!-- Empty list after search filter -->\n <div\n role=\"status\"\n aria-live=\"polite\"\n ngProjectAs=\"empty\"\n class=\"dsfrx-multiselect__list dsfrx-multiselect__list--empty\">\n @if (loading) {\n <p>\n <span class=\"label\"> {{ 'multiselect.loading' | dsfrI18n }}...</span>\n </p>\n } @else if (displayDropdown && optionsFilter.length === 0) {\n <p>\n {{ noResultsMessage ?? ('multiselect.noResult' | dsfrI18n) }}\n </p>\n }\n </div>\n </edu-dropdown-container>\n <div id=\"{{ selectId }}-messages\" class=\"fr-messages-group\" aria-live=\"polite\">\n @if (message) {\n <p class=\"fr-message\" [eduMessageSeverity]=\"messageSeverity\">\n {{ message }}\n </p>\n }\n </div>\n</div>\n\n<!-- template option -->\n<ng-template #item let-option=\"option\" let-index=\"index\">\n <li\n role=\"option\"\n class=\"dsfrx-multiselect__item\"\n [attr.data-dsfrx-option]=\"true\"\n id=\"{{ selectId }}-listbox-{{ index }}\"\n [ngClass]=\"{ selected: isSelected(option) }\"\n [attr.data-id]=\"option.value\"\n [attr.data-index]=\"index\"\n [attr.aria-selected]=\"this.isSingleMode ? isSelected(option) : null\"\n [attr.aria-checked]=\"this.isSingleMode ? null : isSelected(option)\"\n tabindex=\"-1\"\n (click)=\"onOptionClick($event, option)\"\n (keydown)=\"onOptionKeydown($event, option)\">\n <ng-container\n [ngTemplateOutlet]=\"insideItem\"\n [ngTemplateOutletContext]=\"{ option: option, index: index }\"></ng-container>\n </li>\n</ng-template>\n\n<!-- template inside option -->\n<ng-template #insideItem let-option=\"option\" let-index=\"index\">\n @if (showCheckboxes) {\n <dsfr-ext-checkbox\n [id]=\"selectId + '-checkbox-item-' + index\"\n [disabled]=\"option.disabled\"\n [tabIndex]=\"-1\"\n [icon]=\"option.icon\"\n [label]=\"optionTemplate ? undefined : option.label\"\n (checkboxClick)=\"onOptionClick($event, option)\"\n [selected]=\"isSelected(option)\">\n @if (optionTemplate) {\n <ng-container label\n ><ng-container *ngTemplateOutlet=\"optionTemplate; context: { $implicit: option }\"></ng-container\n ></ng-container>\n }\n </dsfr-ext-checkbox>\n } @else {\n @if (optionTemplate) {\n <!-- Slot -->\n <span><ng-container *ngTemplateOutlet=\"optionTemplate; context: { $implicit: option }\"></ng-container></span>\n } @else {\n <span>{{ option.label }} </span>\n }\n }\n</ng-template>\n", styles: [".dsfrx-multiselect{display:block;position:relative}.dsfrx-multiselect .fr-select{min-height:2.5rem;text-align:left;cursor:pointer;font-weight:400}.dsfrx-multiselect .fr-select:hover{background-color:var(--background-contrast-grey)}.dsfrx-multiselect .fr-select--disabled{pointer-events:none;color:var(--text-disabled-grey);box-shadow:inset 0 -2px 0 0 var(--border-disabled-grey);--data-uri-svg: url(\"data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' viewBox='0 0 24 24' ><path fill='%23929292' d='M12,13.1l5-4.9l1.4,1.4L12,15.9L5.6,9.5l1.4-1.4L12,13.1z'/></svg>\");background-image:var(--data-uri-svg)}.dsfrx-multiselect__toolbar{display:block;padding:.5rem .75rem;border-bottom:1px solid var(--background-contrast-grey)}.dsfrx-multiselect__toolbar input[type=search]::-webkit-search-cancel-button{display:none}.dsfrx-multiselect__toolbar .fr-input-wrap{flex-grow:1}.dsfrx-multiselect__toolbar .fr-btn--select-all{text-align:center;display:block;margin:0;min-width:100%;margin-bottom:.75rem}.dsfrx-multiselect__list{overflow:auto;list-style:none;background-color:var(--background-overlap-grey);margin:0;line-height:1.5rem;color:var(--text-title-grey);text-align:left;padding:0;max-height:224px}.dsfrx-multiselect__list--has-viewport{overflow:hidden}.dsfrx-multiselect__list .viewport{height:224px}.dsfrx-multiselect__list--empty p{padding:.5rem 1rem 0rem;background-color:var(--background-default-grey);margin:0}.dsfrx-multiselect--placeholder{color:var(--text-default-grey)}.fr-label+.dsfrx-multiselect{margin-top:.5rem}.dsfrx-multiselect .fr-tags-group{margin:0;gap:.5rem 0}.dsfrx-multiselect .fr-tags-group li{line-height:1rem}.dsfrx-multiselect .fr-tags-group .fr-tag{margin-bottom:0}dsfr-ext-multiselect .fr-select-group--error label span,dsfr-ext-multiselect .fr-select-group--valid label span,dsfrx-treeselect .fr-select-group--error label span,dsfrx-treeselect .fr-select-group--valid label span{color:var(--text-label-grey)}@container (min-width: 500px){.dsfrx-multiselect .dsfrx-multiselect__toolbar{display:flex;padding:.5rem;justify-content:space-between;align-items:center}.dsfrx-multiselect .dsfrx-multiselect__toolbar dsfr-button{width:initial}.dsfrx-multiselect .dsfrx-multiselect__toolbar .fr-btn--select-all{margin:0;min-width:10rem;margin-right:1rem}}.dsfrx-select>.fr-label{margin-bottom:.5rem}.dsfrx-multiselect__list .dsfrx-multiselect__group{list-style-type:none;margin:0;padding:0}.dsfrx-multiselect__list .dsfrx-multiselect__group--label{padding-left:.25rem}.dsfrx-multiselect__list .dsfrx-multiselect__item{line-height:1.5rem;padding:.25rem .5rem;outline-offset:0}.dsfrx-multiselect__list .dsfrx-multiselect__item:hover,.dsfrx-multiselect__list .dsfrx-multiselect__item:focus{background:var(--background-alt-blue-france-hover)}.dsfrx-multiselect__list .dsfrx-multiselect__item.selected:not(:has(dsfr-ext-checkbox)){background:var(--background-alt-blue-france-active);position:relative}.dsfrx-multiselect__list .dsfrx-multiselect__item.selected:not(:has(dsfr-ext-checkbox)):after{mask-image:url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\"><path d=\"m10 15.172 9.192-9.193 1.415 1.414L10 18l-6.364-6.364 1.414-1.414 4.95 4.95Z\"/></svg>');content:\"\";--icon-size: 1rem;height:var(--icon-size);mask-size:100% 100%;width:var(--icon-size);background-color:var(--text-action-high-blue-france);right:1rem;position:absolute;top:25%}.dsfrx-multiselect__list .viewport ul{list-style-type:none;padding:0;margin:0}.dsfrx-select.fr-select-group--disabled{cursor:not-allowed}.dsfrx-select.fr-select-group--disabled .fr-label{color:var(--text-disabled-grey)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i3.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i3.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i3.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: DropdownContainerComponent, selector: "edu-dropdown-container", inputs: ["virtualScroll", "virtualScrollItemHeight", "appendTo", "hasOptions", "zIndex", "scrollHeight", "classToWrap", "hasToolBar", "indexFirstSelected", "autofocus", "autoWidthDropdown", "displayDropdown"], outputs: ["hideList", "focusOnList", "dropdownAttached"] }, { kind: "ngmodule", type: DsfrTagModule }, { kind: "component", type: EduCheckboxComponent, selector: "dsfr-ext-checkbox, dsfrx-checkbox", inputs: ["id", "label", "selected", "tabIndex", "disabled", "icon"], outputs: ["checkboxClick"] }, { kind: "ngmodule", type: DsfrButtonModule }, { kind: "component", type: i4.DsfrButtonComponent, selector: "dsfr-button", inputs: ["label", "type", "tooltipMessage", "variant", "buttonSize", "icon", "iconPosition", "disabled", "uppercase", "loader", "ariaLabel", "invertedOutlineContrast", "id", "buttonId", "ariaControls", "ariaPressed", "ariaHasPopup", "ariaExpanded", "tabIndex", "customClass", "buttonRole", "labelSrOnly", "size"] }, { kind: "pipe", type: DsfrI18nPipe, name: "dsfrI18n" }, { kind: "component", type: SelectInputLabelComponent, selector: "edu-select-input-label", inputs: ["placeHolder", "maxDisplayOptions", "maxDisplayOptionsTemplate", "selectedOptionTemplate", "clearableOptions", "selectedOptions"], outputs: ["clearAllOptionsSelect", "clearOptionSelect"] }, { kind: "directive", type: EduMessageSeverityDirective, selector: "[eduMessageSeverity]", inputs: ["eduMessageSeverity"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: DsfrMultiselectComponent, decorators: [{
type: Component,
args: [{ selector: 'dsfr-ext-multiselect, dsfrx-multiselect,', encapsulation: ViewEncapsulation.None, standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
CommonModule,
FormsModule,
ScrollingModule,
ReactiveFormsModule,
DropdownContainerComponent,
DsfrTagModule,
EduCheckboxComponent,
DsfrButtonModule,
DsfrI18nPipe,
SelectInputLabelComponent,
EduMessageSeverityDirective,
], providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => DsfrMultiselectComponent), multi: true }], template: "<div\n class=\"fr-select-group dsfrx-select\"\n [class]=\"message ? 'fr-select-group--' + messageSeverity : ''\"\n [ngClass]=\"{\n 'fr-select-group--disabled': disabled,\n 'fr-select-group--valid': message && messageSeverity === severityConst.SUCCESS,\n }\">\n <div class=\"fr-label\" [ngClass]=\"{ 'fr-sr-only': labelSrOnly }\" id=\"{{ selectId }}-label\">\n @if (label) {\n {{ label }}\n } @else {\n <ng-content select=\"[label]\"></ng-content>\n }\n @if (hint) {\n <span class=\"fr-hint-text\">{{ hint }}</span>\n }\n </div>\n\n <edu-dropdown-container\n [displayDropdown]=\"displayDropdown\"\n [appendTo]=\"appendTo\"\n [classToWrap]=\"'dsfrx-multiselect'\"\n [autoWidthDropdown]=\"autoWidthDropdown\"\n [autofocus]=\"true\"\n [hasToolBar]=\"showSearch || showSelectAll\"\n [zIndex]=\"zIndex\"\n [scrollHeight]=\"scrollHeight\"\n [indexFirstSelected]=\"indexFirstSelected\"\n (focusOnList)=\"handleListFocus($event)\"\n (hideList)=\"hide()\"\n [id]=\"selectId\"\n [virtualScroll]=\"virtualScroll\"\n [hasOptions]=\"optionsFilter.length > 0\">\n <!-- Control input -->\n <div\n ngProjectAs=\"control\"\n ngDefaultControl\n [(ngModel)]=\"value\"\n id=\"{{ selectId }}\"\n [attr.tabindex]=\"disabled ? -1 : 0\"\n role=\"combobox\"\n (click)=\"toggleDropdown($event)\"\n (keydown)=\"onButtonKeyDown($event)\"\n aria-haspopup=\"listbox\"\n [ngClass]=\"{ 'fr-select--disabled': disabled }\"\n [disabled]=\"disabled\"\n [attr.aria-controls]=\"selectId + '-listbox'\"\n [attr.aria-expanded]=\"displayDropdown\"\n [attr.aria-describedby]=\"message ? selectId + '-messages' : null\"\n [attr.aria-labelledby]=\"selectId + '-label'\"\n [attr.aria-required]=\"ariaRequired || null\"\n [attr.aria-invalid]=\"ariaInvalid || null\"\n class=\"fr-select\">\n <edu-select-input-label\n [placeHolder]=\"placeHolder\"\n [clearableOptions]=\"clearableOptions\"\n [maxDisplayOptions]=\"maxDisplayOptions\"\n [selectedOptionTemplate]=\"selectedOptionTemplate\"\n [selectedOptions]=\"selectedOptions\"\n [maxDisplayOptionsTemplate]=\"maxDisplayOptionsTemplate\"\n (clearOptionSelect)=\"clearOption($event)\"\n (clearAllOptionsSelect)=\"clearAllOptions()\">\n </edu-select-input-label>\n </div>\n\n <!-- If toolbar is shown -->\n <ng-container toolbar>\n @if (showSelectAll && !maxSelectedOptions) {\n <dsfr-button\n size=\"SM\"\n variant=\"tertiary\"\n customClass=\"fr-btn--select-all\"\n (click)=\"selectAll()\"\n [label]=\"hasisAllSelected() ? ('multiselect.unselectAll' | dsfrI18n) : ('multiselect.selectAll' | dsfrI18n)\">\n </dsfr-button>\n }\n\n @if (showSearch) {\n <label for=\"{{ selectId }}-searchbox\" class=\"fr-sr-only\">{{ 'multiselect.searchList' | dsfrI18n }}</label>\n <div\n class=\"fr-input-wrap\"\n [class.fr-icon-search-line]=\"!showClearSearch\"\n [class.fr-input-wrap--action]=\"showClearSearch\">\n <input\n #searchBox\n class=\"fr-input\"\n type=\"search\"\n aria-invalid=\"false\"\n id=\"{{ selectId }}-searchbox\"\n role=\"combobox\"\n [attr.aria-label]=\"searchMessage ?? ('multiselect.search' | dsfrI18n)\"\n [placeholder]=\"searchMessage ?? ('multiselect.search' | dsfrI18n)\"\n (input)=\"onSearch()\"\n aria-controls=\"listboxid\"\n aria-expanded=\"true\"\n aria-autocomplete=\"list\"\n autocomplete=\"off\"\n aria-haspopup=\"listbox\"\n [(ngModel)]=\"searchText\" />\n @if (showClearSearch) {\n <button\n type=\"button\"\n class=\"fr-btn fr-icon-close-line fr-btn--tertiary\"\n id=\"{{ selectId }}-reset\"\n (click)=\"resetSearchInput()\"\n [title]=\"'multiselect.resetSearch' | dsfrI18n\">\n {{ 'multiselect.resetSearch' | dsfrI18n }}\n </button>\n }\n </div>\n }\n <div role=\"status\" aria-live=\"polite\" class=\"fr-sr-only\">\n @if (nbFilterResults() > 0) {\n <p>{{ nbFilterResults() }} {{ 'multiselect.availableResults' | dsfrI18n }}</p>\n }\n </div>\n </ng-container>\n\n <!-- template de listbox (options) en virtual scroll -->\n @if (virtualScroll) {\n <div\n [ngStyle]=\"{ 'z-index': zIndex }\"\n [style.max-height]=\"scrollHeight\"\n #listbox\n class=\"dsfrx-multiselect__list dsfrx-multiselect__list--has-viewport\">\n <cdk-virtual-scroll-viewport\n #viewport\n minBufferPx=\"200\"\n maxBufferPx=\"600\"\n [itemSize]=\"virtualScrollItemHeight\"\n class=\"viewport\">\n @if (optionsFilter.length > 0) {\n <ul\n role=\"listbox\"\n [attr.aria-multiselectable]=\"!this.isSingleMode\"\n tabindex=\"-1\"\n id=\"{{ selectId }}-listbox\">\n <li\n *cdkVirtualFor=\"let option of optionsFilter; let i = index\"\n role=\"option\"\n class=\"dsfrx-multiselect__item\"\n [attr.data-dsfrx-option]=\"true\"\n id=\"{{ selectId }}-listbox-{{ i }}\"\n [ngClass]=\"{ selected: isSelected(option) }\"\n [attr.data-id]=\"option.value\"\n [attr.data-index]=\"i\"\n [attr.aria-selected]=\"this.isSingleMode ? isSelected(option) : null\"\n [attr.aria-checked]=\"this.isSingleMode ? null : isSelected(option)\"\n tabindex=\"-1\"\n (click)=\"onOptionClick($event, option)\"\n (keydown)=\"onOptionKeydown($event, option)\">\n <ng-container\n [ngTemplateOutlet]=\"insideItem\"\n [ngTemplateOutletContext]=\"{ option: option, index: i }\"></ng-container>\n </li>\n </ul>\n }\n </cdk-virtual-scroll-viewport>\n </div>\n } @else {\n <!-- template de listbox (options) sans virtual scroll -->\n <ul\n #listbox\n id=\"{{ selectId }}-listbox\"\n [ngStyle]=\"{ 'z-index': zIndex }\"\n role=\"listbox\"\n tabindex=\"-1\"\n [style.max-height]=\"scrollHeight\"\n [attr.aria-multiselectable]=\"!this.isSingleMode\"\n [class]=\"containerListClass\">\n @for (option of optionsFilter; track option; let i = $index) {\n @if (option.options) {\n <ul class=\"dsfrx-multiselect__group\" role=\"group\" [attr.aria-labelledby]=\"selectId + '-group-' + i\">\n <li id=\"{{ selectId }}-group-{{ i }}\" role=\"presentation\" class=\"dsfrx-multiselect__group--label\">\n @if (groupTemplate) {\n <ng-container *ngTemplateOutlet=\"groupTemplate; context: { $implicit: option }\"> </ng-container>\n } @else {\n <b>{{ option.label }}</b>\n }\n </li>\n @for (optChild of option.options; track optChild; let j = $index) {\n <!-- template option -->\n <ng-container\n [ngTemplateOutlet]=\"item\"\n [ngTemplateOutletContext]=\"{ option: optChild, index: i + '-' + j }\"></ng-container>\n }\n </ul>\n } @else {\n <!-- template option -->\n <ng-container\n [ngTemplateOutlet]=\"item\"\n [ngTemplateOutletContext]=\"{ option: option, index: i }\"></ng-container>\n }\n }\n </ul>\n }\n <!-- Empty list after search filter -->\n <div\n role=\"status\"\n aria-live=\"polite\"\n ngProjectAs=\"empty\"\n class=\"dsfrx-multiselect__list dsfrx-multiselect__list--empty\">\n @if (loading) {\n <p>\n <span class=\"label\"> {{ 'multiselect.loading' | dsfrI18n }}...</span>\n </p>\n } @else if (displayDropdown && optionsFilter.length === 0) {\n <p>\n {{ noResultsMessage ?? ('multiselect.noResult' | dsfrI18n) }}\n </p>\n }\n </div>\n </edu-dropdown-container>\n <div id=\"{{ selectId }}-messages\" class=\"fr-messages-group\" aria-live=\"polite\">\n @if (message) {\n <p class=\"fr-message\" [eduMessageSeverity]=\"messageSeverity\">\n {{ message }}\n </p>\n }\n </div>\n</div>\n\n<!-- template option -->\n<ng-template #item let-option=\"option\" let-index=\"index\">\n <li\n role=\"option\"\n class=\"dsfrx-multiselect__item\"\n [attr.data-dsfrx-option]=\"true\"\n id=\"{{ selectId }}-listbox-{{ index }}\"\n [ngClass]=\"{ selected: isSelected(option) }\"\n [attr.data-id]=\"option.value\"\n [attr.data-index]=\"index\"\n [attr.aria-selected]=\"this.isSingleMode ? isSelected(option) : null\"\n [attr.aria-checked]=\"this.isSingleMode ? null : isSelected(option)\"\n tabindex=\"-1\"\n (click)=\"onOptionClick($event, option)\"\n (keydown)=\"onOptionKeydown($event, option)\">\n <ng-container\n [ngTemplateOutlet]=\"insideItem\"\n [ngTemplateOutletContext]=\"{ option: option, index: index }\"></ng-container>\n </li>\n</ng-template>\n\n<!-- template inside option -->\n<ng-template #insideItem let-option=\"option\" let-index=\"index\">\n @if (showCheckboxes) {\n <dsfr-ext-checkbox\n [id]=\"selectId + '-checkbox-item-' + index\"\n [disabled]=\"option.disabled\"\n [tabIndex]=\"-1\"\n [icon]=\"option.icon\"\n [label]=\"optionTemplate ? undefined : option.label\"\n (checkboxClick)=\"onOptionClick($event, option)\"\n [selected]=\"isSelected(option)\">\n @if (optionTemplate) {\n <ng-container label\n ><ng-container *ngTemplateOutlet=\"optionTemplate; context: { $implicit: option }\"></ng-container\n ></ng-container>\n }\n </dsfr-ext-checkbox>\n } @else {\n @if (optionTemplate) {\n <!-- Slot -->\n <span><ng-container *ngTemplateOutlet=\"optionTemplate; context: { $implicit: option }\"></ng-container></span>\n } @else {\n <span>{{ option.label }} </span>\n }\n }\n</ng-template>\n", styles: [".dsfrx-multiselect{display:block;position:relative}.dsfrx-multiselect .fr-select{min-height:2.5rem;text-align:left;cursor:pointer;font-weight:400}.dsfrx-multiselect .fr-select:hover{background-color:var(--background-contrast-grey)}.dsfrx-multiselect .fr-select--disabled{pointer-events:none;color:var(--text-disabled-grey);box-shadow:inset 0 -2px 0 0 var(--border-disabled-grey);--data-uri-svg: url(\"data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' viewBox='0 0 24 24' ><path fill='%23929292' d='M12,13.1l5-4.9l1.4,1.4L12,15.9L5.6,9.5l1.4-1.4L12,13.1z'/></svg>\");background-image:var(--data-uri-svg)}.dsfrx-multiselect__toolbar{display:block;padding:.5rem .75rem;border-bottom:1px solid var(--background-contrast-grey)}.dsfrx-multiselect__toolbar input[type=search]::-webkit-search-cancel-button{display:none}.dsfrx-multiselect__toolbar .fr-input-wrap{flex-grow:1}.dsfrx-multiselect__toolbar .fr-btn--select-all{text-align:center;display:block;margin:0;min-width:100%;margin-bottom:.75rem}.dsfrx-multiselect__list{overflow:auto;list-style:none;background-color:var(--background-overlap-grey);margin:0;line-height:1.5rem;color:var(--text-title-grey);text-align:left;padding:0;max-height:224px}.dsfrx-multiselect__list--has-viewport{overflow:hidden}.dsfrx-multiselect__list .viewport{height:224px}.dsfrx-multiselect__list--empty p{padding:.5rem 1rem 0rem;background-color:var(--background-default-grey);margin:0}.dsfrx-multiselect--placeholder{color:var(--text-default-grey)}.fr-label+.dsfrx-multiselect{margin-top:.5rem}.dsfrx-multiselect .fr-tags-group{margin:0;gap:.5rem 0}.dsfrx-multiselect .fr-tags-group li{line-height:1rem}.dsfrx-multiselect .fr-tags-group .fr-tag{margin-bottom:0}dsfr-ext-multiselect .fr-select-group--error label span,dsfr-ext-multiselect .fr-select-group--valid label span,dsfrx-treeselect .fr-select-group--error label span,dsfrx-treeselect .fr-select-group--valid label span{color:var(--text-label-grey)}@container (min-width: 500px){.dsfrx-multiselect .dsfrx-multiselect__toolbar{display:flex;padding:.5rem;justify-content:space-between;align-items:center}.dsfrx-multiselect .dsfrx-multiselect__toolbar dsfr-button{width:initial}.dsfrx-multiselect .dsfrx-multiselect__toolbar .fr-btn--select-all{margin:0;min-width:10rem;margin-right:1rem}}.dsfrx-select>.fr-label{margin-bottom:.5rem}.dsfrx-multiselect__list .dsfrx-multiselect__group{list-style-type:none;margin:0;padding:0}.dsfrx-multiselect__list .dsfrx-multiselect__group--label{padding-left:.25rem}.dsfrx-multiselect__list .dsfrx-multiselect__item{line-height:1.5rem;padding:.25rem .5rem;outline-offset:0}.dsfrx-multiselect__list .dsfrx-multiselect__item:hover,.dsfrx-multiselect__list .dsfrx-multiselect__item:focus{background:var(--background-alt-blue-france-hover)}.dsfrx-multiselect__list .dsfrx-multiselect__item.selected:not(:has(dsfr-ext-checkbox)){background:var(--background-alt-blue-france-active);position:relative}.dsfrx-multiselect__list .dsfrx-multiselect__item.selected:not(:has(dsfr-ext-checkbox)):after{mask-image:url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\"><path d=\"m10 15.172 9.192-9.193 1.415 1.414L10 18l-6.364-6.364 1.414-1.414 4.95 4.95Z\"/></svg>');content:\"\";--icon-size: 1rem;height:var(--icon-size);mask-size:100% 100%;width:var(--icon-size);background-color:var(--text-action-high-blue-france);right:1rem;position:absolute;top:25%}.dsfrx-multiselect__list .viewport ul{list-style-type:none;padding:0;margin:0}.dsfrx-select.fr-select-group--disabled{cursor:not-allowed}.dsfrx-select.fr-select-group--disabled .fr-label{color:var(--text-disabled-grey)}\n"] }]
}], propDecorators: { viewport: [{
type: ViewChild,
args: [CdkVirtualScrollViewport]
}], groupTemplate: [{
type: ContentChild,
args: ['groupTemplate', { static: true }]
}], searchFn: [{
type: Input
}], options: [{
type: Input
}] } });
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibXVsdGlzZWxlY3QuY29tcG9uZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vcHJvamVjdHMvbmd4LWRzZnItZXh0L3NyYy9saWIvbXVsdGlzZWxlY3QvbXVsdGlzZWxlY3QuY29tcG9uZW50LnRzIiwiLi4vLi4vLi4vLi4vLi4vcHJvamVjdHMvbmd4LWRzZnItZXh0L3NyYy9saWIvbXVsdGlzZWxlY3QvbXVsdGlzZWxlY3QuY29tcG9uZW50Lmh0bWwiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLHdCQUF3QixFQUFFLGVBQWUsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQ25GLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUMvQyxPQUFPLEVBQ0wsdUJBQXVCLEVBQ3ZCLFNBQVMsRUFDVCxZQUFZLEVBQ1osS0FBSyxFQUdMLFNBQVMsRUFDVCxpQkFBaUIsRUFDakIsVUFBVSxFQUNWLE1BQU0sR0FDUCxNQUFNLGVBQWUsQ0FBQztBQUN2QixPQUFPLEVBQUUsV0FBVyxFQUFFLGlCQUFpQixFQUFFLG1CQUFtQixFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFDckYsT0FBTyxFQUFFLGdCQUFnQixFQUFFLFlBQVksRUFBZ0MsYUFBYSxFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFDbEgsT0FBTyxFQUFFLHVCQUF1QixFQUFFLDBCQUEwQixFQUFFLG9CQUFvQixFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDakgsT0FBTyxFQUFFLHlCQUF5QixFQUFFLE1BQU0sc0VBQXNFLENBQUM7QUFDakgsT0FBTyxFQUFFLDJCQUEyQixFQUFFLE1BQU0saURBQWlELENBQUM7QUFDOUYsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sc0JBQXNCLENBQUM7Ozs7OztBQTZCeEQsTUFBTSxPQUFPLHdCQUF5QixTQUFRLHVCQUF5QztJQXRCdkY7O1FBNkJFLHlDQUF5QztRQUN6QixrQkFBYSxHQUF1QixF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