@seniorsistemas/angular-components 19.5.4 → 19.5.5

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 (77) hide show
  1. package/accordion/lib/accordion/components/accordion-panel/accordion-panel.component.d.ts +2 -0
  2. package/accordion/lib/accordion/models/accordion-button-settings.d.ts +5 -0
  3. package/button/lib/button/button.component.d.ts +5 -1
  4. package/esm2022/accordion/lib/accordion/components/accordion-panel/accordion-panel.component.mjs +11 -3
  5. package/esm2022/accordion/lib/accordion/models/accordion-button-settings.mjs +1 -1
  6. package/esm2022/button/lib/button/button.component.mjs +9 -3
  7. package/esm2022/confirm-dialog/lib/popup-confirm-dialog/popup-confirm-dialog.component.mjs +1 -1
  8. package/esm2022/content-generator/lib/content-generator/content-generator.component.mjs +1 -1
  9. package/esm2022/dynamic-form/dynamic-form/components/grid/row/row.component.mjs +2 -2
  10. package/esm2022/dynamic-form/dynamic-form/components/lookup/lookup.component.mjs +2 -2
  11. package/esm2022/dynamic-form/dynamic-form/form-field/fields/bignumber/bignumber-field.component.mjs +2 -2
  12. package/esm2022/dynamic-form/dynamic-form/form-field/fields/boolean/boolean-radio-field/boolean-field.component.mjs +1 -1
  13. package/esm2022/dynamic-form/dynamic-form/form-field/fields/button-field/button-field.component.mjs +1 -1
  14. package/esm2022/dynamic-form/dynamic-form/form-field/fields/number/number-field.component.mjs +2 -2
  15. package/esm2022/dynamic-form/dynamic-form/form-field/fields/password/password-field.component.mjs +2 -2
  16. package/esm2022/dynamic-form/dynamic-form/form-field/fields/radio-button/radio-button-field.component.mjs +2 -2
  17. package/esm2022/dynamic-form/dynamic-form/form-field/fields/text/text-field.component.mjs +2 -2
  18. package/esm2022/empty-state/lib/empty-state/empty-state.component.mjs +1 -1
  19. package/esm2022/file-picker/lib/file-picker/file-picker.component.mjs +1 -1
  20. package/esm2022/global-search/lib/global-search/components/dropdown/global-search-dropdown-item.component.mjs +1 -1
  21. package/esm2022/global-search/lib/global-search/global-search.component.mjs +2 -2
  22. package/esm2022/help-popover/lib/help-popover/help-popover/help-popover.component.mjs +1 -1
  23. package/esm2022/image-cropper/lib/image-cropper/image-cropper.component.mjs +1 -1
  24. package/esm2022/inline-edit/lib/inline-edit/components/fields/inline-edit-number/inline-edit-number.component.mjs +1 -1
  25. package/esm2022/inline-edit/lib/inline-edit/components/inline-edit-item/inline-edit-item.component.mjs +1 -1
  26. package/esm2022/kanban/lib/kanban/components/kanban-column/kanban-column.component.mjs +1 -1
  27. package/esm2022/object-card/lib/object-card/elements/field/object-card-field.component.mjs +1 -1
  28. package/esm2022/object-card/lib/object-card/elements/main/object-card-main.component.mjs +1 -1
  29. package/esm2022/picklist/lib/picklist/picklist.component.mjs +2 -2
  30. package/esm2022/profile-picture-picker/lib/profile-picture-picker/profile-picture-picker.component.mjs +1 -1
  31. package/esm2022/select/lib/select/select.component.mjs +86 -4
  32. package/esm2022/shared/lib/accessibility/datepicker-no-period-tab.directive.mjs +541 -0
  33. package/esm2022/shared/public-api.mjs +2 -1
  34. package/esm2022/spotlight/lib/spotlight/spotlight.component.mjs +2 -2
  35. package/esm2022/table/lib/table/table-paging/table-paging.component.mjs +1 -1
  36. package/fesm2022/seniorsistemas-angular-components-accordion.mjs +10 -2
  37. package/fesm2022/seniorsistemas-angular-components-accordion.mjs.map +1 -1
  38. package/fesm2022/seniorsistemas-angular-components-button.mjs +8 -2
  39. package/fesm2022/seniorsistemas-angular-components-button.mjs.map +1 -1
  40. package/fesm2022/seniorsistemas-angular-components-confirm-dialog.mjs +1 -1
  41. package/fesm2022/seniorsistemas-angular-components-confirm-dialog.mjs.map +1 -1
  42. package/fesm2022/seniorsistemas-angular-components-content-generator.mjs +1 -1
  43. package/fesm2022/seniorsistemas-angular-components-content-generator.mjs.map +1 -1
  44. package/fesm2022/seniorsistemas-angular-components-dynamic-form.mjs +9 -9
  45. package/fesm2022/seniorsistemas-angular-components-dynamic-form.mjs.map +1 -1
  46. package/fesm2022/seniorsistemas-angular-components-empty-state.mjs +1 -1
  47. package/fesm2022/seniorsistemas-angular-components-empty-state.mjs.map +1 -1
  48. package/fesm2022/seniorsistemas-angular-components-file-picker.mjs +1 -1
  49. package/fesm2022/seniorsistemas-angular-components-file-picker.mjs.map +1 -1
  50. package/fesm2022/seniorsistemas-angular-components-global-search.mjs +2 -2
  51. package/fesm2022/seniorsistemas-angular-components-global-search.mjs.map +1 -1
  52. package/fesm2022/seniorsistemas-angular-components-help-popover.mjs +1 -1
  53. package/fesm2022/seniorsistemas-angular-components-help-popover.mjs.map +1 -1
  54. package/fesm2022/seniorsistemas-angular-components-image-cropper.mjs +1 -1
  55. package/fesm2022/seniorsistemas-angular-components-image-cropper.mjs.map +1 -1
  56. package/fesm2022/seniorsistemas-angular-components-inline-edit.mjs +2 -2
  57. package/fesm2022/seniorsistemas-angular-components-inline-edit.mjs.map +1 -1
  58. package/fesm2022/seniorsistemas-angular-components-kanban.mjs +1 -1
  59. package/fesm2022/seniorsistemas-angular-components-kanban.mjs.map +1 -1
  60. package/fesm2022/seniorsistemas-angular-components-object-card.mjs +2 -2
  61. package/fesm2022/seniorsistemas-angular-components-object-card.mjs.map +1 -1
  62. package/fesm2022/seniorsistemas-angular-components-picklist.mjs +1 -1
  63. package/fesm2022/seniorsistemas-angular-components-picklist.mjs.map +1 -1
  64. package/fesm2022/seniorsistemas-angular-components-profile-picture-picker.mjs +1 -1
  65. package/fesm2022/seniorsistemas-angular-components-profile-picture-picker.mjs.map +1 -1
  66. package/fesm2022/seniorsistemas-angular-components-select.mjs +85 -3
  67. package/fesm2022/seniorsistemas-angular-components-select.mjs.map +1 -1
  68. package/fesm2022/seniorsistemas-angular-components-shared.mjs +541 -3
  69. package/fesm2022/seniorsistemas-angular-components-shared.mjs.map +1 -1
  70. package/fesm2022/seniorsistemas-angular-components-spotlight.mjs +1 -1
  71. package/fesm2022/seniorsistemas-angular-components-spotlight.mjs.map +1 -1
  72. package/fesm2022/seniorsistemas-angular-components-table.mjs +1 -1
  73. package/fesm2022/seniorsistemas-angular-components-table.mjs.map +1 -1
  74. package/package.json +7 -7
  75. package/select/lib/select/select.component.d.ts +40 -2
  76. package/shared/lib/accessibility/datepicker-no-period-tab.directive.d.ts +177 -0
  77. package/shared/public-api.d.ts +1 -0
