UNPKG

@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
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() }}&nbsp;{{ '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() }}&nbsp;{{ '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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