@seniorsistemas/angular-components 19.7.4 → 19.7.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/alert/lib/alert/alert.component.d.ts +8 -1
- package/chips/README.md +3 -2
- package/chips/lib/chips/chip-item/chip-item.component.d.ts +2 -0
- package/chips/lib/chips/chips/chips.component.d.ts +2 -1
- package/datepicker/README.md +15 -2
- package/datepicker/lib/datepicker/date-segment-input.d.ts +82 -0
- package/datepicker/lib/datepicker/date.utils.d.ts +3 -7
- package/datepicker/lib/datepicker/datepicker.component.d.ts +101 -8
- package/dynamic-form/dynamic-form/components/lookup/models/lookup-api.model.d.ts +7 -1
- package/dynamic-form/dynamic-form/form-field/configurations/fields/lookup-field.d.ts +1 -1
- package/dynamic-form/public-api.d.ts +1 -1
- package/esm2022/alert/lib/alert/alert.component.mjs +12 -6
- package/esm2022/chips/lib/chips/chip-item/chip-item.component.mjs +15 -4
- package/esm2022/chips/lib/chips/chips/chips.component.mjs +26 -14
- package/esm2022/datepicker/lib/datepicker/date-segment-input.mjs +418 -0
- package/esm2022/datepicker/lib/datepicker/date.utils.mjs +7 -79
- package/esm2022/datepicker/lib/datepicker/datepicker.component.mjs +451 -41
- package/esm2022/dynamic-form/dynamic-form/components/lookup/lookup.component.mjs +2 -2
- package/esm2022/dynamic-form/dynamic-form/components/lookup/models/lookup-api.model.mjs +1 -1
- package/esm2022/dynamic-form/dynamic-form/form-field/configurations/fields/lookup-field.mjs +1 -1
- package/esm2022/dynamic-form/public-api.mjs +1 -1
- package/esm2022/panel/lib/panel/panel.component.mjs +3 -3
- package/esm2022/sidebar/lib/sidebar/sidebar.component.mjs +8 -6
- package/esm2022/thumbnail/lib/thumbnail/thumbnail.component.mjs +3 -3
- package/fesm2022/seniorsistemas-angular-components-alert.mjs +12 -6
- package/fesm2022/seniorsistemas-angular-components-alert.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-chips.mjs +39 -16
- package/fesm2022/seniorsistemas-angular-components-chips.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-datepicker.mjs +873 -118
- package/fesm2022/seniorsistemas-angular-components-datepicker.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-dynamic-form.mjs +1 -1
- package/fesm2022/seniorsistemas-angular-components-dynamic-form.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-panel.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-panel.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-sidebar.mjs +7 -5
- package/fesm2022/seniorsistemas-angular-components-sidebar.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-thumbnail.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-thumbnail.mjs.map +1 -1
- package/package.json +13 -13
- package/schematics/migrations/migrate-steps-to-stepper/index.d.ts +2 -0
- package/schematics/migrations/migrate-steps-to-stepper/index.js +104 -0
- package/schematics/migrations/migrate-steps-to-stepper/index.js.map +1 -0
- package/schematics/migrations.json +5 -0
- package/schematics/utils/ast-utils.d.ts +13 -0
- package/schematics/utils/ast-utils.js +76 -0
- package/schematics/utils/ast-utils.js.map +1 -1
- package/sidebar/lib/sidebar/sidebar.component.d.ts +3 -1
- package/schematics/migrations/migrate-dynamic-form-v19/index.test.d.ts +0 -1
- package/schematics/migrations/migrate-dynamic-form-v19/index.test.js +0 -888
- package/schematics/migrations/migrate-dynamic-form-v19/index.test.js.map +0 -1
- package/schematics/migrations/migrate-p-datepicker/index.test.d.ts +0 -1
- package/schematics/migrations/migrate-p-datepicker/index.test.js +0 -392
- package/schematics/migrations/migrate-p-datepicker/index.test.js.map +0 -1
- package/schematics/migrations/migrate-p-editor/index.test.d.ts +0 -1
- package/schematics/migrations/migrate-p-editor/index.test.js +0 -393
- package/schematics/migrations/migrate-p-editor/index.test.js.map +0 -1
- package/schematics/migrations/migrate-s-chips-outputs/index.test.d.ts +0 -1
- package/schematics/migrations/migrate-s-chips-outputs/index.test.js +0 -125
- package/schematics/migrations/migrate-s-chips-outputs/index.test.js.map +0 -1
- package/schematics/utils/ast-utils.test.d.ts +0 -1
- package/schematics/utils/ast-utils.test.js +0 -138
- package/schematics/utils/ast-utils.test.js.map +0 -1
- package/schematics/utils/workspace-utils.test.d.ts +0 -1
- package/schematics/utils/workspace-utils.test.js +0 -152
- package/schematics/utils/workspace-utils.test.js.map +0 -1
|
@@ -3,15 +3,432 @@ import { CdkTrapFocus } from '@angular/cdk/a11y';
|
|
|
3
3
|
import { CdkOverlayOrigin, CdkConnectedOverlay } from '@angular/cdk/overlay';
|
|
4
4
|
import { NgClass } from '@angular/common';
|
|
5
5
|
import * as i0 from '@angular/core';
|
|
6
|
-
import { input, model, output, signal, viewChild, ElementRef, inject, Injector, computed, afterNextRender, Component } from '@angular/core';
|
|
6
|
+
import { input, model, output, signal, viewChild, ElementRef, inject, Injector, DestroyRef, computed, afterNextRender, Component } from '@angular/core';
|
|
7
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
7
8
|
import * as i2 from '@angular/forms';
|
|
8
9
|
import { NgControl, Validators, NG_VALUE_ACCESSOR, NG_VALIDATORS, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|
9
10
|
import * as i1 from '@ngx-translate/core';
|
|
10
|
-
import { TranslateModule } from '@ngx-translate/core';
|
|
11
|
+
import { TranslateService, TranslateModule } from '@ngx-translate/core';
|
|
12
|
+
|
|
13
|
+
const DATE_FORMAT_TOKEN_RE = /yyyy|yy|MM|mm|dd|d/g;
|
|
14
|
+
const YEAR_SEGMENT_LENGTH = 4;
|
|
15
|
+
const SHORT_SEGMENT_LENGTH = 2;
|
|
16
|
+
const HOURS_PER_MERIDIEM = 12;
|
|
17
|
+
const MAX_HOUR_24 = 23;
|
|
18
|
+
const MAX_MINUTE_SECOND = 59;
|
|
19
|
+
const GENERIC_MAX_DAY = 31;
|
|
20
|
+
const LEAP_REFERENCE_YEAR = 2000;
|
|
21
|
+
function isEditableSegment(segment) {
|
|
22
|
+
return segment.kind !== 'literal';
|
|
23
|
+
}
|
|
24
|
+
/** Tokeniza um `dateFormat` (ex.: `dd/MM/yyyy`, `MM/dd/yy`) em tokens de dia/mês/ano + literais. */
|
|
25
|
+
function tokenizeDateFormat(format) {
|
|
26
|
+
const tokens = [];
|
|
27
|
+
let lastIndex = 0;
|
|
28
|
+
DATE_FORMAT_TOKEN_RE.lastIndex = 0;
|
|
29
|
+
let match;
|
|
30
|
+
while ((match = DATE_FORMAT_TOKEN_RE.exec(format))) {
|
|
31
|
+
if (match.index > lastIndex) {
|
|
32
|
+
tokens.push({ kind: 'literal', raw: format.slice(lastIndex, match.index) });
|
|
33
|
+
}
|
|
34
|
+
const raw = match[0];
|
|
35
|
+
const kind = raw[0] === 'y' ? 'year' : raw[0] === 'd' ? 'day' : 'month';
|
|
36
|
+
tokens.push({ kind, raw });
|
|
37
|
+
lastIndex = DATE_FORMAT_TOKEN_RE.lastIndex;
|
|
38
|
+
}
|
|
39
|
+
if (lastIndex < format.length) {
|
|
40
|
+
tokens.push({ kind: 'literal', raw: format.slice(lastIndex) });
|
|
41
|
+
}
|
|
42
|
+
return tokens;
|
|
43
|
+
}
|
|
44
|
+
/** Dias no mês (1-12), já resolvendo fevereiro/ano bissexto via rollover nativo do `Date`. */
|
|
45
|
+
function daysInMonth(month, year) {
|
|
46
|
+
return new Date(year, month, 0).getDate();
|
|
47
|
+
}
|
|
48
|
+
function buildDateSegments(dateFormat) {
|
|
49
|
+
return tokenizeDateFormat(dateFormat).map((token) => {
|
|
50
|
+
if (token.kind === 'literal')
|
|
51
|
+
return { kind: 'literal', text: token.raw };
|
|
52
|
+
if (token.kind === 'year')
|
|
53
|
+
return { kind: 'year', length: YEAR_SEGMENT_LENGTH, text: '' };
|
|
54
|
+
if (token.kind === 'month')
|
|
55
|
+
return { kind: 'month', length: SHORT_SEGMENT_LENGTH, text: '' };
|
|
56
|
+
return { kind: 'day', length: SHORT_SEGMENT_LENGTH, text: '' };
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function buildMonthSegments(dateFormat) {
|
|
60
|
+
const all = buildDateSegments(dateFormat);
|
|
61
|
+
const dayIdx = all.findIndex((s) => s.kind === 'day');
|
|
62
|
+
if (dayIdx === -1)
|
|
63
|
+
return all;
|
|
64
|
+
const dropIndexes = new Set([dayIdx]);
|
|
65
|
+
if (all[dayIdx + 1]?.kind === 'literal') {
|
|
66
|
+
dropIndexes.add(dayIdx + 1);
|
|
67
|
+
}
|
|
68
|
+
else if (all[dayIdx - 1]?.kind === 'literal') {
|
|
69
|
+
dropIndexes.add(dayIdx - 1);
|
|
70
|
+
}
|
|
71
|
+
return all.filter((_, i) => !dropIndexes.has(i));
|
|
72
|
+
}
|
|
73
|
+
function buildTimeSegments(hourFormat, showSeconds) {
|
|
74
|
+
const segments = [
|
|
75
|
+
{ kind: 'hour', length: SHORT_SEGMENT_LENGTH, text: '' },
|
|
76
|
+
{ kind: 'literal', text: ':' },
|
|
77
|
+
{ kind: 'minute', length: SHORT_SEGMENT_LENGTH, text: '' },
|
|
78
|
+
];
|
|
79
|
+
if (showSeconds) {
|
|
80
|
+
segments.push({ kind: 'literal', text: ':' }, { kind: 'second', length: SHORT_SEGMENT_LENGTH, text: '' });
|
|
81
|
+
}
|
|
82
|
+
if (hourFormat === '12') {
|
|
83
|
+
segments.push({ kind: 'literal', text: ' ' }, { kind: 'ampm', length: SHORT_SEGMENT_LENGTH, text: '' });
|
|
84
|
+
}
|
|
85
|
+
return segments;
|
|
86
|
+
}
|
|
87
|
+
function buildSegmentsFor(config) {
|
|
88
|
+
switch (config.mode) {
|
|
89
|
+
case 'date':
|
|
90
|
+
return buildDateSegments(config.dateFormat);
|
|
91
|
+
case 'month':
|
|
92
|
+
return buildMonthSegments(config.dateFormat);
|
|
93
|
+
case 'time':
|
|
94
|
+
return buildTimeSegments(config.hourFormat, config.showSeconds);
|
|
95
|
+
case 'datetime':
|
|
96
|
+
return [
|
|
97
|
+
...buildDateSegments(config.dateFormat),
|
|
98
|
+
{ kind: 'literal', text: ' ' },
|
|
99
|
+
...buildTimeSegments(config.hourFormat, config.showSeconds),
|
|
100
|
+
];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Engine de segmentos (dia/mês/ano/hora/minuto/segundo/ampm) para o trigger do datepicker.
|
|
105
|
+
* Sem dependências de Angular — DOM/CVA ficam por conta do componente que a consome.
|
|
106
|
+
*/
|
|
107
|
+
class DateSegmentEngine {
|
|
108
|
+
segments = [];
|
|
109
|
+
config = null;
|
|
110
|
+
activeIndex = null;
|
|
111
|
+
freshEntry = true;
|
|
112
|
+
configure(config) {
|
|
113
|
+
this.config = config;
|
|
114
|
+
this.segments = buildSegmentsFor(config);
|
|
115
|
+
this.activeIndex = null;
|
|
116
|
+
this.freshEntry = true;
|
|
117
|
+
}
|
|
118
|
+
getDisplayString() {
|
|
119
|
+
return this.segments.map((s) => (isEditableSegment(s) ? this.renderSegment(s) : s.text)).join('');
|
|
120
|
+
}
|
|
121
|
+
renderSegment(segment) {
|
|
122
|
+
return segment.text + '_'.repeat(Math.max(0, segment.length - segment.text.length));
|
|
123
|
+
}
|
|
124
|
+
segmentRenderLength(segment) {
|
|
125
|
+
return isEditableSegment(segment) ? segment.length : segment.text.length;
|
|
126
|
+
}
|
|
127
|
+
getSegmentRange(index) {
|
|
128
|
+
let start = 0;
|
|
129
|
+
for (let i = 0; i < index; i++) {
|
|
130
|
+
start += this.segmentRenderLength(this.segments[i]);
|
|
131
|
+
}
|
|
132
|
+
return [start, start + this.segmentRenderLength(this.segments[index])];
|
|
133
|
+
}
|
|
134
|
+
getFirstEditableIndex() {
|
|
135
|
+
return this.segments.findIndex((s) => isEditableSegment(s));
|
|
136
|
+
}
|
|
137
|
+
getLastEditableIndex() {
|
|
138
|
+
for (let i = this.segments.length - 1; i >= 0; i--) {
|
|
139
|
+
if (isEditableSegment(this.segments[i]))
|
|
140
|
+
return i;
|
|
141
|
+
}
|
|
142
|
+
return -1;
|
|
143
|
+
}
|
|
144
|
+
getActiveIndex() {
|
|
145
|
+
return this.activeIndex;
|
|
146
|
+
}
|
|
147
|
+
activeKind() {
|
|
148
|
+
if (this.activeIndex === null)
|
|
149
|
+
return null;
|
|
150
|
+
const segment = this.segments[this.activeIndex];
|
|
151
|
+
return isEditableSegment(segment) ? segment.kind : null;
|
|
152
|
+
}
|
|
153
|
+
selectSegmentAt(charIndex) {
|
|
154
|
+
let acc = 0;
|
|
155
|
+
for (let i = 0; i < this.segments.length; i++) {
|
|
156
|
+
const len = this.segmentRenderLength(this.segments[i]);
|
|
157
|
+
if (charIndex < acc + len) {
|
|
158
|
+
return this.nearestEditableFrom(i);
|
|
159
|
+
}
|
|
160
|
+
acc += len;
|
|
161
|
+
}
|
|
162
|
+
return this.getLastEditableIndex();
|
|
163
|
+
}
|
|
164
|
+
nearestEditableFrom(index) {
|
|
165
|
+
for (let i = index; i < this.segments.length; i++) {
|
|
166
|
+
if (isEditableSegment(this.segments[i]))
|
|
167
|
+
return i;
|
|
168
|
+
}
|
|
169
|
+
return this.getLastEditableIndex();
|
|
170
|
+
}
|
|
171
|
+
selectSegment(index) {
|
|
172
|
+
if (index < 0 || index >= this.segments.length || !isEditableSegment(this.segments[index]))
|
|
173
|
+
return;
|
|
174
|
+
this.activeIndex = index;
|
|
175
|
+
this.freshEntry = true;
|
|
176
|
+
}
|
|
177
|
+
selectFirstSegment() {
|
|
178
|
+
this.selectSegment(this.getFirstEditableIndex());
|
|
179
|
+
}
|
|
180
|
+
/** Commita (pad/clamp) o segmento ativo e move para o próximo/anterior editável. */
|
|
181
|
+
moveToAdjacentSegment(direction) {
|
|
182
|
+
this.commitActiveSegment();
|
|
183
|
+
if (this.activeIndex === null)
|
|
184
|
+
return 'boundary';
|
|
185
|
+
let next = this.activeIndex;
|
|
186
|
+
do {
|
|
187
|
+
next += direction;
|
|
188
|
+
} while (next >= 0 && next < this.segments.length && !isEditableSegment(this.segments[next]));
|
|
189
|
+
if (next < 0 || next >= this.segments.length) {
|
|
190
|
+
this.activeIndex = null;
|
|
191
|
+
return 'boundary';
|
|
192
|
+
}
|
|
193
|
+
this.selectSegment(next);
|
|
194
|
+
return 'moved';
|
|
195
|
+
}
|
|
196
|
+
typeDigit(digit) {
|
|
197
|
+
if (this.activeIndex === null)
|
|
198
|
+
return;
|
|
199
|
+
const segment = this.segments[this.activeIndex];
|
|
200
|
+
if (!isEditableSegment(segment) || segment.kind === 'ampm')
|
|
201
|
+
return;
|
|
202
|
+
if (this.freshEntry) {
|
|
203
|
+
segment.text = '';
|
|
204
|
+
this.freshEntry = false;
|
|
205
|
+
}
|
|
206
|
+
if (segment.text.length >= segment.length)
|
|
207
|
+
return;
|
|
208
|
+
segment.text += digit;
|
|
209
|
+
if (segment.text.length >= segment.length) {
|
|
210
|
+
this.commitActiveSegment();
|
|
211
|
+
this.autoAdvance();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
autoAdvance() {
|
|
215
|
+
if (this.activeIndex === null)
|
|
216
|
+
return;
|
|
217
|
+
let next = this.activeIndex;
|
|
218
|
+
do {
|
|
219
|
+
next += 1;
|
|
220
|
+
} while (next < this.segments.length && !isEditableSegment(this.segments[next]));
|
|
221
|
+
if (next < this.segments.length) {
|
|
222
|
+
this.selectSegment(next);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
setAmPm(value) {
|
|
226
|
+
if (this.activeIndex === null)
|
|
227
|
+
return;
|
|
228
|
+
const segment = this.segments[this.activeIndex];
|
|
229
|
+
if (!isEditableSegment(segment) || segment.kind !== 'ampm')
|
|
230
|
+
return;
|
|
231
|
+
segment.text = value === 1 ? 'PM' : 'AM';
|
|
232
|
+
this.freshEntry = true;
|
|
233
|
+
}
|
|
234
|
+
clearActiveSegment(kind) {
|
|
235
|
+
if (this.activeIndex === null)
|
|
236
|
+
return;
|
|
237
|
+
const segment = this.segments[this.activeIndex];
|
|
238
|
+
if (!isEditableSegment(segment))
|
|
239
|
+
return;
|
|
240
|
+
if (segment.text === '') {
|
|
241
|
+
if (kind === 'backspace') {
|
|
242
|
+
let prev = this.activeIndex;
|
|
243
|
+
do {
|
|
244
|
+
prev -= 1;
|
|
245
|
+
} while (prev >= 0 && !isEditableSegment(this.segments[prev]));
|
|
246
|
+
if (prev >= 0) {
|
|
247
|
+
this.activeIndex = prev;
|
|
248
|
+
this.freshEntry = false;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
segment.text = '';
|
|
254
|
+
this.freshEntry = true;
|
|
255
|
+
}
|
|
256
|
+
/** Limpa todos os segmentos editáveis (usado quando o campo inteiro está selecionado, ex.: Ctrl+A). */
|
|
257
|
+
clearAll() {
|
|
258
|
+
for (const segment of this.segments) {
|
|
259
|
+
if (isEditableSegment(segment))
|
|
260
|
+
segment.text = '';
|
|
261
|
+
}
|
|
262
|
+
this.activeIndex = this.getFirstEditableIndex();
|
|
263
|
+
this.freshEntry = true;
|
|
264
|
+
}
|
|
265
|
+
/** Incrementa/decrementa o segmento ativo, dando a volta (wrap) dentro do range válido. */
|
|
266
|
+
incrementActiveSegment(delta) {
|
|
267
|
+
if (this.activeIndex === null)
|
|
268
|
+
return;
|
|
269
|
+
const segment = this.segments[this.activeIndex];
|
|
270
|
+
if (!isEditableSegment(segment))
|
|
271
|
+
return;
|
|
272
|
+
if (segment.kind === 'ampm') {
|
|
273
|
+
this.setAmPm(segment.text === 'PM' ? 0 : 1);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const { min, max } = this.boundsFor(segment.kind);
|
|
277
|
+
const range = max - min + 1;
|
|
278
|
+
const current = segment.text ? parseInt(segment.text, 10) : delta > 0 ? min - 1 : min;
|
|
279
|
+
const next = (((current + delta - min) % range) + range) % range + min;
|
|
280
|
+
segment.text = String(next).padStart(segment.length, '0');
|
|
281
|
+
this.freshEntry = true;
|
|
282
|
+
this.reconcileDayIfNeeded(segment.kind);
|
|
283
|
+
}
|
|
284
|
+
/** Pad/clamp do segmento ativo (ex.: "2" -> "02", "45" no dia -> clamp para daysInMonth). */
|
|
285
|
+
commitActiveSegment() {
|
|
286
|
+
if (this.activeIndex === null)
|
|
287
|
+
return;
|
|
288
|
+
const segment = this.segments[this.activeIndex];
|
|
289
|
+
if (!isEditableSegment(segment))
|
|
290
|
+
return;
|
|
291
|
+
const kind = segment.kind;
|
|
292
|
+
segment.text = this.clampAndPad(segment);
|
|
293
|
+
this.freshEntry = true;
|
|
294
|
+
this.reconcileDayIfNeeded(kind);
|
|
295
|
+
}
|
|
296
|
+
/** Commita o segmento ativo e sai do modo de edição (usado no blur do campo). */
|
|
297
|
+
deactivate() {
|
|
298
|
+
this.commitActiveSegment();
|
|
299
|
+
this.activeIndex = null;
|
|
300
|
+
}
|
|
301
|
+
clampAndPad(segment) {
|
|
302
|
+
if (segment.kind === 'ampm' || segment.kind === 'year' || !segment.text) {
|
|
303
|
+
return segment.text;
|
|
304
|
+
}
|
|
305
|
+
const num = parseInt(segment.text, 10);
|
|
306
|
+
const { min, max } = this.boundsFor(segment.kind);
|
|
307
|
+
const clamped = Math.min(max, Math.max(min, num));
|
|
308
|
+
return String(clamped).padStart(segment.length, '0');
|
|
309
|
+
}
|
|
310
|
+
boundsFor(kind) {
|
|
311
|
+
switch (kind) {
|
|
312
|
+
case 'day':
|
|
313
|
+
return { min: 1, max: this.daysInMonthNow() };
|
|
314
|
+
case 'month':
|
|
315
|
+
return { min: 1, max: HOURS_PER_MERIDIEM };
|
|
316
|
+
case 'hour':
|
|
317
|
+
return this.config?.hourFormat === '12'
|
|
318
|
+
? { min: 1, max: HOURS_PER_MERIDIEM }
|
|
319
|
+
: { min: 0, max: MAX_HOUR_24 };
|
|
320
|
+
case 'minute':
|
|
321
|
+
case 'second':
|
|
322
|
+
return { min: 0, max: MAX_MINUTE_SECOND };
|
|
323
|
+
default:
|
|
324
|
+
return { min: 0, max: 9999 };
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Bound do dia dependendo do que já é conhecido de mês/ano. Nunca "adivinha" com o mês/ano
|
|
329
|
+
* atuais do sistema — isso estreitaria o range antes da hora (ex.: dia=29 digitado antes do
|
|
330
|
+
* ano, mês=fevereiro conhecido mas ano ainda não -> se assumisse o ano atual do sistema e ele
|
|
331
|
+
* não fosse bissexto, o dia seria clampado para 28 e nunca mais reexpandido para 29 mesmo que
|
|
332
|
+
* o ano realmente digitado depois seja bissexto). Em vez disso, fica sempre no lado permissivo
|
|
333
|
+
* até que a informação realmente exista.
|
|
334
|
+
*/
|
|
335
|
+
daysInMonthNow() {
|
|
336
|
+
const monthSegment = this.findEditable('month');
|
|
337
|
+
const monthKnown = !!monthSegment?.text && monthSegment.text.length === monthSegment.length;
|
|
338
|
+
if (!monthSegment || !monthKnown)
|
|
339
|
+
return GENERIC_MAX_DAY;
|
|
340
|
+
const month = parseInt(monthSegment.text, 10);
|
|
341
|
+
const yearSegment = this.findEditable('year');
|
|
342
|
+
const yearKnown = !!yearSegment?.text && yearSegment.text.length === YEAR_SEGMENT_LENGTH;
|
|
343
|
+
if (!yearSegment || !yearKnown)
|
|
344
|
+
return daysInMonth(month, LEAP_REFERENCE_YEAR);
|
|
345
|
+
return daysInMonth(month, parseInt(yearSegment.text, 10));
|
|
346
|
+
}
|
|
347
|
+
reconcileDayIfNeeded(committedKind) {
|
|
348
|
+
if (committedKind !== 'month' && committedKind !== 'year')
|
|
349
|
+
return;
|
|
350
|
+
const daySegment = this.findEditable('day');
|
|
351
|
+
if (!daySegment || daySegment.text.length !== daySegment.length)
|
|
352
|
+
return;
|
|
353
|
+
const max = this.daysInMonthNow();
|
|
354
|
+
const dayNum = parseInt(daySegment.text, 10);
|
|
355
|
+
if (dayNum > max) {
|
|
356
|
+
daySegment.text = String(max).padStart(daySegment.length, '0');
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
findEditable(kind) {
|
|
360
|
+
return this.segments.find((s) => isEditableSegment(s) && s.kind === kind);
|
|
361
|
+
}
|
|
362
|
+
isEmpty() {
|
|
363
|
+
return this.segments.every((s) => !isEditableSegment(s) || s.text === '');
|
|
364
|
+
}
|
|
365
|
+
isComplete() {
|
|
366
|
+
return this.segments.every((s) => !isEditableSegment(s) || s.text.length === s.length);
|
|
367
|
+
}
|
|
368
|
+
loadFromDate(date) {
|
|
369
|
+
for (const segment of this.segments) {
|
|
370
|
+
if (isEditableSegment(segment))
|
|
371
|
+
segment.text = '';
|
|
372
|
+
}
|
|
373
|
+
this.activeIndex = null;
|
|
374
|
+
this.freshEntry = true;
|
|
375
|
+
if (!date)
|
|
376
|
+
return;
|
|
377
|
+
for (const segment of this.segments) {
|
|
378
|
+
if (!isEditableSegment(segment))
|
|
379
|
+
continue;
|
|
380
|
+
segment.text = this.renderValueFor(segment.kind, date);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
renderValueFor(kind, date) {
|
|
384
|
+
switch (kind) {
|
|
385
|
+
case 'day':
|
|
386
|
+
return String(date.getDate()).padStart(SHORT_SEGMENT_LENGTH, '0');
|
|
387
|
+
case 'month':
|
|
388
|
+
return String(date.getMonth() + 1).padStart(SHORT_SEGMENT_LENGTH, '0');
|
|
389
|
+
case 'year':
|
|
390
|
+
return String(date.getFullYear()).padStart(YEAR_SEGMENT_LENGTH, '0');
|
|
391
|
+
case 'hour': {
|
|
392
|
+
const h = date.getHours();
|
|
393
|
+
const display = this.config?.hourFormat === '12' ? (h % HOURS_PER_MERIDIEM === 0 ? HOURS_PER_MERIDIEM : h % HOURS_PER_MERIDIEM) : h;
|
|
394
|
+
return String(display).padStart(SHORT_SEGMENT_LENGTH, '0');
|
|
395
|
+
}
|
|
396
|
+
case 'minute':
|
|
397
|
+
return String(date.getMinutes()).padStart(SHORT_SEGMENT_LENGTH, '0');
|
|
398
|
+
case 'second':
|
|
399
|
+
return String(date.getSeconds()).padStart(SHORT_SEGMENT_LENGTH, '0');
|
|
400
|
+
case 'ampm':
|
|
401
|
+
return date.getHours() < HOURS_PER_MERIDIEM ? 'AM' : 'PM';
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
/** Constrói o `Date` a partir dos segmentos; `null` enquanto incompleto. Usa `referenceDate` para preencher partes ausentes (ex.: hora ao editar só a data). */
|
|
405
|
+
toDate(referenceDate) {
|
|
406
|
+
if (!this.isComplete())
|
|
407
|
+
return null;
|
|
408
|
+
const ref = referenceDate ?? new Date();
|
|
409
|
+
const getNum = (kind) => {
|
|
410
|
+
const segment = this.findEditable(kind);
|
|
411
|
+
return segment ? parseInt(segment.text, 10) : null;
|
|
412
|
+
};
|
|
413
|
+
const year = getNum('year') ?? ref.getFullYear();
|
|
414
|
+
const monthNum = getNum('month');
|
|
415
|
+
const month = monthNum !== null ? monthNum - 1 : ref.getMonth();
|
|
416
|
+
const day = this.config?.mode === 'month' ? 1 : (getNum('day') ?? ref.getDate());
|
|
417
|
+
let hour = getNum('hour');
|
|
418
|
+
const minute = getNum('minute') ?? ref.getMinutes();
|
|
419
|
+
const second = getNum('second') ?? ref.getSeconds();
|
|
420
|
+
if (hour !== null && this.config?.hourFormat === '12') {
|
|
421
|
+
const ampmSegment = this.findEditable('ampm');
|
|
422
|
+
const isPm = ampmSegment?.text === 'PM';
|
|
423
|
+
hour = hour % HOURS_PER_MERIDIEM;
|
|
424
|
+
if (isPm)
|
|
425
|
+
hour += HOURS_PER_MERIDIEM;
|
|
426
|
+
}
|
|
427
|
+
return new Date(year, month, day, hour ?? ref.getHours(), minute, second);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
11
430
|
|
|
12
431
|
const DAY_MATCH_GROUP = 3;
|
|
13
|
-
const MINUTES_MATCH_GROUP = 5;
|
|
14
|
-
const MAX_MONTH_INDEX = 11;
|
|
15
432
|
function formatNativeDate(date) {
|
|
16
433
|
const year = date.getFullYear();
|
|
17
434
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
@@ -20,8 +437,7 @@ function formatNativeDate(date) {
|
|
|
20
437
|
}
|
|
21
438
|
/**
|
|
22
439
|
* Formata uma data segundo um padrão simples contendo os tokens `dd`, `MM` e `yyyy`.
|
|
23
|
-
* Usado
|
|
24
|
-
* não controla o formato. Para `type="date"` nativo o formato é definido pelo browser.
|
|
440
|
+
* Usado para exibição em campos `type="text"` (modos `range` e seleção múltipla).
|
|
25
441
|
*/
|
|
26
442
|
function formatWithPattern(date, pattern) {
|
|
27
443
|
const dd = String(date.getDate()).padStart(2, '0');
|
|
@@ -35,10 +451,6 @@ function formatWithPattern(date, pattern) {
|
|
|
35
451
|
.replace('MM', MM)
|
|
36
452
|
.replace('mm', MM); // PrimeNG compat: lowercase mm = month
|
|
37
453
|
}
|
|
38
|
-
function formatTime(h, m, s) {
|
|
39
|
-
const base = `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
|
|
40
|
-
return s !== undefined ? `${base}:${String(s).padStart(2, '0')}` : base;
|
|
41
|
-
}
|
|
42
454
|
/** Parses yyyy-MM-dd. Returns null when invalid. */
|
|
43
455
|
function parseDate(raw) {
|
|
44
456
|
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(raw.trim());
|
|
@@ -78,19 +490,6 @@ function parseRangeInput(raw, separator = '—') {
|
|
|
78
490
|
}
|
|
79
491
|
return { start, end };
|
|
80
492
|
}
|
|
81
|
-
function parseTimeInput(raw, base, maxHours, maxMinutes) {
|
|
82
|
-
const match = /^(\d{1,2}):(\d{2})$/.exec(raw.trim());
|
|
83
|
-
if (!match) {
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
const hours = parseInt(match[1], 10);
|
|
87
|
-
const mins = parseInt(match[2], 10);
|
|
88
|
-
if (hours >= maxHours || mins >= maxMinutes) {
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
const baseDate = base ?? new Date();
|
|
92
|
-
return new Date(baseDate.getFullYear(), baseDate.getMonth(), baseDate.getDate(), hours, mins);
|
|
93
|
-
}
|
|
94
493
|
function parseYearInput(raw) {
|
|
95
494
|
const match = /^(\d{4})$/.exec(raw.trim());
|
|
96
495
|
if (!match) {
|
|
@@ -102,59 +501,20 @@ function parseYearInput(raw) {
|
|
|
102
501
|
}
|
|
103
502
|
return new Date(year, 0, 1);
|
|
104
503
|
}
|
|
105
|
-
function
|
|
106
|
-
const match = /^(\d{4})-(\d{2})$/.exec(raw.trim());
|
|
107
|
-
if (!match) {
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
const year = parseInt(match[1], 10);
|
|
111
|
-
const month = parseInt(match[2], 10) - 1;
|
|
112
|
-
if (year < 1000) {
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
115
|
-
if (month < 0 || month > MAX_MONTH_INDEX) {
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
return new Date(year, month, 1);
|
|
119
|
-
}
|
|
120
|
-
function parseDateTimeInput(raw) {
|
|
121
|
-
const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})$/.exec(raw.trim());
|
|
122
|
-
if (!match) {
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
|
-
const year = parseInt(match[1], 10);
|
|
126
|
-
const month = parseInt(match[2], 10) - 1;
|
|
127
|
-
const day = parseInt(match[DAY_MATCH_GROUP], 10);
|
|
128
|
-
const hours = parseInt(match[4], 10);
|
|
129
|
-
const mins = parseInt(match[MINUTES_MATCH_GROUP], 10);
|
|
130
|
-
if (year < 1000 || hours >= 24 || mins >= 60) {
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
133
|
-
const date = new Date(year, month, day, hours, mins);
|
|
134
|
-
if (date.getFullYear() !== year || date.getMonth() !== month || date.getDate() !== day) {
|
|
135
|
-
return null;
|
|
136
|
-
}
|
|
137
|
-
return date;
|
|
138
|
-
}
|
|
139
|
-
function parseInput(mode, raw, base, maxHours, maxMinutes, rangeSeparator = '—') {
|
|
504
|
+
function parseInput(mode, raw, rangeSeparator = '—') {
|
|
140
505
|
if (!raw?.trim()) {
|
|
141
506
|
return null;
|
|
142
507
|
}
|
|
143
508
|
switch (mode) {
|
|
144
509
|
case 'range':
|
|
145
510
|
return parseRangeInput(raw, rangeSeparator);
|
|
146
|
-
case 'time':
|
|
147
|
-
return parseTimeInput(raw, base, maxHours, maxMinutes);
|
|
148
|
-
case 'month':
|
|
149
|
-
return parseMonthInput(raw);
|
|
150
511
|
case 'year':
|
|
151
512
|
return parseYearInput(raw);
|
|
152
|
-
case 'datetime':
|
|
153
|
-
return parseDateTimeInput(raw);
|
|
154
513
|
default:
|
|
155
514
|
return parseDate(raw);
|
|
156
515
|
}
|
|
157
516
|
}
|
|
517
|
+
/** Formata range/seleção múltipla (dateFormat) e o ano em `mode='year'`. Demais modos são resolvidos pela engine de segmentos. */
|
|
158
518
|
function formatValueForInput(mode, value, rangeSeparator = ' — ', dateFormat, multipleSeparator = ', ') {
|
|
159
519
|
if (!value) {
|
|
160
520
|
return '';
|
|
@@ -172,23 +532,10 @@ function formatValueForInput(mode, value, rangeSeparator = ' — ', dateFormat,
|
|
|
172
532
|
? `${formatPart(range.start)}${rangeSeparator}${formatPart(range.end)}`
|
|
173
533
|
: formatPart(range.start);
|
|
174
534
|
}
|
|
175
|
-
if (
|
|
176
|
-
return
|
|
177
|
-
}
|
|
178
|
-
switch (mode) {
|
|
179
|
-
case 'time':
|
|
180
|
-
return formatTime(value.getHours(), value.getMinutes());
|
|
181
|
-
case 'month': {
|
|
182
|
-
const month = String(value.getMonth() + 1).padStart(2, '0');
|
|
183
|
-
return `${value.getFullYear()}-${month}`;
|
|
184
|
-
}
|
|
185
|
-
case 'year':
|
|
186
|
-
return String(value.getFullYear());
|
|
187
|
-
case 'datetime':
|
|
188
|
-
return `${formatNativeDate(value)}T${formatTime(value.getHours(), value.getMinutes())}`;
|
|
189
|
-
default:
|
|
190
|
-
return formatNativeDate(value);
|
|
535
|
+
if (value instanceof Date && mode === 'year') {
|
|
536
|
+
return String(value.getFullYear());
|
|
191
537
|
}
|
|
538
|
+
return '';
|
|
192
539
|
}
|
|
193
540
|
|
|
194
541
|
function numericKeysOnly() {
|
|
@@ -217,6 +564,7 @@ class DatepickerComponent {
|
|
|
217
564
|
static MAX_HOURS = 24;
|
|
218
565
|
static MAX_MINUTES = 60;
|
|
219
566
|
static HOURS_PER_MERIDIEM = 12;
|
|
567
|
+
static DOUBLE_CLICK_THRESHOLD_MS = 400;
|
|
220
568
|
static nextId() {
|
|
221
569
|
DatepickerComponent.idCounter++;
|
|
222
570
|
return `s-datepicker-${DatepickerComponent.idCounter}`;
|
|
@@ -258,10 +606,16 @@ class DatepickerComponent {
|
|
|
258
606
|
max = input(null);
|
|
259
607
|
/** Data exibida ao abrir o calendário quando nenhuma data está selecionada. */
|
|
260
608
|
defaultDate = input(null);
|
|
261
|
-
/**
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
609
|
+
/**
|
|
610
|
+
* Formato de exibição da data no campo. Quando omitido, é resolvido a partir do
|
|
611
|
+
* locale ativo (chave `platform.angular_components.date_format`), com fallback para `dd/MM/yyyy`.
|
|
612
|
+
*/
|
|
613
|
+
dateFormat = input(undefined);
|
|
614
|
+
/**
|
|
615
|
+
* Formato de exibição das horas: `'12'` ou `'24'`. Quando omitido, é resolvido a partir
|
|
616
|
+
* do locale ativo (chave `platform.angular_components.hour_format`), com fallback para `'24'`.
|
|
617
|
+
*/
|
|
618
|
+
hourFormat = input(undefined);
|
|
265
619
|
/** Incremento em minutos ao usar as setas do time picker. */
|
|
266
620
|
minuteStep = input(DatepickerComponent.DEFAULT_MINUTE_STEP);
|
|
267
621
|
/** Quando `true`, exibe o campo de segundos no time picker. */
|
|
@@ -339,19 +693,68 @@ class DatepickerComponent {
|
|
|
339
693
|
pendingRangeStart = signal(null);
|
|
340
694
|
hoverDate = signal(null);
|
|
341
695
|
_hasFormatError = false;
|
|
342
|
-
_isBadInput = false;
|
|
343
696
|
onValidatorChange = () => { };
|
|
344
697
|
_onChange = () => { };
|
|
345
698
|
_onTouched = () => { };
|
|
346
699
|
popupPanel = viewChild('popupPanel', { read: ElementRef });
|
|
347
700
|
increaseHourButton = viewChild('increaseHourButton', { read: ElementRef });
|
|
348
701
|
iconButton = viewChild('iconButton', { read: ElementRef });
|
|
702
|
+
triggerInput = viewChild.required('triggerInput', { read: ElementRef });
|
|
349
703
|
injector = inject(Injector);
|
|
350
704
|
hostElement = inject(ElementRef);
|
|
705
|
+
translateService = inject(TranslateService);
|
|
706
|
+
destroyRef = inject(DestroyRef);
|
|
707
|
+
currentLang = signal(this.translateService.currentLang);
|
|
708
|
+
/** Reflete `(pointer: coarse)` — dispositivos touch preferem o popup do calendário ao teclado. */
|
|
709
|
+
isTouchDevice = signal(typeof window !== 'undefined' && typeof window.matchMedia === 'function'
|
|
710
|
+
? window.matchMedia('(pointer: coarse)').matches
|
|
711
|
+
: false);
|
|
712
|
+
segmentEngine = new DateSegmentEngine();
|
|
713
|
+
lastSegmentConfig = null;
|
|
714
|
+
measureCanvas;
|
|
715
|
+
/** Incrementado a cada mutação do usuário — invalida callbacks de clique/foco adiados que ficaram obsoletos. */
|
|
716
|
+
interactionGeneration = 0;
|
|
717
|
+
/** `timeStamp` do último mousedown no input segmentado — usado pra detectar duplo clique na mão. */
|
|
718
|
+
lastMouseDownAt = 0;
|
|
719
|
+
constructor() {
|
|
720
|
+
this.translateService.onLangChange.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => {
|
|
721
|
+
this.currentLang.set(event.lang);
|
|
722
|
+
});
|
|
723
|
+
if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {
|
|
724
|
+
const mediaQuery = window.matchMedia('(pointer: coarse)');
|
|
725
|
+
const handlePointerChange = (event) => this.isTouchDevice.set(event.matches);
|
|
726
|
+
mediaQuery.addEventListener('change', handlePointerChange);
|
|
727
|
+
this.destroyRef.onDestroy(() => mediaQuery.removeEventListener('change', handlePointerChange));
|
|
728
|
+
}
|
|
729
|
+
}
|
|
351
730
|
// ── Computed ──────────────────────────────────────────────────────────
|
|
352
731
|
resolvedIcon = computed(() => (this.mode() === 'time' ? 'fas fa-clock' : this.icon()));
|
|
732
|
+
/** `dateFormat` explícito ou resolvido a partir do locale ativo (fallback `dd/MM/yyyy`). */
|
|
733
|
+
resolvedDateFormat = computed(() => {
|
|
734
|
+
this.currentLang();
|
|
735
|
+
return (this.dateFormat() ??
|
|
736
|
+
this.resolveTranslatedOrDefault('platform.angular_components.date_format', 'dd/MM/yyyy'));
|
|
737
|
+
});
|
|
738
|
+
/** `hourFormat` explícito ou resolvido a partir do locale ativo (fallback `'24'`). */
|
|
739
|
+
resolvedHourFormat = computed(() => {
|
|
740
|
+
this.currentLang();
|
|
741
|
+
const explicit = this.hourFormat();
|
|
742
|
+
if (explicit)
|
|
743
|
+
return explicit;
|
|
744
|
+
const translated = this.resolveTranslatedOrDefault('platform.angular_components.hour_format', '24');
|
|
745
|
+
return translated === '12' ? '12' : '24';
|
|
746
|
+
});
|
|
747
|
+
/** Modos cujo formato de exibição é resolvido pela engine de segmentos (independente de touch/readonly). */
|
|
748
|
+
isSegmentModeCandidate = computed(() => {
|
|
749
|
+
if (this.isMultipleMode())
|
|
750
|
+
return false;
|
|
751
|
+
const mode = this.mode();
|
|
752
|
+
return mode === 'date' || mode === 'month' || mode === 'time' || mode === 'datetime';
|
|
753
|
+
});
|
|
754
|
+
/** Interatividade de digitação por segmento — desligada em touch ou quando `readonlyInput` é usado. */
|
|
755
|
+
isSegmentedMode = computed(() => this.isSegmentModeCandidate() && !this.isTouchDevice() && !this.readonlyInput());
|
|
353
756
|
/** Indica se o time picker deve usar o formato de 12 horas com seletor AM/PM. */
|
|
354
|
-
is12HourFormat = computed(() => this.
|
|
757
|
+
is12HourFormat = computed(() => this.resolvedHourFormat() === '12');
|
|
355
758
|
/** Hora exibida no time picker: 1–12 no formato 12h, 0–23 no formato 24h. */
|
|
356
759
|
displayHours = computed(() => {
|
|
357
760
|
if (!this.is12HourFormat())
|
|
@@ -377,16 +780,6 @@ class DatepickerComponent {
|
|
|
377
780
|
const start = this.firstDayOfWeek();
|
|
378
781
|
return [...this.weekDayKeys.slice(start), ...this.weekDayKeys.slice(0, start)];
|
|
379
782
|
});
|
|
380
|
-
inputType = computed(() => {
|
|
381
|
-
if (this.isMultipleMode())
|
|
382
|
-
return 'text';
|
|
383
|
-
const mode = this.mode();
|
|
384
|
-
if (mode === 'datetime')
|
|
385
|
-
return 'datetime-local';
|
|
386
|
-
if (mode === 'range' || mode === 'year')
|
|
387
|
-
return 'text';
|
|
388
|
-
return mode;
|
|
389
|
-
});
|
|
390
783
|
dialogAriaLabelKey = computed(() => this.mode() === 'range'
|
|
391
784
|
? 'platform.angular_components.datepicker_select_range'
|
|
392
785
|
: 'platform.angular_components.datepicker_select_date');
|
|
@@ -520,7 +913,7 @@ class DatepickerComponent {
|
|
|
520
913
|
const d = this.defaultDate();
|
|
521
914
|
if (d && !this.value() && !this.rangeValue() && this.multipleValues().length === 0) {
|
|
522
915
|
this.value.set(d);
|
|
523
|
-
this.
|
|
916
|
+
this.setDisplayFor(d);
|
|
524
917
|
this.viewDate.set(new Date(d.getFullYear(), d.getMonth(), 1));
|
|
525
918
|
this._onChange(d);
|
|
526
919
|
}
|
|
@@ -532,7 +925,7 @@ class DatepickerComponent {
|
|
|
532
925
|
this.value.set(null);
|
|
533
926
|
this.rangeValue.set(null);
|
|
534
927
|
this.multipleValues.set([]);
|
|
535
|
-
this.
|
|
928
|
+
this.setDisplayFor(null);
|
|
536
929
|
return;
|
|
537
930
|
}
|
|
538
931
|
if (Array.isArray(value)) {
|
|
@@ -552,8 +945,8 @@ class DatepickerComponent {
|
|
|
552
945
|
return;
|
|
553
946
|
}
|
|
554
947
|
this.value.set(value);
|
|
555
|
-
this.inputDate = this.formatForDisplay(value);
|
|
556
948
|
const d = value;
|
|
949
|
+
this.setDisplayFor(d);
|
|
557
950
|
this.viewDate.set(new Date(d.getFullYear(), d.getMonth(), 1));
|
|
558
951
|
this.hours.set(d.getHours());
|
|
559
952
|
this.minutes.set(d.getMinutes());
|
|
@@ -649,6 +1042,42 @@ class DatepickerComponent {
|
|
|
649
1042
|
this.iconButton()?.nativeElement.focus();
|
|
650
1043
|
}, { injector: this.injector });
|
|
651
1044
|
}
|
|
1045
|
+
/**
|
|
1046
|
+
* O backdrop do CDK Overlay cobre a tela inteira, incluindo o próprio trigger — clicar no
|
|
1047
|
+
* input ou nos botões do trigger enquanto o painel já está aberto também dispara
|
|
1048
|
+
* `backdropClick` (o próprio `<input>` nunca recebe o `click` nesse caso, então
|
|
1049
|
+
* `handleInputClick` nunca dispara). Sem este filtro, clicar no input para trocar de
|
|
1050
|
+
* segmento fecharia o painel e roubaria o foco para o botão do ícone (via `closePanel`).
|
|
1051
|
+
* Quando o clique caiu especificamente sobre o `<input>`, reaplicamos a mesma lógica de
|
|
1052
|
+
* reposicionamento de segmento que `handleInputClick` faria, em vez de só ignorar o clique.
|
|
1053
|
+
*/
|
|
1054
|
+
handleBackdropClick(event) {
|
|
1055
|
+
// `event.target` de um backdropClick do CDK Overlay é sempre o próprio <div> do
|
|
1056
|
+
// backdrop, nunca o elemento realmente clicado — então checar `.contains(event.target)`
|
|
1057
|
+
// nunca funciona. Em vez disso, comparamos a coordenada real do clique (que o
|
|
1058
|
+
// MouseEvent sempre carrega corretamente) contra os retângulos dos elementos do trigger.
|
|
1059
|
+
const inputEl = this.triggerInput()?.nativeElement;
|
|
1060
|
+
if (inputEl && this.isSegmentedMode()) {
|
|
1061
|
+
const inputRect = inputEl.getBoundingClientRect();
|
|
1062
|
+
const clickedOnInput = event.clientX >= inputRect.left &&
|
|
1063
|
+
event.clientX <= inputRect.right &&
|
|
1064
|
+
event.clientY >= inputRect.top &&
|
|
1065
|
+
event.clientY <= inputRect.bottom;
|
|
1066
|
+
if (clickedOnInput) {
|
|
1067
|
+
this.handleSegmentPointerDown(inputEl, event.clientX, event.timeStamp);
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
const rect = this.hostElement.nativeElement.getBoundingClientRect();
|
|
1072
|
+
const clickedOnTrigger = event.clientX >= rect.left &&
|
|
1073
|
+
event.clientX <= rect.right &&
|
|
1074
|
+
event.clientY >= rect.top &&
|
|
1075
|
+
event.clientY <= rect.bottom;
|
|
1076
|
+
if (clickedOnTrigger) {
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
this.closePanel();
|
|
1080
|
+
}
|
|
652
1081
|
toggle() {
|
|
653
1082
|
if (this.isOpen()) {
|
|
654
1083
|
this.closePanel();
|
|
@@ -662,14 +1091,169 @@ class DatepickerComponent {
|
|
|
662
1091
|
this.closePanel();
|
|
663
1092
|
}
|
|
664
1093
|
}
|
|
665
|
-
handleInputClick() {
|
|
1094
|
+
handleInputClick(event) {
|
|
1095
|
+
if (!this.isSegmentedMode() || !(event.target instanceof HTMLInputElement)) {
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
// Bound to `mousedown`, não `click` (veja o comentário em `applySegmentClick`). O
|
|
1099
|
+
// comportamento nativo padrão do browser pra mousedown num `<input>` de texto é
|
|
1100
|
+
// posicionar o caret/iniciar seleção de texto na coordenada do clique — isso roda
|
|
1101
|
+
// *depois* dos nossos listeners e sobrescreveria o `setSelectionRange` do segmento
|
|
1102
|
+
// aplicado logo abaixo. `preventDefault()` evita essa corrida.
|
|
1103
|
+
event.preventDefault();
|
|
1104
|
+
this.handleSegmentPointerDown(event.target, event.clientX, event.timeStamp);
|
|
1105
|
+
}
|
|
1106
|
+
/**
|
|
1107
|
+
* Ponto de entrada único pro clique no input segmentado — chamado tanto por
|
|
1108
|
+
* `handleInputClick` (mousedown direto no `<input>`, o primeiro clique, antes do backdrop
|
|
1109
|
+
* existir) quanto por `handleBackdropClick` (cliques seguintes, já com o painel aberto e o
|
|
1110
|
+
* backdrop cobrindo o input). Precisa ser único porque os dois cliques de um duplo clique
|
|
1111
|
+
* real costumam chegar por caminhos DIFERENTES — o primeiro via mousedown no input, o
|
|
1112
|
+
* segundo via backdropClick — então rastrear o timestamp do "último clique" separadamente
|
|
1113
|
+
* em cada handler nunca detectaria um duplo clique de verdade. Nem `dblclick` nem
|
|
1114
|
+
* `event.detail` são confiáveis aqui (dependem da contagem/targeting interna de cliques do
|
|
1115
|
+
* browser, que nossas manipulações de foco/seleção parecem atrapalhar — observado
|
|
1116
|
+
* empiricamente: `event.detail` fica sempre 1), daí a detecção manual por timestamp.
|
|
1117
|
+
*/
|
|
1118
|
+
handleSegmentPointerDown(input, clientX, timeStamp) {
|
|
1119
|
+
const isDoubleClick = timeStamp - this.lastMouseDownAt < DatepickerComponent.DOUBLE_CLICK_THRESHOLD_MS;
|
|
1120
|
+
this.lastMouseDownAt = timeStamp;
|
|
1121
|
+
if (isDoubleClick) {
|
|
1122
|
+
this.lastMouseDownAt = 0; // evita que um terceiro clique encadeie em outro "duplo"
|
|
1123
|
+
this.selectWholeField(input);
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
this.applySegmentClick(input, clientX);
|
|
1127
|
+
}
|
|
1128
|
+
/**
|
|
1129
|
+
* Seleciona o segmento sob a coordenada `clientX` de um clique no input segmentado.
|
|
1130
|
+
* Compartilhado por `handleInputClick` (mousedown real no `<input>` — usamos `mousedown`
|
|
1131
|
+
* em vez de `click` porque o `focus`/`openOnFocus` disparado no meio do gesto pode montar
|
|
1132
|
+
* o backdrop do CDK Overlay embaixo do cursor antes do `mouseup`, fazendo o browser resolver
|
|
1133
|
+
* o `click` pro ancestral comum entre o alvo do mousedown e o do mouseup — na prática,
|
|
1134
|
+
* `<body>` — em vez do `<input>` ou do backdrop) e `handleBackdropClick` (clique no trigger
|
|
1135
|
+
* com o painel já aberto em regime estável, interceptado pelo backdrop do CDK Overlay).
|
|
1136
|
+
*/
|
|
1137
|
+
applySegmentClick(input, clientX) {
|
|
1138
|
+
this.ensureSegmentEngineConfigured();
|
|
1139
|
+
// Calcula o segmento a partir da coordenada do clique (clientX), não de
|
|
1140
|
+
// `input.selectionStart` — o browser ajusta a posição do caret de forma assíncrona
|
|
1141
|
+
// após o clique (observado empiricamente, especialmente na transição de foco), então
|
|
1142
|
+
// ler `selectionStart` nesse momento é uma fonte não confiável. `clientX` é síncrono
|
|
1143
|
+
// e imediato, sem essa corrida.
|
|
1144
|
+
const charIndex = this.clickClientXToCharIndex(input, clientX);
|
|
1145
|
+
const generation = ++this.interactionGeneration;
|
|
1146
|
+
// Foca e seleciona o segmento juntos, e reaplica os dois algumas vezes logo em seguida —
|
|
1147
|
+
// o browser/CDK Overlay pode mexer no foco de forma assíncrona depois do clique (blur
|
|
1148
|
+
// nativo do backdrop, cdkTrapFocus do painel, etc.); em vez de tentar prever/evitar cada
|
|
1149
|
+
// causa, simplesmente vencemos reafirmando foco+seleção por cima.
|
|
1150
|
+
const focusAndSelect = () => {
|
|
1151
|
+
if (generation !== this.interactionGeneration)
|
|
1152
|
+
return;
|
|
1153
|
+
const index = this.segmentEngine.selectSegmentAt(charIndex);
|
|
1154
|
+
this.segmentEngine.selectSegment(index);
|
|
1155
|
+
input.focus();
|
|
1156
|
+
this.selectActiveSegmentRange(input);
|
|
1157
|
+
};
|
|
1158
|
+
focusAndSelect();
|
|
1159
|
+
setTimeout(focusAndSelect, 0);
|
|
1160
|
+
setTimeout(focusAndSelect, 50);
|
|
1161
|
+
}
|
|
1162
|
+
/** Duplo clique seleciona o input inteiro (todos os segmentos), como num `<input>` de texto comum. */
|
|
1163
|
+
handleInputDblClick(event) {
|
|
1164
|
+
if (!this.isSegmentedMode() || !(event.target instanceof HTMLInputElement))
|
|
1165
|
+
return;
|
|
1166
|
+
this.selectWholeField(event.target);
|
|
1167
|
+
}
|
|
1168
|
+
/**
|
|
1169
|
+
* Seleciona todos os segmentos do input (usado tanto pelo duplo clique detectado na mão em
|
|
1170
|
+
* `handleSegmentPointerDown` quanto por `handleInputDblClick`, mantido como caminho direto
|
|
1171
|
+
* pra quem dispara um `dblclick` de verdade, ex.: acessibilidade/testes).
|
|
1172
|
+
*/
|
|
1173
|
+
selectWholeField(input) {
|
|
1174
|
+
this.ensureSegmentEngineConfigured();
|
|
1175
|
+
// Garante um segmento ativo *antes* de focar: se o CDK Overlay tiver roubado o foco de
|
|
1176
|
+
// forma assíncrona (blur) entre os cliques que compõem o duplo clique, o `input.focus()`
|
|
1177
|
+
// abaixo dispara um evento `focus` genuíno, e handleInputFocus só evita agendar sua própria
|
|
1178
|
+
// reaplicação concorrente (que sobrescreveria nossa seleção do campo inteiro mais tarde)
|
|
1179
|
+
// quando encontra `getActiveIndex() !== null` — o mesmo motivo pelo qual handleInputClick
|
|
1180
|
+
// sempre chama `segmentEngine.selectSegment(index)` antes de `input.focus()`.
|
|
1181
|
+
if (this.segmentEngine.getActiveIndex() === null) {
|
|
1182
|
+
this.segmentEngine.selectFirstSegment();
|
|
1183
|
+
}
|
|
1184
|
+
const generation = ++this.interactionGeneration;
|
|
1185
|
+
// Mesmo padrão defensivo de handleInputClick: o clique que fecha o duplo clique já rodou
|
|
1186
|
+
// handleInputClick (selecionando só um segmento) e pode ter agendado suas próprias
|
|
1187
|
+
// reaplicações adiadas — a geração recém-incrementada as invalida, então só a seleção do
|
|
1188
|
+
// campo inteiro sobrevive.
|
|
1189
|
+
const focusAndSelectAll = () => {
|
|
1190
|
+
if (generation !== this.interactionGeneration)
|
|
1191
|
+
return;
|
|
1192
|
+
input.focus();
|
|
1193
|
+
input.setSelectionRange(0, input.value.length);
|
|
1194
|
+
};
|
|
1195
|
+
focusAndSelectAll();
|
|
1196
|
+
setTimeout(focusAndSelectAll, 0);
|
|
1197
|
+
setTimeout(focusAndSelectAll, 50);
|
|
1198
|
+
}
|
|
666
1199
|
handleInputFocus(event) {
|
|
667
1200
|
this.isFocused.set(true);
|
|
668
1201
|
this.focused.emit(event);
|
|
1202
|
+
if (this.isSegmentedMode() && event.target instanceof HTMLInputElement) {
|
|
1203
|
+
this.ensureSegmentEngineConfigured();
|
|
1204
|
+
const input = event.target;
|
|
1205
|
+
if (input.value === '') {
|
|
1206
|
+
input.value = this.segmentEngine.getDisplayString();
|
|
1207
|
+
}
|
|
1208
|
+
if (this.segmentEngine.getActiveIndex() === null) {
|
|
1209
|
+
// Foco "genuíno" (Tab a partir de outro campo, ou primeiro foco no campo) —
|
|
1210
|
+
// nenhum segmento ativo ainda, então seleciona o primeiro.
|
|
1211
|
+
const generation = ++this.interactionGeneration;
|
|
1212
|
+
this.segmentEngine.selectFirstSegment();
|
|
1213
|
+
this.selectActiveSegmentRange(input);
|
|
1214
|
+
setTimeout(() => {
|
|
1215
|
+
if (generation !== this.interactionGeneration)
|
|
1216
|
+
return;
|
|
1217
|
+
if (this.segmentEngine.getActiveIndex() !== null) {
|
|
1218
|
+
this.selectActiveSegmentRange(input);
|
|
1219
|
+
}
|
|
1220
|
+
}, 0);
|
|
1221
|
+
}
|
|
1222
|
+
else {
|
|
1223
|
+
// Já existe um segmento ativo — por exemplo, handleInputClick acabou de
|
|
1224
|
+
// defini-lo e está refocando o input programaticamente após um blur espúrio.
|
|
1225
|
+
// Não reinicia para o primeiro segmento; só garante que a seleção visual bata
|
|
1226
|
+
// com o estado atual.
|
|
1227
|
+
this.selectActiveSegmentRange(input);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
669
1230
|
if (this.showOnFocus()) {
|
|
670
1231
|
this.openOnFocus();
|
|
671
1232
|
}
|
|
672
1233
|
}
|
|
1234
|
+
/** Mapeia a coordenada X (viewport) de um clique para o índice de caractere mais próximo no valor exibido. */
|
|
1235
|
+
clickClientXToCharIndex(input, clientX) {
|
|
1236
|
+
const style = getComputedStyle(input);
|
|
1237
|
+
const canvas = (this.measureCanvas ??= document.createElement('canvas'));
|
|
1238
|
+
const ctx = canvas.getContext('2d');
|
|
1239
|
+
if (!ctx)
|
|
1240
|
+
return 0;
|
|
1241
|
+
ctx.font = `${style.fontStyle} ${style.fontWeight} ${style.fontSize} ${style.fontFamily}`;
|
|
1242
|
+
const rect = input.getBoundingClientRect();
|
|
1243
|
+
const paddingLeft = parseFloat(style.paddingLeft) || 0;
|
|
1244
|
+
const localX = clientX - rect.left - paddingLeft + input.scrollLeft;
|
|
1245
|
+
const text = input.value;
|
|
1246
|
+
let bestIndex = 0;
|
|
1247
|
+
let bestDistance = Infinity;
|
|
1248
|
+
for (let i = 0; i <= text.length; i++) {
|
|
1249
|
+
const distance = Math.abs(ctx.measureText(text.slice(0, i)).width - localX);
|
|
1250
|
+
if (distance < bestDistance) {
|
|
1251
|
+
bestDistance = distance;
|
|
1252
|
+
bestIndex = i;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
return bestIndex;
|
|
1256
|
+
}
|
|
673
1257
|
handleNativeInput(event) {
|
|
674
1258
|
this.valueInput.emit(event);
|
|
675
1259
|
}
|
|
@@ -677,14 +1261,27 @@ class DatepickerComponent {
|
|
|
677
1261
|
this.isFocused.set(false);
|
|
678
1262
|
this.blurred.emit(event);
|
|
679
1263
|
const input = event.target;
|
|
680
|
-
|
|
1264
|
+
const wasSegmented = this.isSegmentedMode();
|
|
1265
|
+
if (wasSegmented) {
|
|
1266
|
+
this.ensureSegmentEngineConfigured();
|
|
1267
|
+
this.segmentEngine.deactivate();
|
|
1268
|
+
this.commitSegmentedValueIfComplete();
|
|
1269
|
+
this.inputDate = this.segmentEngine.isEmpty() ? '' : this.segmentEngine.getDisplayString();
|
|
1270
|
+
input.value = this.inputDate;
|
|
1271
|
+
}
|
|
681
1272
|
if (!input.value?.trim()) {
|
|
682
1273
|
this.onValidatorChange();
|
|
683
1274
|
}
|
|
684
1275
|
if (this.isOpen()) {
|
|
685
1276
|
const relatedTarget = event.relatedTarget;
|
|
686
1277
|
const panel = this.popupPanel()?.nativeElement;
|
|
687
|
-
|
|
1278
|
+
// Em modo segmentado, um blur sem `relatedTarget` normalmente não é uma saída real
|
|
1279
|
+
// de foco — é o backdrop transparente do próprio popup (não focável) interceptando
|
|
1280
|
+
// um clique no trigger para trocar de segmento. Não redireciona o foco pro painel
|
|
1281
|
+
// nesse caso; deixa handleInputClick/handleInputFocus resolverem (eles refocam o
|
|
1282
|
+
// input e reaplicam o segmento correto logo em seguida).
|
|
1283
|
+
const looksLikeOwnTriggerClick = wasSegmented && !relatedTarget;
|
|
1284
|
+
if (!looksLikeOwnTriggerClick && (!relatedTarget || !panel?.contains(relatedTarget))) {
|
|
688
1285
|
if (this.mode() === 'time') {
|
|
689
1286
|
this.increaseHourButton()?.nativeElement.focus();
|
|
690
1287
|
}
|
|
@@ -698,6 +1295,8 @@ class DatepickerComponent {
|
|
|
698
1295
|
}
|
|
699
1296
|
}
|
|
700
1297
|
handleInputChange(raw) {
|
|
1298
|
+
if (this.isSegmentModeCandidate())
|
|
1299
|
+
return;
|
|
701
1300
|
if (!raw?.trim()) {
|
|
702
1301
|
this.value.set(null);
|
|
703
1302
|
this.rangeValue.set(null);
|
|
@@ -706,14 +1305,13 @@ class DatepickerComponent {
|
|
|
706
1305
|
this._onChange(null);
|
|
707
1306
|
return;
|
|
708
1307
|
}
|
|
709
|
-
const parsed = parseInput(this.mode(), raw, this.
|
|
1308
|
+
const parsed = parseInput(this.mode(), raw, this.rangeSeparator());
|
|
710
1309
|
if (parsed === null) {
|
|
711
1310
|
this._hasFormatError = true;
|
|
712
1311
|
this.onValidatorChange();
|
|
713
1312
|
return;
|
|
714
1313
|
}
|
|
715
1314
|
this._hasFormatError = false;
|
|
716
|
-
this._isBadInput = false;
|
|
717
1315
|
this.onValidatorChange();
|
|
718
1316
|
if (this.isDateRange(parsed)) {
|
|
719
1317
|
this.rangeValue.set(parsed);
|
|
@@ -729,11 +1327,121 @@ class DatepickerComponent {
|
|
|
729
1327
|
}
|
|
730
1328
|
}
|
|
731
1329
|
handleTriggerKeydown(event) {
|
|
1330
|
+
if (this.isSegmentedMode() && event.target instanceof HTMLInputElement) {
|
|
1331
|
+
this.handleSegmentedKeydown(event, event.target);
|
|
1332
|
+
}
|
|
732
1333
|
if (event.key === 'Enter' || event.key === ' ') {
|
|
733
1334
|
event.preventDefault();
|
|
734
1335
|
this.open();
|
|
735
1336
|
}
|
|
736
1337
|
}
|
|
1338
|
+
/** Teclas que devem passar direto para o browser sem inserir/alterar nada no campo. */
|
|
1339
|
+
static SEGMENTED_PASSTHROUGH_KEYS = ['Home', 'End', 'Shift', 'CapsLock', 'Escape', 'Enter', ' '];
|
|
1340
|
+
/** Digitação por segmento (dia/mês/ano/hora/minuto/segundo/ampm) no input do trigger. */
|
|
1341
|
+
handleSegmentedKeydown(event, input) {
|
|
1342
|
+
this.ensureSegmentEngineConfigured();
|
|
1343
|
+
const { key, ctrlKey, metaKey, altKey, shiftKey } = event;
|
|
1344
|
+
if (ctrlKey || metaKey || altKey || key.startsWith('F'))
|
|
1345
|
+
return;
|
|
1346
|
+
if (DatepickerComponent.SEGMENTED_PASSTHROUGH_KEYS.includes(key))
|
|
1347
|
+
return;
|
|
1348
|
+
this.interactionGeneration++;
|
|
1349
|
+
if (key === 'Tab') {
|
|
1350
|
+
const result = this.segmentEngine.moveToAdjacentSegment(shiftKey ? -1 : 1);
|
|
1351
|
+
this.syncSegmentedInput(input);
|
|
1352
|
+
if (result === 'moved') {
|
|
1353
|
+
event.preventDefault();
|
|
1354
|
+
}
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
if (key === 'ArrowLeft' || key === 'ArrowRight') {
|
|
1358
|
+
event.preventDefault();
|
|
1359
|
+
// Ao contrário do Tab, seta no primeiro/último segmento não deve sair do campo nem
|
|
1360
|
+
// desativar a seleção — `moveToAdjacentSegment` zera `activeIndex` ao bater no limite
|
|
1361
|
+
// (comportamento certo pro Tab escapar do campo), então na borda a gente reseleciona
|
|
1362
|
+
// o segmento que já estava ativo, imitando o `<input type="date">` nativo (a seta some
|
|
1363
|
+
// no limite, não navega pra fora).
|
|
1364
|
+
const before = this.segmentEngine.getActiveIndex();
|
|
1365
|
+
const result = this.segmentEngine.moveToAdjacentSegment(key === 'ArrowLeft' ? -1 : 1);
|
|
1366
|
+
if (result === 'boundary' && before !== null) {
|
|
1367
|
+
this.segmentEngine.selectSegment(before);
|
|
1368
|
+
}
|
|
1369
|
+
this.syncSegmentedInput(input);
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
if (key === 'ArrowUp' || key === 'ArrowDown') {
|
|
1373
|
+
event.preventDefault();
|
|
1374
|
+
this.segmentEngine.incrementActiveSegment(key === 'ArrowUp' ? 1 : -1);
|
|
1375
|
+
this.syncSegmentedInput(input);
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
if (key === 'Backspace' || key === 'Delete') {
|
|
1379
|
+
event.preventDefault();
|
|
1380
|
+
const isWholeFieldSelected = input.selectionStart === 0 && input.selectionEnd === input.value.length;
|
|
1381
|
+
if (isWholeFieldSelected) {
|
|
1382
|
+
this.segmentEngine.clearAll();
|
|
1383
|
+
}
|
|
1384
|
+
else {
|
|
1385
|
+
this.segmentEngine.clearActiveSegment(key === 'Backspace' ? 'backspace' : 'delete');
|
|
1386
|
+
}
|
|
1387
|
+
this.syncSegmentedInput(input);
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
if (/^[0-9]$/.test(key)) {
|
|
1391
|
+
event.preventDefault();
|
|
1392
|
+
this.segmentEngine.typeDigit(key);
|
|
1393
|
+
this.syncSegmentedInput(input);
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
if (this.segmentEngine.activeKind() === 'ampm' && /^[apAP]$/.test(key)) {
|
|
1397
|
+
event.preventDefault();
|
|
1398
|
+
this.segmentEngine.setAmPm(key.toLowerCase() === 'a' ? 0 : 1);
|
|
1399
|
+
this.syncSegmentedInput(input);
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
// Qualquer outra tecla imprimível (letras, símbolos) não é inserida no campo.
|
|
1403
|
+
event.preventDefault();
|
|
1404
|
+
}
|
|
1405
|
+
/** Sincroniza o DOM com o estado atual da engine (valor + seleção do segmento ativo) após uma mutação. */
|
|
1406
|
+
syncSegmentedInput(input) {
|
|
1407
|
+
this.inputDate = this.segmentEngine.getDisplayString();
|
|
1408
|
+
input.value = this.inputDate;
|
|
1409
|
+
this.selectActiveSegmentRange(input);
|
|
1410
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
1411
|
+
this.commitSegmentedValueIfComplete();
|
|
1412
|
+
}
|
|
1413
|
+
selectActiveSegmentRange(input) {
|
|
1414
|
+
const index = this.segmentEngine.getActiveIndex();
|
|
1415
|
+
if (index === null)
|
|
1416
|
+
return;
|
|
1417
|
+
const [start, end] = this.segmentEngine.getSegmentRange(index);
|
|
1418
|
+
input.setSelectionRange(start, end);
|
|
1419
|
+
}
|
|
1420
|
+
/** Propaga o valor da engine para o modelo (CVA) assim que todos os segmentos estiverem completos. */
|
|
1421
|
+
commitSegmentedValueIfComplete() {
|
|
1422
|
+
if (!this.segmentEngine.isComplete()) {
|
|
1423
|
+
if (this.value() !== null) {
|
|
1424
|
+
this.value.set(null);
|
|
1425
|
+
this._onChange(null);
|
|
1426
|
+
}
|
|
1427
|
+
return;
|
|
1428
|
+
}
|
|
1429
|
+
const base = this.value() ??
|
|
1430
|
+
new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate(), this.hours(), this.minutes(), this.showSeconds() ? this.seconds() : 0);
|
|
1431
|
+
const date = this.segmentEngine.toDate(base);
|
|
1432
|
+
if (!date)
|
|
1433
|
+
return;
|
|
1434
|
+
this.value.set(date);
|
|
1435
|
+
if (this.showCalendar()) {
|
|
1436
|
+
this.viewDate.set(new Date(date.getFullYear(), date.getMonth(), 1));
|
|
1437
|
+
}
|
|
1438
|
+
if (this.showTimePicker()) {
|
|
1439
|
+
this.hours.set(date.getHours());
|
|
1440
|
+
this.minutes.set(date.getMinutes());
|
|
1441
|
+
this.seconds.set(date.getSeconds());
|
|
1442
|
+
}
|
|
1443
|
+
this._onChange(date);
|
|
1444
|
+
}
|
|
737
1445
|
// ── Navigation ────────────────────────────────────────────────────────
|
|
738
1446
|
navigatePrev() {
|
|
739
1447
|
const view = this.viewDate();
|
|
@@ -861,13 +1569,13 @@ class DatepickerComponent {
|
|
|
861
1569
|
const newDate = new Date(day.date.getFullYear(), day.date.getMonth(), day.date.getDate(), this.hours(), this.minutes(), this.showSeconds() ? this.seconds() : 0);
|
|
862
1570
|
this.value.set(newDate);
|
|
863
1571
|
if (this.mode() === 'date') {
|
|
864
|
-
this.
|
|
1572
|
+
this.setDisplayFor(newDate);
|
|
865
1573
|
this._onChange(newDate);
|
|
866
1574
|
this.selected.emit(newDate);
|
|
867
1575
|
this.closePanel();
|
|
868
1576
|
}
|
|
869
1577
|
else if (this.mode() === 'datetime') {
|
|
870
|
-
this.
|
|
1578
|
+
this.setDisplayFor(newDate);
|
|
871
1579
|
this._onChange(newDate);
|
|
872
1580
|
this.selected.emit(newDate);
|
|
873
1581
|
if (this.hideOnDateTimeSelect()) {
|
|
@@ -907,7 +1615,7 @@ class DatepickerComponent {
|
|
|
907
1615
|
const newViewDate = new Date(year, monthIndex, 1);
|
|
908
1616
|
if (this.mode() === 'month') {
|
|
909
1617
|
this.value.set(newViewDate);
|
|
910
|
-
this.
|
|
1618
|
+
this.setDisplayFor(newViewDate);
|
|
911
1619
|
this._onChange(newViewDate);
|
|
912
1620
|
this.selected.emit(newViewDate);
|
|
913
1621
|
this.closePanel();
|
|
@@ -944,7 +1652,7 @@ class DatepickerComponent {
|
|
|
944
1652
|
this.rangeValue.set(null);
|
|
945
1653
|
this.multipleValues.set([]);
|
|
946
1654
|
this.pendingRangeStart.set(null);
|
|
947
|
-
this.
|
|
1655
|
+
this.setDisplayFor(null);
|
|
948
1656
|
this._onChange(null);
|
|
949
1657
|
this.clearClick.emit(event);
|
|
950
1658
|
this.closePanel();
|
|
@@ -953,7 +1661,7 @@ class DatepickerComponent {
|
|
|
953
1661
|
const current = this.value() ?? new Date();
|
|
954
1662
|
const confirmed = new Date(current.getFullYear(), current.getMonth(), current.getDate(), this.hours(), this.minutes(), this.showSeconds() ? this.seconds() : 0);
|
|
955
1663
|
this.value.set(confirmed);
|
|
956
|
-
this.
|
|
1664
|
+
this.setDisplayFor(confirmed);
|
|
957
1665
|
this._onChange(confirmed);
|
|
958
1666
|
this.selected.emit(confirmed);
|
|
959
1667
|
this.closePanel();
|
|
@@ -1068,7 +1776,7 @@ class DatepickerComponent {
|
|
|
1068
1776
|
if (disabledDays.length && disabledDays.includes(date.getDay()))
|
|
1069
1777
|
return true;
|
|
1070
1778
|
const disabledDates = this.disabledDates();
|
|
1071
|
-
if (disabledDates.length && disabledDates.some(d => isSameDay(d, date)))
|
|
1779
|
+
if (disabledDates.length && disabledDates.some((d) => isSameDay(d, date)))
|
|
1072
1780
|
return true;
|
|
1073
1781
|
return false;
|
|
1074
1782
|
}
|
|
@@ -1108,7 +1816,7 @@ class DatepickerComponent {
|
|
|
1108
1816
|
return String(value).padStart(2, '0');
|
|
1109
1817
|
}
|
|
1110
1818
|
formatDayAriaLabel(date) {
|
|
1111
|
-
return date.toLocaleDateString(
|
|
1819
|
+
return date.toLocaleDateString(this.currentLang(), {
|
|
1112
1820
|
weekday: 'long',
|
|
1113
1821
|
year: 'numeric',
|
|
1114
1822
|
month: 'long',
|
|
@@ -1117,13 +1825,62 @@ class DatepickerComponent {
|
|
|
1117
1825
|
}
|
|
1118
1826
|
/** Formata o valor para exibição no input, aplicando rangeSeparator, dateFormat e multipleSeparator. */
|
|
1119
1827
|
formatForDisplay(value) {
|
|
1120
|
-
return formatValueForInput(this.mode(), value, this.rangeSeparator(), this.
|
|
1828
|
+
return formatValueForInput(this.mode(), value, this.rangeSeparator(), this.resolvedDateFormat(), this.multipleSeparator());
|
|
1829
|
+
}
|
|
1830
|
+
/**
|
|
1831
|
+
* Atualiza `inputDate` a partir de um valor único, roteando pela engine de segmentos
|
|
1832
|
+
* (date/month/time/datetime) ou pelo formatador legado (range/year/multiple).
|
|
1833
|
+
*/
|
|
1834
|
+
setDisplayFor(value) {
|
|
1835
|
+
if (this.isSegmentModeCandidate()) {
|
|
1836
|
+
this.ensureSegmentEngineConfigured();
|
|
1837
|
+
this.segmentEngine.loadFromDate(value);
|
|
1838
|
+
this.inputDate = value ? this.segmentEngine.getDisplayString() : '';
|
|
1839
|
+
return;
|
|
1840
|
+
}
|
|
1841
|
+
this.inputDate = this.formatForDisplay(value);
|
|
1842
|
+
}
|
|
1843
|
+
/**
|
|
1844
|
+
* (Re)configura a engine de segmentos quando `mode`/`dateFormat`/`hourFormat`/`showSeconds`
|
|
1845
|
+
* mudaram desde a última chamada, recarregando o valor atual. Chamado de forma síncrona a
|
|
1846
|
+
* partir de todo ponto de entrada que usa a engine — não depende de um `effect()` (que só
|
|
1847
|
+
* roda em um ciclo de change detection, tarde demais para leituras/escritas imediatas).
|
|
1848
|
+
*/
|
|
1849
|
+
ensureSegmentEngineConfigured() {
|
|
1850
|
+
const mode = this.mode();
|
|
1851
|
+
if (mode !== 'date' && mode !== 'month' && mode !== 'time' && mode !== 'datetime')
|
|
1852
|
+
return;
|
|
1853
|
+
const config = {
|
|
1854
|
+
mode,
|
|
1855
|
+
dateFormat: this.resolvedDateFormat(),
|
|
1856
|
+
hourFormat: this.resolvedHourFormat(),
|
|
1857
|
+
showSeconds: this.showSeconds(),
|
|
1858
|
+
};
|
|
1859
|
+
const last = this.lastSegmentConfig;
|
|
1860
|
+
if (last &&
|
|
1861
|
+
last.mode === config.mode &&
|
|
1862
|
+
last.dateFormat === config.dateFormat &&
|
|
1863
|
+
last.hourFormat === config.hourFormat &&
|
|
1864
|
+
last.showSeconds === config.showSeconds) {
|
|
1865
|
+
return;
|
|
1866
|
+
}
|
|
1867
|
+
this.lastSegmentConfig = config;
|
|
1868
|
+
this.segmentEngine.configure(config);
|
|
1869
|
+
this.segmentEngine.loadFromDate(this.value());
|
|
1870
|
+
}
|
|
1871
|
+
/** Retorna a tradução de `key`, ou `fallback` quando a chave não está traduzida (ngx-translate retorna a própria chave). */
|
|
1872
|
+
resolveTranslatedOrDefault(key, fallback) {
|
|
1873
|
+
const translated = this.translateService.instant(key);
|
|
1874
|
+
if (typeof translated !== 'string' || !translated || translated === key) {
|
|
1875
|
+
return fallback;
|
|
1876
|
+
}
|
|
1877
|
+
return translated;
|
|
1121
1878
|
}
|
|
1122
1879
|
syncTimeToValue() {
|
|
1123
1880
|
const date = this.value() ?? new Date();
|
|
1124
1881
|
const newDate = new Date(date.getFullYear(), date.getMonth(), date.getDate(), this.hours(), this.minutes(), this.showSeconds() ? this.seconds() : 0);
|
|
1125
1882
|
this.value.set(newDate);
|
|
1126
|
-
this.
|
|
1883
|
+
this.setDisplayFor(newDate);
|
|
1127
1884
|
if (this.mode() === 'time') {
|
|
1128
1885
|
this._onChange(newDate);
|
|
1129
1886
|
}
|
|
@@ -1155,8 +1912,6 @@ class DatepickerComponent {
|
|
|
1155
1912
|
computeValidationErrors(value, control) {
|
|
1156
1913
|
if (this._hasFormatError)
|
|
1157
1914
|
return { invalidFormat: true };
|
|
1158
|
-
if (this._isBadInput)
|
|
1159
|
-
return { badInput: true };
|
|
1160
1915
|
if (!value || (Array.isArray(value) && value.length === 0))
|
|
1161
1916
|
return Validators.nullValidator(control);
|
|
1162
1917
|
const min = this.min();
|
|
@@ -1208,7 +1963,7 @@ class DatepickerComponent {
|
|
|
1208
1963
|
useExisting: DatepickerComponent,
|
|
1209
1964
|
multi: true,
|
|
1210
1965
|
},
|
|
1211
|
-
], viewQueries: [{ propertyName: "popupPanel", first: true, predicate: ["popupPanel"], descendants: true, read: ElementRef, isSignal: true }, { propertyName: "increaseHourButton", first: true, predicate: ["increaseHourButton"], descendants: true, read: ElementRef, isSignal: true }, { propertyName: "iconButton", first: true, predicate: ["iconButton"], descendants: true, read: ElementRef, isSignal: true }], ngImport: i0, template: "<div\n cdkOverlayOrigin\n #trigger=\"cdkOverlayOrigin\"\n class=\"relative flex w-full items-center\"\n role=\"combobox\"\n aria-haspopup=\"grid\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"popupId()\"\n>\n <input\n class=\"hide-native-icon h-[35px] w-full rounded-small border border-solid border-grayscale-30 pl-3 text-sm outline-none transition-colors focus:border-primary\"\n [class.pr-10]=\"showIcon() && !showClear()\"\n [class.pr-16]=\"showIcon() && showClear()\"\n [class.pr-6]=\"!showIcon() && showClear()\"\n [class.bg-grayscale-0]=\"!disabled()\"\n [class.text-grayscale-90]=\"!disabled()\"\n [class.border-grayscale-20]=\"disabled()\"\n [class.bg-grayscale-5]=\"disabled()\"\n [class.text-grayscale-30]=\"disabled()\"\n [class.cursor-not-allowed]=\"disabled()\"\n [id]=\"inputId()\"\n [name]=\"name() ?? inputId()\"\n [type]=\"inputType()\"\n [placeholder]=\"placeholder() ?? (placeholderKey() | translate)\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonlyInput() || mode() === 'range' || mode() === 'year' || isMultipleMode()\"\n [tabIndex]=\"tabindex() ?? null\"\n [autofocus]=\"autofocus()\"\n [(ngModel)]=\"inputDate\"\n (ngModelChange)=\"handleInputChange($event)\"\n (focus)=\"handleInputFocus($event)\"\n (blur)=\"handleInputBlur($event)\"\n (click)=\"handleInputClick()\"\n (keydown)=\"handleTriggerKeydown($event)\"\n (input)=\"handleNativeInput($event)\"\n [attr.aria-invalid]=\"ngControl?.control?.invalid || null\"\n />\n\n @if (showClear() && inputDate) {\n <button\n type=\"button\"\n class=\"absolute flex h-[35px] w-6 items-center justify-center text-grayscale-60 transition-colors hover:text-grayscale-90\"\n [class.right-9]=\"showIcon()\"\n [class.right-0]=\"!showIcon()\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_clear' | translate\"\n (click)=\"clearValue($event)\"\n >\n <i class=\"fas fa-times text-xs\" aria-hidden=\"true\"></i>\n </button>\n }\n\n @if (showIcon()) {\n <button\n #iconButton\n type=\"button\"\n class=\"absolute right-0 flex h-[35px] w-9 items-center justify-center border-l border-solid transition-colors\"\n [class.border-grayscale-20]=\"disabled()\"\n [class.text-grayscale-30]=\"disabled()\"\n [class.cursor-not-allowed]=\"disabled()\"\n [class.border-grayscale-30]=\"!disabled() && !isFocused()\"\n [class.border-primary]=\"isFocused() && !disabled()\"\n [class.text-grayscale-90]=\"!disabled()\"\n [class.hover:bg-grayscale-10]=\"!disabled()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_open' | translate\"\n (click)=\"toggle()\"\n (keydown)=\"handleTriggerKeydown($event)\"\n >\n <i [class]=\"resolvedIcon()\" aria-hidden=\"true\"></i>\n </button>\n }\n</div>\n\n<ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"trigger\"\n [cdkConnectedOverlayOpen]=\"isOpen()\"\n [cdkConnectedOverlayHasBackdrop]=\"true\"\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayPositions]=\"positions\"\n (backdropClick)=\"closePanel()\"\n (overlayKeydown)=\"handleOverlayKeydown($event)\"\n (detach)=\"isOpen.set(false)\"\n>\n <div\n #popupPanel\n class=\"z-50 w-72 rounded-medium border border-solid border-grayscale-30 bg-grayscale-0 shadow-md\"\n role=\"dialog\"\n cdkTrapFocus\n [id]=\"popupId()\"\n [attr.aria-label]=\"dialogAriaLabelKey() | translate\"\n >\n @if (showCalendar()) {\n @if (pickerView() === 'calendar') {\n <!-- Cabe\u00E7alho de navega\u00E7\u00E3o mensal -->\n <div class=\"flex items-center justify-between border-b border-solid border-grayscale-30 px-3 py-2\">\n <button\n type=\"button\"\n class=\"flex h-7 w-7 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10 disabled:cursor-not-allowed disabled:text-grayscale-30\"\n [disabled]=\"prevDisabled()\"\n [attr.aria-disabled]=\"prevDisabled() ? 'true' : null\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_prev_month' | translate\"\n (click)=\"navigatePrev()\"\n >\n <i class=\"fas fa-chevron-left text-xs\" aria-hidden=\"true\"></i>\n </button>\n\n <button\n type=\"button\"\n class=\"flex items-center gap-1 rounded-small px-2 py-1 text-sm font-medium text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_select_month_year' | translate\"\n (click)=\"switchToMonths()\"\n >\n {{ currentMonthKey() | translate }} {{ currentYear() }}\n <i class=\"fas fa-chevron-down text-[10px]\" aria-hidden=\"true\"></i>\n </button>\n\n <button\n type=\"button\"\n class=\"flex h-7 w-7 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10 disabled:cursor-not-allowed disabled:text-grayscale-30\"\n [disabled]=\"nextDisabled()\"\n [attr.aria-disabled]=\"nextDisabled() ? 'true' : null\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_next_month' | translate\"\n (click)=\"navigateNext()\"\n >\n <i class=\"fas fa-chevron-right text-xs\" aria-hidden=\"true\"></i>\n </button>\n </div>\n\n <!-- Cabe\u00E7alhos dos dias da semana -->\n <div class=\"grid grid-cols-7 px-3 pt-2\" role=\"row\">\n @for (dayKey of orderedWeekDayKeys(); track dayKey) {\n <div\n class=\"flex h-8 items-center justify-center text-xs font-medium text-grayscale-60\"\n role=\"columnheader\"\n [attr.aria-label]=\"dayKey | translate\"\n >\n {{ dayKey | translate }}\n </div>\n }\n </div>\n\n <!-- Grade do calend\u00E1rio -->\n <div\n class=\"grid grid-cols-7 px-3 pb-2\"\n role=\"grid\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_calendar_label' | translate: { month: (currentMonthKey() | translate), year: currentYear() }\"\n (keydown)=\"handleCalendarGridKeydown($event)\"\n >\n @for (week of calendarWeeks(); track $index) {\n <div role=\"row\" class=\"contents\">\n @for (day of week; track day.date.toISOString()) {\n <button\n type=\"button\"\n role=\"gridcell\"\n class=\"flex h-8 w-8 items-center justify-center rounded-[50%] text-sm font-normal transition-colors\"\n [class.invisible]=\"!showOtherMonths() && !day.isCurrentMonth\"\n [attr.aria-label]=\"formatDayAriaLabel(day.date)\"\n [attr.aria-selected]=\"day.isSelected || day.isRangeStart || day.isRangeEnd || null\"\n [attr.aria-current]=\"day.isToday ? 'date' : null\"\n [attr.aria-disabled]=\"day.isDisabled ? 'true' : null\"\n [attr.aria-hidden]=\"!showOtherMonths() && !day.isCurrentMonth ? 'true' : null\"\n [attr.data-focus]=\"day.isSelected || day.isRangeStart || day.isRangeEnd ? 'selected' : day.isToday ? 'today' : null\"\n [disabled]=\"day.isDisabled\"\n [ngClass]=\"{\n 'bg-primary font-semibold text-grayscale-0':\n day.isSelected || day.isRangeStart || day.isRangeEnd,\n 'bg-grayscale-10 hover:bg-grayscale-20':\n day.isInRange && !day.isRangeStart && !day.isRangeEnd,\n 'text-grayscale-90 hover:bg-grayscale-10':\n !day.isSelected &&\n !day.isRangeStart &&\n !day.isRangeEnd &&\n !day.isDisabled &&\n day.isCurrentMonth,\n 'text-grayscale-60 hover:bg-grayscale-10':\n !day.isSelected &&\n !day.isRangeStart &&\n !day.isRangeEnd &&\n !day.isDisabled &&\n !day.isCurrentMonth,\n 'ring-2 ring-inset ring-primary':\n day.isToday &&\n !day.isSelected &&\n !day.isRangeStart &&\n !day.isRangeEnd,\n 'cursor-not-allowed text-grayscale-30': day.isDisabled\n }\"\n (mouseenter)=\"onDayHover(day.date)\"\n (mouseleave)=\"onDayHover(null)\"\n (click)=\"selectDate(day)\"\n >\n {{ day.dayOfMonth }}\n </button>\n }\n </div>\n }\n </div>\n }\n\n @if (pickerView() === 'years') {\n <!-- Cabe\u00E7alho de navega\u00E7\u00E3o por d\u00E9cada -->\n <div class=\"flex items-center justify-between border-b border-solid border-grayscale-30 px-3 py-2\">\n <button\n type=\"button\"\n class=\"flex h-7 w-7 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10 disabled:cursor-not-allowed disabled:text-grayscale-30\"\n [disabled]=\"prevDisabled()\"\n [attr.aria-disabled]=\"prevDisabled() ? 'true' : null\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_prev_decade' | translate\"\n (click)=\"navigatePrev()\"\n >\n <i class=\"fas fa-chevron-left text-xs\" aria-hidden=\"true\"></i>\n </button>\n\n <span class=\"text-sm font-medium text-grayscale-90\">{{ currentDecadeStart() }} \u2013 {{ currentDecadeStart() + 11 }}</span>\n\n <button\n type=\"button\"\n class=\"flex h-7 w-7 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10 disabled:cursor-not-allowed disabled:text-grayscale-30\"\n [disabled]=\"nextDisabled()\"\n [attr.aria-disabled]=\"nextDisabled() ? 'true' : null\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_next_decade' | translate\"\n (click)=\"navigateNext()\"\n >\n <i class=\"fas fa-chevron-right text-xs\" aria-hidden=\"true\"></i>\n </button>\n </div>\n\n <!-- Grade de anos -->\n <div\n class=\"grid grid-cols-3 gap-2 px-3 py-3\"\n role=\"grid\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_select_year' | translate\"\n >\n @for (row of yearsGrid(); track $index) {\n @for (year of row; track year) {\n <button\n type=\"button\"\n role=\"gridcell\"\n class=\"flex h-9 items-center justify-center rounded-small text-sm font-normal transition-colors\"\n [attr.aria-selected]=\"isYearSelected(year) || null\"\n [attr.aria-disabled]=\"isYearDisabled(year) ? 'true' : null\"\n [attr.aria-current]=\"isCurrentYear(year) ? 'date' : null\"\n [attr.data-focus]=\"isYearSelected(year) ? 'selected' : isCurrentYear(year) ? 'today' : null\"\n [disabled]=\"isYearDisabled(year)\"\n [ngClass]=\"{\n 'bg-primary font-semibold text-grayscale-0': isYearSelected(year),\n 'ring-2 ring-inset ring-primary': isCurrentYear(year) && !isYearSelected(year) && !isYearDisabled(year),\n 'text-grayscale-90 hover:bg-grayscale-10': !isYearSelected(year) && !isYearDisabled(year),\n 'cursor-not-allowed text-grayscale-30': isYearDisabled(year)\n }\"\n (click)=\"selectYear(year)\"\n >\n {{ year }}\n </button>\n }\n }\n </div>\n }\n\n @if (pickerView() === 'months') {\n <!-- Cabe\u00E7alho de navega\u00E7\u00E3o anual -->\n <div class=\"flex items-center justify-between border-b border-solid border-grayscale-30 px-3 py-2\">\n <button\n type=\"button\"\n class=\"flex h-7 w-7 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10 disabled:cursor-not-allowed disabled:text-grayscale-30\"\n [disabled]=\"prevDisabled()\"\n [attr.aria-disabled]=\"prevDisabled() ? 'true' : null\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_prev_year' | translate\"\n (click)=\"navigatePrev()\"\n >\n <i class=\"fas fa-chevron-left text-xs\" aria-hidden=\"true\"></i>\n </button>\n\n <span class=\"text-sm font-medium text-grayscale-90\">{{ currentYear() }}</span>\n\n <button\n type=\"button\"\n class=\"flex h-7 w-7 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10 disabled:cursor-not-allowed disabled:text-grayscale-30\"\n [disabled]=\"nextDisabled()\"\n [attr.aria-disabled]=\"nextDisabled() ? 'true' : null\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_next_year' | translate\"\n (click)=\"navigateNext()\"\n >\n <i class=\"fas fa-chevron-right text-xs\" aria-hidden=\"true\"></i>\n </button>\n </div>\n\n <!-- Grade de meses -->\n <div\n class=\"grid grid-cols-3 gap-2 px-3 py-3\"\n role=\"grid\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_select_month' | translate\"\n >\n @for (row of monthsGrid(); track $index) {\n @for (month of row; track month.index) {\n <button\n type=\"button\"\n role=\"gridcell\"\n class=\"flex h-9 items-center justify-center rounded-small text-sm font-normal transition-colors\"\n [attr.aria-selected]=\"isMonthSelected(month.index) || null\"\n [attr.aria-disabled]=\"isMonthDisabled(month.index) ? 'true' : null\"\n [attr.data-focus]=\"isMonthSelected(month.index) ? 'selected' : null\"\n [disabled]=\"isMonthDisabled(month.index)\"\n [ngClass]=\"{\n 'bg-primary font-semibold text-grayscale-0': isMonthSelected(month.index),\n 'text-grayscale-90 hover:bg-grayscale-10':\n !isMonthSelected(month.index) && !isMonthDisabled(month.index),\n 'cursor-not-allowed text-grayscale-30': isMonthDisabled(month.index)\n }\"\n (click)=\"selectMonth(month.index)\"\n >\n {{ month.key | translate }}\n </button>\n }\n }\n </div>\n\n @if (mode() !== 'month') {\n <div class=\"border-t border-solid border-grayscale-30 px-3 py-2\">\n <button\n type=\"button\"\n class=\"flex items-center gap-2 rounded-small px-2 py-1 text-sm text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n (click)=\"backToCalendar()\"\n >\n <i class=\"fas fa-arrow-left\" aria-hidden=\"true\"></i>\n {{ 'platform.angular_components.datepicker_back_to_calendar' | translate }}\n </button>\n </div>\n }\n }\n }\n\n <!-- Time picker -->\n @if (showTimePicker()) {\n <div\n class=\"flex items-center justify-center gap-3 px-4 py-3\"\n [class.border-t]=\"showCalendar()\"\n [class.border-solid]=\"showCalendar()\"\n [class.border-grayscale-30]=\"showCalendar()\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_select_time' | translate\"\n >\n <!-- Horas -->\n <div class=\"flex flex-col items-center gap-1\">\n <button\n #increaseHourButton\n type=\"button\"\n class=\"flex h-6 w-6 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_increment_hour' | translate\"\n (click)=\"incrementHours()\"\n >\n <i class=\"fas fa-chevron-up text-xs\" aria-hidden=\"true\"></i>\n </button>\n <input\n type=\"text\"\n inputmode=\"numeric\"\n [attr.maxlength]=\"timeInputMaxLength\"\n class=\"h-9 w-12 rounded-small border border-solid border-grayscale-30 bg-grayscale-5 text-center text-base font-medium text-grayscale-90 outline-none focus:border-primary focus:outline-1 focus:outline-primary\"\n [value]=\"padTime(displayHours())\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_hour' | translate\"\n aria-live=\"polite\"\n pattern=\"[0-9]+\"\n (keydown)=\"handleHoursKeydown($event)\"\n (change)=\"handleHoursInput($event)\"\n />\n <button\n type=\"button\"\n class=\"flex h-6 w-6 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_decrement_hour' | translate\"\n (click)=\"decrementHours()\"\n >\n <i class=\"fas fa-chevron-down text-xs\" aria-hidden=\"true\"></i>\n </button>\n </div>\n\n <span class=\"text-xl font-bold text-grayscale-60\" aria-hidden=\"true\">:</span>\n\n <!-- Minutos -->\n <div class=\"flex flex-col items-center gap-1\">\n <button\n type=\"button\"\n class=\"flex h-6 w-6 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_increment_minute' | translate\"\n (click)=\"incrementMinutes()\"\n >\n <i class=\"fas fa-chevron-up text-xs\" aria-hidden=\"true\"></i>\n </button>\n <input\n type=\"text\"\n inputmode=\"numeric\"\n [attr.maxlength]=\"timeInputMaxLength\"\n class=\"h-9 w-12 rounded-small border border-solid border-grayscale-30 bg-grayscale-5 text-center text-base font-medium text-grayscale-90 outline-none focus:border-primary focus:outline-1 focus:outline-primary\"\n [value]=\"padTime(minutes())\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_minute' | translate\"\n aria-live=\"polite\"\n pattern=\"[0-9]+\"\n (keydown)=\"handleMinutesKeydown($event)\"\n (change)=\"handleMinutesInput($event)\"\n />\n <button\n type=\"button\"\n class=\"flex h-6 w-6 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_decrement_minute' | translate\"\n (click)=\"decrementMinutes()\"\n >\n <i class=\"fas fa-chevron-down text-xs\" aria-hidden=\"true\"></i>\n </button>\n </div>\n\n @if (showSeconds()) {\n <span class=\"text-xl font-bold text-grayscale-60\" aria-hidden=\"true\">:</span>\n\n <!-- Segundos -->\n <div class=\"flex flex-col items-center gap-1\">\n <button\n type=\"button\"\n class=\"flex h-6 w-6 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_increment_second' | translate\"\n (click)=\"incrementSeconds()\"\n >\n <i class=\"fas fa-chevron-up text-xs\" aria-hidden=\"true\"></i>\n </button>\n <input\n type=\"text\"\n inputmode=\"numeric\"\n [attr.maxlength]=\"timeInputMaxLength\"\n class=\"h-9 w-12 rounded-small border border-solid border-grayscale-30 bg-grayscale-5 text-center text-base font-medium text-grayscale-90 outline-none focus:border-primary focus:outline-1 focus:outline-primary\"\n [value]=\"padTime(seconds())\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_second' | translate\"\n aria-live=\"polite\"\n pattern=\"[0-9]+\"\n (keydown)=\"handleSecondsKeydown($event)\"\n (change)=\"handleSecondsInput($event)\"\n />\n <button\n type=\"button\"\n class=\"flex h-6 w-6 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_decrement_second' | translate\"\n (click)=\"decrementSeconds()\"\n >\n <i class=\"fas fa-chevron-down text-xs\" aria-hidden=\"true\"></i>\n </button>\n </div>\n }\n\n @if (is12HourFormat()) {\n <!-- Seletor AM/PM -->\n <button\n type=\"button\"\n class=\"flex h-9 w-12 items-center justify-center rounded-small border border-solid border-grayscale-30 bg-grayscale-5 text-base font-medium text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_toggle_meridiem' | translate\"\n aria-live=\"polite\"\n (click)=\"toggleAmPm()\"\n >\n {{ amPm() }}\n </button>\n }\n </div>\n }\n\n <!-- Rodap\u00E9 de a\u00E7\u00F5es -->\n @if (showButtonBar() || mode() === 'datetime' || mode() === 'time') {\n <div class=\"flex items-center justify-between border-t border-solid border-grayscale-30 px-3 py-2\">\n @if (showButtonBar()) {\n <div>\n @if (mode() === 'date' || mode() === 'datetime' || mode() === 'range') {\n <button\n type=\"button\"\n class=\"rounded-small px-2 py-1 text-sm text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n (click)=\"selectToday()\"\n >\n {{ 'platform.angular_components.datepicker_today' | translate }}\n </button>\n }\n </div>\n\n <div class=\"flex gap-2\">\n <button\n type=\"button\"\n class=\"rounded-small px-2 py-1 text-sm text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n (click)=\"clearValue($event)\"\n >\n {{ 'platform.angular_components.datepicker_clear' | translate }}\n </button>\n\n @if (mode() === 'datetime' || mode() === 'time') {\n <button\n type=\"button\"\n class=\"rounded-small bg-primary px-3 py-1 text-sm font-medium text-grayscale-0 transition-colors hover:opacity-90\"\n (click)=\"confirm()\"\n >\n {{ 'platform.angular_components.datepicker_confirm' | translate }}\n </button>\n }\n </div>\n } @else {\n <div></div>\n <div>\n @if (mode() === 'datetime' || mode() === 'time') {\n <button\n type=\"button\"\n class=\"rounded-small bg-primary px-3 py-1 text-sm font-medium text-grayscale-0 transition-colors hover:opacity-90\"\n (click)=\"confirm()\"\n >\n {{ 'platform.angular_components.datepicker_confirm' | translate }}\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n</ng-template>\n", styles: [":host{display:block;position:relative;width:100%}:host.ng-invalid.ng-dirty input{border-color:#c13018}:host.ng-invalid.ng-dirty input:focus{border-color:#c13018;outline-color:#c13018}:host.ng-invalid.ng-dirty button[type=button]{border-color:#c13018}:host.no-icon button[type=button]{display:none}:host.no-icon input{padding-right:.75rem}input.hide-native-icon::-webkit-calendar-picker-indicator{display:none}button[type=button]:focus-visible{outline:2px solid #428BCA;outline-offset:1px;border-radius:inherit}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1.TranslatePipe, name: "translate" }, { kind: "directive", type: CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "directive", type: CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "directive", type: 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: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ReactiveFormsModule }] });
|
|
1966
|
+
], viewQueries: [{ propertyName: "popupPanel", first: true, predicate: ["popupPanel"], descendants: true, read: ElementRef, isSignal: true }, { propertyName: "increaseHourButton", first: true, predicate: ["increaseHourButton"], descendants: true, read: ElementRef, isSignal: true }, { propertyName: "iconButton", first: true, predicate: ["iconButton"], descendants: true, read: ElementRef, isSignal: true }, { propertyName: "triggerInput", first: true, predicate: ["triggerInput"], descendants: true, read: ElementRef, isSignal: true }], ngImport: i0, template: "<div\n cdkOverlayOrigin\n #trigger=\"cdkOverlayOrigin\"\n class=\"relative flex w-full items-center\"\n role=\"combobox\"\n aria-haspopup=\"grid\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"popupId()\"\n>\n <input\n #triggerInput\n class=\"h-[35px] w-full rounded-small border border-solid border-grayscale-30 pl-3 text-sm outline-none transition-colors focus:border-primary\"\n [class.pr-10]=\"showIcon() && !showClear()\"\n [class.pr-16]=\"showIcon() && showClear()\"\n [class.pr-6]=\"!showIcon() && showClear()\"\n [class.bg-grayscale-0]=\"!disabled()\"\n [class.text-grayscale-90]=\"!disabled()\"\n [class.border-grayscale-20]=\"disabled()\"\n [class.bg-grayscale-5]=\"disabled()\"\n [class.text-grayscale-30]=\"disabled()\"\n [class.cursor-not-allowed]=\"disabled()\"\n [id]=\"inputId()\"\n [name]=\"name() ?? inputId()\"\n type=\"text\"\n [placeholder]=\"placeholder() ?? (placeholderKey() | translate)\"\n [disabled]=\"disabled()\"\n [readonly]=\"\n readonlyInput() ||\n mode() === 'range' ||\n mode() === 'year' ||\n isMultipleMode() ||\n (isSegmentModeCandidate() && isTouchDevice())\n \"\n [tabIndex]=\"tabindex() ?? null\"\n [autofocus]=\"autofocus()\"\n [(ngModel)]=\"inputDate\"\n (ngModelChange)=\"handleInputChange($event)\"\n (focus)=\"handleInputFocus($event)\"\n (blur)=\"handleInputBlur($event)\"\n (mousedown)=\"handleInputClick($event)\"\n (keydown)=\"handleTriggerKeydown($event)\"\n (input)=\"handleNativeInput($event)\"\n [attr.aria-invalid]=\"ngControl?.control?.invalid || null\"\n />\n\n @if (showClear() && inputDate) {\n <button\n type=\"button\"\n class=\"absolute flex h-[35px] w-6 items-center justify-center text-grayscale-60 transition-colors hover:text-grayscale-90\"\n [class.right-9]=\"showIcon()\"\n [class.right-0]=\"!showIcon()\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_clear' | translate\"\n (click)=\"clearValue($event)\"\n >\n <i\n class=\"fas fa-times text-xs\"\n aria-hidden=\"true\"\n ></i>\n </button>\n }\n\n @if (showIcon()) {\n <button\n #iconButton\n type=\"button\"\n class=\"absolute right-0 flex h-[35px] w-9 items-center justify-center border-l border-solid transition-colors\"\n [class.border-grayscale-20]=\"disabled()\"\n [class.text-grayscale-30]=\"disabled()\"\n [class.cursor-not-allowed]=\"disabled()\"\n [class.border-grayscale-30]=\"!disabled() && !isFocused()\"\n [class.border-primary]=\"isFocused() && !disabled()\"\n [class.text-grayscale-90]=\"!disabled()\"\n [class.hover:bg-grayscale-10]=\"!disabled()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_open' | translate\"\n (click)=\"toggle()\"\n (keydown)=\"handleTriggerKeydown($event)\"\n >\n <i\n [class]=\"resolvedIcon()\"\n aria-hidden=\"true\"\n ></i>\n </button>\n }\n</div>\n\n<ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"trigger\"\n [cdkConnectedOverlayOpen]=\"isOpen()\"\n [cdkConnectedOverlayHasBackdrop]=\"true\"\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayPositions]=\"positions\"\n (backdropClick)=\"handleBackdropClick($event)\"\n (overlayKeydown)=\"handleOverlayKeydown($event)\"\n (detach)=\"isOpen.set(false)\"\n>\n <div\n #popupPanel\n class=\"z-50 w-72 rounded-medium border border-solid border-grayscale-30 bg-grayscale-0 shadow-md\"\n role=\"dialog\"\n cdkTrapFocus\n [id]=\"popupId()\"\n [attr.aria-label]=\"dialogAriaLabelKey() | translate\"\n >\n @if (showCalendar()) {\n @if (pickerView() === 'calendar') {\n <!-- Cabe\u00E7alho de navega\u00E7\u00E3o mensal -->\n <div class=\"flex items-center justify-between border-b border-solid border-grayscale-30 px-3 py-2\">\n <button\n type=\"button\"\n class=\"flex h-7 w-7 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10 disabled:cursor-not-allowed disabled:text-grayscale-30\"\n [disabled]=\"prevDisabled()\"\n [attr.aria-disabled]=\"prevDisabled() ? 'true' : null\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_prev_month' | translate\"\n (click)=\"navigatePrev()\"\n >\n <i\n class=\"fas fa-chevron-left text-xs\"\n aria-hidden=\"true\"\n ></i>\n </button>\n\n <button\n type=\"button\"\n class=\"flex items-center gap-1 rounded-small px-2 py-1 text-sm font-medium text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_select_month_year' | translate\"\n (click)=\"switchToMonths()\"\n >\n {{ currentMonthKey() | translate }} {{ currentYear() }}\n <i\n class=\"fas fa-chevron-down text-[10px]\"\n aria-hidden=\"true\"\n ></i>\n </button>\n\n <button\n type=\"button\"\n class=\"flex h-7 w-7 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10 disabled:cursor-not-allowed disabled:text-grayscale-30\"\n [disabled]=\"nextDisabled()\"\n [attr.aria-disabled]=\"nextDisabled() ? 'true' : null\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_next_month' | translate\"\n (click)=\"navigateNext()\"\n >\n <i\n class=\"fas fa-chevron-right text-xs\"\n aria-hidden=\"true\"\n ></i>\n </button>\n </div>\n\n <!-- Cabe\u00E7alhos dos dias da semana -->\n <div\n class=\"grid grid-cols-7 px-3 pt-2\"\n role=\"row\"\n >\n @for (dayKey of orderedWeekDayKeys(); track dayKey) {\n <div\n class=\"flex h-8 items-center justify-center text-xs font-medium text-grayscale-60\"\n role=\"columnheader\"\n [attr.aria-label]=\"dayKey | translate\"\n >\n {{ dayKey | translate }}\n </div>\n }\n </div>\n\n <!-- Grade do calend\u00E1rio -->\n <div\n class=\"grid grid-cols-7 px-3 pb-2\"\n role=\"grid\"\n [attr.aria-label]=\"\n 'platform.angular_components.datepicker_calendar_label'\n | translate: { month: (currentMonthKey() | translate), year: currentYear() }\n \"\n (keydown)=\"handleCalendarGridKeydown($event)\"\n >\n @for (week of calendarWeeks(); track $index) {\n <div\n role=\"row\"\n class=\"contents\"\n >\n @for (day of week; track day.date.toISOString()) {\n <button\n type=\"button\"\n role=\"gridcell\"\n class=\"flex h-8 w-8 items-center justify-center rounded-[50%] text-sm font-normal transition-colors\"\n [class.invisible]=\"!showOtherMonths() && !day.isCurrentMonth\"\n [attr.aria-label]=\"formatDayAriaLabel(day.date)\"\n [attr.aria-selected]=\"day.isSelected || day.isRangeStart || day.isRangeEnd || null\"\n [attr.aria-current]=\"day.isToday ? 'date' : null\"\n [attr.aria-disabled]=\"day.isDisabled ? 'true' : null\"\n [attr.aria-hidden]=\"!showOtherMonths() && !day.isCurrentMonth ? 'true' : null\"\n [attr.data-focus]=\"\n day.isSelected || day.isRangeStart || day.isRangeEnd\n ? 'selected'\n : day.isToday\n ? 'today'\n : null\n \"\n [disabled]=\"day.isDisabled\"\n [ngClass]=\"{\n 'bg-primary font-semibold text-grayscale-0':\n day.isSelected || day.isRangeStart || day.isRangeEnd,\n 'bg-grayscale-10 hover:bg-grayscale-20':\n day.isInRange && !day.isRangeStart && !day.isRangeEnd,\n 'text-grayscale-90 hover:bg-grayscale-10':\n !day.isSelected &&\n !day.isRangeStart &&\n !day.isRangeEnd &&\n !day.isDisabled &&\n day.isCurrentMonth,\n 'text-grayscale-60 hover:bg-grayscale-10':\n !day.isSelected &&\n !day.isRangeStart &&\n !day.isRangeEnd &&\n !day.isDisabled &&\n !day.isCurrentMonth,\n 'ring-2 ring-inset ring-primary':\n day.isToday && !day.isSelected && !day.isRangeStart && !day.isRangeEnd,\n 'cursor-not-allowed text-grayscale-30': day.isDisabled,\n }\"\n (mouseenter)=\"onDayHover(day.date)\"\n (mouseleave)=\"onDayHover(null)\"\n (click)=\"selectDate(day)\"\n >\n {{ day.dayOfMonth }}\n </button>\n }\n </div>\n }\n </div>\n }\n\n @if (pickerView() === 'years') {\n <!-- Cabe\u00E7alho de navega\u00E7\u00E3o por d\u00E9cada -->\n <div class=\"flex items-center justify-between border-b border-solid border-grayscale-30 px-3 py-2\">\n <button\n type=\"button\"\n class=\"flex h-7 w-7 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10 disabled:cursor-not-allowed disabled:text-grayscale-30\"\n [disabled]=\"prevDisabled()\"\n [attr.aria-disabled]=\"prevDisabled() ? 'true' : null\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_prev_decade' | translate\"\n (click)=\"navigatePrev()\"\n >\n <i\n class=\"fas fa-chevron-left text-xs\"\n aria-hidden=\"true\"\n ></i>\n </button>\n\n <span class=\"text-sm font-medium text-grayscale-90\"\n >{{ currentDecadeStart() }} \u2013 {{ currentDecadeStart() + 11 }}</span\n >\n\n <button\n type=\"button\"\n class=\"flex h-7 w-7 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10 disabled:cursor-not-allowed disabled:text-grayscale-30\"\n [disabled]=\"nextDisabled()\"\n [attr.aria-disabled]=\"nextDisabled() ? 'true' : null\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_next_decade' | translate\"\n (click)=\"navigateNext()\"\n >\n <i\n class=\"fas fa-chevron-right text-xs\"\n aria-hidden=\"true\"\n ></i>\n </button>\n </div>\n\n <!-- Grade de anos -->\n <div\n class=\"grid grid-cols-3 gap-2 px-3 py-3\"\n role=\"grid\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_select_year' | translate\"\n >\n @for (row of yearsGrid(); track $index) {\n @for (year of row; track year) {\n <button\n type=\"button\"\n role=\"gridcell\"\n class=\"flex h-9 items-center justify-center rounded-small text-sm font-normal transition-colors\"\n [attr.aria-selected]=\"isYearSelected(year) || null\"\n [attr.aria-disabled]=\"isYearDisabled(year) ? 'true' : null\"\n [attr.aria-current]=\"isCurrentYear(year) ? 'date' : null\"\n [attr.data-focus]=\"\n isYearSelected(year) ? 'selected' : isCurrentYear(year) ? 'today' : null\n \"\n [disabled]=\"isYearDisabled(year)\"\n [ngClass]=\"{\n 'bg-primary font-semibold text-grayscale-0': isYearSelected(year),\n 'ring-2 ring-inset ring-primary':\n isCurrentYear(year) && !isYearSelected(year) && !isYearDisabled(year),\n 'text-grayscale-90 hover:bg-grayscale-10':\n !isYearSelected(year) && !isYearDisabled(year),\n 'cursor-not-allowed text-grayscale-30': isYearDisabled(year),\n }\"\n (click)=\"selectYear(year)\"\n >\n {{ year }}\n </button>\n }\n }\n </div>\n }\n\n @if (pickerView() === 'months') {\n <!-- Cabe\u00E7alho de navega\u00E7\u00E3o anual -->\n <div class=\"flex items-center justify-between border-b border-solid border-grayscale-30 px-3 py-2\">\n <button\n type=\"button\"\n class=\"flex h-7 w-7 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10 disabled:cursor-not-allowed disabled:text-grayscale-30\"\n [disabled]=\"prevDisabled()\"\n [attr.aria-disabled]=\"prevDisabled() ? 'true' : null\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_prev_year' | translate\"\n (click)=\"navigatePrev()\"\n >\n <i\n class=\"fas fa-chevron-left text-xs\"\n aria-hidden=\"true\"\n ></i>\n </button>\n\n <span class=\"text-sm font-medium text-grayscale-90\">{{ currentYear() }}</span>\n\n <button\n type=\"button\"\n class=\"flex h-7 w-7 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10 disabled:cursor-not-allowed disabled:text-grayscale-30\"\n [disabled]=\"nextDisabled()\"\n [attr.aria-disabled]=\"nextDisabled() ? 'true' : null\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_next_year' | translate\"\n (click)=\"navigateNext()\"\n >\n <i\n class=\"fas fa-chevron-right text-xs\"\n aria-hidden=\"true\"\n ></i>\n </button>\n </div>\n\n <!-- Grade de meses -->\n <div\n class=\"grid grid-cols-3 gap-2 px-3 py-3\"\n role=\"grid\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_select_month' | translate\"\n >\n @for (row of monthsGrid(); track $index) {\n @for (month of row; track month.index) {\n <button\n type=\"button\"\n role=\"gridcell\"\n class=\"flex h-9 items-center justify-center rounded-small text-sm font-normal transition-colors\"\n [attr.aria-selected]=\"isMonthSelected(month.index) || null\"\n [attr.aria-disabled]=\"isMonthDisabled(month.index) ? 'true' : null\"\n [attr.data-focus]=\"isMonthSelected(month.index) ? 'selected' : null\"\n [disabled]=\"isMonthDisabled(month.index)\"\n [ngClass]=\"{\n 'bg-primary font-semibold text-grayscale-0': isMonthSelected(month.index),\n 'text-grayscale-90 hover:bg-grayscale-10':\n !isMonthSelected(month.index) && !isMonthDisabled(month.index),\n 'cursor-not-allowed text-grayscale-30': isMonthDisabled(month.index),\n }\"\n (click)=\"selectMonth(month.index)\"\n >\n {{ month.key | translate }}\n </button>\n }\n }\n </div>\n\n @if (mode() !== 'month') {\n <div class=\"border-t border-solid border-grayscale-30 px-3 py-2\">\n <button\n type=\"button\"\n class=\"flex items-center gap-2 rounded-small px-2 py-1 text-sm text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n (click)=\"backToCalendar()\"\n >\n <i\n class=\"fas fa-arrow-left\"\n aria-hidden=\"true\"\n ></i>\n {{ 'platform.angular_components.datepicker_back_to_calendar' | translate }}\n </button>\n </div>\n }\n }\n }\n\n <!-- Time picker -->\n @if (showTimePicker()) {\n <div\n class=\"flex items-center justify-center gap-3 px-4 py-3\"\n [class.border-t]=\"showCalendar()\"\n [class.border-solid]=\"showCalendar()\"\n [class.border-grayscale-30]=\"showCalendar()\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_select_time' | translate\"\n >\n <!-- Horas -->\n <div class=\"flex flex-col items-center gap-1\">\n <button\n #increaseHourButton\n type=\"button\"\n class=\"flex h-6 w-6 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_increment_hour' | translate\"\n (click)=\"incrementHours()\"\n >\n <i\n class=\"fas fa-chevron-up text-xs\"\n aria-hidden=\"true\"\n ></i>\n </button>\n <input\n type=\"text\"\n inputmode=\"numeric\"\n [attr.maxlength]=\"timeInputMaxLength\"\n class=\"h-9 w-12 rounded-small border border-solid border-grayscale-30 bg-grayscale-5 text-center text-base font-medium text-grayscale-90 outline-none focus:border-primary focus:outline-1 focus:outline-primary\"\n [value]=\"padTime(displayHours())\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_hour' | translate\"\n aria-live=\"polite\"\n pattern=\"[0-9]+\"\n (keydown)=\"handleHoursKeydown($event)\"\n (change)=\"handleHoursInput($event)\"\n />\n <button\n type=\"button\"\n class=\"flex h-6 w-6 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_decrement_hour' | translate\"\n (click)=\"decrementHours()\"\n >\n <i\n class=\"fas fa-chevron-down text-xs\"\n aria-hidden=\"true\"\n ></i>\n </button>\n </div>\n\n <span\n class=\"text-xl font-bold text-grayscale-60\"\n aria-hidden=\"true\"\n >:</span\n >\n\n <!-- Minutos -->\n <div class=\"flex flex-col items-center gap-1\">\n <button\n type=\"button\"\n class=\"flex h-6 w-6 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_increment_minute' | translate\"\n (click)=\"incrementMinutes()\"\n >\n <i\n class=\"fas fa-chevron-up text-xs\"\n aria-hidden=\"true\"\n ></i>\n </button>\n <input\n type=\"text\"\n inputmode=\"numeric\"\n [attr.maxlength]=\"timeInputMaxLength\"\n class=\"h-9 w-12 rounded-small border border-solid border-grayscale-30 bg-grayscale-5 text-center text-base font-medium text-grayscale-90 outline-none focus:border-primary focus:outline-1 focus:outline-primary\"\n [value]=\"padTime(minutes())\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_minute' | translate\"\n aria-live=\"polite\"\n pattern=\"[0-9]+\"\n (keydown)=\"handleMinutesKeydown($event)\"\n (change)=\"handleMinutesInput($event)\"\n />\n <button\n type=\"button\"\n class=\"flex h-6 w-6 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_decrement_minute' | translate\"\n (click)=\"decrementMinutes()\"\n >\n <i\n class=\"fas fa-chevron-down text-xs\"\n aria-hidden=\"true\"\n ></i>\n </button>\n </div>\n\n @if (showSeconds()) {\n <span\n class=\"text-xl font-bold text-grayscale-60\"\n aria-hidden=\"true\"\n >:</span\n >\n\n <!-- Segundos -->\n <div class=\"flex flex-col items-center gap-1\">\n <button\n type=\"button\"\n class=\"flex h-6 w-6 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_increment_second' | translate\"\n (click)=\"incrementSeconds()\"\n >\n <i\n class=\"fas fa-chevron-up text-xs\"\n aria-hidden=\"true\"\n ></i>\n </button>\n <input\n type=\"text\"\n inputmode=\"numeric\"\n [attr.maxlength]=\"timeInputMaxLength\"\n class=\"h-9 w-12 rounded-small border border-solid border-grayscale-30 bg-grayscale-5 text-center text-base font-medium text-grayscale-90 outline-none focus:border-primary focus:outline-1 focus:outline-primary\"\n [value]=\"padTime(seconds())\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_second' | translate\"\n aria-live=\"polite\"\n pattern=\"[0-9]+\"\n (keydown)=\"handleSecondsKeydown($event)\"\n (change)=\"handleSecondsInput($event)\"\n />\n <button\n type=\"button\"\n class=\"flex h-6 w-6 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_decrement_second' | translate\"\n (click)=\"decrementSeconds()\"\n >\n <i\n class=\"fas fa-chevron-down text-xs\"\n aria-hidden=\"true\"\n ></i>\n </button>\n </div>\n }\n\n @if (is12HourFormat()) {\n <!-- Seletor AM/PM -->\n <button\n type=\"button\"\n class=\"flex h-9 w-12 items-center justify-center rounded-small border border-solid border-grayscale-30 bg-grayscale-5 text-base font-medium text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_toggle_meridiem' | translate\"\n aria-live=\"polite\"\n (click)=\"toggleAmPm()\"\n >\n {{ amPm() }}\n </button>\n }\n </div>\n }\n\n <!-- Rodap\u00E9 de a\u00E7\u00F5es -->\n @if (showButtonBar() || mode() === 'datetime' || mode() === 'time') {\n <div class=\"flex items-center justify-between border-t border-solid border-grayscale-30 px-3 py-2\">\n @if (showButtonBar()) {\n <div>\n @if (mode() === 'date' || mode() === 'datetime' || mode() === 'range') {\n <button\n type=\"button\"\n class=\"rounded-small px-2 py-1 text-sm text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n (click)=\"selectToday()\"\n >\n {{ 'platform.angular_components.datepicker_today' | translate }}\n </button>\n }\n </div>\n\n <div class=\"flex gap-2\">\n <button\n type=\"button\"\n class=\"rounded-small px-2 py-1 text-sm text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n (click)=\"clearValue($event)\"\n >\n {{ 'platform.angular_components.datepicker_clear' | translate }}\n </button>\n\n @if (mode() === 'datetime' || mode() === 'time') {\n <button\n type=\"button\"\n class=\"rounded-small bg-primary px-3 py-1 text-sm font-medium text-grayscale-0 transition-colors hover:opacity-90\"\n (click)=\"confirm()\"\n >\n {{ 'platform.angular_components.datepicker_confirm' | translate }}\n </button>\n }\n </div>\n } @else {\n <div></div>\n <div>\n @if (mode() === 'datetime' || mode() === 'time') {\n <button\n type=\"button\"\n class=\"rounded-small bg-primary px-3 py-1 text-sm font-medium text-grayscale-0 transition-colors hover:opacity-90\"\n (click)=\"confirm()\"\n >\n {{ 'platform.angular_components.datepicker_confirm' | translate }}\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n</ng-template>\n\n", styles: [":host{display:block;position:relative;width:100%}:host.ng-invalid.ng-dirty input{border-color:#c13018}:host.ng-invalid.ng-dirty input:focus{border-color:#c13018;outline-color:#c13018}:host.ng-invalid.ng-dirty button[type=button]{border-color:#c13018}:host.no-icon button[type=button]{display:none}:host.no-icon input{padding-right:.75rem}button[type=button]:focus-visible{outline:2px solid #428bca;outline-offset:1px;border-radius:inherit}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1.TranslatePipe, name: "translate" }, { kind: "directive", type: CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "directive", type: CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "directive", type: 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: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ReactiveFormsModule }] });
|
|
1212
1967
|
}
|
|
1213
1968
|
__decorate([
|
|
1214
1969
|
numericKeysOnly()
|
|
@@ -1242,8 +1997,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
1242
1997
|
useExisting: DatepickerComponent,
|
|
1243
1998
|
multi: true,
|
|
1244
1999
|
},
|
|
1245
|
-
], template: "<div\n cdkOverlayOrigin\n #trigger=\"cdkOverlayOrigin\"\n class=\"relative flex w-full items-center\"\n role=\"combobox\"\n aria-haspopup=\"grid\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"popupId()\"\n>\n <input\n class=\"hide-native-icon h-[35px] w-full rounded-small border border-solid border-grayscale-30 pl-3 text-sm outline-none transition-colors focus:border-primary\"\n [class.pr-10]=\"showIcon() && !showClear()\"\n [class.pr-16]=\"showIcon() && showClear()\"\n [class.pr-6]=\"!showIcon() && showClear()\"\n [class.bg-grayscale-0]=\"!disabled()\"\n [class.text-grayscale-90]=\"!disabled()\"\n [class.border-grayscale-20]=\"disabled()\"\n [class.bg-grayscale-5]=\"disabled()\"\n [class.text-grayscale-30]=\"disabled()\"\n [class.cursor-not-allowed]=\"disabled()\"\n [id]=\"inputId()\"\n [name]=\"name() ?? inputId()\"\n [type]=\"inputType()\"\n [placeholder]=\"placeholder() ?? (placeholderKey() | translate)\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonlyInput() || mode() === 'range' || mode() === 'year' || isMultipleMode()\"\n [tabIndex]=\"tabindex() ?? null\"\n [autofocus]=\"autofocus()\"\n [(ngModel)]=\"inputDate\"\n (ngModelChange)=\"handleInputChange($event)\"\n (focus)=\"handleInputFocus($event)\"\n (blur)=\"handleInputBlur($event)\"\n (click)=\"handleInputClick()\"\n (keydown)=\"handleTriggerKeydown($event)\"\n (input)=\"handleNativeInput($event)\"\n [attr.aria-invalid]=\"ngControl?.control?.invalid || null\"\n />\n\n @if (showClear() && inputDate) {\n <button\n type=\"button\"\n class=\"absolute flex h-[35px] w-6 items-center justify-center text-grayscale-60 transition-colors hover:text-grayscale-90\"\n [class.right-9]=\"showIcon()\"\n [class.right-0]=\"!showIcon()\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_clear' | translate\"\n (click)=\"clearValue($event)\"\n >\n <i class=\"fas fa-times text-xs\" aria-hidden=\"true\"></i>\n </button>\n }\n\n @if (showIcon()) {\n <button\n #iconButton\n type=\"button\"\n class=\"absolute right-0 flex h-[35px] w-9 items-center justify-center border-l border-solid transition-colors\"\n [class.border-grayscale-20]=\"disabled()\"\n [class.text-grayscale-30]=\"disabled()\"\n [class.cursor-not-allowed]=\"disabled()\"\n [class.border-grayscale-30]=\"!disabled() && !isFocused()\"\n [class.border-primary]=\"isFocused() && !disabled()\"\n [class.text-grayscale-90]=\"!disabled()\"\n [class.hover:bg-grayscale-10]=\"!disabled()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_open' | translate\"\n (click)=\"toggle()\"\n (keydown)=\"handleTriggerKeydown($event)\"\n >\n <i [class]=\"resolvedIcon()\" aria-hidden=\"true\"></i>\n </button>\n }\n</div>\n\n<ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"trigger\"\n [cdkConnectedOverlayOpen]=\"isOpen()\"\n [cdkConnectedOverlayHasBackdrop]=\"true\"\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayPositions]=\"positions\"\n (backdropClick)=\"closePanel()\"\n (overlayKeydown)=\"handleOverlayKeydown($event)\"\n (detach)=\"isOpen.set(false)\"\n>\n <div\n #popupPanel\n class=\"z-50 w-72 rounded-medium border border-solid border-grayscale-30 bg-grayscale-0 shadow-md\"\n role=\"dialog\"\n cdkTrapFocus\n [id]=\"popupId()\"\n [attr.aria-label]=\"dialogAriaLabelKey() | translate\"\n >\n @if (showCalendar()) {\n @if (pickerView() === 'calendar') {\n <!-- Cabe\u00E7alho de navega\u00E7\u00E3o mensal -->\n <div class=\"flex items-center justify-between border-b border-solid border-grayscale-30 px-3 py-2\">\n <button\n type=\"button\"\n class=\"flex h-7 w-7 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10 disabled:cursor-not-allowed disabled:text-grayscale-30\"\n [disabled]=\"prevDisabled()\"\n [attr.aria-disabled]=\"prevDisabled() ? 'true' : null\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_prev_month' | translate\"\n (click)=\"navigatePrev()\"\n >\n <i class=\"fas fa-chevron-left text-xs\" aria-hidden=\"true\"></i>\n </button>\n\n <button\n type=\"button\"\n class=\"flex items-center gap-1 rounded-small px-2 py-1 text-sm font-medium text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_select_month_year' | translate\"\n (click)=\"switchToMonths()\"\n >\n {{ currentMonthKey() | translate }} {{ currentYear() }}\n <i class=\"fas fa-chevron-down text-[10px]\" aria-hidden=\"true\"></i>\n </button>\n\n <button\n type=\"button\"\n class=\"flex h-7 w-7 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10 disabled:cursor-not-allowed disabled:text-grayscale-30\"\n [disabled]=\"nextDisabled()\"\n [attr.aria-disabled]=\"nextDisabled() ? 'true' : null\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_next_month' | translate\"\n (click)=\"navigateNext()\"\n >\n <i class=\"fas fa-chevron-right text-xs\" aria-hidden=\"true\"></i>\n </button>\n </div>\n\n <!-- Cabe\u00E7alhos dos dias da semana -->\n <div class=\"grid grid-cols-7 px-3 pt-2\" role=\"row\">\n @for (dayKey of orderedWeekDayKeys(); track dayKey) {\n <div\n class=\"flex h-8 items-center justify-center text-xs font-medium text-grayscale-60\"\n role=\"columnheader\"\n [attr.aria-label]=\"dayKey | translate\"\n >\n {{ dayKey | translate }}\n </div>\n }\n </div>\n\n <!-- Grade do calend\u00E1rio -->\n <div\n class=\"grid grid-cols-7 px-3 pb-2\"\n role=\"grid\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_calendar_label' | translate: { month: (currentMonthKey() | translate), year: currentYear() }\"\n (keydown)=\"handleCalendarGridKeydown($event)\"\n >\n @for (week of calendarWeeks(); track $index) {\n <div role=\"row\" class=\"contents\">\n @for (day of week; track day.date.toISOString()) {\n <button\n type=\"button\"\n role=\"gridcell\"\n class=\"flex h-8 w-8 items-center justify-center rounded-[50%] text-sm font-normal transition-colors\"\n [class.invisible]=\"!showOtherMonths() && !day.isCurrentMonth\"\n [attr.aria-label]=\"formatDayAriaLabel(day.date)\"\n [attr.aria-selected]=\"day.isSelected || day.isRangeStart || day.isRangeEnd || null\"\n [attr.aria-current]=\"day.isToday ? 'date' : null\"\n [attr.aria-disabled]=\"day.isDisabled ? 'true' : null\"\n [attr.aria-hidden]=\"!showOtherMonths() && !day.isCurrentMonth ? 'true' : null\"\n [attr.data-focus]=\"day.isSelected || day.isRangeStart || day.isRangeEnd ? 'selected' : day.isToday ? 'today' : null\"\n [disabled]=\"day.isDisabled\"\n [ngClass]=\"{\n 'bg-primary font-semibold text-grayscale-0':\n day.isSelected || day.isRangeStart || day.isRangeEnd,\n 'bg-grayscale-10 hover:bg-grayscale-20':\n day.isInRange && !day.isRangeStart && !day.isRangeEnd,\n 'text-grayscale-90 hover:bg-grayscale-10':\n !day.isSelected &&\n !day.isRangeStart &&\n !day.isRangeEnd &&\n !day.isDisabled &&\n day.isCurrentMonth,\n 'text-grayscale-60 hover:bg-grayscale-10':\n !day.isSelected &&\n !day.isRangeStart &&\n !day.isRangeEnd &&\n !day.isDisabled &&\n !day.isCurrentMonth,\n 'ring-2 ring-inset ring-primary':\n day.isToday &&\n !day.isSelected &&\n !day.isRangeStart &&\n !day.isRangeEnd,\n 'cursor-not-allowed text-grayscale-30': day.isDisabled\n }\"\n (mouseenter)=\"onDayHover(day.date)\"\n (mouseleave)=\"onDayHover(null)\"\n (click)=\"selectDate(day)\"\n >\n {{ day.dayOfMonth }}\n </button>\n }\n </div>\n }\n </div>\n }\n\n @if (pickerView() === 'years') {\n <!-- Cabe\u00E7alho de navega\u00E7\u00E3o por d\u00E9cada -->\n <div class=\"flex items-center justify-between border-b border-solid border-grayscale-30 px-3 py-2\">\n <button\n type=\"button\"\n class=\"flex h-7 w-7 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10 disabled:cursor-not-allowed disabled:text-grayscale-30\"\n [disabled]=\"prevDisabled()\"\n [attr.aria-disabled]=\"prevDisabled() ? 'true' : null\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_prev_decade' | translate\"\n (click)=\"navigatePrev()\"\n >\n <i class=\"fas fa-chevron-left text-xs\" aria-hidden=\"true\"></i>\n </button>\n\n <span class=\"text-sm font-medium text-grayscale-90\">{{ currentDecadeStart() }} \u2013 {{ currentDecadeStart() + 11 }}</span>\n\n <button\n type=\"button\"\n class=\"flex h-7 w-7 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10 disabled:cursor-not-allowed disabled:text-grayscale-30\"\n [disabled]=\"nextDisabled()\"\n [attr.aria-disabled]=\"nextDisabled() ? 'true' : null\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_next_decade' | translate\"\n (click)=\"navigateNext()\"\n >\n <i class=\"fas fa-chevron-right text-xs\" aria-hidden=\"true\"></i>\n </button>\n </div>\n\n <!-- Grade de anos -->\n <div\n class=\"grid grid-cols-3 gap-2 px-3 py-3\"\n role=\"grid\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_select_year' | translate\"\n >\n @for (row of yearsGrid(); track $index) {\n @for (year of row; track year) {\n <button\n type=\"button\"\n role=\"gridcell\"\n class=\"flex h-9 items-center justify-center rounded-small text-sm font-normal transition-colors\"\n [attr.aria-selected]=\"isYearSelected(year) || null\"\n [attr.aria-disabled]=\"isYearDisabled(year) ? 'true' : null\"\n [attr.aria-current]=\"isCurrentYear(year) ? 'date' : null\"\n [attr.data-focus]=\"isYearSelected(year) ? 'selected' : isCurrentYear(year) ? 'today' : null\"\n [disabled]=\"isYearDisabled(year)\"\n [ngClass]=\"{\n 'bg-primary font-semibold text-grayscale-0': isYearSelected(year),\n 'ring-2 ring-inset ring-primary': isCurrentYear(year) && !isYearSelected(year) && !isYearDisabled(year),\n 'text-grayscale-90 hover:bg-grayscale-10': !isYearSelected(year) && !isYearDisabled(year),\n 'cursor-not-allowed text-grayscale-30': isYearDisabled(year)\n }\"\n (click)=\"selectYear(year)\"\n >\n {{ year }}\n </button>\n }\n }\n </div>\n }\n\n @if (pickerView() === 'months') {\n <!-- Cabe\u00E7alho de navega\u00E7\u00E3o anual -->\n <div class=\"flex items-center justify-between border-b border-solid border-grayscale-30 px-3 py-2\">\n <button\n type=\"button\"\n class=\"flex h-7 w-7 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10 disabled:cursor-not-allowed disabled:text-grayscale-30\"\n [disabled]=\"prevDisabled()\"\n [attr.aria-disabled]=\"prevDisabled() ? 'true' : null\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_prev_year' | translate\"\n (click)=\"navigatePrev()\"\n >\n <i class=\"fas fa-chevron-left text-xs\" aria-hidden=\"true\"></i>\n </button>\n\n <span class=\"text-sm font-medium text-grayscale-90\">{{ currentYear() }}</span>\n\n <button\n type=\"button\"\n class=\"flex h-7 w-7 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10 disabled:cursor-not-allowed disabled:text-grayscale-30\"\n [disabled]=\"nextDisabled()\"\n [attr.aria-disabled]=\"nextDisabled() ? 'true' : null\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_next_year' | translate\"\n (click)=\"navigateNext()\"\n >\n <i class=\"fas fa-chevron-right text-xs\" aria-hidden=\"true\"></i>\n </button>\n </div>\n\n <!-- Grade de meses -->\n <div\n class=\"grid grid-cols-3 gap-2 px-3 py-3\"\n role=\"grid\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_select_month' | translate\"\n >\n @for (row of monthsGrid(); track $index) {\n @for (month of row; track month.index) {\n <button\n type=\"button\"\n role=\"gridcell\"\n class=\"flex h-9 items-center justify-center rounded-small text-sm font-normal transition-colors\"\n [attr.aria-selected]=\"isMonthSelected(month.index) || null\"\n [attr.aria-disabled]=\"isMonthDisabled(month.index) ? 'true' : null\"\n [attr.data-focus]=\"isMonthSelected(month.index) ? 'selected' : null\"\n [disabled]=\"isMonthDisabled(month.index)\"\n [ngClass]=\"{\n 'bg-primary font-semibold text-grayscale-0': isMonthSelected(month.index),\n 'text-grayscale-90 hover:bg-grayscale-10':\n !isMonthSelected(month.index) && !isMonthDisabled(month.index),\n 'cursor-not-allowed text-grayscale-30': isMonthDisabled(month.index)\n }\"\n (click)=\"selectMonth(month.index)\"\n >\n {{ month.key | translate }}\n </button>\n }\n }\n </div>\n\n @if (mode() !== 'month') {\n <div class=\"border-t border-solid border-grayscale-30 px-3 py-2\">\n <button\n type=\"button\"\n class=\"flex items-center gap-2 rounded-small px-2 py-1 text-sm text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n (click)=\"backToCalendar()\"\n >\n <i class=\"fas fa-arrow-left\" aria-hidden=\"true\"></i>\n {{ 'platform.angular_components.datepicker_back_to_calendar' | translate }}\n </button>\n </div>\n }\n }\n }\n\n <!-- Time picker -->\n @if (showTimePicker()) {\n <div\n class=\"flex items-center justify-center gap-3 px-4 py-3\"\n [class.border-t]=\"showCalendar()\"\n [class.border-solid]=\"showCalendar()\"\n [class.border-grayscale-30]=\"showCalendar()\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_select_time' | translate\"\n >\n <!-- Horas -->\n <div class=\"flex flex-col items-center gap-1\">\n <button\n #increaseHourButton\n type=\"button\"\n class=\"flex h-6 w-6 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_increment_hour' | translate\"\n (click)=\"incrementHours()\"\n >\n <i class=\"fas fa-chevron-up text-xs\" aria-hidden=\"true\"></i>\n </button>\n <input\n type=\"text\"\n inputmode=\"numeric\"\n [attr.maxlength]=\"timeInputMaxLength\"\n class=\"h-9 w-12 rounded-small border border-solid border-grayscale-30 bg-grayscale-5 text-center text-base font-medium text-grayscale-90 outline-none focus:border-primary focus:outline-1 focus:outline-primary\"\n [value]=\"padTime(displayHours())\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_hour' | translate\"\n aria-live=\"polite\"\n pattern=\"[0-9]+\"\n (keydown)=\"handleHoursKeydown($event)\"\n (change)=\"handleHoursInput($event)\"\n />\n <button\n type=\"button\"\n class=\"flex h-6 w-6 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_decrement_hour' | translate\"\n (click)=\"decrementHours()\"\n >\n <i class=\"fas fa-chevron-down text-xs\" aria-hidden=\"true\"></i>\n </button>\n </div>\n\n <span class=\"text-xl font-bold text-grayscale-60\" aria-hidden=\"true\">:</span>\n\n <!-- Minutos -->\n <div class=\"flex flex-col items-center gap-1\">\n <button\n type=\"button\"\n class=\"flex h-6 w-6 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_increment_minute' | translate\"\n (click)=\"incrementMinutes()\"\n >\n <i class=\"fas fa-chevron-up text-xs\" aria-hidden=\"true\"></i>\n </button>\n <input\n type=\"text\"\n inputmode=\"numeric\"\n [attr.maxlength]=\"timeInputMaxLength\"\n class=\"h-9 w-12 rounded-small border border-solid border-grayscale-30 bg-grayscale-5 text-center text-base font-medium text-grayscale-90 outline-none focus:border-primary focus:outline-1 focus:outline-primary\"\n [value]=\"padTime(minutes())\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_minute' | translate\"\n aria-live=\"polite\"\n pattern=\"[0-9]+\"\n (keydown)=\"handleMinutesKeydown($event)\"\n (change)=\"handleMinutesInput($event)\"\n />\n <button\n type=\"button\"\n class=\"flex h-6 w-6 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_decrement_minute' | translate\"\n (click)=\"decrementMinutes()\"\n >\n <i class=\"fas fa-chevron-down text-xs\" aria-hidden=\"true\"></i>\n </button>\n </div>\n\n @if (showSeconds()) {\n <span class=\"text-xl font-bold text-grayscale-60\" aria-hidden=\"true\">:</span>\n\n <!-- Segundos -->\n <div class=\"flex flex-col items-center gap-1\">\n <button\n type=\"button\"\n class=\"flex h-6 w-6 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_increment_second' | translate\"\n (click)=\"incrementSeconds()\"\n >\n <i class=\"fas fa-chevron-up text-xs\" aria-hidden=\"true\"></i>\n </button>\n <input\n type=\"text\"\n inputmode=\"numeric\"\n [attr.maxlength]=\"timeInputMaxLength\"\n class=\"h-9 w-12 rounded-small border border-solid border-grayscale-30 bg-grayscale-5 text-center text-base font-medium text-grayscale-90 outline-none focus:border-primary focus:outline-1 focus:outline-primary\"\n [value]=\"padTime(seconds())\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_second' | translate\"\n aria-live=\"polite\"\n pattern=\"[0-9]+\"\n (keydown)=\"handleSecondsKeydown($event)\"\n (change)=\"handleSecondsInput($event)\"\n />\n <button\n type=\"button\"\n class=\"flex h-6 w-6 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_decrement_second' | translate\"\n (click)=\"decrementSeconds()\"\n >\n <i class=\"fas fa-chevron-down text-xs\" aria-hidden=\"true\"></i>\n </button>\n </div>\n }\n\n @if (is12HourFormat()) {\n <!-- Seletor AM/PM -->\n <button\n type=\"button\"\n class=\"flex h-9 w-12 items-center justify-center rounded-small border border-solid border-grayscale-30 bg-grayscale-5 text-base font-medium text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_toggle_meridiem' | translate\"\n aria-live=\"polite\"\n (click)=\"toggleAmPm()\"\n >\n {{ amPm() }}\n </button>\n }\n </div>\n }\n\n <!-- Rodap\u00E9 de a\u00E7\u00F5es -->\n @if (showButtonBar() || mode() === 'datetime' || mode() === 'time') {\n <div class=\"flex items-center justify-between border-t border-solid border-grayscale-30 px-3 py-2\">\n @if (showButtonBar()) {\n <div>\n @if (mode() === 'date' || mode() === 'datetime' || mode() === 'range') {\n <button\n type=\"button\"\n class=\"rounded-small px-2 py-1 text-sm text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n (click)=\"selectToday()\"\n >\n {{ 'platform.angular_components.datepicker_today' | translate }}\n </button>\n }\n </div>\n\n <div class=\"flex gap-2\">\n <button\n type=\"button\"\n class=\"rounded-small px-2 py-1 text-sm text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n (click)=\"clearValue($event)\"\n >\n {{ 'platform.angular_components.datepicker_clear' | translate }}\n </button>\n\n @if (mode() === 'datetime' || mode() === 'time') {\n <button\n type=\"button\"\n class=\"rounded-small bg-primary px-3 py-1 text-sm font-medium text-grayscale-0 transition-colors hover:opacity-90\"\n (click)=\"confirm()\"\n >\n {{ 'platform.angular_components.datepicker_confirm' | translate }}\n </button>\n }\n </div>\n } @else {\n <div></div>\n <div>\n @if (mode() === 'datetime' || mode() === 'time') {\n <button\n type=\"button\"\n class=\"rounded-small bg-primary px-3 py-1 text-sm font-medium text-grayscale-0 transition-colors hover:opacity-90\"\n (click)=\"confirm()\"\n >\n {{ 'platform.angular_components.datepicker_confirm' | translate }}\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n</ng-template>\n", styles: [":host{display:block;position:relative;width:100%}:host.ng-invalid.ng-dirty input{border-color:#c13018}:host.ng-invalid.ng-dirty input:focus{border-color:#c13018;outline-color:#c13018}:host.ng-invalid.ng-dirty button[type=button]{border-color:#c13018}:host.no-icon button[type=button]{display:none}:host.no-icon input{padding-right:.75rem}input.hide-native-icon::-webkit-calendar-picker-indicator{display:none}button[type=button]:focus-visible{outline:2px solid #428BCA;outline-offset:1px;border-radius:inherit}\n"] }]
|
|
1246
|
-
}], propDecorators: { handleHoursKeydown: [], handleMinutesKeydown: [], handleSecondsKeydown: [] } });
|
|
2000
|
+
], template: "<div\n cdkOverlayOrigin\n #trigger=\"cdkOverlayOrigin\"\n class=\"relative flex w-full items-center\"\n role=\"combobox\"\n aria-haspopup=\"grid\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"popupId()\"\n>\n <input\n #triggerInput\n class=\"h-[35px] w-full rounded-small border border-solid border-grayscale-30 pl-3 text-sm outline-none transition-colors focus:border-primary\"\n [class.pr-10]=\"showIcon() && !showClear()\"\n [class.pr-16]=\"showIcon() && showClear()\"\n [class.pr-6]=\"!showIcon() && showClear()\"\n [class.bg-grayscale-0]=\"!disabled()\"\n [class.text-grayscale-90]=\"!disabled()\"\n [class.border-grayscale-20]=\"disabled()\"\n [class.bg-grayscale-5]=\"disabled()\"\n [class.text-grayscale-30]=\"disabled()\"\n [class.cursor-not-allowed]=\"disabled()\"\n [id]=\"inputId()\"\n [name]=\"name() ?? inputId()\"\n type=\"text\"\n [placeholder]=\"placeholder() ?? (placeholderKey() | translate)\"\n [disabled]=\"disabled()\"\n [readonly]=\"\n readonlyInput() ||\n mode() === 'range' ||\n mode() === 'year' ||\n isMultipleMode() ||\n (isSegmentModeCandidate() && isTouchDevice())\n \"\n [tabIndex]=\"tabindex() ?? null\"\n [autofocus]=\"autofocus()\"\n [(ngModel)]=\"inputDate\"\n (ngModelChange)=\"handleInputChange($event)\"\n (focus)=\"handleInputFocus($event)\"\n (blur)=\"handleInputBlur($event)\"\n (mousedown)=\"handleInputClick($event)\"\n (keydown)=\"handleTriggerKeydown($event)\"\n (input)=\"handleNativeInput($event)\"\n [attr.aria-invalid]=\"ngControl?.control?.invalid || null\"\n />\n\n @if (showClear() && inputDate) {\n <button\n type=\"button\"\n class=\"absolute flex h-[35px] w-6 items-center justify-center text-grayscale-60 transition-colors hover:text-grayscale-90\"\n [class.right-9]=\"showIcon()\"\n [class.right-0]=\"!showIcon()\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_clear' | translate\"\n (click)=\"clearValue($event)\"\n >\n <i\n class=\"fas fa-times text-xs\"\n aria-hidden=\"true\"\n ></i>\n </button>\n }\n\n @if (showIcon()) {\n <button\n #iconButton\n type=\"button\"\n class=\"absolute right-0 flex h-[35px] w-9 items-center justify-center border-l border-solid transition-colors\"\n [class.border-grayscale-20]=\"disabled()\"\n [class.text-grayscale-30]=\"disabled()\"\n [class.cursor-not-allowed]=\"disabled()\"\n [class.border-grayscale-30]=\"!disabled() && !isFocused()\"\n [class.border-primary]=\"isFocused() && !disabled()\"\n [class.text-grayscale-90]=\"!disabled()\"\n [class.hover:bg-grayscale-10]=\"!disabled()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_open' | translate\"\n (click)=\"toggle()\"\n (keydown)=\"handleTriggerKeydown($event)\"\n >\n <i\n [class]=\"resolvedIcon()\"\n aria-hidden=\"true\"\n ></i>\n </button>\n }\n</div>\n\n<ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"trigger\"\n [cdkConnectedOverlayOpen]=\"isOpen()\"\n [cdkConnectedOverlayHasBackdrop]=\"true\"\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayPositions]=\"positions\"\n (backdropClick)=\"handleBackdropClick($event)\"\n (overlayKeydown)=\"handleOverlayKeydown($event)\"\n (detach)=\"isOpen.set(false)\"\n>\n <div\n #popupPanel\n class=\"z-50 w-72 rounded-medium border border-solid border-grayscale-30 bg-grayscale-0 shadow-md\"\n role=\"dialog\"\n cdkTrapFocus\n [id]=\"popupId()\"\n [attr.aria-label]=\"dialogAriaLabelKey() | translate\"\n >\n @if (showCalendar()) {\n @if (pickerView() === 'calendar') {\n <!-- Cabe\u00E7alho de navega\u00E7\u00E3o mensal -->\n <div class=\"flex items-center justify-between border-b border-solid border-grayscale-30 px-3 py-2\">\n <button\n type=\"button\"\n class=\"flex h-7 w-7 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10 disabled:cursor-not-allowed disabled:text-grayscale-30\"\n [disabled]=\"prevDisabled()\"\n [attr.aria-disabled]=\"prevDisabled() ? 'true' : null\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_prev_month' | translate\"\n (click)=\"navigatePrev()\"\n >\n <i\n class=\"fas fa-chevron-left text-xs\"\n aria-hidden=\"true\"\n ></i>\n </button>\n\n <button\n type=\"button\"\n class=\"flex items-center gap-1 rounded-small px-2 py-1 text-sm font-medium text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_select_month_year' | translate\"\n (click)=\"switchToMonths()\"\n >\n {{ currentMonthKey() | translate }} {{ currentYear() }}\n <i\n class=\"fas fa-chevron-down text-[10px]\"\n aria-hidden=\"true\"\n ></i>\n </button>\n\n <button\n type=\"button\"\n class=\"flex h-7 w-7 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10 disabled:cursor-not-allowed disabled:text-grayscale-30\"\n [disabled]=\"nextDisabled()\"\n [attr.aria-disabled]=\"nextDisabled() ? 'true' : null\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_next_month' | translate\"\n (click)=\"navigateNext()\"\n >\n <i\n class=\"fas fa-chevron-right text-xs\"\n aria-hidden=\"true\"\n ></i>\n </button>\n </div>\n\n <!-- Cabe\u00E7alhos dos dias da semana -->\n <div\n class=\"grid grid-cols-7 px-3 pt-2\"\n role=\"row\"\n >\n @for (dayKey of orderedWeekDayKeys(); track dayKey) {\n <div\n class=\"flex h-8 items-center justify-center text-xs font-medium text-grayscale-60\"\n role=\"columnheader\"\n [attr.aria-label]=\"dayKey | translate\"\n >\n {{ dayKey | translate }}\n </div>\n }\n </div>\n\n <!-- Grade do calend\u00E1rio -->\n <div\n class=\"grid grid-cols-7 px-3 pb-2\"\n role=\"grid\"\n [attr.aria-label]=\"\n 'platform.angular_components.datepicker_calendar_label'\n | translate: { month: (currentMonthKey() | translate), year: currentYear() }\n \"\n (keydown)=\"handleCalendarGridKeydown($event)\"\n >\n @for (week of calendarWeeks(); track $index) {\n <div\n role=\"row\"\n class=\"contents\"\n >\n @for (day of week; track day.date.toISOString()) {\n <button\n type=\"button\"\n role=\"gridcell\"\n class=\"flex h-8 w-8 items-center justify-center rounded-[50%] text-sm font-normal transition-colors\"\n [class.invisible]=\"!showOtherMonths() && !day.isCurrentMonth\"\n [attr.aria-label]=\"formatDayAriaLabel(day.date)\"\n [attr.aria-selected]=\"day.isSelected || day.isRangeStart || day.isRangeEnd || null\"\n [attr.aria-current]=\"day.isToday ? 'date' : null\"\n [attr.aria-disabled]=\"day.isDisabled ? 'true' : null\"\n [attr.aria-hidden]=\"!showOtherMonths() && !day.isCurrentMonth ? 'true' : null\"\n [attr.data-focus]=\"\n day.isSelected || day.isRangeStart || day.isRangeEnd\n ? 'selected'\n : day.isToday\n ? 'today'\n : null\n \"\n [disabled]=\"day.isDisabled\"\n [ngClass]=\"{\n 'bg-primary font-semibold text-grayscale-0':\n day.isSelected || day.isRangeStart || day.isRangeEnd,\n 'bg-grayscale-10 hover:bg-grayscale-20':\n day.isInRange && !day.isRangeStart && !day.isRangeEnd,\n 'text-grayscale-90 hover:bg-grayscale-10':\n !day.isSelected &&\n !day.isRangeStart &&\n !day.isRangeEnd &&\n !day.isDisabled &&\n day.isCurrentMonth,\n 'text-grayscale-60 hover:bg-grayscale-10':\n !day.isSelected &&\n !day.isRangeStart &&\n !day.isRangeEnd &&\n !day.isDisabled &&\n !day.isCurrentMonth,\n 'ring-2 ring-inset ring-primary':\n day.isToday && !day.isSelected && !day.isRangeStart && !day.isRangeEnd,\n 'cursor-not-allowed text-grayscale-30': day.isDisabled,\n }\"\n (mouseenter)=\"onDayHover(day.date)\"\n (mouseleave)=\"onDayHover(null)\"\n (click)=\"selectDate(day)\"\n >\n {{ day.dayOfMonth }}\n </button>\n }\n </div>\n }\n </div>\n }\n\n @if (pickerView() === 'years') {\n <!-- Cabe\u00E7alho de navega\u00E7\u00E3o por d\u00E9cada -->\n <div class=\"flex items-center justify-between border-b border-solid border-grayscale-30 px-3 py-2\">\n <button\n type=\"button\"\n class=\"flex h-7 w-7 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10 disabled:cursor-not-allowed disabled:text-grayscale-30\"\n [disabled]=\"prevDisabled()\"\n [attr.aria-disabled]=\"prevDisabled() ? 'true' : null\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_prev_decade' | translate\"\n (click)=\"navigatePrev()\"\n >\n <i\n class=\"fas fa-chevron-left text-xs\"\n aria-hidden=\"true\"\n ></i>\n </button>\n\n <span class=\"text-sm font-medium text-grayscale-90\"\n >{{ currentDecadeStart() }} \u2013 {{ currentDecadeStart() + 11 }}</span\n >\n\n <button\n type=\"button\"\n class=\"flex h-7 w-7 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10 disabled:cursor-not-allowed disabled:text-grayscale-30\"\n [disabled]=\"nextDisabled()\"\n [attr.aria-disabled]=\"nextDisabled() ? 'true' : null\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_next_decade' | translate\"\n (click)=\"navigateNext()\"\n >\n <i\n class=\"fas fa-chevron-right text-xs\"\n aria-hidden=\"true\"\n ></i>\n </button>\n </div>\n\n <!-- Grade de anos -->\n <div\n class=\"grid grid-cols-3 gap-2 px-3 py-3\"\n role=\"grid\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_select_year' | translate\"\n >\n @for (row of yearsGrid(); track $index) {\n @for (year of row; track year) {\n <button\n type=\"button\"\n role=\"gridcell\"\n class=\"flex h-9 items-center justify-center rounded-small text-sm font-normal transition-colors\"\n [attr.aria-selected]=\"isYearSelected(year) || null\"\n [attr.aria-disabled]=\"isYearDisabled(year) ? 'true' : null\"\n [attr.aria-current]=\"isCurrentYear(year) ? 'date' : null\"\n [attr.data-focus]=\"\n isYearSelected(year) ? 'selected' : isCurrentYear(year) ? 'today' : null\n \"\n [disabled]=\"isYearDisabled(year)\"\n [ngClass]=\"{\n 'bg-primary font-semibold text-grayscale-0': isYearSelected(year),\n 'ring-2 ring-inset ring-primary':\n isCurrentYear(year) && !isYearSelected(year) && !isYearDisabled(year),\n 'text-grayscale-90 hover:bg-grayscale-10':\n !isYearSelected(year) && !isYearDisabled(year),\n 'cursor-not-allowed text-grayscale-30': isYearDisabled(year),\n }\"\n (click)=\"selectYear(year)\"\n >\n {{ year }}\n </button>\n }\n }\n </div>\n }\n\n @if (pickerView() === 'months') {\n <!-- Cabe\u00E7alho de navega\u00E7\u00E3o anual -->\n <div class=\"flex items-center justify-between border-b border-solid border-grayscale-30 px-3 py-2\">\n <button\n type=\"button\"\n class=\"flex h-7 w-7 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10 disabled:cursor-not-allowed disabled:text-grayscale-30\"\n [disabled]=\"prevDisabled()\"\n [attr.aria-disabled]=\"prevDisabled() ? 'true' : null\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_prev_year' | translate\"\n (click)=\"navigatePrev()\"\n >\n <i\n class=\"fas fa-chevron-left text-xs\"\n aria-hidden=\"true\"\n ></i>\n </button>\n\n <span class=\"text-sm font-medium text-grayscale-90\">{{ currentYear() }}</span>\n\n <button\n type=\"button\"\n class=\"flex h-7 w-7 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10 disabled:cursor-not-allowed disabled:text-grayscale-30\"\n [disabled]=\"nextDisabled()\"\n [attr.aria-disabled]=\"nextDisabled() ? 'true' : null\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_next_year' | translate\"\n (click)=\"navigateNext()\"\n >\n <i\n class=\"fas fa-chevron-right text-xs\"\n aria-hidden=\"true\"\n ></i>\n </button>\n </div>\n\n <!-- Grade de meses -->\n <div\n class=\"grid grid-cols-3 gap-2 px-3 py-3\"\n role=\"grid\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_select_month' | translate\"\n >\n @for (row of monthsGrid(); track $index) {\n @for (month of row; track month.index) {\n <button\n type=\"button\"\n role=\"gridcell\"\n class=\"flex h-9 items-center justify-center rounded-small text-sm font-normal transition-colors\"\n [attr.aria-selected]=\"isMonthSelected(month.index) || null\"\n [attr.aria-disabled]=\"isMonthDisabled(month.index) ? 'true' : null\"\n [attr.data-focus]=\"isMonthSelected(month.index) ? 'selected' : null\"\n [disabled]=\"isMonthDisabled(month.index)\"\n [ngClass]=\"{\n 'bg-primary font-semibold text-grayscale-0': isMonthSelected(month.index),\n 'text-grayscale-90 hover:bg-grayscale-10':\n !isMonthSelected(month.index) && !isMonthDisabled(month.index),\n 'cursor-not-allowed text-grayscale-30': isMonthDisabled(month.index),\n }\"\n (click)=\"selectMonth(month.index)\"\n >\n {{ month.key | translate }}\n </button>\n }\n }\n </div>\n\n @if (mode() !== 'month') {\n <div class=\"border-t border-solid border-grayscale-30 px-3 py-2\">\n <button\n type=\"button\"\n class=\"flex items-center gap-2 rounded-small px-2 py-1 text-sm text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n (click)=\"backToCalendar()\"\n >\n <i\n class=\"fas fa-arrow-left\"\n aria-hidden=\"true\"\n ></i>\n {{ 'platform.angular_components.datepicker_back_to_calendar' | translate }}\n </button>\n </div>\n }\n }\n }\n\n <!-- Time picker -->\n @if (showTimePicker()) {\n <div\n class=\"flex items-center justify-center gap-3 px-4 py-3\"\n [class.border-t]=\"showCalendar()\"\n [class.border-solid]=\"showCalendar()\"\n [class.border-grayscale-30]=\"showCalendar()\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_select_time' | translate\"\n >\n <!-- Horas -->\n <div class=\"flex flex-col items-center gap-1\">\n <button\n #increaseHourButton\n type=\"button\"\n class=\"flex h-6 w-6 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_increment_hour' | translate\"\n (click)=\"incrementHours()\"\n >\n <i\n class=\"fas fa-chevron-up text-xs\"\n aria-hidden=\"true\"\n ></i>\n </button>\n <input\n type=\"text\"\n inputmode=\"numeric\"\n [attr.maxlength]=\"timeInputMaxLength\"\n class=\"h-9 w-12 rounded-small border border-solid border-grayscale-30 bg-grayscale-5 text-center text-base font-medium text-grayscale-90 outline-none focus:border-primary focus:outline-1 focus:outline-primary\"\n [value]=\"padTime(displayHours())\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_hour' | translate\"\n aria-live=\"polite\"\n pattern=\"[0-9]+\"\n (keydown)=\"handleHoursKeydown($event)\"\n (change)=\"handleHoursInput($event)\"\n />\n <button\n type=\"button\"\n class=\"flex h-6 w-6 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_decrement_hour' | translate\"\n (click)=\"decrementHours()\"\n >\n <i\n class=\"fas fa-chevron-down text-xs\"\n aria-hidden=\"true\"\n ></i>\n </button>\n </div>\n\n <span\n class=\"text-xl font-bold text-grayscale-60\"\n aria-hidden=\"true\"\n >:</span\n >\n\n <!-- Minutos -->\n <div class=\"flex flex-col items-center gap-1\">\n <button\n type=\"button\"\n class=\"flex h-6 w-6 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_increment_minute' | translate\"\n (click)=\"incrementMinutes()\"\n >\n <i\n class=\"fas fa-chevron-up text-xs\"\n aria-hidden=\"true\"\n ></i>\n </button>\n <input\n type=\"text\"\n inputmode=\"numeric\"\n [attr.maxlength]=\"timeInputMaxLength\"\n class=\"h-9 w-12 rounded-small border border-solid border-grayscale-30 bg-grayscale-5 text-center text-base font-medium text-grayscale-90 outline-none focus:border-primary focus:outline-1 focus:outline-primary\"\n [value]=\"padTime(minutes())\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_minute' | translate\"\n aria-live=\"polite\"\n pattern=\"[0-9]+\"\n (keydown)=\"handleMinutesKeydown($event)\"\n (change)=\"handleMinutesInput($event)\"\n />\n <button\n type=\"button\"\n class=\"flex h-6 w-6 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_decrement_minute' | translate\"\n (click)=\"decrementMinutes()\"\n >\n <i\n class=\"fas fa-chevron-down text-xs\"\n aria-hidden=\"true\"\n ></i>\n </button>\n </div>\n\n @if (showSeconds()) {\n <span\n class=\"text-xl font-bold text-grayscale-60\"\n aria-hidden=\"true\"\n >:</span\n >\n\n <!-- Segundos -->\n <div class=\"flex flex-col items-center gap-1\">\n <button\n type=\"button\"\n class=\"flex h-6 w-6 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_increment_second' | translate\"\n (click)=\"incrementSeconds()\"\n >\n <i\n class=\"fas fa-chevron-up text-xs\"\n aria-hidden=\"true\"\n ></i>\n </button>\n <input\n type=\"text\"\n inputmode=\"numeric\"\n [attr.maxlength]=\"timeInputMaxLength\"\n class=\"h-9 w-12 rounded-small border border-solid border-grayscale-30 bg-grayscale-5 text-center text-base font-medium text-grayscale-90 outline-none focus:border-primary focus:outline-1 focus:outline-primary\"\n [value]=\"padTime(seconds())\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_second' | translate\"\n aria-live=\"polite\"\n pattern=\"[0-9]+\"\n (keydown)=\"handleSecondsKeydown($event)\"\n (change)=\"handleSecondsInput($event)\"\n />\n <button\n type=\"button\"\n class=\"flex h-6 w-6 items-center justify-center rounded-small text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_decrement_second' | translate\"\n (click)=\"decrementSeconds()\"\n >\n <i\n class=\"fas fa-chevron-down text-xs\"\n aria-hidden=\"true\"\n ></i>\n </button>\n </div>\n }\n\n @if (is12HourFormat()) {\n <!-- Seletor AM/PM -->\n <button\n type=\"button\"\n class=\"flex h-9 w-12 items-center justify-center rounded-small border border-solid border-grayscale-30 bg-grayscale-5 text-base font-medium text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n [attr.aria-label]=\"'platform.angular_components.datepicker_toggle_meridiem' | translate\"\n aria-live=\"polite\"\n (click)=\"toggleAmPm()\"\n >\n {{ amPm() }}\n </button>\n }\n </div>\n }\n\n <!-- Rodap\u00E9 de a\u00E7\u00F5es -->\n @if (showButtonBar() || mode() === 'datetime' || mode() === 'time') {\n <div class=\"flex items-center justify-between border-t border-solid border-grayscale-30 px-3 py-2\">\n @if (showButtonBar()) {\n <div>\n @if (mode() === 'date' || mode() === 'datetime' || mode() === 'range') {\n <button\n type=\"button\"\n class=\"rounded-small px-2 py-1 text-sm text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n (click)=\"selectToday()\"\n >\n {{ 'platform.angular_components.datepicker_today' | translate }}\n </button>\n }\n </div>\n\n <div class=\"flex gap-2\">\n <button\n type=\"button\"\n class=\"rounded-small px-2 py-1 text-sm text-grayscale-90 transition-colors hover:bg-grayscale-10\"\n (click)=\"clearValue($event)\"\n >\n {{ 'platform.angular_components.datepicker_clear' | translate }}\n </button>\n\n @if (mode() === 'datetime' || mode() === 'time') {\n <button\n type=\"button\"\n class=\"rounded-small bg-primary px-3 py-1 text-sm font-medium text-grayscale-0 transition-colors hover:opacity-90\"\n (click)=\"confirm()\"\n >\n {{ 'platform.angular_components.datepicker_confirm' | translate }}\n </button>\n }\n </div>\n } @else {\n <div></div>\n <div>\n @if (mode() === 'datetime' || mode() === 'time') {\n <button\n type=\"button\"\n class=\"rounded-small bg-primary px-3 py-1 text-sm font-medium text-grayscale-0 transition-colors hover:opacity-90\"\n (click)=\"confirm()\"\n >\n {{ 'platform.angular_components.datepicker_confirm' | translate }}\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n</ng-template>\n\n", styles: [":host{display:block;position:relative;width:100%}:host.ng-invalid.ng-dirty input{border-color:#c13018}:host.ng-invalid.ng-dirty input:focus{border-color:#c13018;outline-color:#c13018}:host.ng-invalid.ng-dirty button[type=button]{border-color:#c13018}:host.no-icon button[type=button]{display:none}:host.no-icon input{padding-right:.75rem}button[type=button]:focus-visible{outline:2px solid #428bca;outline-offset:1px;border-radius:inherit}\n"] }]
|
|
2001
|
+
}], ctorParameters: () => [], propDecorators: { handleHoursKeydown: [], handleMinutesKeydown: [], handleSecondsKeydown: [] } });
|
|
1247
2002
|
|
|
1248
2003
|
/**
|
|
1249
2004
|
* Generated bundle index. Do not edit.
|