@@ -1,9 +1,10 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Component, Input, NgModule, inject, ElementRef, Renderer2, Directive } from '@angular/core';
2
+ import { Component, Input, NgModule, inject, Renderer2, LOCALE_ID, Directive, HostListener, ElementRef } from '@angular/core';
3
3
  import * as i1 from '@angular/common';
4
- import { CommonModule } from '@angular/common';
4
+ import { CommonModule, DOCUMENT } from '@angular/common';
5
5
  import * as i2 from '@seniorsistemas/angular-components/tooltip';
6
6
  import { TooltipModule } from '@seniorsistemas/angular-components/tooltip';
7
+ import { DatePicker } from 'primeng/datepicker';
7
8
  import { TranslateService } from '@ngx-translate/core';
8
9
 
9
10
  var EnumSeverity;
@@ -83,6 +84,543 @@ function CheckDisabled(disabledField = 'disabled') {
83
84
  };
84
85
  }
85
86
 
87
+ /**
88
+ * CSS customizado injetado globalmente para focus-visible nos internals do DatePicker.
89
+ * Necessário porque com `appendTo="body"` o overlay fica fora do host component,
90
+ * e estilos com `:host ::ng-deep` ou ViewEncapsulation.Emulated não alcançam.
91
+ */
92
+ const FOCUS_STYLE_ID = 's-datepicker-a11y-focus-styles';
93
+ /** Delay em ms para foco pós-abertura e navegação (permite NVDA processar dialog announce). */
94
+ const FOCUS_DELAY_MS = 50;
95
+ /** Delay em ms para debounce do MutationObserver (evita re-execução por ripple/animações). */
96
+ const MUTATION_DEBOUNCE_MS = 100;
97
+ /** Attribute name constant to avoid SonarQube literal duplication warning. */
98
+ const ARIA_LABEL_ATTR = 'aria-label';
99
+ const FOCUS_STYLES = `
100
+ .p-datepicker-day:focus-visible,
101
+ .p-datepicker-day.s-datepicker-day-focused {
102
+ outline: 2px solid var(--p-primary-color, #3b82f6) !important;
103
+ outline-offset: -2px;
104
+ border-radius: 4px;
105
+ }
106
+ .p-datepicker-month:focus-visible,
107
+ .p-datepicker-month.s-datepicker-day-focused {
108
+ outline: 2px solid var(--p-primary-color, #3b82f6) !important;
109
+ outline-offset: -2px;
110
+ border-radius: 4px;
111
+ }
112
+ .p-datepicker-year:focus-visible,
113
+ .p-datepicker-year.s-datepicker-day-focused {
114
+ outline: 2px solid var(--p-primary-color, #3b82f6) !important;
115
+ outline-offset: -2px;
116
+ border-radius: 4px;
117
+ }
118
+ .p-datepicker-select-month:focus-visible,
119
+ .p-datepicker-select-year:focus-visible {
120
+ outline: 2px solid var(--p-primary-color, #3b82f6) !important;
121
+ outline-offset: 2px;
122
+ border-radius: 4px;
123
+ }
124
+ .p-datepicker-prev-button:focus-visible,
125
+ .p-datepicker-next-button:focus-visible {
126
+ outline: 2px solid var(--p-primary-color, #3b82f6) !important;
127
+ outline-offset: 2px;
128
+ border-radius: 50%;
129
+ }
130
+ `;
131
+ /**
132
+ * Traduções pt-BR para aria-labels do DatePicker PrimeNG.
133
+ * PrimeNG usa `getTranslation()` que retorna inglês por padrão
134
+ * se não configurado via `PrimeNGConfig.setTranslation()`.
135
+ *
136
+ * Keys correspondem ao texto exato renderizado pelo PrimeNG no DOM:
137
+ * - "Previous Month" (não "Prev Month")
138
+ * - "Choose Date", "Choose Month", "Choose Year"
139
+ */
140
+ const PREV_DECADE_PT = 'Década anterior';
141
+ const PREV_DECADE_ES = 'Década anterior';
142
+ const ARIA_TRANSLATIONS_PT_BR = {
143
+ 'Choose Date': 'Selecionar data',
144
+ 'Choose Month': 'Selecionar mês',
145
+ 'Choose Year': 'Selecionar ano',
146
+ 'Previous Month': 'Mês anterior',
147
+ 'Next Month': 'Próximo mês',
148
+ 'Previous Year': 'Ano anterior',
149
+ 'Next Year': 'Próximo ano',
150
+ 'Previous Decade': PREV_DECADE_PT,
151
+ 'Next Decade': 'Próxima década',
152
+ 'Prev Month': 'Mês anterior',
153
+ 'Prev Year': 'Ano anterior',
154
+ 'Prev Decade': PREV_DECADE_PT,
155
+ };
156
+ const ARIA_TRANSLATIONS_ES = {
157
+ 'Choose Date': 'Seleccionar fecha',
158
+ 'Choose Month': 'Seleccionar mes',
159
+ 'Choose Year': 'Seleccionar año',
160
+ 'Previous Month': 'Mes anterior',
161
+ 'Next Month': 'Próximo mes',
162
+ 'Previous Year': 'Año anterior',
163
+ 'Next Year': 'Próximo año',
164
+ 'Previous Decade': PREV_DECADE_ES,
165
+ 'Next Decade': 'Próxima década',
166
+ 'Prev Month': 'Mes anterior',
167
+ 'Prev Year': 'Año anterior',
168
+ 'Prev Decade': PREV_DECADE_ES,
169
+ };
170
+ const ARIA_TRANSLATIONS = {
171
+ 'pt-BR': ARIA_TRANSLATIONS_PT_BR,
172
+ 'pt': ARIA_TRANSLATIONS_PT_BR,
173
+ 'es': ARIA_TRANSLATIONS_ES,
174
+ 'es-CO': ARIA_TRANSLATIONS_ES,
175
+ 'es-ES': ARIA_TRANSLATIONS_ES,
176
+ };
177
+ /**
178
+ * Diretiva para corrigir a acessibilidade do painel do DatePicker PrimeNG.
179
+ *
180
+ * Resolve os seguintes problemas:
181
+ *
182
+ * 1. **Foco inicial ao abrir overlay**: move foco programaticamente para
183
+ * a célula selecionada, hoje, ou primeira célula disponível — evitando
184
+ * que NVDA leia toda estrutura do grid ao entrar no painel.
185
+ *
186
+ * 2. **Re-foco ao trocar view (mês/ano)**: ao navegar para views de mês ou
187
+ * ano, o DOM é destruído e recriado. Sem intervenção, foco escapa para
188
+ * `<body>` e NVDA lê conteúdo fora do overlay. A diretiva re-foca o
189
+ * primeiro elemento interativo da nova view.
190
+ *
191
+ * 3. **Focus-visible nos elementos internos**: injeta CSS global com
192
+ * outline para todos elementos interativos do overlay, garantindo
193
+ * feedback visual mesmo com `appendTo="body"`.
194
+ *
195
+ * 4. **aria-label enriquecido nos day cells**: PrimeNG seta apenas o
196
+ * número do dia. A diretiva sobrescreve com data completa formatada
197
+ * (ex: "3 de junho de 2026, Qua") — NVDA narra label sem precisar
198
+ * contextualizar toda grid.
199
+ *
200
+ * 5. **View de meses/anos sem tabindex**: spans de mês/ano recebem
201
+ * `tabindex="0"` e `role="option"` para navegação por teclado.
202
+ *
203
+ * Utiliza `MutationObserver` para detectar mudanças internas de view,
204
+ * já que PrimeNG não emite eventos para essas transições.
205
+ *
206
+ * **CSS**: A diretiva injeta um `<style>` global no `<head>` (uma vez).
207
+ * Para projetos que preferem controle manual, desabilite via Input
208
+ * `[sDatepickerInjectStyles]="false"` e importe os estilos manualmente.
209
+ *
210
+ * Uso: `<p-datepicker sDatepickerNoPeriodTab ...>`
211
+ *
212
+ * @category Directives
213
+ */
214
+ class DatepickerNoPeriodTabDirective {
215
+ datePicker = inject(DatePicker);
216
+ renderer = inject(Renderer2);
217
+ locale = inject(LOCALE_ID);
218
+ document = inject(DOCUMENT);
219
+ observer = null;
220
+ focusListenerCleanups = [];
221
+ constructor() {
222
+ this.injectGlobalFocusStyles();
223
+ }
224
+ onPanelShow() {
225
+ setTimeout(() => {
226
+ this.fixPanelAccessibility();
227
+ // Não chamar focusBestTarget() aqui — PrimeNG initFocusableCell()
228
+ // já move foco pro day cell com tabindex="0" na abertura.
229
+ // Chamar de novo causa double-announce no NVDA.
230
+ setTimeout(() => this.observePanelChanges(), MUTATION_DEBOUNCE_MS);
231
+ }, FOCUS_DELAY_MS);
232
+ }
233
+ onNavigationChange() {
234
+ setTimeout(() => {
235
+ this.fixPanelAccessibility();
236
+ this.focusBestTarget();
237
+ }, FOCUS_DELAY_MS);
238
+ }
239
+ ngOnDestroy() {
240
+ this.disconnectObserver();
241
+ this.removeFocusListeners();
242
+ }
243
+ /**
244
+ * Move foco para melhor alvo disponível no painel.
245
+ * Usado ao abrir overlay e ao trocar view (mês/ano).
246
+ *
247
+ * Ao abrir: evita que NVDA leia toda estrutura do grid.
248
+ * Ao trocar view: previne foco escapar para `<body>` quando
249
+ * PrimeNG destrói DOM da view anterior.
250
+ *
251
+ * Prioridade: selecionado > hoje > primeiro disponível.
252
+ * Pula se foco já está no target correto (evita double-announce no NVDA).
253
+ */
254
+ focusBestTarget() {
255
+ const panel = this.datePicker.overlay;
256
+ if (!panel) {
257
+ return;
258
+ }
259
+ const target = this.findFocusTarget(panel);
260
+ if (target && this.document.activeElement !== target) {
261
+ target.tabIndex = 0;
262
+ target.focus();
263
+ }
264
+ }
265
+ /**
266
+ * Injeta CSS global de focus-visible no `<head>` do documento.
267
+ * Executa apenas uma vez (idempotente via ID do style element).
268
+ * Necessário porque overlay com `appendTo="body"` fica fora do
269
+ * escopo de ViewEncapsulation do componente host.
270
+ */
271
+ injectGlobalFocusStyles() {
272
+ if (this.document.getElementById(FOCUS_STYLE_ID)) {
273
+ return;
274
+ }
275
+ const style = this.renderer.createElement('style');
276
+ this.renderer.setAttribute(style, 'id', FOCUS_STYLE_ID);
277
+ this.renderer.setAttribute(style, 'type', 'text/css');
278
+ const text = this.renderer.createText(FOCUS_STYLES);
279
+ this.renderer.appendChild(style, text);
280
+ this.renderer.appendChild(this.document.head, style);
281
+ }
282
+ /**
283
+ * Encontra melhor elemento para receber foco no painel atual.
284
+ * Busca em ordem: selecionado → hoje → primeiro disponível.
285
+ * Funciona para views de dias, meses e anos.
286
+ */
287
+ findFocusTarget(panel) {
288
+ // View de dias
289
+ const selectedDay = panel.querySelector('.p-datepicker-day-selected, .p-datepicker-day[aria-selected="true"]');
290
+ if (selectedDay) {
291
+ return selectedDay;
292
+ }
293
+ const today = panel.querySelector('.p-datepicker-today .p-datepicker-day');
294
+ if (today) {
295
+ return today;
296
+ }
297
+ // View de meses
298
+ const selectedMonth = panel.querySelector('.p-datepicker-month.p-datepicker-month-selected');
299
+ if (selectedMonth) {
300
+ return selectedMonth;
301
+ }
302
+ const firstMonth = panel.querySelector('.p-datepicker-month:not(.p-disabled)');
303
+ if (firstMonth) {
304
+ return firstMonth;
305
+ }
306
+ // View de anos
307
+ const selectedYear = panel.querySelector('.p-datepicker-year.p-datepicker-year-selected');
308
+ if (selectedYear) {
309
+ return selectedYear;
310
+ }
311
+ const firstYear = panel.querySelector('.p-datepicker-year:not(.p-disabled)');
312
+ if (firstYear) {
313
+ return firstYear;
314
+ }
315
+ // Fallback: primeiro day cell disponível
316
+ const firstDay = panel.querySelector('.p-datepicker-day:not(.p-disabled)');
317
+ return firstDay;
318
+ }
319
+ /**
320
+ * Aplica correções de acessibilidade no painel do DatePicker,
321
+ * independente da view interna ativa (dias, meses ou anos).
322
+ */
323
+ fixPanelAccessibility() {
324
+ const panel = this.datePicker.overlay;
325
+ if (!panel) {
326
+ return;
327
+ }
328
+ panel.querySelectorAll('.p-datepicker-select-month, .p-datepicker-select-year').forEach((btn) => {
329
+ this.renderer.setAttribute(btn, 'tabindex', '0');
330
+ });
331
+ panel.querySelectorAll('.p-datepicker-month:not([tabindex])').forEach((month) => {
332
+ this.renderer.setAttribute(month, 'tabindex', '0');
333
+ this.renderer.setAttribute(month, 'role', 'option');
334
+ });
335
+ panel.querySelectorAll('.p-datepicker-year:not([tabindex])').forEach((year) => {
336
+ this.renderer.setAttribute(year, 'tabindex', '0');
337
+ this.renderer.setAttribute(year, 'role', 'option');
338
+ });
339
+ this.applyGridcellRoleToDays(panel);
340
+ this.enrichDayCellAriaLabels(panel);
341
+ this.enrichGridAriaLabel(panel);
342
+ this.translateAriaLabels(panel);
343
+ this.fixNavigationButtonLabels(panel);
344
+ this.removeApplicationRoleFromContainer(panel);
345
+ this.setupFocusTracking(panel);
346
+ }
347
+ /**
348
+ * Adiciona `role="gridcell"` nos `<span class="p-datepicker-day">` que recebem foco.
349
+ *
350
+ * PrimeNG coloca `tabindex` no `<span>` dentro do `<td>`, mas sem role explícito.
351
+ * NVDA não entra automaticamente em focus mode quando foca um elemento sem
352
+ * role de widget (gridcell, button, etc). Resultado: NVDA fica em browse mode
353
+ * e lê toda a tabela sequencialmente ao invés de anunciar só a célula focada.
354
+ *
355
+ * Ao adicionar `role="gridcell"` no span focável, NVDA reconhece que é um
356
+ * widget interativo e entra em focus mode — anunciando apenas o aria-label
357
+ * da célula focada.
358
+ */
359
+ applyGridcellRoleToDays(panel) {
360
+ panel.querySelectorAll('.p-datepicker-day:not(.p-disabled)').forEach((daySpan) => {
361
+ if (!daySpan.getAttribute('role')) {
362
+ this.renderer.setAttribute(daySpan, 'role', 'gridcell');
363
+ }
364
+ });
365
+ }
366
+ /**
367
+ * Enriquece `aria-label` dos day cells com data completa formatada.
368
+ * PrimeNG por padrão usa apenas o número do dia, forçando NVDA
369
+ * a narrar contexto de grid (linha/coluna/weekday) para informar a data.
370
+ * Com aria-label completo, NVDA narra diretamente a data sem verbosidade.
371
+ */
372
+ enrichDayCellAriaLabels(panel) {
373
+ const { currentMonth, currentYear } = this.datePicker;
374
+ if (currentMonth == null || currentYear == null) {
375
+ return;
376
+ }
377
+ const weekdayHeaders = panel.querySelectorAll('.p-datepicker-weekday');
378
+ const weekdays = [];
379
+ weekdayHeaders.forEach((header) => {
380
+ weekdays.push(header.textContent?.trim() || '');
381
+ });
382
+ const rows = panel.querySelectorAll('.p-datepicker-day-view tbody tr');
383
+ rows.forEach((row) => {
384
+ const cells = row.querySelectorAll('td');
385
+ cells.forEach((cell, cellIndex) => {
386
+ const daySpan = cell.querySelector('.p-datepicker-day');
387
+ if (!daySpan) {
388
+ return;
389
+ }
390
+ const dayNumber = parseInt(daySpan.textContent?.trim() || '0', 10);
391
+ if (!dayNumber) {
392
+ return;
393
+ }
394
+ const isOtherMonth = cell.classList.contains('p-datepicker-other-month');
395
+ if (isOtherMonth) {
396
+ return;
397
+ }
398
+ const date = new Date(currentYear, currentMonth, dayNumber);
399
+ const formattedDate = this.formatDateForAriaLabel(date);
400
+ const weekday = weekdays[cellIndex] || '';
401
+ const ariaLabel = weekday ? `${formattedDate}, ${weekday}` : formattedDate;
402
+ this.renderer.setAttribute(daySpan, ARIA_LABEL_ATTR, ariaLabel);
403
+ });
404
+ });
405
+ }
406
+ /**
407
+ * Adiciona aria-label no grid com mês/ano atual para dar contexto
408
+ * imediato ao NVDA ao entrar na tabela de dias.
409
+ * Exemplo: "Calendário, Junho 2026"
410
+ */
411
+ enrichGridAriaLabel(panel) {
412
+ const grid = panel.querySelector('.p-datepicker-day-view[role="grid"]');
413
+ if (!grid) {
414
+ return;
415
+ }
416
+ const { currentMonth, currentYear } = this.datePicker;
417
+ if (currentMonth == null || currentYear == null) {
418
+ return;
419
+ }
420
+ const monthName = new Date(currentYear, currentMonth, 1).toLocaleDateString('pt-BR', {
421
+ month: 'long',
422
+ });
423
+ const capitalizedMonth = monthName.charAt(0).toUpperCase() + monthName.slice(1);
424
+ const label = `Calendário, ${capitalizedMonth} ${currentYear}`;
425
+ this.renderer.setAttribute(grid, ARIA_LABEL_ATTR, label);
426
+ }
427
+ /**
428
+ * Formata data para aria-label legível por leitor de tela.
429
+ * Sempre usa pt-BR para garantir consistência com labels traduzidos,
430
+ * independente do LOCALE_ID configurado no Angular.
431
+ * Exemplo: "3 de junho de 2026"
432
+ */
433
+ formatDateForAriaLabel(date) {
434
+ try {
435
+ return date.toLocaleDateString('pt-BR', {
436
+ day: 'numeric',
437
+ month: 'long',
438
+ year: 'numeric',
439
+ });
440
+ }
441
+ catch {
442
+ return `${date.getDate()}/${date.getMonth() + 1}/${date.getFullYear()}`;
443
+ }
444
+ }
445
+ /**
446
+ * Retorna locale string para formatação.
447
+ */
448
+ getLocale() {
449
+ return this.locale || 'pt-BR';
450
+ }
451
+ /**
452
+ * Traduz aria-labels dos botões do header que PrimeNG renderiza em inglês.
453
+ * PrimeNG usa `getTranslation('chooseDate')` etc, que retorna inglês
454
+ * se `PrimeNGConfig.setTranslation()` não foi configurado pelo projeto.
455
+ *
456
+ * Busca botões com aria-label em inglês e traduz pro locale atual.
457
+ */
458
+ translateAriaLabels(panel) {
459
+ const translations = this.getTranslations();
460
+ // Dialog aria-label ("Choose Date")
461
+ const dialog = panel.closest('[role="dialog"]') || panel;
462
+ const dialogLabel = dialog.getAttribute(ARIA_LABEL_ATTR);
463
+ if (dialogLabel && translations[dialogLabel]) {
464
+ this.renderer.setAttribute(dialog, ARIA_LABEL_ATTR, translations[dialogLabel]);
465
+ }
466
+ // Botões "Choose Month" e "Choose Year" no header
467
+ panel.querySelectorAll('.p-datepicker-select-month, .p-datepicker-select-year').forEach((btn) => {
468
+ const label = btn.getAttribute(ARIA_LABEL_ATTR);
469
+ if (label && translations[label]) {
470
+ this.renderer.setAttribute(btn, ARIA_LABEL_ATTR, translations[label]);
471
+ }
472
+ });
473
+ }
474
+ /**
475
+ * Corrige aria-label dos botões prev/next do PrimeNG.
476
+ * PrimeNG usa `p-button` component que seta `aria-label` no host element `<p-button>`,
477
+ * mas NVDA foca o `<button>` interno que não herda o label do custom element.
478
+ *
479
+ * Estrutura DOM: `<p-button aria-label="Previous Month"><button class="p-datepicker-prev-button">...</button></p-button>`
480
+ *
481
+ * Fix: lê aria-label do `<p-button>` host, traduz, e propaga pro `<button>` interno.
482
+ */
483
+ fixNavigationButtonLabels(panel) {
484
+ const translations = this.getTranslations();
485
+ // Buscar <p-button> hosts que contém os botões prev/next
486
+ const pButtons = panel.querySelectorAll('p-button');
487
+ pButtons.forEach((pBtn) => {
488
+ const innerButton = pBtn.querySelector('button.p-datepicker-prev-button, button.p-datepicker-next-button');
489
+ if (!innerButton) {
490
+ return;
491
+ }
492
+ let label = pBtn.getAttribute(ARIA_LABEL_ATTR) || '';
493
+ // Traduzir
494
+ if (label && translations[label]) {
495
+ label = translations[label];
496
+ this.renderer.setAttribute(pBtn, ARIA_LABEL_ATTR, label);
497
+ }
498
+ // Propagar pro <button> interno
499
+ if (label) {
500
+ this.renderer.setAttribute(innerButton, ARIA_LABEL_ATTR, label);
501
+ }
502
+ });
503
+ }
504
+ /**
505
+ * Remove `role="application"` do container do calendário.
506
+ *
507
+ * PrimeNG seta `role="application"` no `.p-datepicker-calendar-container`
508
+ * para que arrow keys cheguem no JavaScript sem interceptação do browse mode.
509
+ * Porém `role="grid"` na table já garante esse comportamento — NVDA entra
510
+ * automaticamente em focus mode ao focar uma gridcell.
511
+ *
512
+ * O `role="application"` no container PAI causa NVDA a despeja o conteúdo
513
+ * virtual do container inteiro ao entrar no dialog, antes de pousar na célula.
514
+ * Removê-lo resolve a leitura excessiva sem quebrar navegação por setas.
515
+ */
516
+ removeApplicationRoleFromContainer(panel) {
517
+ const calendarContainer = panel.querySelector('.p-datepicker-calendar-container');
518
+ if (calendarContainer?.getAttribute('role') === 'application') {
519
+ this.renderer.removeAttribute(calendarContainer, 'role');
520
+ }
521
+ }
522
+ /**
523
+ * Retorna mapa de traduções pro locale atual.
524
+ * Se locale é inglês ou não reconhecido, fallback pra pt-BR
525
+ * (assumimos que labels em inglês devem ser traduzidos).
526
+ */
527
+ getTranslations() {
528
+ const locale = this.getLocale();
529
+ return ARIA_TRANSLATIONS[locale]
530
+ || ARIA_TRANSLATIONS[locale.split('-')[0]]
531
+ || ARIA_TRANSLATIONS_PT_BR;
532
+ }
533
+ /**
534
+ * Adiciona listeners de focus/blur nos elementos interativos para
535
+ * garantir indicador visual quando `.focus()` é chamado programaticamente.
536
+ *
537
+ * `:focus-visible` pode não ser aplicado quando foco é movido via JS,
538
+ * então usamos classe auxiliar `s-datepicker-day-focused` como fallback.
539
+ */
540
+ setupFocusTracking(panel) {
541
+ this.removeFocusListeners();
542
+ const focusableElements = panel.querySelectorAll('.p-datepicker-day, .p-datepicker-month, .p-datepicker-year');
543
+ focusableElements.forEach((el) => {
544
+ const focusCleanup = this.renderer.listen(el, 'focus', () => {
545
+ this.renderer.addClass(el, 's-datepicker-day-focused');
546
+ });
547
+ const blurCleanup = this.renderer.listen(el, 'blur', () => {
548
+ this.renderer.removeClass(el, 's-datepicker-day-focused');
549
+ });
550
+ this.focusListenerCleanups.push(focusCleanup, blurCleanup);
551
+ });
552
+ }
553
+ /**
554
+ * Remove listeners de focus/blur registrados anteriormente.
555
+ */
556
+ removeFocusListeners() {
557
+ this.focusListenerCleanups.forEach((cleanup) => cleanup());
558
+ this.focusListenerCleanups = [];
559
+ }
560
+ /**
561
+ * Observa mudanças no DOM do painel para reaplicar correções
562
+ * quando o PrimeNG troca a view interna (dias → meses → anos).
563
+ * Usa debounce e só reage a childList changes (não attributes)
564
+ * para evitar loop infinito com as próprias mutações da diretiva.
565
+ */
566
+ observePanelChanges() {
567
+ this.disconnectObserver();
568
+ const panel = this.datePicker.overlay;
569
+ if (!panel) {
570
+ return;
571
+ }
572
+ let debounceTimer = null;
573
+ let isProcessing = false;
574
+ this.observer = new MutationObserver((mutations) => {
575
+ if (isProcessing) {
576
+ return;
577
+ }
578
+ // Só reagir se houve adição/remoção de nós (troca de view)
579
+ const hasStructuralChange = mutations.some((m) => m.type === 'childList' && (m.addedNodes.length > 0 || m.removedNodes.length > 0));
580
+ if (!hasStructuralChange) {
581
+ return;
582
+ }
583
+ if (debounceTimer) {
584
+ clearTimeout(debounceTimer);
585
+ }
586
+ debounceTimer = setTimeout(() => {
587
+ isProcessing = true;
588
+ this.fixPanelAccessibility();
589
+ this.focusBestTarget();
590
+ isProcessing = false;
591
+ }, MUTATION_DEBOUNCE_MS);
592
+ });
593
+ this.observer.observe(panel, {
594
+ childList: true,
595
+ subtree: true,
596
+ });
597
+ }
598
+ disconnectObserver() {
599
+ if (this.observer) {
600
+ this.observer.disconnect();
601
+ this.observer = null;
602
+ }
603
+ }
604
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DatepickerNoPeriodTabDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
605
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: DatepickerNoPeriodTabDirective, isStandalone: true, selector: "[sDatepickerNoPeriodTab]", host: { listeners: { "onShow": "onPanelShow()", "onMonthChange": "onNavigationChange()", "onYearChange": "onNavigationChange()" } }, ngImport: i0 });
606
+ }
607
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DatepickerNoPeriodTabDirective, decorators: [{
608
+ type: Directive,
609
+ args: [{
610
+ selector: '[sDatepickerNoPeriodTab]',
611
+ standalone: true,
612
+ }]
613
+ }], ctorParameters: () => [], propDecorators: { onPanelShow: [{
614
+ type: HostListener,
615
+ args: ['onShow']
616
+ }], onNavigationChange: [{
617
+ type: HostListener,
618
+ args: ['onMonthChange']
619
+ }, {
620
+ type: HostListener,
621
+ args: ['onYearChange']
622
+ }] } });
623
+
86
624
  /**
87
625
  * Diretiva para melhorar a acessibilidade dos botões do paginador PrimeNG
88
626
  * Adiciona tabindex="0" aos botões habilitados e tabindex="-1" aos desabilitados
@@ -430,5 +968,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
430
968
  * Generated bundle index. Do not edit.
431
969
  */
432
970
 
433
- export { BorderButtonComponent, BorderButtonModule, CheckDisabled, EnumSeverity, PaginatorAccessibilityDirective, TableCheckboxAccessibilityDirective };
971
+ export { BorderButtonComponent, BorderButtonModule, CheckDisabled, DatepickerNoPeriodTabDirective, EnumSeverity, PaginatorAccessibilityDirective, TableCheckboxAccessibilityDirective };
434
972
  //# sourceMappingURL=seniorsistemas-angular-components-shared.mjs.map