@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.
Files changed (170) hide show
  1. package/card/lib/card/card.component.d.ts +6 -2
  2. package/country-phone-picker/lib/country-phone-picker/country-phone-picker.component.d.ts +0 -1
  3. package/datepicker/README.md +6 -2
  4. package/datepicker/lib/datepicker/date-segment-input.d.ts +7 -1
  5. package/datepicker/lib/datepicker/datepicker.component.d.ts +137 -3
  6. package/dialog/lib/src/dialog/services/dialog.service.d.ts +2 -0
  7. package/esm2022/accordion/lib/accordion/components/accordion-panel/accordion-panel.component.mjs +3 -3
  8. package/esm2022/autocomplete/lib/autocomplete/autocomplete.component.mjs +8 -3
  9. package/esm2022/autocomplete/lib/autocomplete/components/autocomplete-chip/autocomplete-chip.component.mjs +4 -3
  10. package/esm2022/breadcrumb/lib/breadcrumb/breadcrumb.component.mjs +3 -3
  11. package/esm2022/breadcrumb/lib/dynamic-breadcrumb/dynamic-breadcrumb.component.mjs +4 -2
  12. package/esm2022/button/lib/button/button.component.mjs +3 -3
  13. package/esm2022/card/lib/card/card.component.mjs +13 -5
  14. package/esm2022/checkbox/lib/checkbox/checkbox.component.mjs +3 -3
  15. package/esm2022/checkbox-list/lib/checkbox-list/checkbox-list.component.mjs +3 -3
  16. package/esm2022/chips/lib/chips/chip-item/chip-item.component.mjs +3 -3
  17. package/esm2022/chips/lib/chips/chips/chips.component.mjs +3 -3
  18. package/esm2022/code-editor/lib/code-editor/infra/cores/codemirror-6/themes/sds-light/sds-light-style.mjs +3 -2
  19. package/esm2022/collapse-link/lib/collapse-link/collapse-link.component.mjs +3 -3
  20. package/esm2022/country-phone-picker/lib/country-phone-picker/country-phone-picker.component.mjs +14 -9
  21. package/esm2022/datepicker/lib/datepicker/date-segment-input.mjs +19 -2
  22. package/esm2022/datepicker/lib/datepicker/datepicker.component.mjs +405 -38
  23. package/esm2022/dialog/lib/src/dialog/dialog.component.mjs +3 -3
  24. package/esm2022/dialog/lib/src/dialog/services/dialog.service.mjs +13 -1
  25. package/esm2022/drawer/lib/drawer/drawer.component.mjs +3 -3
  26. package/esm2022/dynamic-form/dynamic-form/components/lookup/lookup.component.mjs +3 -3
  27. package/esm2022/dynamic-form/dynamic-form/form-field/fields/bignumber/bignumber-field.component.mjs +3 -3
  28. package/esm2022/dynamic-form/dynamic-form/form-field/fields/currency/currency-field.component.mjs +3 -3
  29. package/esm2022/dynamic-form/dynamic-form/form-field/fields/number/number-field.component.mjs +3 -3
  30. package/esm2022/dynamic-form/dynamic-form/form-field/fields/password/password-field.component.mjs +3 -3
  31. package/esm2022/dynamic-form/dynamic-form/form-field/fields/text/text-field.component.mjs +3 -3
  32. package/esm2022/editor/lib/editor/editor.component.mjs +3 -3
  33. package/esm2022/editor/lib/editor/toolbar-button/editor-toolbar-button.component.mjs +3 -3
  34. package/esm2022/fieldset/lib/fieldset/fieldset.component.mjs +3 -3
  35. package/esm2022/file-picker/lib/file-picker/file-picker.component.mjs +3 -3
  36. package/esm2022/gantt/lib/gantt/components/gantt/bar.mjs +2 -1
  37. package/esm2022/gantt/lib/gantt/gantt.component.mjs +3 -3
  38. package/esm2022/global-search/lib/global-search/global-search.component.mjs +16 -25
  39. package/esm2022/grid-menu/lib/grid-menu/components/grid-menu-item/grid-menu-item.component.mjs +3 -3
  40. package/esm2022/input-text/lib/input-text/input-text.directive.mjs +3 -3
  41. package/esm2022/lib/locale/fallback.mjs +47 -13
  42. package/esm2022/navigation-button/lib/navigation-button/navigation-button.component.mjs +3 -3
  43. package/esm2022/object-card/lib/object-card/object-card.component.mjs +3 -3
  44. package/esm2022/paginator/lib/paginator/paginator.component.mjs +3 -3
  45. package/esm2022/pin-code-field/lib/pin-code-field/pin-code-field.component.mjs +3 -3
  46. package/esm2022/radio-button/lib/radio-button/radio-button.component.mjs +4 -10
  47. package/esm2022/rating-scale/lib/rating-scale/rating-scale.component.mjs +3 -3
  48. package/esm2022/select/lib/select/components/select-option/select-option.component.mjs +31 -3
  49. package/esm2022/select/lib/select/select.component.mjs +240 -23
  50. package/esm2022/select-button/lib/select-button/components/select-button-item/select-button-item.component.mjs +3 -3
  51. package/esm2022/shared/lib/accessibility/datepicker-no-period-tab.directive.mjs +6 -6
  52. package/esm2022/side-sheet/lib/side-sheet/side-sheet.component.mjs +3 -3
  53. package/esm2022/slider/lib/slider/slider.component.mjs +67 -65
  54. package/esm2022/slider/lib/slider/slider.module.mjs +2 -2
  55. package/esm2022/split-button/lib/split-button/split-button.component.mjs +3 -3
  56. package/esm2022/star-rating/star-rating/star-rating.component.mjs +3 -3
  57. package/esm2022/stepper/lib/stepper/stepper.component.mjs +3 -3
  58. package/esm2022/switch/lib/switch/switch.component.mjs +5 -19
  59. package/esm2022/tabs/lib/tabs/tabs.component.mjs +3 -3
  60. package/esm2022/text-area/lib/text-area/text-area.component.mjs +3 -3
  61. package/esm2022/thumbnail/lib/thumbnail/thumbnail.component.mjs +10 -3
  62. package/esm2022/thumbnails/lib/thumbnails/components/thumbnail-item/thumbnail-item.component.mjs +3 -3
  63. package/esm2022/tiered-menu/lib/tiered-menu/components/tiered-menu-item/tiered-menu-item.component.mjs +3 -3
  64. package/esm2022/tiered-menu/lib/tiered-menu/services/tiered-menu.global.service.mjs +4 -3
  65. package/esm2022/tile/lib/tile/tile.component.mjs +3 -3
  66. package/esm2022/tree/lib/tree/components/tree-node-item/tree-node-item.component.mjs +20 -3
  67. package/esm2022/workspace-switch/lib/workspace-switch/workspace-switch.component.mjs +3 -3
  68. package/fesm2022/seniorsistemas-angular-components-accordion.mjs +2 -2
  69. package/fesm2022/seniorsistemas-angular-components-accordion.mjs.map +1 -1
  70. package/fesm2022/seniorsistemas-angular-components-autocomplete.mjs +10 -5
  71. package/fesm2022/seniorsistemas-angular-components-autocomplete.mjs.map +1 -1
  72. package/fesm2022/seniorsistemas-angular-components-breadcrumb.mjs +5 -3
  73. package/fesm2022/seniorsistemas-angular-components-breadcrumb.mjs.map +1 -1
  74. package/fesm2022/seniorsistemas-angular-components-button.mjs +2 -2
  75. package/fesm2022/seniorsistemas-angular-components-button.mjs.map +1 -1
  76. package/fesm2022/seniorsistemas-angular-components-card.mjs +12 -4
  77. package/fesm2022/seniorsistemas-angular-components-card.mjs.map +1 -1
  78. package/fesm2022/seniorsistemas-angular-components-checkbox-list.mjs +2 -2
  79. package/fesm2022/seniorsistemas-angular-components-checkbox-list.mjs.map +1 -1
  80. package/fesm2022/seniorsistemas-angular-components-checkbox.mjs +2 -2
  81. package/fesm2022/seniorsistemas-angular-components-checkbox.mjs.map +1 -1
  82. package/fesm2022/seniorsistemas-angular-components-chips.mjs +4 -4
  83. package/fesm2022/seniorsistemas-angular-components-chips.mjs.map +1 -1
  84. package/fesm2022/seniorsistemas-angular-components-code-editor.mjs +2 -1
  85. package/fesm2022/seniorsistemas-angular-components-code-editor.mjs.map +1 -1
  86. package/fesm2022/seniorsistemas-angular-components-collapse-link.mjs +2 -2
  87. package/fesm2022/seniorsistemas-angular-components-collapse-link.mjs.map +1 -1
  88. package/fesm2022/seniorsistemas-angular-components-country-phone-picker.mjs +13 -8
  89. package/fesm2022/seniorsistemas-angular-components-country-phone-picker.mjs.map +1 -1
  90. package/fesm2022/seniorsistemas-angular-components-datepicker.mjs +422 -38
  91. package/fesm2022/seniorsistemas-angular-components-datepicker.mjs.map +1 -1
  92. package/fesm2022/seniorsistemas-angular-components-dialog.mjs +14 -2
  93. package/fesm2022/seniorsistemas-angular-components-dialog.mjs.map +1 -1
  94. package/fesm2022/seniorsistemas-angular-components-drawer.mjs +2 -2
  95. package/fesm2022/seniorsistemas-angular-components-drawer.mjs.map +1 -1
  96. package/fesm2022/seniorsistemas-angular-components-dynamic-form.mjs +12 -12
  97. package/fesm2022/seniorsistemas-angular-components-dynamic-form.mjs.map +1 -1
  98. package/fesm2022/seniorsistemas-angular-components-editor.mjs +4 -4
  99. package/fesm2022/seniorsistemas-angular-components-editor.mjs.map +1 -1
  100. package/fesm2022/seniorsistemas-angular-components-fieldset.mjs +2 -2
  101. package/fesm2022/seniorsistemas-angular-components-fieldset.mjs.map +1 -1
  102. package/fesm2022/seniorsistemas-angular-components-file-picker.mjs +2 -2
  103. package/fesm2022/seniorsistemas-angular-components-file-picker.mjs.map +1 -1
  104. package/fesm2022/seniorsistemas-angular-components-gantt.mjs +3 -2
  105. package/fesm2022/seniorsistemas-angular-components-gantt.mjs.map +1 -1
  106. package/fesm2022/seniorsistemas-angular-components-global-search.mjs +14 -23
  107. package/fesm2022/seniorsistemas-angular-components-global-search.mjs.map +1 -1
  108. package/fesm2022/seniorsistemas-angular-components-grid-menu.mjs +2 -2
  109. package/fesm2022/seniorsistemas-angular-components-grid-menu.mjs.map +1 -1
  110. package/fesm2022/seniorsistemas-angular-components-input-text.mjs +2 -2
  111. package/fesm2022/seniorsistemas-angular-components-input-text.mjs.map +1 -1
  112. package/fesm2022/seniorsistemas-angular-components-navigation-button.mjs +2 -2
  113. package/fesm2022/seniorsistemas-angular-components-navigation-button.mjs.map +1 -1
  114. package/fesm2022/seniorsistemas-angular-components-object-card.mjs +2 -2
  115. package/fesm2022/seniorsistemas-angular-components-object-card.mjs.map +1 -1
  116. package/fesm2022/seniorsistemas-angular-components-paginator.mjs +2 -2
  117. package/fesm2022/seniorsistemas-angular-components-paginator.mjs.map +1 -1
  118. package/fesm2022/seniorsistemas-angular-components-pin-code-field.mjs +2 -2
  119. package/fesm2022/seniorsistemas-angular-components-pin-code-field.mjs.map +1 -1
  120. package/fesm2022/seniorsistemas-angular-components-radio-button.mjs +3 -9
  121. package/fesm2022/seniorsistemas-angular-components-radio-button.mjs.map +1 -1
  122. package/fesm2022/seniorsistemas-angular-components-rating-scale.mjs +2 -2
  123. package/fesm2022/seniorsistemas-angular-components-rating-scale.mjs.map +1 -1
  124. package/fesm2022/seniorsistemas-angular-components-select-button.mjs +2 -2
  125. package/fesm2022/seniorsistemas-angular-components-select-button.mjs.map +1 -1
  126. package/fesm2022/seniorsistemas-angular-components-select.mjs +269 -24
  127. package/fesm2022/seniorsistemas-angular-components-select.mjs.map +1 -1
  128. package/fesm2022/seniorsistemas-angular-components-shared.mjs +5 -5
  129. package/fesm2022/seniorsistemas-angular-components-shared.mjs.map +1 -1
  130. package/fesm2022/seniorsistemas-angular-components-side-sheet.mjs +2 -2
  131. package/fesm2022/seniorsistemas-angular-components-side-sheet.mjs.map +1 -1
  132. package/fesm2022/seniorsistemas-angular-components-slider.mjs +67 -65
  133. package/fesm2022/seniorsistemas-angular-components-slider.mjs.map +1 -1
  134. package/fesm2022/seniorsistemas-angular-components-split-button.mjs +2 -2
  135. package/fesm2022/seniorsistemas-angular-components-split-button.mjs.map +1 -1
  136. package/fesm2022/seniorsistemas-angular-components-star-rating.mjs +2 -2
  137. package/fesm2022/seniorsistemas-angular-components-star-rating.mjs.map +1 -1
  138. package/fesm2022/seniorsistemas-angular-components-stepper.mjs +2 -2
  139. package/fesm2022/seniorsistemas-angular-components-stepper.mjs.map +1 -1
  140. package/fesm2022/seniorsistemas-angular-components-switch.mjs +4 -18
  141. package/fesm2022/seniorsistemas-angular-components-switch.mjs.map +1 -1
  142. package/fesm2022/seniorsistemas-angular-components-tabs.mjs +2 -2
  143. package/fesm2022/seniorsistemas-angular-components-tabs.mjs.map +1 -1
  144. package/fesm2022/seniorsistemas-angular-components-text-area.mjs +2 -2
  145. package/fesm2022/seniorsistemas-angular-components-text-area.mjs.map +1 -1
  146. package/fesm2022/seniorsistemas-angular-components-thumbnail.mjs +9 -2
  147. package/fesm2022/seniorsistemas-angular-components-thumbnail.mjs.map +1 -1
  148. package/fesm2022/seniorsistemas-angular-components-thumbnails.mjs +2 -2
  149. package/fesm2022/seniorsistemas-angular-components-thumbnails.mjs.map +1 -1
  150. package/fesm2022/seniorsistemas-angular-components-tiered-menu.mjs +5 -4
  151. package/fesm2022/seniorsistemas-angular-components-tiered-menu.mjs.map +1 -1
  152. package/fesm2022/seniorsistemas-angular-components-tile.mjs +2 -2
  153. package/fesm2022/seniorsistemas-angular-components-tile.mjs.map +1 -1
  154. package/fesm2022/seniorsistemas-angular-components-tree.mjs +20 -3
  155. package/fesm2022/seniorsistemas-angular-components-tree.mjs.map +1 -1
  156. package/fesm2022/seniorsistemas-angular-components-workspace-switch.mjs +2 -2
  157. package/fesm2022/seniorsistemas-angular-components-workspace-switch.mjs.map +1 -1
  158. package/fesm2022/seniorsistemas-angular-components.mjs +46 -12
  159. package/fesm2022/seniorsistemas-angular-components.mjs.map +1 -1
  160. package/global-search/lib/global-search/global-search.component.d.ts +7 -3
  161. package/package.json +1 -1
  162. package/radio-button/lib/radio-button/radio-button.component.d.ts +0 -1
  163. package/select/README.md +16 -3
  164. package/select/lib/select/components/select-option/select-option.component.d.ts +23 -1
  165. package/select/lib/select/select.component.d.ts +111 -17
  166. package/slider/lib/slider/slider.component.d.ts +25 -7
  167. package/styles.css +5 -0
  168. package/switch/lib/switch/switch.component.d.ts +0 -2
  169. package/thumbnail/lib/thumbnail/thumbnail.component.d.ts +1 -0
  170. 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, inject, ElementRef, Renderer2, effect, forwardRef } from '@angular/core';
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 [checked]=\"isSelected()\"></s-checkbox>\n }\n <span\n [class]=\"isSelected() ? 'text-[#428BCA]' : 'text-grayscale-90'\"\n [class.font-bold]=\"isGrouper()\"\n >\n {{ label() }}\n </span>\n</div>\n\n", dependencies: [{ kind: "component", type: CheckboxComponent, selector: "s-checkbox", inputs: ["disabled", "checked", "indeterminate", "label", "ariaLabel", "ariaLabelledBy"], outputs: ["disabledChange", "checkedChange"] }] });
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], 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 [checked]=\"isSelected()\"></s-checkbox>\n }\n <span\n [class]=\"isSelected() ? 'text-[#428BCA]' : 'text-grayscale-90'\"\n [class.font-bold]=\"isGrouper()\"\n >\n {{ label() }}\n </span>\n</div>\n\n" }]
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
- _selectAllCheckbox = viewChild('selectAllCheckbox');
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
- setTimeout(() => {
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': Seleciona a opção atualmente focada e fecha o dropdown.
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
- // Quando há filtro aberto, Tab deve navegar para dentro do overlay (não fechar)
564
- if (this.showOptions() && this.filter()) {
565
- return;
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
- * @private
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 item. Caso a opção seja um grupo
749
- * (detectado pela presença de um array `items`), adiciona também a propriedade `grouper: true`.
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: `id_${timestamp}_${index++}`,
1005
+ id: `${this.componentId}_id_${timestamp}_${index++}`,
761
1006
  grouper: true,
762
1007
  };
763
1008
  }
764
1009
  return {
765
1010
  data: option,
766
- id: `id_${timestamp}_${index++}`,
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
- setTimeout(() => this._scrollToFocusedOption());
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-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"] }]
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
  /**