@seniorsistemas/angular-components 19.9.8 → 19.9.10
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/card/lib/card/card.component.d.ts +6 -2
- package/country-phone-picker/lib/country-phone-picker/country-phone-picker.component.d.ts +0 -1
- package/datepicker/README.md +6 -2
- package/datepicker/lib/datepicker/date-segment-input.d.ts +7 -1
- package/datepicker/lib/datepicker/datepicker.component.d.ts +137 -3
- package/dialog/lib/src/dialog/services/dialog.service.d.ts +2 -0
- package/esm2022/accordion/lib/accordion/components/accordion-panel/accordion-panel.component.mjs +3 -3
- package/esm2022/autocomplete/lib/autocomplete/autocomplete.component.mjs +8 -3
- package/esm2022/autocomplete/lib/autocomplete/components/autocomplete-chip/autocomplete-chip.component.mjs +4 -3
- package/esm2022/breadcrumb/lib/breadcrumb/breadcrumb.component.mjs +3 -3
- package/esm2022/breadcrumb/lib/dynamic-breadcrumb/dynamic-breadcrumb.component.mjs +4 -2
- package/esm2022/button/lib/button/button.component.mjs +3 -3
- package/esm2022/card/lib/card/card.component.mjs +13 -5
- package/esm2022/checkbox/lib/checkbox/checkbox.component.mjs +3 -3
- package/esm2022/checkbox-list/lib/checkbox-list/checkbox-list.component.mjs +3 -3
- package/esm2022/chips/lib/chips/chip-item/chip-item.component.mjs +3 -3
- package/esm2022/chips/lib/chips/chips/chips.component.mjs +3 -3
- package/esm2022/code-editor/lib/code-editor/infra/cores/codemirror-6/themes/sds-light/sds-light-style.mjs +3 -2
- package/esm2022/collapse-link/lib/collapse-link/collapse-link.component.mjs +3 -3
- package/esm2022/country-phone-picker/lib/country-phone-picker/country-phone-picker.component.mjs +14 -9
- package/esm2022/datepicker/lib/datepicker/date-segment-input.mjs +19 -2
- package/esm2022/datepicker/lib/datepicker/datepicker.component.mjs +405 -38
- package/esm2022/dialog/lib/src/dialog/dialog.component.mjs +3 -3
- package/esm2022/dialog/lib/src/dialog/services/dialog.service.mjs +13 -1
- package/esm2022/drawer/lib/drawer/drawer.component.mjs +3 -3
- package/esm2022/dynamic-form/dynamic-form/components/lookup/lookup.component.mjs +3 -3
- package/esm2022/dynamic-form/dynamic-form/form-field/fields/bignumber/bignumber-field.component.mjs +3 -3
- package/esm2022/dynamic-form/dynamic-form/form-field/fields/currency/currency-field.component.mjs +3 -3
- package/esm2022/dynamic-form/dynamic-form/form-field/fields/number/number-field.component.mjs +3 -3
- package/esm2022/dynamic-form/dynamic-form/form-field/fields/password/password-field.component.mjs +3 -3
- package/esm2022/dynamic-form/dynamic-form/form-field/fields/text/text-field.component.mjs +3 -3
- package/esm2022/editor/lib/editor/editor.component.mjs +3 -3
- package/esm2022/editor/lib/editor/toolbar-button/editor-toolbar-button.component.mjs +3 -3
- package/esm2022/fieldset/lib/fieldset/fieldset.component.mjs +3 -3
- package/esm2022/file-picker/lib/file-picker/file-picker.component.mjs +3 -3
- package/esm2022/gantt/lib/gantt/components/gantt/bar.mjs +2 -1
- package/esm2022/gantt/lib/gantt/gantt.component.mjs +3 -3
- package/esm2022/global-search/lib/global-search/global-search.component.mjs +16 -25
- package/esm2022/grid-menu/lib/grid-menu/components/grid-menu-item/grid-menu-item.component.mjs +3 -3
- package/esm2022/input-text/lib/input-text/input-text.directive.mjs +3 -3
- package/esm2022/lib/locale/fallback.mjs +47 -13
- package/esm2022/navigation-button/lib/navigation-button/navigation-button.component.mjs +3 -3
- package/esm2022/object-card/lib/object-card/object-card.component.mjs +3 -3
- package/esm2022/paginator/lib/paginator/paginator.component.mjs +3 -3
- package/esm2022/pin-code-field/lib/pin-code-field/pin-code-field.component.mjs +3 -3
- package/esm2022/radio-button/lib/radio-button/radio-button.component.mjs +4 -10
- package/esm2022/rating-scale/lib/rating-scale/rating-scale.component.mjs +3 -3
- package/esm2022/select/lib/select/components/select-option/select-option.component.mjs +31 -3
- package/esm2022/select/lib/select/select.component.mjs +240 -23
- package/esm2022/select-button/lib/select-button/components/select-button-item/select-button-item.component.mjs +3 -3
- package/esm2022/shared/lib/accessibility/datepicker-no-period-tab.directive.mjs +6 -6
- package/esm2022/side-sheet/lib/side-sheet/side-sheet.component.mjs +3 -3
- package/esm2022/slider/lib/slider/slider.component.mjs +67 -65
- package/esm2022/slider/lib/slider/slider.module.mjs +2 -2
- package/esm2022/split-button/lib/split-button/split-button.component.mjs +3 -3
- package/esm2022/star-rating/star-rating/star-rating.component.mjs +3 -3
- package/esm2022/stepper/lib/stepper/stepper.component.mjs +3 -3
- package/esm2022/switch/lib/switch/switch.component.mjs +5 -19
- package/esm2022/tabs/lib/tabs/tabs.component.mjs +3 -3
- package/esm2022/text-area/lib/text-area/text-area.component.mjs +3 -3
- package/esm2022/thumbnail/lib/thumbnail/thumbnail.component.mjs +10 -3
- package/esm2022/thumbnails/lib/thumbnails/components/thumbnail-item/thumbnail-item.component.mjs +3 -3
- package/esm2022/tiered-menu/lib/tiered-menu/components/tiered-menu-item/tiered-menu-item.component.mjs +3 -3
- package/esm2022/tiered-menu/lib/tiered-menu/services/tiered-menu.global.service.mjs +4 -3
- package/esm2022/tile/lib/tile/tile.component.mjs +3 -3
- package/esm2022/tree/lib/tree/components/tree-node-item/tree-node-item.component.mjs +20 -3
- package/esm2022/workspace-switch/lib/workspace-switch/workspace-switch.component.mjs +3 -3
- package/fesm2022/seniorsistemas-angular-components-accordion.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-accordion.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-autocomplete.mjs +10 -5
- package/fesm2022/seniorsistemas-angular-components-autocomplete.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-breadcrumb.mjs +5 -3
- package/fesm2022/seniorsistemas-angular-components-breadcrumb.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-button.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-button.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-card.mjs +12 -4
- package/fesm2022/seniorsistemas-angular-components-card.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-checkbox-list.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-checkbox-list.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-checkbox.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-checkbox.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-chips.mjs +4 -4
- package/fesm2022/seniorsistemas-angular-components-chips.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-code-editor.mjs +2 -1
- package/fesm2022/seniorsistemas-angular-components-code-editor.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-collapse-link.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-collapse-link.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-country-phone-picker.mjs +13 -8
- package/fesm2022/seniorsistemas-angular-components-country-phone-picker.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-datepicker.mjs +422 -38
- package/fesm2022/seniorsistemas-angular-components-datepicker.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-dialog.mjs +14 -2
- package/fesm2022/seniorsistemas-angular-components-dialog.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-drawer.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-drawer.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-dynamic-form.mjs +12 -12
- package/fesm2022/seniorsistemas-angular-components-dynamic-form.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-editor.mjs +4 -4
- package/fesm2022/seniorsistemas-angular-components-editor.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-fieldset.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-fieldset.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-file-picker.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-file-picker.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-gantt.mjs +3 -2
- package/fesm2022/seniorsistemas-angular-components-gantt.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-global-search.mjs +14 -23
- package/fesm2022/seniorsistemas-angular-components-global-search.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-grid-menu.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-grid-menu.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-input-text.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-input-text.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-navigation-button.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-navigation-button.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-object-card.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-object-card.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-paginator.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-paginator.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-pin-code-field.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-pin-code-field.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-radio-button.mjs +3 -9
- package/fesm2022/seniorsistemas-angular-components-radio-button.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-rating-scale.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-rating-scale.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-select-button.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-select-button.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-select.mjs +269 -24
- package/fesm2022/seniorsistemas-angular-components-select.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-shared.mjs +5 -5
- package/fesm2022/seniorsistemas-angular-components-shared.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-side-sheet.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-side-sheet.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-slider.mjs +67 -65
- package/fesm2022/seniorsistemas-angular-components-slider.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-split-button.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-split-button.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-star-rating.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-star-rating.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-stepper.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-stepper.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-switch.mjs +4 -18
- package/fesm2022/seniorsistemas-angular-components-switch.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-tabs.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-tabs.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-text-area.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-text-area.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-thumbnail.mjs +9 -2
- package/fesm2022/seniorsistemas-angular-components-thumbnail.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-thumbnails.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-thumbnails.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-tiered-menu.mjs +5 -4
- package/fesm2022/seniorsistemas-angular-components-tiered-menu.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-tile.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-tile.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-tree.mjs +20 -3
- package/fesm2022/seniorsistemas-angular-components-tree.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-workspace-switch.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-workspace-switch.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components.mjs +46 -12
- package/fesm2022/seniorsistemas-angular-components.mjs.map +1 -1
- package/global-search/lib/global-search/global-search.component.d.ts +7 -3
- package/package.json +1 -1
- package/radio-button/lib/radio-button/radio-button.component.d.ts +0 -1
- package/select/README.md +16 -3
- package/select/lib/select/components/select-option/select-option.component.d.ts +23 -1
- package/select/lib/select/select.component.d.ts +111 -17
- package/slider/lib/slider/slider.component.d.ts +25 -7
- package/styles.css +5 -0
- package/switch/lib/switch/switch.component.d.ts +0 -2
- package/thumbnail/lib/thumbnail/thumbnail.component.d.ts +1 -0
- package/tree/lib/tree/components/tree-node-item/tree-node-item.component.d.ts +4 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { LiveAnnouncer } from '@angular/cdk/a11y';
|
|
1
2
|
import * as i4 from '@angular/cdk/overlay';
|
|
2
3
|
import { OverlayModule } from '@angular/cdk/overlay';
|
|
3
4
|
import * as i2 from '@angular/cdk/scrolling';
|
|
4
5
|
import { ScrollingModule } from '@angular/cdk/scrolling';
|
|
5
6
|
import * as i0 from '@angular/core';
|
|
6
|
-
import { input, model, output, Component, signal, computed, viewChild,
|
|
7
|
+
import { input, model, output, Component, signal, computed, viewChild, ElementRef, inject, Renderer2, Injector, effect, afterNextRender, forwardRef } from '@angular/core';
|
|
7
8
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
8
9
|
import * as i1 from '@angular/forms';
|
|
9
10
|
import { FormGroup, FormControl, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms';
|
|
@@ -23,6 +24,28 @@ class SelectOptionComponent {
|
|
|
23
24
|
isSelected = model.required();
|
|
24
25
|
/** @description Classe do ícone de checkmark exibido quando a opção está selecionada. @default 'far fa-check' */
|
|
25
26
|
checkIconClass = input('far fa-check');
|
|
27
|
+
/**
|
|
28
|
+
* @description Texto anunciado via `aria-describedby` informando a que grupo esta opção
|
|
29
|
+
* pertence. O agrupador visual tem `role="presentation"` (silencioso pro leitor de tela), e
|
|
30
|
+
* `select.component.ts` só passa este input na primeira opção de cada grupo, pra não repetir
|
|
31
|
+
* o anúncio em toda opção subsequente.
|
|
32
|
+
* @default null
|
|
33
|
+
*/
|
|
34
|
+
groupAnnouncement = input(null);
|
|
35
|
+
/**
|
|
36
|
+
* @description Posição (1-based) desta opção dentro do conjunto navegável, exposta via
|
|
37
|
+
* `aria-posinset`. Necessário com `virtualScroll`, já que só uma janela de opções existe de
|
|
38
|
+
* fato no DOM a cada momento — sem isso, o leitor de tela tenta inferir a posição contando
|
|
39
|
+
* os `role="option"` irmãos presentes no DOM, o que fica errado numa lista virtualizada.
|
|
40
|
+
* @default null
|
|
41
|
+
*/
|
|
42
|
+
posinset = input(null);
|
|
43
|
+
/**
|
|
44
|
+
* @description Tamanho total do conjunto navegável ao qual esta opção pertence, exposto via
|
|
45
|
+
* `aria-setsize`. Ver `posinset`.
|
|
46
|
+
* @default null
|
|
47
|
+
*/
|
|
48
|
+
setsize = input(null);
|
|
26
49
|
selected = output();
|
|
27
50
|
onClick() {
|
|
28
51
|
if (this.isGrouper()) {
|
|
@@ -31,11 +54,17 @@ class SelectOptionComponent {
|
|
|
31
54
|
this.selected.emit();
|
|
32
55
|
}
|
|
33
56
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SelectOptionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
34
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SelectOptionComponent, isStandalone: true, selector: "s-select-option", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: true, transformFunction: null }, checkmark: { classPropertyName: "checkmark", publicName: "checkmark", isSignal: true, isRequired: true, transformFunction: null }, isGrouper: { classPropertyName: "isGrouper", publicName: "isGrouper", isSignal: true, isRequired: true, transformFunction: null }, isFocused: { classPropertyName: "isFocused", publicName: "isFocused", isSignal: true, isRequired: true, transformFunction: null }, isSelected: { classPropertyName: "isSelected", publicName: "isSelected", isSignal: true, isRequired: true, transformFunction: null }, checkIconClass: { classPropertyName: "checkIconClass", publicName: "checkIconClass", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { isSelected: "isSelectedChange", selected: "selected" }, ngImport: i0, template: "<div\n [attr.id]=\"id()\"\n [attr.role]=\"isGrouper() ? 'presentation' : 'option'\"\n [attr.aria-selected]=\"isGrouper() ? null : isSelected()\"\n class=\"flex select-none items-center gap-3 px-3 py-2\"\n [class.hover:bg-grayscale-10]=\"!isGrouper()\"\n [class.bg-grayscale-10]=\"!isGrouper() && isFocused()\"\n (click)=\"onClick()\"\n (keydown.enter)=\"onClick()\"\n (keydown.space)=\"onClick(); $event.preventDefault()\"\n>\n @if (!isGrouper() && checkmark() && !multiple() && isSelected()) {\n <i\n class=\"text-grayscale-60\"\n [class]=\"checkIconClass()\"\n aria-hidden=\"true\"\n ></i>\n }\n\n @if (!isGrouper() && multiple()) {\n <s-checkbox
|
|
57
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SelectOptionComponent, isStandalone: true, selector: "s-select-option", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: true, transformFunction: null }, checkmark: { classPropertyName: "checkmark", publicName: "checkmark", isSignal: true, isRequired: true, transformFunction: null }, isGrouper: { classPropertyName: "isGrouper", publicName: "isGrouper", isSignal: true, isRequired: true, transformFunction: null }, isFocused: { classPropertyName: "isFocused", publicName: "isFocused", isSignal: true, isRequired: true, transformFunction: null }, isSelected: { classPropertyName: "isSelected", publicName: "isSelected", isSignal: true, isRequired: true, transformFunction: null }, checkIconClass: { classPropertyName: "checkIconClass", publicName: "checkIconClass", isSignal: true, isRequired: false, transformFunction: null }, groupAnnouncement: { classPropertyName: "groupAnnouncement", publicName: "groupAnnouncement", isSignal: true, isRequired: false, transformFunction: null }, posinset: { classPropertyName: "posinset", publicName: "posinset", isSignal: true, isRequired: false, transformFunction: null }, setsize: { classPropertyName: "setsize", publicName: "setsize", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { isSelected: "isSelectedChange", selected: "selected" }, host: { attributes: { "role": "presentation" } }, ngImport: i0, template: "<div\n [attr.id]=\"id()\"\n [attr.role]=\"isGrouper() ? 'presentation' : 'option'\"\n [attr.aria-selected]=\"isGrouper() ? null : isSelected()\"\n [attr.aria-describedby]=\"groupAnnouncement() ? id() + '-group' : null\"\n [attr.aria-posinset]=\"isGrouper() ? null : posinset()\"\n [attr.aria-setsize]=\"isGrouper() ? null : setsize()\"\n class=\"flex select-none items-center gap-3 px-3 py-2\"\n [class.hover:bg-grayscale-10]=\"!isGrouper()\"\n [class.bg-grayscale-10]=\"!isGrouper() && isFocused()\"\n (click)=\"onClick()\"\n (keydown.enter)=\"onClick()\"\n (keydown.space)=\"onClick(); $event.preventDefault()\"\n>\n @if (!isGrouper() && checkmark() && !multiple() && isSelected()) {\n <i\n class=\"text-grayscale-60\"\n [class]=\"checkIconClass()\"\n aria-hidden=\"true\"\n ></i>\n }\n\n @if (!isGrouper() && multiple()) {\n <!-- aria-hidden: puramente visual \u2014 o clique borbulha pro (click) do pr\u00F3prio option acima,\n e o estado de sele\u00E7\u00E3o j\u00E1 \u00E9 comunicado pelo aria-selected do option. Sem isso, esse\n checkbox aninhado (sem aria-label, com seu pr\u00F3prio aria-checked independente) era um\n segundo objeto de acessibilidade \"concorrente\" anunciando o mesmo estado \u2014 reportado\n via NVDA: o leitor de tela anunciava esse checkbox (sem nome/posi\u00E7\u00E3o pr\u00F3pria) antes\n da op\u00E7\u00E3o correta ser anunciada. -->\n <s-checkbox\n [checked]=\"isSelected()\"\n aria-hidden=\"true\"\n ></s-checkbox>\n }\n <span\n [class]=\"isSelected() ? 'text-[#428BCA]' : 'text-grayscale-90'\"\n [class.font-bold]=\"isGrouper()\"\n >\n {{ label() }}\n </span>\n\n @if (groupAnnouncement()) {\n <!-- aria-hidden pra n\u00E3o entrar no nome acess\u00EDvel da op\u00E7\u00E3o (computado a partir do\n conte\u00FAdo de texto vis\u00EDvel) \u2014 o `aria-describedby` acima ainda consegue ler seu\n texto normalmente, j\u00E1 que essa leitura ignora deliberadamente o aria-hidden do\n elemento referenciado. Sem isso, o texto seria anunciado duas vezes (uma como\n parte do nome, outra como descri\u00E7\u00E3o). -->\n <span\n class=\"sr-only\"\n [id]=\"id() + '-group'\"\n aria-hidden=\"true\"\n >{{ groupAnnouncement() }}</span>\n }\n</div>\n\n", dependencies: [{ kind: "component", type: CheckboxComponent, selector: "s-checkbox", inputs: ["disabled", "checked", "indeterminate", "label", "ariaLabel", "ariaLabelledBy"], outputs: ["disabledChange", "checkedChange"] }] });
|
|
35
58
|
}
|
|
36
59
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SelectOptionComponent, decorators: [{
|
|
37
60
|
type: Component,
|
|
38
|
-
args: [{ selector: 's-select-option', standalone: true, imports: [CheckboxComponent],
|
|
61
|
+
args: [{ selector: 's-select-option', standalone: true, imports: [CheckboxComponent], host: {
|
|
62
|
+
// Sem role="presentation" este elemento custom pode quebrar, em determinadas
|
|
63
|
+
// combinações de navegador/AT, a relação ARIA pai/filho entre o role="listbox"
|
|
64
|
+
// e o role="option" do template interno (ver WAI-ARIA APG: wrappers entre um
|
|
65
|
+
// role pai e um role filho precisam de role="presentation"/"none").
|
|
66
|
+
role: 'presentation',
|
|
67
|
+
}, template: "<div\n [attr.id]=\"id()\"\n [attr.role]=\"isGrouper() ? 'presentation' : 'option'\"\n [attr.aria-selected]=\"isGrouper() ? null : isSelected()\"\n [attr.aria-describedby]=\"groupAnnouncement() ? id() + '-group' : null\"\n [attr.aria-posinset]=\"isGrouper() ? null : posinset()\"\n [attr.aria-setsize]=\"isGrouper() ? null : setsize()\"\n class=\"flex select-none items-center gap-3 px-3 py-2\"\n [class.hover:bg-grayscale-10]=\"!isGrouper()\"\n [class.bg-grayscale-10]=\"!isGrouper() && isFocused()\"\n (click)=\"onClick()\"\n (keydown.enter)=\"onClick()\"\n (keydown.space)=\"onClick(); $event.preventDefault()\"\n>\n @if (!isGrouper() && checkmark() && !multiple() && isSelected()) {\n <i\n class=\"text-grayscale-60\"\n [class]=\"checkIconClass()\"\n aria-hidden=\"true\"\n ></i>\n }\n\n @if (!isGrouper() && multiple()) {\n <!-- aria-hidden: puramente visual \u2014 o clique borbulha pro (click) do pr\u00F3prio option acima,\n e o estado de sele\u00E7\u00E3o j\u00E1 \u00E9 comunicado pelo aria-selected do option. Sem isso, esse\n checkbox aninhado (sem aria-label, com seu pr\u00F3prio aria-checked independente) era um\n segundo objeto de acessibilidade \"concorrente\" anunciando o mesmo estado \u2014 reportado\n via NVDA: o leitor de tela anunciava esse checkbox (sem nome/posi\u00E7\u00E3o pr\u00F3pria) antes\n da op\u00E7\u00E3o correta ser anunciada. -->\n <s-checkbox\n [checked]=\"isSelected()\"\n aria-hidden=\"true\"\n ></s-checkbox>\n }\n <span\n [class]=\"isSelected() ? 'text-[#428BCA]' : 'text-grayscale-90'\"\n [class.font-bold]=\"isGrouper()\"\n >\n {{ label() }}\n </span>\n\n @if (groupAnnouncement()) {\n <!-- aria-hidden pra n\u00E3o entrar no nome acess\u00EDvel da op\u00E7\u00E3o (computado a partir do\n conte\u00FAdo de texto vis\u00EDvel) \u2014 o `aria-describedby` acima ainda consegue ler seu\n texto normalmente, j\u00E1 que essa leitura ignora deliberadamente o aria-hidden do\n elemento referenciado. Sem isso, o texto seria anunciado duas vezes (uma como\n parte do nome, outra como descri\u00E7\u00E3o). -->\n <span\n class=\"sr-only\"\n [id]=\"id() + '-group'\"\n aria-hidden=\"true\"\n >{{ groupAnnouncement() }}</span>\n }\n</div>\n\n" }]
|
|
39
68
|
}] });
|
|
40
69
|
|
|
41
70
|
/**
|
|
@@ -123,6 +152,17 @@ class SelectComponent {
|
|
|
123
152
|
const ret = options.length > 0 && options.every((option) => selectedIds.includes(option.id));
|
|
124
153
|
return ret;
|
|
125
154
|
});
|
|
155
|
+
/**
|
|
156
|
+
* Label de acessibilidade do checkbox "Selecionar Todos", contextual ao estado atual —
|
|
157
|
+
* reportado: sem `ariaLabel` explícito, o leitor de tela só anunciava "checkbox, não
|
|
158
|
+
* marcado", sem indicar a ação (selecionar ou desmarcar todas as opções).
|
|
159
|
+
*/
|
|
160
|
+
selectAllCheckboxAriaLabel = computed(() => {
|
|
161
|
+
const key = this.allSelected()
|
|
162
|
+
? 'platform.angular_components.select_all_options_deselect_action'
|
|
163
|
+
: 'platform.angular_components.select_all_options_select_action';
|
|
164
|
+
return this._translateService.instant(key);
|
|
165
|
+
});
|
|
126
166
|
/**
|
|
127
167
|
* Retorna o item atualmente focado na lista de opções filtradas.
|
|
128
168
|
* @returns O dado do item focado ou `null` caso nenhum item esteja focado.
|
|
@@ -237,24 +277,86 @@ class SelectComponent {
|
|
|
237
277
|
return label.toLowerCase().includes(search);
|
|
238
278
|
});
|
|
239
279
|
});
|
|
280
|
+
/**
|
|
281
|
+
* Mapeia o id de cada opção que é a PRIMEIRA de um grupo (`group()` habilitado) ao texto
|
|
282
|
+
* anunciado via `aria-describedby` (ex.: "Agrupado por América do Sul"). O agrupador visual
|
|
283
|
+
* tem `role="presentation"` (silencioso pro leitor de tela) — reportado: ao navegar pra
|
|
284
|
+
* dentro de um grupo, nada indicava a que grupo a opção pertencia. Só a primeira opção de
|
|
285
|
+
* cada grupo recebe o anúncio, pra não repetir a cada opção subsequente do mesmo grupo.
|
|
286
|
+
*/
|
|
287
|
+
groupAnnouncements = computed(() => {
|
|
288
|
+
const announcements = new Map();
|
|
289
|
+
if (!this.group()) {
|
|
290
|
+
return announcements;
|
|
291
|
+
}
|
|
292
|
+
let pendingGroupLabel = null;
|
|
293
|
+
for (const option of this.filteredOptions()) {
|
|
294
|
+
if (option.grouper) {
|
|
295
|
+
pendingGroupLabel = this.getOptionLabel(option.data);
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
if (pendingGroupLabel) {
|
|
299
|
+
announcements.set(option.id, this._translateService.instant('platform.angular_components.select_grouped_by', {
|
|
300
|
+
group: pendingGroupLabel,
|
|
301
|
+
}));
|
|
302
|
+
pendingGroupLabel = null;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return announcements;
|
|
306
|
+
});
|
|
307
|
+
/**
|
|
308
|
+
* Mapeia o id de cada opção à sua posição (`posinset`) e ao tamanho total do conjunto
|
|
309
|
+
* (`setsize`), ignorando agrupadores (que não são `role="option"`). Necessário porque, com
|
|
310
|
+
* `virtualScroll`, só uma janela de opções existe de fato no DOM a cada momento — sem
|
|
311
|
+
* `aria-posinset`/`aria-setsize` explícitos, o leitor de tela tenta inferir a posição
|
|
312
|
+
* contando os `role="option"` irmãos presentes no DOM, o que fica errado pra uma lista
|
|
313
|
+
* virtualizada (reportado via NVDA: ao selecionar a 3ª opção de uma lista de ~240 países
|
|
314
|
+
* com `virtualScroll`, o anúncio de posição "voltava" pra 1 antes de se corrigir).
|
|
315
|
+
*/
|
|
316
|
+
optionPositions = computed(() => {
|
|
317
|
+
const positions = new Map();
|
|
318
|
+
const nonGrouperOptions = this.filteredOptions().filter((option) => !option.grouper);
|
|
319
|
+
const setsize = nonGrouperOptions.length;
|
|
320
|
+
nonGrouperOptions.forEach((option, index) => {
|
|
321
|
+
positions.set(option.id, { posinset: index + 1, setsize });
|
|
322
|
+
});
|
|
323
|
+
return positions;
|
|
324
|
+
});
|
|
240
325
|
filterForm = new FormGroup({
|
|
241
326
|
filter: new FormControl('', { nonNullable: true }),
|
|
242
327
|
});
|
|
243
328
|
_containerDiv = viewChild('containerDiv');
|
|
244
329
|
_filterInput = viewChild('filterInput');
|
|
245
|
-
|
|
330
|
+
// `read: ElementRef` é obrigatório aqui: `#selectAllCheckbox` está num elemento de
|
|
331
|
+
// componente (`<s-checkbox>`), então sem isso o viewChild resolve pra instância do
|
|
332
|
+
// `CheckboxComponent` (que não tem `.nativeElement`), não pro `ElementRef` do host —
|
|
333
|
+
// resultado, o foco automático no checkbox ao abrir (`toggle()`) nunca funcionava.
|
|
334
|
+
_selectAllCheckbox = viewChild('selectAllCheckbox', {
|
|
335
|
+
read: ElementRef,
|
|
336
|
+
});
|
|
337
|
+
_dropdownPanel = viewChild('dropdownPanel');
|
|
246
338
|
_onChange = () => { };
|
|
247
339
|
_onTouched = () => { };
|
|
248
340
|
_clickableLabel = null;
|
|
249
341
|
_translateService = inject(TranslateService);
|
|
250
342
|
_hostRef = inject(ElementRef);
|
|
251
343
|
_renderer = inject(Renderer2);
|
|
344
|
+
_injector = inject(Injector);
|
|
345
|
+
_liveAnnouncer = inject(LiveAnnouncer);
|
|
252
346
|
constructor() {
|
|
253
347
|
this.filterForm
|
|
254
348
|
.get('filter')
|
|
255
349
|
.valueChanges.pipe(debounceTime(300), distinctUntilChanged(), takeUntilDestroyed())
|
|
256
350
|
.subscribe((value) => {
|
|
257
351
|
this.filterValue.set(value);
|
|
352
|
+
// `focusedIndex` é um índice cru dentro de `filteredOptions()` — ao digitar um
|
|
353
|
+
// filtro que reduz a lista (ou limpar o filtro, que a aumenta de volta), o índice
|
|
354
|
+
// antigo passa a apontar pra uma posição inválida ou pra uma opção completamente
|
|
355
|
+
// diferente da que estava focada antes. Reportado via NVDA: sem resetar aqui,
|
|
356
|
+
// ArrowDown/Espaço/Enter viravam no-op silencioso sempre que o índice ficava fora
|
|
357
|
+
// dos limites da nova lista filtrada — o usuário só continuava ouvindo o anúncio
|
|
358
|
+
// da ÚLTIMA seleção válida, sem nenhum feedback de que a tecla não fez nada.
|
|
359
|
+
this.focusedIndex.set(-1);
|
|
258
360
|
});
|
|
259
361
|
// Efeito para tentar aplicar valor pendente quando options mudar.
|
|
260
362
|
// A escrita em value/values é necessária para sincronizar o ControlValueAccessor.
|
|
@@ -368,7 +470,7 @@ class SelectComponent {
|
|
|
368
470
|
// Quando há filtro com múltipla seleção, foco vai para o checkbox primeiro;
|
|
369
471
|
// caso contrário, vai direto para o input de filtro.
|
|
370
472
|
// O foco é movido após a renderização do overlay.
|
|
371
|
-
|
|
473
|
+
afterNextRender(() => {
|
|
372
474
|
if (this.multiple()) {
|
|
373
475
|
const checkboxHost = this._selectAllCheckbox()?.nativeElement;
|
|
374
476
|
const focusable = checkboxHost?.querySelector('[tabindex]');
|
|
@@ -377,7 +479,7 @@ class SelectComponent {
|
|
|
377
479
|
else {
|
|
378
480
|
this._filterInput()?.nativeElement.focus();
|
|
379
481
|
}
|
|
380
|
-
});
|
|
482
|
+
}, { injector: this._injector });
|
|
381
483
|
}
|
|
382
484
|
else {
|
|
383
485
|
this._focusSelectedOption();
|
|
@@ -402,6 +504,7 @@ class SelectComponent {
|
|
|
402
504
|
const index = store.findIndex((selectedItem) => {
|
|
403
505
|
return selectedItem === item;
|
|
404
506
|
});
|
|
507
|
+
const isNowSelected = index === -1;
|
|
405
508
|
if (index !== -1) {
|
|
406
509
|
store.splice(index, 1);
|
|
407
510
|
}
|
|
@@ -411,16 +514,41 @@ class SelectComponent {
|
|
|
411
514
|
this.values.set(store);
|
|
412
515
|
const result = optionValue ? store.map((item) => item.data[optionValue]) : store;
|
|
413
516
|
this._onChange(result);
|
|
517
|
+
this._announceOptionToggle(item, isNowSelected);
|
|
518
|
+
// Acompanha o item que acabou de ser selecionado/desmarcado via aria-activedescendant
|
|
519
|
+
// — sem isso, clicar com o mouse (em vez de navegar por teclado) nunca move o
|
|
520
|
+
// "foco" virtual, que fica parado onde estava (ou nulo), e o leitor de tela passa a
|
|
521
|
+
// anunciar sempre a mesma opção (a primeira, ou a última alcançada por teclado) em
|
|
522
|
+
// vez da que foi de fato clicada.
|
|
523
|
+
const itemIndex = this.filteredOptions().findIndex((option) => option.id === item.id);
|
|
524
|
+
if (itemIndex >= 0) {
|
|
525
|
+
this.focusedIndex.set(itemIndex);
|
|
526
|
+
}
|
|
414
527
|
}
|
|
415
528
|
else {
|
|
416
529
|
this.value.set(item);
|
|
417
530
|
const result = optionValue ? item.data[optionValue] : item.data;
|
|
418
531
|
this._onChange(result);
|
|
532
|
+
this._announceOptionToggle(item, true);
|
|
533
|
+
// Devolve o foco pro container só se ele estava dentro do painel (ex.: opção
|
|
534
|
+
// escolhida com o filtro focado) — ver `_closeDropdown()`.
|
|
419
535
|
this._closeDropdown();
|
|
420
|
-
this._containerDiv()?.nativeElement.focus();
|
|
421
536
|
}
|
|
422
537
|
this._onTouched();
|
|
423
538
|
}
|
|
539
|
+
/**
|
|
540
|
+
* Anuncia via `LiveAnnouncer` a opção selecionada/desmarcada. Necessário porque, na prática,
|
|
541
|
+
* nem todo navegador/leitor de tela anuncia sozinho a mudança de `aria-selected` da opção
|
|
542
|
+
* apontada por `aria-activedescendant` (reportado via NVDA no Chrome: nenhuma mensagem era
|
|
543
|
+
* ouvida ao selecionar).
|
|
544
|
+
*/
|
|
545
|
+
_announceOptionToggle(item, isNowSelected) {
|
|
546
|
+
const key = isNowSelected
|
|
547
|
+
? 'platform.angular_components.select_option_selected'
|
|
548
|
+
: 'platform.angular_components.select_option_deselected';
|
|
549
|
+
const message = this._translateService.instant(key, { option: this.getOptionLabel(item.data) });
|
|
550
|
+
void this._liveAnnouncer.announce(message, 'polite');
|
|
551
|
+
}
|
|
424
552
|
/**
|
|
425
553
|
* Limpa o valor selecionado no componente select.
|
|
426
554
|
* Se o componente estiver desabilitado, não executa nenhuma ação.
|
|
@@ -437,14 +565,25 @@ class SelectComponent {
|
|
|
437
565
|
if (this.multiple()) {
|
|
438
566
|
this.values.set([]);
|
|
439
567
|
this._onChange(this.values());
|
|
568
|
+
void this._liveAnnouncer.announce(this._translateService.instant('platform.angular_components.select_all_options_cleared'), 'polite');
|
|
440
569
|
}
|
|
441
570
|
else {
|
|
571
|
+
const removedLabel = this.value() ? this.getOptionLabel(this.value().data) : null;
|
|
442
572
|
this.value.set(null);
|
|
443
573
|
this._onChange(this.value());
|
|
574
|
+
if (removedLabel) {
|
|
575
|
+
void this._liveAnnouncer.announce(this._translateService.instant('platform.angular_components.select_option_removed', {
|
|
576
|
+
option: removedLabel,
|
|
577
|
+
}), 'polite');
|
|
578
|
+
}
|
|
444
579
|
}
|
|
445
580
|
this._closeDropdown();
|
|
446
581
|
this._onTouched();
|
|
447
582
|
event.stopPropagation();
|
|
583
|
+
// O botão "Limpar" só é renderizado enquanto `!isClean()` — ao chamar `clear()` ele
|
|
584
|
+
// deixa de existir no DOM na mesma re-renderização em que acabou de ser clicado,
|
|
585
|
+
// levando o foco junto pra `document.body` (perdido) se nada o resgatar explicitamente.
|
|
586
|
+
afterNextRender(() => this._containerDiv()?.nativeElement.focus(), { injector: this._injector });
|
|
448
587
|
}
|
|
449
588
|
/**
|
|
450
589
|
* Limpa o filtro do formulário, resetando o campo 'filter' do formulário de filtro.
|
|
@@ -477,8 +616,14 @@ class SelectComponent {
|
|
|
477
616
|
* Manipula eventos de teclado no input de filtro do overlay.
|
|
478
617
|
* - 'ArrowDown': Move o foco para a primeira opção da lista.
|
|
479
618
|
* - 'ArrowUp': Move o foco para a última opção da lista.
|
|
619
|
+
* - 'Tab' (sem Shift): Move o foco para a próxima opção da lista, em vez de deixar o Tab
|
|
620
|
+
* nativo tentar sair do overlay (as opções não são elementos tabuláveis, então o Tab
|
|
621
|
+
* nativo "vazava" o foco pra fora do painel sem nunca alcançar a lista).
|
|
480
622
|
* - 'Escape': Fecha o dropdown e retorna o foco ao combobox.
|
|
481
|
-
* - 'Enter': Seleciona a opção focada, se houver.
|
|
623
|
+
* - 'Enter' / 'Espaço': Seleciona a opção focada, se houver. Espaço só é interceptado
|
|
624
|
+
* quando já existe uma opção com foco virtual (`focusedIndex() >= 0`) — sem uma opção
|
|
625
|
+
* focada, o espaço é deixado passar pro comportamento nativo do `<input>` (digitar um
|
|
626
|
+
* espaço, necessário pra buscas com múltiplas palavras).
|
|
482
627
|
* @param event O evento de teclado disparado no input de filtro.
|
|
483
628
|
*/
|
|
484
629
|
onFilterKeyDown(event) {
|
|
@@ -511,7 +656,6 @@ class SelectComponent {
|
|
|
511
656
|
event.preventDefault();
|
|
512
657
|
event.stopPropagation();
|
|
513
658
|
this._closeDropdown();
|
|
514
|
-
this._containerDiv()?.nativeElement.focus();
|
|
515
659
|
break;
|
|
516
660
|
case 'Enter':
|
|
517
661
|
if (this.focusedIndex() >= 0 && this.focusedIndex() < options.length) {
|
|
@@ -519,13 +663,41 @@ class SelectComponent {
|
|
|
519
663
|
this.selectItem(options[this.focusedIndex()]);
|
|
520
664
|
}
|
|
521
665
|
break;
|
|
666
|
+
case ' ':
|
|
667
|
+
if (this.focusedIndex() >= 0 && this.focusedIndex() < options.length) {
|
|
668
|
+
event.preventDefault();
|
|
669
|
+
this.selectItem(options[this.focusedIndex()]);
|
|
670
|
+
}
|
|
671
|
+
break;
|
|
672
|
+
case 'Tab':
|
|
673
|
+
if (!event.shiftKey) {
|
|
674
|
+
event.preventDefault();
|
|
675
|
+
this._focusNextOption(options);
|
|
676
|
+
}
|
|
677
|
+
break;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Manipula o evento de teclado no botão "Limpar filtro" do overlay.
|
|
682
|
+
* É o último controle real (tabulável) do overlay antes da lista de opções — sem
|
|
683
|
+
* interceptar o Tab aqui, ele "vazava" o foco pra fora do painel sem nunca alcançar a
|
|
684
|
+
* lista, já que as opções não são elementos tabuláveis (foco virtual via
|
|
685
|
+
* aria-activedescendant).
|
|
686
|
+
* @param event O evento de teclado disparado no botão.
|
|
687
|
+
*/
|
|
688
|
+
onClearFilterButtonKeyDown(event) {
|
|
689
|
+
if (event.key === 'Tab' && !event.shiftKey) {
|
|
690
|
+
event.preventDefault();
|
|
691
|
+
this._focusNextOption(this.filteredOptions());
|
|
522
692
|
}
|
|
523
693
|
}
|
|
524
694
|
/**
|
|
525
695
|
* Manipula eventos de teclado para navegação e seleção de opções no componente select.
|
|
526
696
|
* - 'ArrowDown': Move o foco para a próxima opção disponível.
|
|
527
697
|
* - 'ArrowUp': Move o foco para a opção anterior.
|
|
528
|
-
* - 'Tab':
|
|
698
|
+
* - 'Tab'/'Shift+Tab': Com o dropdown aberto e sem filtro, navega entre as opções (mesmo
|
|
699
|
+
* efeito de ArrowDown/ArrowUp) em vez de fechar o painel e "vazar" o foco pro próximo
|
|
700
|
+
* campo da página sem o usuário nunca ter alcançado as opções.
|
|
529
701
|
* - 'Enter': Seleciona a opção focada se o dropdown estiver aberto, ou alterna a abertura do dropdown.
|
|
530
702
|
* - 'Escape': Fecha o dropdown.
|
|
531
703
|
* - ' ': (barra de espaço) Seleciona a opção focada se o dropdown estiver aberto, ou alterna a abertura do dropdown.
|
|
@@ -560,12 +732,26 @@ class SelectComponent {
|
|
|
560
732
|
}
|
|
561
733
|
break;
|
|
562
734
|
case 'Tab':
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
735
|
+
if (this.showOptions()) {
|
|
736
|
+
if (this.filter()) {
|
|
737
|
+
// Com filtro, Tab navega pelos controles reais do overlay (checkbox →
|
|
738
|
+
// input → botão "Limpar filtro"); a partir do último deles, o próprio
|
|
739
|
+
// controle intercepta o Tab pra entrar na lista — ver onFilterKeyDown /
|
|
740
|
+
// onClearFilterButtonKeyDown.
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
// Sem filtro, o foco DOM nunca sai do combobox (navegação virtual via
|
|
744
|
+
// aria-activedescendant) — reportado: sem interceptar, Tab fechava o
|
|
745
|
+
// dropdown e o foco "vazava" pro próximo campo da página sem o usuário
|
|
746
|
+
// nunca ter alcançado as opções. Tab passa a funcionar como ArrowDown/Up.
|
|
747
|
+
event.preventDefault();
|
|
748
|
+
if (event.shiftKey) {
|
|
749
|
+
this._focusPreviousOption(options);
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
this._focusNextOption(options);
|
|
753
|
+
}
|
|
566
754
|
}
|
|
567
|
-
_selectOption();
|
|
568
|
-
this._closeDropdown();
|
|
569
755
|
break;
|
|
570
756
|
case 'Enter':
|
|
571
757
|
if (this.showOptions()) {
|
|
@@ -601,6 +787,31 @@ class SelectComponent {
|
|
|
601
787
|
getOptionLabel(option) {
|
|
602
788
|
return this._getLabelFromOption(option);
|
|
603
789
|
}
|
|
790
|
+
/**
|
|
791
|
+
* Retorna o texto de anúncio de grupo (ex.: "Agrupado por América do Sul") pra uma opção,
|
|
792
|
+
* caso ela seja a primeira do seu grupo — ver `groupAnnouncements`.
|
|
793
|
+
* @param optionId Id da opção interna.
|
|
794
|
+
* @returns O texto de anúncio, ou `null` se a opção não for a primeira do grupo.
|
|
795
|
+
*/
|
|
796
|
+
groupAnnouncementFor(optionId) {
|
|
797
|
+
return this.groupAnnouncements().get(optionId) ?? null;
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Retorna a posição (1-based) de uma opção dentro do conjunto navegável — ver `optionPositions`.
|
|
801
|
+
* @param optionId Id da opção interna.
|
|
802
|
+
* @returns A posição, ou `null` se a opção não for encontrada (ex.: é um agrupador).
|
|
803
|
+
*/
|
|
804
|
+
optionPosinsetFor(optionId) {
|
|
805
|
+
return this.optionPositions().get(optionId)?.posinset ?? null;
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Retorna o tamanho total do conjunto navegável ao qual uma opção pertence — ver `optionPositions`.
|
|
809
|
+
* @param optionId Id da opção interna.
|
|
810
|
+
* @returns O tamanho total, ou `null` se a opção não for encontrada (ex.: é um agrupador).
|
|
811
|
+
*/
|
|
812
|
+
optionSetsizeFor(optionId) {
|
|
813
|
+
return this.optionPositions().get(optionId)?.setsize ?? null;
|
|
814
|
+
}
|
|
604
815
|
/**
|
|
605
816
|
* Função de trackBy utilizada em blocos @for para otimizar a renderização de listas.
|
|
606
817
|
* Retorna o identificador único (`id`) de cada item do tipo `InternalDropdownOption<T>`.
|
|
@@ -634,12 +845,27 @@ class SelectComponent {
|
|
|
634
845
|
const options = this.filteredOptions().filter((option) => !option.grouper);
|
|
635
846
|
if (this.allSelected()) {
|
|
636
847
|
this.values.set([]);
|
|
848
|
+
this._announceSelectAllToggle(false, 0);
|
|
637
849
|
}
|
|
638
850
|
else {
|
|
639
851
|
this.values.set(options);
|
|
852
|
+
this._announceSelectAllToggle(true, options.length);
|
|
640
853
|
}
|
|
641
854
|
this._onChange(this.values());
|
|
642
855
|
}
|
|
856
|
+
/**
|
|
857
|
+
* Anuncia via `LiveAnnouncer` o resultado de "Selecionar Todos"/"Limpar Todos". Necessário
|
|
858
|
+
* porque o checkbox que dispara a ação tem foco real e anuncia seu próprio `aria-checked`
|
|
859
|
+
* normalmente, mas as N opções afetadas (que não estão focadas) não — ao contrário do toggle
|
|
860
|
+
* de uma opção individual, cujo `aria-selected` já é lido pelo leitor de tela porque ela é o
|
|
861
|
+
* elemento apontado por `aria-activedescendant` no momento da mudança.
|
|
862
|
+
*/
|
|
863
|
+
_announceSelectAllToggle(isNowSelected, count) {
|
|
864
|
+
const message = isNowSelected
|
|
865
|
+
? this._translateService.instant('platform.angular_components.count_items_selected', { count })
|
|
866
|
+
: this._translateService.instant('platform.angular_components.select_all_options_cleared');
|
|
867
|
+
void this._liveAnnouncer.announce(message, 'polite');
|
|
868
|
+
}
|
|
643
869
|
/**
|
|
644
870
|
* Retorna o rótulo (label) de uma opção fornecida.
|
|
645
871
|
* Este método utiliza a propriedade definida por `optionLabel` para extrair o valor do rótulo da opção.
|
|
@@ -735,18 +961,37 @@ class SelectComponent {
|
|
|
735
961
|
/**
|
|
736
962
|
* Fecha o dropdown de opções do componente select.
|
|
737
963
|
* Este método oculta as opções disponíveis e redefine o índice do item focado para -1,
|
|
738
|
-
* indicando que nenhum item está atualmente focado.
|
|
739
|
-
*
|
|
964
|
+
* indicando que nenhum item está atualmente focado. Se o foco estava dentro do painel
|
|
965
|
+
* sendo fechado (ex.: campo de filtro), devolve o foco pro container/combobox — nunca
|
|
966
|
+
* força o foco se ele já estiver em outro lugar (ex.: opção escolhida via clique do mouse
|
|
967
|
+
* sem filtro, onde o foco DOM nunca saiu do combobox por causa do aria-activedescendant).
|
|
968
|
+
* `protected` (não `private`) porque também é chamado a partir do template
|
|
969
|
+
* (`backdropClick`/`detach` do `cdkConnectedOverlay`).
|
|
740
970
|
*/
|
|
741
971
|
_closeDropdown() {
|
|
972
|
+
const panelElement = this._dropdownPanel()?.nativeElement;
|
|
742
973
|
this.showOptions.set(false);
|
|
743
974
|
this.focusedIndex.set(-1);
|
|
975
|
+
const focusIsInsidePanel = panelElement != null && document.activeElement instanceof Node && panelElement.contains(document.activeElement);
|
|
976
|
+
if (!focusIsInsidePanel) {
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
afterNextRender(() => this._containerDiv()?.nativeElement.focus(), { injector: this._injector });
|
|
744
980
|
}
|
|
745
981
|
/**
|
|
746
982
|
* Adiciona um identificador único a cada opção fornecida.
|
|
747
983
|
* Para cada item no array de opções, gera um objeto `InternalDropdownOption` contendo o dado original
|
|
748
|
-
* e um campo `id` único baseado no timestamp atual e no índice do
|
|
749
|
-
* (detectado pela presença de um array `items`), adiciona também a
|
|
984
|
+
* e um campo `id` único baseado no `componentId` desta instância, no timestamp atual e no índice do
|
|
985
|
+
* item. Caso a opção seja um grupo (detectado pela presença de um array `items`), adiciona também a
|
|
986
|
+
* propriedade `grouper: true`.
|
|
987
|
+
*
|
|
988
|
+
* O `componentId` é obrigatório no prefixo: `Date.now()` sozinho tem resolução de apenas 1ms, e duas
|
|
989
|
+
* instâncias de `s-select` na mesma página frequentemente inicializam dentro do mesmo milissegundo
|
|
990
|
+
* (confirmado via teste manual — reportado via NVDA: uma opção já selecionada em OUTRO `s-select` da
|
|
991
|
+
* página era anunciada em vez da opção de fato clicada). Sem o `componentId`, os ids colidiam entre
|
|
992
|
+
* instâncias diferentes (`id_<mesmo-timestamp>_0` em ambas), e `aria-activedescendant`/`getElementById`
|
|
993
|
+
* resolviam pro elemento errado — de outro select. O timestamp continua no id (além do `componentId`)
|
|
994
|
+
* pra manter ids únicos entre recomputações da MESMA instância quando `options()` muda de referência.
|
|
750
995
|
* @private
|
|
751
996
|
* @param options Array de opções do tipo `T` a serem processadas.
|
|
752
997
|
* @returns Um array de objetos `InternalDropdownOption<T>` com identificadores únicos.
|
|
@@ -757,13 +1002,13 @@ class SelectComponent {
|
|
|
757
1002
|
if (this.group() && Array.isArray(option.items)) {
|
|
758
1003
|
return {
|
|
759
1004
|
data: option,
|
|
760
|
-
id:
|
|
1005
|
+
id: `${this.componentId}_id_${timestamp}_${index++}`,
|
|
761
1006
|
grouper: true,
|
|
762
1007
|
};
|
|
763
1008
|
}
|
|
764
1009
|
return {
|
|
765
1010
|
data: option,
|
|
766
|
-
id:
|
|
1011
|
+
id: `${this.componentId}_id_${timestamp}_${index++}`,
|
|
767
1012
|
};
|
|
768
1013
|
});
|
|
769
1014
|
}
|
|
@@ -862,7 +1107,7 @@ class SelectComponent {
|
|
|
862
1107
|
}
|
|
863
1108
|
this.focusedIndex.set(selectedIndex);
|
|
864
1109
|
if (selectedIndex >= 0) {
|
|
865
|
-
|
|
1110
|
+
afterNextRender(() => this._scrollToFocusedOption(), { injector: this._injector });
|
|
866
1111
|
}
|
|
867
1112
|
}
|
|
868
1113
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
@@ -872,7 +1117,7 @@ class SelectComponent {
|
|
|
872
1117
|
useExisting: forwardRef(() => SelectComponent),
|
|
873
1118
|
multi: true,
|
|
874
1119
|
},
|
|
875
|
-
], viewQueries: [{ propertyName: "_containerDiv", first: true, predicate: ["containerDiv"], descendants: true, isSignal: true }, { propertyName: "_filterInput", first: true, predicate: ["filterInput"], descendants: true, isSignal: true }, { propertyName: "_selectAllCheckbox", first: true, predicate: ["selectAllCheckbox"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n #containerDiv\n cdkOverlayOrigin\n #trigger=\"cdkOverlayOrigin\"\n class=\"select-container group flex h-[35px] w-full overflow-hidden rounded-[3px] border outline-1 outline-primary focus:outline\"\n [class.pointer-events-none]=\"disabled()\"\n [class.border-grayscale-20]=\"disabled()\"\n [class.bg-grayscale-5]=\"disabled()\"\n [class.pointer-events-auto]=\"!disabled()\"\n [class.border-grayscale-30]=\"!disabled()\"\n [class.bg-grayscale-0]=\"!disabled()\"\n [attr.aria-disabled]=\"disabled()\"\n [attr.aria-expanded]=\"showOptions()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-controls]=\"componentId + '-listbox'\"\n [attr.aria-owns]=\"showOptions() ? componentId + '-listbox' : null\"\n [attr.aria-activedescendant]=\"showOptions() ? focusedOptionId() : null\"\n role=\"combobox\"\n [id]=\"componentId + '-combobox'\"\n (click)=\"onContainerDivClick()\"\n (keydown)=\"onKeyDown($event)\"\n [attr.tabindex]=\"disabled() ? -1 : 0\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-labelledby]=\"resolvedAriaLabelledBy() || null\"\n>\n <span\n class=\"flex flex-grow select-none px-3 py-[7px]\"\n [class.text-grayscale-90]=\"!disabled() && !isClean()\"\n [class.text-grayscale-60]=\"!disabled() && isClean()\"\n [class.text-grayscale-30]=\"disabled()\"\n >\n {{ print() || placeholder() }}\n </span>\n\n @if (!disabled() && showClear() && !isClean()) {\n <button\n class=\"mx-3\"\n (click)=\"clear($event)\"\n [attr.aria-label]=\"'platform.angular_components.aria_clear_selection' | translate\"\n >\n <i\n class=\"fas fa-times flex items-center\"\n aria-hidden=\"true\"\n ></i>\n </button>\n }\n\n <div\n class=\"dropdown-container flex items-center border-l px-3 group-hover:bg-grayscale-10 group-focus:border-primary\"\n [class.border-grayscale-20]=\"disabled()\"\n [class.border-grayscale-30]=\"!disabled()\"\n >\n <i\n class=\"fas\"\n aria-hidden=\"true\"\n [class.text-grayscale-30]=\"disabled()\"\n [class.text-grayscale-90]=\"!disabled()\"\n [class.fa-caret-down]=\"!showOptions()\"\n [class.fa-caret-up]=\"showOptions()\"\n ></i>\n </div>\n</div>\n\n<ng-template\n #dropdownTemplate\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"trigger\"\n [cdkConnectedOverlayOpen]=\"showOptions()\"\n [cdkConnectedOverlayHasBackdrop]=\"true\"\n (backdropClick)=\"showOptions.set(false)\"\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n (detach)=\"showOptions.set(false)\"\n>\n <div\n [id]=\"componentId + '-listbox'\"\n class=\"dropdown-body-class z-[1000] rounded-[3px] bg-grayscale-0 py-1 shadow-md\"\n (click)=\"$event.stopPropagation()\"\n [style.width.px]=\"containerDiv.offsetWidth\"\n [attr.aria-multiselectable]=\"multiple() ? true : null\"\n [@visibilityChanged]\n role=\"listbox\"\n >\n @if (filter()) {\n <div class=\"flex w-full items-center gap-3 px-3 py-1.5\">\n @if (multiple()) {\n <s-checkbox\n #selectAllCheckbox\n [checked]=\"allSelected()\"\n [indeterminate]=\"selectAllIsIndeterminate()\"\n (checkedChange)=\"toggleSelectAll()\"\n ></s-checkbox>\n }\n\n <div class=\"relative flex h-[35px] grow\">\n <form\n class=\"flex w-full grow\"\n [formGroup]=\"filterForm\"\n >\n <input\n #filterInput\n class=\"w-full grow rounded-[3px] border border-grayscale-30 pl-2.5 pr-7 outline-1 outline-primary\"\n type=\"text\"\n formControlName=\"filter\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"onFilterKeyDown($event)\"\n [attr.aria-label]=\"'platform.angular_components.filter_options' | translate\"\n [attr.aria-controls]=\"componentId + '-listbox'\"\n [attr.aria-activedescendant]=\"focusedOptionId()\"\n autocomplete=\"off\"\n />\n </form>\n <i\n class=\"fas fa-search absolute right-2.5 top-2.5 text-grayscale-90\"\n aria-hidden=\"true\"\n ></i>\n </div>\n <button\n (click)=\"clearFilter()\"\n [attr.aria-label]=\"'platform.angular_components.clear_filter' | translate\"\n >\n <i\n class=\"fas fa-times flex items-center\"\n aria-hidden=\"true\"\n ></i>\n </button>\n </div>\n }\n\n <!-- Virtual scroll -->\n @if (virtualScroll() && filteredOptions().length > 10) {\n <cdk-virtual-scroll-viewport\n [itemSize]=\"virtualScrollItemSize()\"\n class=\"h-52 overflow-auto\"\n >\n <ng-container *cdkVirtualFor=\"let option of filteredOptions(); trackBy: trackById\">\n <s-select-option\n [id]=\"option.id\"\n [label]=\"getOptionLabel(option.data)\"\n [multiple]=\"multiple()\"\n [checkmark]=\"checkmark()\"\n [checkIconClass]=\"checkIconClass()\"\n [isSelected]=\"isOptionSelected(option)\"\n (selected)=\"selectItem(option)\"\n [isGrouper]=\"option.grouper ?? false\"\n [isFocused]=\"option.data === focusedItem()\"\n ></s-select-option>\n </ng-container>\n </cdk-virtual-scroll-viewport>\n }\n\n <!-- Normal list -->\n @if (!virtualScroll() || filteredOptions().length <= 10) {\n <ul class=\"max-h-52 overflow-auto\">\n @for (option of filteredOptions(); track option.id) {\n <s-select-option\n [id]=\"option.id\"\n [label]=\"getOptionLabel(option.data)\"\n [multiple]=\"multiple()\"\n [checkmark]=\"checkmark()\"\n [checkIconClass]=\"checkIconClass()\"\n [isSelected]=\"isOptionSelected(option)\"\n (selected)=\"selectItem(option)\"\n [isGrouper]=\"option.grouper ?? false\"\n [isFocused]=\"option.data === focusedItem()\"\n ></s-select-option>\n }\n </ul>\n }\n\n @if (filteredOptions().length === 0) {\n <span class=\"m-3 text-grayscale-60\">\n {{ emptyMessage() ?? 'platform.angular_components.no_records_found' | translate }}\n </span>\n }\n </div>\n</ng-template>\n\n", styles: [":host.ng-dirty.ng-invalid{.dropdown-container,.select-container{border-color:#c13018;outline:none}}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.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: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i2.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i2.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i2.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "component", type: SelectOptionComponent, selector: "s-select-option", inputs: ["id", "label", "multiple", "checkmark", "isGrouper", "isFocused", "isSelected", "checkIconClass"], outputs: ["isSelectedChange", "selected"] }, { kind: "component", type: CheckboxComponent, selector: "s-checkbox", inputs: ["disabled", "checked", "indeterminate", "label", "ariaLabel", "ariaLabelledBy"], outputs: ["disabledChange", "checkedChange"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i4.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i4.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }], animations: [
|
|
1120
|
+
], viewQueries: [{ propertyName: "_containerDiv", first: true, predicate: ["containerDiv"], descendants: true, isSignal: true }, { propertyName: "_filterInput", first: true, predicate: ["filterInput"], descendants: true, isSignal: true }, { propertyName: "_selectAllCheckbox", first: true, predicate: ["selectAllCheckbox"], descendants: true, read: ElementRef, isSignal: true }, { propertyName: "_dropdownPanel", first: true, predicate: ["dropdownPanel"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n #containerDiv\n cdkOverlayOrigin\n #trigger=\"cdkOverlayOrigin\"\n class=\"select-container group flex h-[35px] w-full overflow-hidden rounded-[3px] border outline-2 outline-[var(--s-focus-ring-color,#0090ff)] outline-offset-[-2px] focus-visible:outline\"\n [class.pointer-events-none]=\"disabled()\"\n [class.border-grayscale-20]=\"disabled()\"\n [class.bg-grayscale-5]=\"disabled()\"\n [class.pointer-events-auto]=\"!disabled()\"\n [class.border-grayscale-30]=\"!disabled()\"\n [class.bg-grayscale-0]=\"!disabled()\"\n [attr.aria-disabled]=\"disabled()\"\n [attr.aria-expanded]=\"showOptions()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-controls]=\"componentId + '-listbox'\"\n [attr.aria-owns]=\"showOptions() ? componentId + '-listbox' : null\"\n [attr.aria-activedescendant]=\"showOptions() ? focusedOptionId() : null\"\n role=\"combobox\"\n [id]=\"componentId + '-combobox'\"\n (click)=\"onContainerDivClick()\"\n (keydown)=\"onKeyDown($event)\"\n [attr.tabindex]=\"disabled() ? -1 : 0\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-labelledby]=\"resolvedAriaLabelledBy() || null\"\n>\n <span\n class=\"flex flex-grow select-none px-3 py-[7px]\"\n [class.text-grayscale-90]=\"!disabled() && !isClean()\"\n [class.text-grayscale-60]=\"!disabled() && isClean()\"\n [class.text-grayscale-30]=\"disabled()\"\n >\n {{ print() || placeholder() }}\n </span>\n\n @if (!disabled() && showClear() && !isClean()) {\n <button\n class=\"mx-3\"\n (click)=\"clear($event)\"\n (keydown)=\"$event.stopPropagation()\"\n [attr.aria-label]=\"'platform.angular_components.aria_clear_selection' | translate\"\n >\n <i\n class=\"fas fa-times flex items-center\"\n aria-hidden=\"true\"\n ></i>\n </button>\n }\n\n <div\n class=\"dropdown-container flex items-center border-l px-3 group-hover:bg-grayscale-10\"\n [class.border-grayscale-20]=\"disabled()\"\n [class.border-grayscale-30]=\"!disabled()\"\n >\n <i\n class=\"fas\"\n aria-hidden=\"true\"\n [class.text-grayscale-30]=\"disabled()\"\n [class.text-grayscale-90]=\"!disabled()\"\n [class.fa-caret-down]=\"!showOptions()\"\n [class.fa-caret-up]=\"showOptions()\"\n ></i>\n </div>\n</div>\n\n<ng-template\n #dropdownTemplate\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"trigger\"\n [cdkConnectedOverlayOpen]=\"showOptions()\"\n [cdkConnectedOverlayHasBackdrop]=\"true\"\n (backdropClick)=\"_closeDropdown()\"\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n (detach)=\"_closeDropdown()\"\n>\n <div\n #dropdownPanel\n [id]=\"componentId + '-listbox'\"\n class=\"dropdown-body-class z-[1000] rounded-[3px] bg-grayscale-0 py-1 shadow-md\"\n (click)=\"$event.stopPropagation()\"\n [style.width.px]=\"containerDiv.offsetWidth\"\n [attr.aria-multiselectable]=\"multiple() ? true : null\"\n [@visibilityChanged]\n role=\"listbox\"\n >\n @if (filter()) {\n <div class=\"flex w-full items-center gap-3 px-3 py-1.5\">\n @if (multiple()) {\n <s-checkbox\n #selectAllCheckbox\n [checked]=\"allSelected()\"\n [indeterminate]=\"selectAllIsIndeterminate()\"\n [ariaLabel]=\"selectAllCheckboxAriaLabel()\"\n (checkedChange)=\"toggleSelectAll()\"\n ></s-checkbox>\n }\n\n <div class=\"relative flex h-[35px] grow\">\n <form\n class=\"flex w-full grow\"\n [formGroup]=\"filterForm\"\n >\n <input\n #filterInput\n class=\"w-full grow rounded-[3px] border border-grayscale-30 pl-2.5 pr-7 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-[var(--s-focus-ring-color,#0090ff)]\"\n type=\"text\"\n formControlName=\"filter\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"onFilterKeyDown($event)\"\n [attr.aria-label]=\"'platform.angular_components.filter_options' | translate\"\n [attr.aria-controls]=\"componentId + '-listbox'\"\n [attr.aria-activedescendant]=\"focusedOptionId()\"\n autocomplete=\"off\"\n />\n </form>\n <i\n class=\"fas fa-search absolute right-2.5 top-2.5 text-grayscale-90\"\n aria-hidden=\"true\"\n ></i>\n </div>\n <button\n (click)=\"clearFilter()\"\n (keydown)=\"onClearFilterButtonKeyDown($event)\"\n [attr.aria-label]=\"'platform.angular_components.clear_filter' | translate\"\n >\n <i\n class=\"fas fa-times flex items-center\"\n aria-hidden=\"true\"\n ></i>\n </button>\n </div>\n }\n\n <!-- Virtual scroll -->\n @if (virtualScroll() && filteredOptions().length > 10) {\n <cdk-virtual-scroll-viewport\n [itemSize]=\"virtualScrollItemSize()\"\n class=\"h-52 overflow-auto\"\n role=\"presentation\"\n >\n <ng-container *cdkVirtualFor=\"let option of filteredOptions(); trackBy: trackById\">\n <s-select-option\n [id]=\"option.id\"\n [label]=\"getOptionLabel(option.data)\"\n [multiple]=\"multiple()\"\n [checkmark]=\"checkmark()\"\n [checkIconClass]=\"checkIconClass()\"\n [isSelected]=\"isOptionSelected(option)\"\n (selected)=\"selectItem(option)\"\n [isGrouper]=\"option.grouper ?? false\"\n [isFocused]=\"option.data === focusedItem()\"\n [groupAnnouncement]=\"groupAnnouncementFor(option.id)\"\n [posinset]=\"optionPosinsetFor(option.id)\"\n [setsize]=\"optionSetsizeFor(option.id)\"\n ></s-select-option>\n </ng-container>\n </cdk-virtual-scroll-viewport>\n }\n\n <!-- Normal list -->\n @if (!virtualScroll() || filteredOptions().length <= 10) {\n <ul\n class=\"max-h-52 overflow-auto\"\n role=\"presentation\"\n >\n @for (option of filteredOptions(); track option.id) {\n <s-select-option\n [id]=\"option.id\"\n [label]=\"getOptionLabel(option.data)\"\n [multiple]=\"multiple()\"\n [checkmark]=\"checkmark()\"\n [checkIconClass]=\"checkIconClass()\"\n [isSelected]=\"isOptionSelected(option)\"\n (selected)=\"selectItem(option)\"\n [isGrouper]=\"option.grouper ?? false\"\n [isFocused]=\"option.data === focusedItem()\"\n [groupAnnouncement]=\"groupAnnouncementFor(option.id)\"\n [posinset]=\"optionPosinsetFor(option.id)\"\n [setsize]=\"optionSetsizeFor(option.id)\"\n ></s-select-option>\n }\n </ul>\n }\n\n @if (filteredOptions().length === 0) {\n <span class=\"m-3 text-grayscale-60\">\n {{ emptyMessage() ?? 'platform.angular_components.no_records_found' | translate }}\n </span>\n }\n </div>\n</ng-template>\n\n", styles: [":host.ng-dirty.ng-invalid{.dropdown-container,.select-container{border-color:#c13018;outline:none}}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.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: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i2.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i2.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i2.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "component", type: SelectOptionComponent, selector: "s-select-option", inputs: ["id", "label", "multiple", "checkmark", "isGrouper", "isFocused", "isSelected", "checkIconClass", "groupAnnouncement", "posinset", "setsize"], outputs: ["isSelectedChange", "selected"] }, { kind: "component", type: CheckboxComponent, selector: "s-checkbox", inputs: ["disabled", "checked", "indeterminate", "label", "ariaLabel", "ariaLabelledBy"], outputs: ["disabledChange", "checkedChange"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i4.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i4.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }], animations: [
|
|
876
1121
|
trigger('visibilityChanged', [
|
|
877
1122
|
state('void', style({ opacity: 0, transform: 'scale(0.95)' })),
|
|
878
1123
|
transition(':enter', [
|
|
@@ -911,7 +1156,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
911
1156
|
animate('500ms cubic-bezier(0.4, 0, 0.2, 1)', style({ opacity: 0, transform: 'scale(0.95)' })),
|
|
912
1157
|
]),
|
|
913
1158
|
]),
|
|
914
|
-
], template: "<div\n #containerDiv\n cdkOverlayOrigin\n #trigger=\"cdkOverlayOrigin\"\n class=\"select-container group flex h-[35px] w-full overflow-hidden rounded-[3px] border outline-
|
|
1159
|
+
], template: "<div\n #containerDiv\n cdkOverlayOrigin\n #trigger=\"cdkOverlayOrigin\"\n class=\"select-container group flex h-[35px] w-full overflow-hidden rounded-[3px] border outline-2 outline-[var(--s-focus-ring-color,#0090ff)] outline-offset-[-2px] focus-visible:outline\"\n [class.pointer-events-none]=\"disabled()\"\n [class.border-grayscale-20]=\"disabled()\"\n [class.bg-grayscale-5]=\"disabled()\"\n [class.pointer-events-auto]=\"!disabled()\"\n [class.border-grayscale-30]=\"!disabled()\"\n [class.bg-grayscale-0]=\"!disabled()\"\n [attr.aria-disabled]=\"disabled()\"\n [attr.aria-expanded]=\"showOptions()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-controls]=\"componentId + '-listbox'\"\n [attr.aria-owns]=\"showOptions() ? componentId + '-listbox' : null\"\n [attr.aria-activedescendant]=\"showOptions() ? focusedOptionId() : null\"\n role=\"combobox\"\n [id]=\"componentId + '-combobox'\"\n (click)=\"onContainerDivClick()\"\n (keydown)=\"onKeyDown($event)\"\n [attr.tabindex]=\"disabled() ? -1 : 0\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-labelledby]=\"resolvedAriaLabelledBy() || null\"\n>\n <span\n class=\"flex flex-grow select-none px-3 py-[7px]\"\n [class.text-grayscale-90]=\"!disabled() && !isClean()\"\n [class.text-grayscale-60]=\"!disabled() && isClean()\"\n [class.text-grayscale-30]=\"disabled()\"\n >\n {{ print() || placeholder() }}\n </span>\n\n @if (!disabled() && showClear() && !isClean()) {\n <button\n class=\"mx-3\"\n (click)=\"clear($event)\"\n (keydown)=\"$event.stopPropagation()\"\n [attr.aria-label]=\"'platform.angular_components.aria_clear_selection' | translate\"\n >\n <i\n class=\"fas fa-times flex items-center\"\n aria-hidden=\"true\"\n ></i>\n </button>\n }\n\n <div\n class=\"dropdown-container flex items-center border-l px-3 group-hover:bg-grayscale-10\"\n [class.border-grayscale-20]=\"disabled()\"\n [class.border-grayscale-30]=\"!disabled()\"\n >\n <i\n class=\"fas\"\n aria-hidden=\"true\"\n [class.text-grayscale-30]=\"disabled()\"\n [class.text-grayscale-90]=\"!disabled()\"\n [class.fa-caret-down]=\"!showOptions()\"\n [class.fa-caret-up]=\"showOptions()\"\n ></i>\n </div>\n</div>\n\n<ng-template\n #dropdownTemplate\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"trigger\"\n [cdkConnectedOverlayOpen]=\"showOptions()\"\n [cdkConnectedOverlayHasBackdrop]=\"true\"\n (backdropClick)=\"_closeDropdown()\"\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n (detach)=\"_closeDropdown()\"\n>\n <div\n #dropdownPanel\n [id]=\"componentId + '-listbox'\"\n class=\"dropdown-body-class z-[1000] rounded-[3px] bg-grayscale-0 py-1 shadow-md\"\n (click)=\"$event.stopPropagation()\"\n [style.width.px]=\"containerDiv.offsetWidth\"\n [attr.aria-multiselectable]=\"multiple() ? true : null\"\n [@visibilityChanged]\n role=\"listbox\"\n >\n @if (filter()) {\n <div class=\"flex w-full items-center gap-3 px-3 py-1.5\">\n @if (multiple()) {\n <s-checkbox\n #selectAllCheckbox\n [checked]=\"allSelected()\"\n [indeterminate]=\"selectAllIsIndeterminate()\"\n [ariaLabel]=\"selectAllCheckboxAriaLabel()\"\n (checkedChange)=\"toggleSelectAll()\"\n ></s-checkbox>\n }\n\n <div class=\"relative flex h-[35px] grow\">\n <form\n class=\"flex w-full grow\"\n [formGroup]=\"filterForm\"\n >\n <input\n #filterInput\n class=\"w-full grow rounded-[3px] border border-grayscale-30 pl-2.5 pr-7 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-[var(--s-focus-ring-color,#0090ff)]\"\n type=\"text\"\n formControlName=\"filter\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"onFilterKeyDown($event)\"\n [attr.aria-label]=\"'platform.angular_components.filter_options' | translate\"\n [attr.aria-controls]=\"componentId + '-listbox'\"\n [attr.aria-activedescendant]=\"focusedOptionId()\"\n autocomplete=\"off\"\n />\n </form>\n <i\n class=\"fas fa-search absolute right-2.5 top-2.5 text-grayscale-90\"\n aria-hidden=\"true\"\n ></i>\n </div>\n <button\n (click)=\"clearFilter()\"\n (keydown)=\"onClearFilterButtonKeyDown($event)\"\n [attr.aria-label]=\"'platform.angular_components.clear_filter' | translate\"\n >\n <i\n class=\"fas fa-times flex items-center\"\n aria-hidden=\"true\"\n ></i>\n </button>\n </div>\n }\n\n <!-- Virtual scroll -->\n @if (virtualScroll() && filteredOptions().length > 10) {\n <cdk-virtual-scroll-viewport\n [itemSize]=\"virtualScrollItemSize()\"\n class=\"h-52 overflow-auto\"\n role=\"presentation\"\n >\n <ng-container *cdkVirtualFor=\"let option of filteredOptions(); trackBy: trackById\">\n <s-select-option\n [id]=\"option.id\"\n [label]=\"getOptionLabel(option.data)\"\n [multiple]=\"multiple()\"\n [checkmark]=\"checkmark()\"\n [checkIconClass]=\"checkIconClass()\"\n [isSelected]=\"isOptionSelected(option)\"\n (selected)=\"selectItem(option)\"\n [isGrouper]=\"option.grouper ?? false\"\n [isFocused]=\"option.data === focusedItem()\"\n [groupAnnouncement]=\"groupAnnouncementFor(option.id)\"\n [posinset]=\"optionPosinsetFor(option.id)\"\n [setsize]=\"optionSetsizeFor(option.id)\"\n ></s-select-option>\n </ng-container>\n </cdk-virtual-scroll-viewport>\n }\n\n <!-- Normal list -->\n @if (!virtualScroll() || filteredOptions().length <= 10) {\n <ul\n class=\"max-h-52 overflow-auto\"\n role=\"presentation\"\n >\n @for (option of filteredOptions(); track option.id) {\n <s-select-option\n [id]=\"option.id\"\n [label]=\"getOptionLabel(option.data)\"\n [multiple]=\"multiple()\"\n [checkmark]=\"checkmark()\"\n [checkIconClass]=\"checkIconClass()\"\n [isSelected]=\"isOptionSelected(option)\"\n (selected)=\"selectItem(option)\"\n [isGrouper]=\"option.grouper ?? false\"\n [isFocused]=\"option.data === focusedItem()\"\n [groupAnnouncement]=\"groupAnnouncementFor(option.id)\"\n [posinset]=\"optionPosinsetFor(option.id)\"\n [setsize]=\"optionSetsizeFor(option.id)\"\n ></s-select-option>\n }\n </ul>\n }\n\n @if (filteredOptions().length === 0) {\n <span class=\"m-3 text-grayscale-60\">\n {{ emptyMessage() ?? 'platform.angular_components.no_records_found' | translate }}\n </span>\n }\n </div>\n</ng-template>\n\n", styles: [":host.ng-dirty.ng-invalid{.dropdown-container,.select-container{border-color:#c13018;outline:none}}\n"] }]
|
|
915
1160
|
}], ctorParameters: () => [] });
|
|
916
1161
|
|
|
917
1162
|
/**
|