@muxima-ui/core 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +7 -4
  2. package/fesm2022/muxima-ui-core-chips.mjs +349 -0
  3. package/fesm2022/muxima-ui-core-chips.mjs.map +1 -0
  4. package/fesm2022/muxima-ui-core-clipboard.mjs +110 -0
  5. package/fesm2022/muxima-ui-core-clipboard.mjs.map +1 -0
  6. package/fesm2022/muxima-ui-core-date-picker.mjs +494 -12
  7. package/fesm2022/muxima-ui-core-date-picker.mjs.map +1 -1
  8. package/fesm2022/muxima-ui-core-file-upload.mjs +392 -0
  9. package/fesm2022/muxima-ui-core-file-upload.mjs.map +1 -0
  10. package/fesm2022/muxima-ui-core-rating.mjs +212 -0
  11. package/fesm2022/muxima-ui-core-rating.mjs.map +1 -0
  12. package/fesm2022/muxima-ui-core-slider.mjs +211 -3
  13. package/fesm2022/muxima-ui-core-slider.mjs.map +1 -1
  14. package/fesm2022/muxima-ui-core-time-picker.mjs +389 -0
  15. package/fesm2022/muxima-ui-core-time-picker.mjs.map +1 -0
  16. package/fesm2022/muxima-ui-core.mjs +1 -1
  17. package/fesm2022/muxima-ui-core.mjs.map +1 -1
  18. package/package.json +21 -1
  19. package/styles/base.css +9 -1
  20. package/styles/components/chips.css +170 -0
  21. package/styles/components/clipboard.css +52 -0
  22. package/styles/components/date-picker.css +135 -0
  23. package/styles/components/file-upload.css +164 -0
  24. package/styles/components/rating.css +89 -0
  25. package/styles/components/slider.css +111 -0
  26. package/styles/components/time-picker.css +39 -0
  27. package/styles/muxima.css +5 -0
  28. package/types/muxima-ui-core-chips.d.ts +120 -0
  29. package/types/muxima-ui-core-clipboard.d.ts +42 -0
  30. package/types/muxima-ui-core-date-picker.d.ts +130 -2
  31. package/types/muxima-ui-core-file-upload.d.ts +96 -0
  32. package/types/muxima-ui-core-rating.d.ts +64 -0
  33. package/types/muxima-ui-core-slider.d.ts +68 -6
  34. package/types/muxima-ui-core-time-picker.d.ts +83 -0
  35. package/types/muxima-ui-core.d.ts +1 -1
@@ -24,8 +24,12 @@ function addMonths(d, n) {
24
24
  *
25
25
  * ```html
26
26
  * <mx-calendar [(value)]="data" [min]="hoje" />
27
+ * <mx-calendar mode="range" [(range)]="periodo" />
27
28
  * ```
28
29
  *
30
+ * Em `mode="range"` o primeiro clique marca o início e o segundo o fim (se for anterior,
31
+ * os dois trocam). Entre os dois cliques o intervalo é pré-visualizado com o rato ou o teclado.
32
+ *
29
33
  * Teclas: setas (dia e semana), Home/End (início e fim da semana), Page Up/Down (mês),
30
34
  * Shift + Page Up/Down (ano), Enter ou Espaço para escolher.
31
35
  */
@@ -39,12 +43,37 @@ class MxCalendar {
39
43
  /** Primeiro dia da semana: 1 = segunda-feira. */
40
44
  firstDayOfWeek = input(1, ...(ngDevMode ? [{ debugName: "firstDayOfWeek" }] : /* istanbul ignore next */ []));
41
45
  selected = output();
46
+ /** `single` escolhe um dia; `range` escolhe um intervalo em [(range)]. */
47
+ mode = input('single', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
48
+ range = model(null, ...(ngDevMode ? [{ debugName: "range" }] : /* istanbul ignore next */ []));
49
+ /** Emite quando o intervalo fica completo (início e fim). */
50
+ rangeSelected = output();
42
51
  host = inject(ElementRef).nativeElement;
43
52
  injector = inject(Injector);
44
53
  titleId = mxUniqueId('mx-calendar-title');
45
54
  /** Dia com foco na grelha. */
46
55
  focusDate = signal(mxStartOfDay(new Date()), ...(ngDevMode ? [{ debugName: "focusDate" }] : /* istanbul ignore next */ []));
47
56
  initialised = false;
57
+ /** Dia sob o rato ou o foco enquanto falta o fim do intervalo. */
58
+ preview = signal(null, ...(ngDevMode ? [{ debugName: "preview" }] : /* istanbul ignore next */ []));
59
+ awaitingEnd = computed(() => this.mode() === 'range' && !!this.range()?.start && !this.range()?.end, ...(ngDevMode ? [{ debugName: "awaitingEnd" }] : /* istanbul ignore next */ []));
60
+ rangeHint = computed(() => {
61
+ const r = this.range();
62
+ if (!r?.start)
63
+ return '';
64
+ const f = new Intl.DateTimeFormat(this.locale(), { day: 'numeric', month: 'long', year: 'numeric' });
65
+ return r.end ? `Intervalo de ${f.format(r.start)} a ${f.format(r.end)}.` : `Início ${f.format(r.start)}. Escolhe a data de fim.`;
66
+ }, ...(ngDevMode ? [{ debugName: "rangeHint" }] : /* istanbul ignore next */ []));
67
+ /** Início e fim efectivos (com pré-visualização), ordenados. */
68
+ bounds = computed(() => {
69
+ if (this.mode() !== 'range')
70
+ return null;
71
+ const r = this.range();
72
+ if (!r?.start)
73
+ return null;
74
+ const end = r.end ?? (this.awaitingEnd() ? this.preview() : null) ?? r.start;
75
+ return r.start <= end ? [r.start, end] : [end, r.start];
76
+ }, ...(ngDevMode ? [{ debugName: "bounds" }] : /* istanbul ignore next */ []));
48
77
  weekdays = computed(() => {
49
78
  const short = new Intl.DateTimeFormat(this.locale(), { weekday: 'narrow' });
50
79
  const long = new Intl.DateTimeFormat(this.locale(), { weekday: 'long' });
@@ -61,18 +90,29 @@ class MxCalendar {
61
90
  const start = addDays(first, -offset);
62
91
  const today = mxStartOfDay(new Date());
63
92
  const fmt = new Intl.DateTimeFormat(this.locale(), { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' });
93
+ const bounds = this.bounds();
94
+ const isRange = this.mode() === 'range';
64
95
  const weeks = [];
65
96
  for (let w = 0; w < 6; w++) {
66
97
  const row = [];
67
98
  for (let i = 0; i < 7; i++) {
68
99
  const date = addDays(start, w * 7 + i);
100
+ const rangeStart = !!bounds && mxSameDay(date, bounds[0]);
101
+ const rangeEnd = !!bounds && mxSameDay(date, bounds[1]);
102
+ const inRange = !!bounds && date > bounds[0] && date < bounds[1];
103
+ const isStart = isRange && mxSameDay(date, this.range()?.start);
104
+ const isEnd = isRange && mxSameDay(date, this.range()?.end);
105
+ const label = fmt.format(date) + (isStart && isEnd ? ', início e fim do intervalo' : isStart ? ', início do intervalo' : isEnd ? ', fim do intervalo' : '');
69
106
  row.push({
70
107
  date,
71
108
  key: `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`,
72
- label: fmt.format(date),
109
+ label,
73
110
  outside: date.getMonth() !== focus.getMonth(),
74
111
  today: mxSameDay(date, today),
75
- selected: mxSameDay(date, this.value()),
112
+ rangeStart,
113
+ rangeEnd,
114
+ inRange,
115
+ selected: isRange ? rangeStart || rangeEnd || inRange : mxSameDay(date, this.value()),
76
116
  focus: mxSameDay(date, focus),
77
117
  disabled: this.isDisabled(date),
78
118
  });
@@ -84,7 +124,7 @@ class MxCalendar {
84
124
  constructor() {
85
125
  afterNextRender(() => {
86
126
  if (!this.initialised) {
87
- const v = this.value();
127
+ const v = this.mode() === 'range' ? this.range()?.start : this.value();
88
128
  if (v)
89
129
  this.focusDate.set(mxStartOfDay(v));
90
130
  this.initialised = true;
@@ -93,7 +133,7 @@ class MxCalendar {
93
133
  }
94
134
  /** Põe o foco no dia activo (seleccionado ou hoje). */
95
135
  focus() {
96
- const v = this.value();
136
+ const v = this.mode() === 'range' ? this.range()?.start : this.value();
97
137
  if (v)
98
138
  this.focusDate.set(mxStartOfDay(v));
99
139
  this.focusActiveDay();
@@ -106,9 +146,27 @@ class MxCalendar {
106
146
  moveMonth(delta) {
107
147
  this.focusDate.set(addMonths(this.focusDate(), delta));
108
148
  }
149
+ hover(date) {
150
+ if (this.awaitingEnd())
151
+ this.preview.set(date);
152
+ }
109
153
  select(date) {
110
154
  if (this.isDisabled(date))
111
155
  return;
156
+ if (this.mode() === 'range') {
157
+ const r = this.range();
158
+ if (r?.start && !r.end) {
159
+ const [start, end] = date < r.start ? [date, r.start] : [r.start, date];
160
+ this.range.set({ start, end });
161
+ this.rangeSelected.emit({ start, end });
162
+ }
163
+ else {
164
+ this.range.set({ start: date, end: null });
165
+ }
166
+ this.preview.set(null);
167
+ this.focusDate.set(date);
168
+ return;
169
+ }
112
170
  this.value.set(date);
113
171
  this.focusDate.set(date);
114
172
  this.selected.emit(date);
@@ -126,10 +184,17 @@ class MxCalendar {
126
184
  PageUp: () => addMonths(f, event.shiftKey ? -12 : -1),
127
185
  PageDown: () => addMonths(f, event.shiftKey ? 12 : 1),
128
186
  };
187
+ if (event.key === 'Enter' || event.key === ' ') {
188
+ // escolhe o dia activo, mesmo que o foco ainda não tenha chegado ao botão
189
+ event.preventDefault();
190
+ this.select(this.focusDate());
191
+ return;
192
+ }
129
193
  const move = moves[event.key];
130
194
  if (move) {
131
195
  event.preventDefault();
132
196
  this.focusDate.set(this.clamp(move()));
197
+ this.hover(this.focusDate());
133
198
  this.focusActiveDay();
134
199
  }
135
200
  }
@@ -155,7 +220,7 @@ class MxCalendar {
155
220
  }, { injector: this.injector });
156
221
  }
157
222
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: MxCalendar, deps: [], target: i0.ɵɵFactoryTarget.Component });
158
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: MxCalendar, isStandalone: true, selector: "mx-calendar", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, dateDisabled: { classPropertyName: "dateDisabled", publicName: "dateDisabled", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, firstDayOfWeek: { classPropertyName: "firstDayOfWeek", publicName: "firstDayOfWeek", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", selected: "selected" }, host: { classAttribute: "mx-calendar" }, exportAs: ["mxCalendar"], ngImport: i0, template: `
223
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: MxCalendar, isStandalone: true, selector: "mx-calendar", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, dateDisabled: { classPropertyName: "dateDisabled", publicName: "dateDisabled", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, firstDayOfWeek: { classPropertyName: "firstDayOfWeek", publicName: "firstDayOfWeek", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, range: { classPropertyName: "range", publicName: "range", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", selected: "selected", range: "rangeChange", rangeSelected: "rangeSelected" }, host: { classAttribute: "mx-calendar" }, exportAs: ["mxCalendar"], ngImport: i0, template: `
159
224
  <div class="mx-calendar-header">
160
225
  <button type="button" class="mx-calendar-nav" (click)="moveMonth(-1)" [attr.aria-label]="'Mês anterior, ' + monthLabel(-1)">
161
226
  <svg viewBox="0 0 16 16" aria-hidden="true"><path d="M10 4L6 8l4 4" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /></svg>
@@ -177,7 +242,13 @@ class MxCalendar {
177
242
  @for (week of weeks(); track $index) {
178
243
  <tr>
179
244
  @for (day of week; track day.date.getTime()) {
180
- <td [attr.aria-selected]="day.selected" role="gridcell">
245
+ <td
246
+ role="gridcell"
247
+ [attr.aria-selected]="day.selected"
248
+ [attr.data-range-start]="day.rangeStart ? '' : null"
249
+ [attr.data-range-end]="day.rangeEnd ? '' : null"
250
+ [attr.data-in-range]="day.inRange ? '' : null"
251
+ >
181
252
  <button
182
253
  type="button"
183
254
  class="mx-calendar-day"
@@ -189,6 +260,7 @@ class MxCalendar {
189
260
  [tabIndex]="day.focus ? 0 : -1"
190
261
  [attr.aria-disabled]="day.disabled ? 'true' : null"
191
262
  (click)="select(day.date)"
263
+ (mouseenter)="hover(day.date)"
192
264
  >
193
265
  {{ day.date.getDate() }}
194
266
  </button>
@@ -198,6 +270,9 @@ class MxCalendar {
198
270
  }
199
271
  </tbody>
200
272
  </table>
273
+ @if (mode() === 'range') {
274
+ <p class="mx-visually-hidden" aria-live="polite">{{ rangeHint() }}</p>
275
+ }
201
276
  `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
202
277
  }
203
278
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: MxCalendar, decorators: [{
@@ -230,7 +305,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImpo
230
305
  @for (week of weeks(); track $index) {
231
306
  <tr>
232
307
  @for (day of week; track day.date.getTime()) {
233
- <td [attr.aria-selected]="day.selected" role="gridcell">
308
+ <td
309
+ role="gridcell"
310
+ [attr.aria-selected]="day.selected"
311
+ [attr.data-range-start]="day.rangeStart ? '' : null"
312
+ [attr.data-range-end]="day.rangeEnd ? '' : null"
313
+ [attr.data-in-range]="day.inRange ? '' : null"
314
+ >
234
315
  <button
235
316
  type="button"
236
317
  class="mx-calendar-day"
@@ -242,6 +323,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImpo
242
323
  [tabIndex]="day.focus ? 0 : -1"
243
324
  [attr.aria-disabled]="day.disabled ? 'true' : null"
244
325
  (click)="select(day.date)"
326
+ (mouseenter)="hover(day.date)"
245
327
  >
246
328
  {{ day.date.getDate() }}
247
329
  </button>
@@ -251,11 +333,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImpo
251
333
  }
252
334
  </tbody>
253
335
  </table>
336
+ @if (mode() === 'range') {
337
+ <p class="mx-visually-hidden" aria-live="polite">{{ rangeHint() }}</p>
338
+ }
254
339
  `,
255
340
  }]
256
- }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], dateDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateDisabled", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], firstDayOfWeek: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstDayOfWeek", required: false }] }], selected: [{ type: i0.Output, args: ["selected"] }] } });
341
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], dateDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateDisabled", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], firstDayOfWeek: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstDayOfWeek", required: false }] }], selected: [{ type: i0.Output, args: ["selected"] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], range: [{ type: i0.Input, args: [{ isSignal: true, alias: "range", required: false }] }, { type: i0.Output, args: ["rangeChange"] }], rangeSelected: [{ type: i0.Output, args: ["rangeSelected"] }] } });
257
342
 
258
- const POSITIONS = [
343
+ const POSITIONS$1 = [
259
344
  { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4 },
260
345
  { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -4 },
261
346
  ];
@@ -306,7 +391,7 @@ class MxDatePicker {
306
391
  host = inject(ElementRef).nativeElement;
307
392
  calendar = viewChild('calendar', ...(ngDevMode ? [{ debugName: "calendar" }] : /* istanbul ignore next */ []));
308
393
  toggleButton = viewChild.required('toggleButton');
309
- positions = POSITIONS;
394
+ positions = POSITIONS$1;
310
395
  generatedId = mxUniqueId('mx-date');
311
396
  controlId = computed(() => this.id() ?? this.generatedId, ...(ngDevMode ? [{ debugName: "controlId" }] : /* istanbul ignore next */ []));
312
397
  isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
@@ -465,7 +550,7 @@ class MxDatePicker {
465
550
  />
466
551
  </div>
467
552
  </ng-template>
468
- `, isInline: true, dependencies: [{ 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", "cdkConnectedOverlayUsePopover", "cdkConnectedOverlayMatchWidth", "cdkConnectedOverlay"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "component", type: MxCalendar, selector: "mx-calendar", inputs: ["value", "min", "max", "dateDisabled", "locale", "firstDayOfWeek"], outputs: ["valueChange", "selected"], exportAs: ["mxCalendar"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
553
+ `, isInline: true, dependencies: [{ 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", "cdkConnectedOverlayUsePopover", "cdkConnectedOverlayMatchWidth", "cdkConnectedOverlay"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "component", type: MxCalendar, selector: "mx-calendar", inputs: ["value", "min", "max", "dateDisabled", "locale", "firstDayOfWeek", "mode", "range"], outputs: ["valueChange", "selected", "rangeChange", "rangeSelected"], exportAs: ["mxCalendar"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
469
554
  }
470
555
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: MxDatePicker, decorators: [{
471
556
  type: Component,
@@ -540,9 +625,406 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImpo
540
625
  }]
541
626
  }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], dateDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateDisabled", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }], calendar: [{ type: i0.ViewChild, args: ['calendar', { isSignal: true }] }], toggleButton: [{ type: i0.ViewChild, args: ['toggleButton', { isSignal: true }] }] } });
542
627
 
628
+ const POSITIONS = [
629
+ { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4 },
630
+ { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -4 },
631
+ ];
632
+ /** Atalhos comuns: últimos 7 e 30 dias, este mês e o mês passado. */
633
+ function mxDefaultRangePresets() {
634
+ const today = () => mxStartOfDay(new Date());
635
+ const shift = (d, days) => new Date(d.getFullYear(), d.getMonth(), d.getDate() + days);
636
+ return [
637
+ { label: 'Últimos 7 dias', range: () => ({ start: shift(today(), -6), end: today() }) },
638
+ { label: 'Últimos 30 dias', range: () => ({ start: shift(today(), -29), end: today() }) },
639
+ {
640
+ label: 'Este mês',
641
+ range: () => {
642
+ const t = today();
643
+ return { start: new Date(t.getFullYear(), t.getMonth(), 1), end: new Date(t.getFullYear(), t.getMonth() + 1, 0) };
644
+ },
645
+ },
646
+ {
647
+ label: 'Mês passado',
648
+ range: () => {
649
+ const t = today();
650
+ return { start: new Date(t.getFullYear(), t.getMonth() - 1, 1), end: new Date(t.getFullYear(), t.getMonth(), 0) };
651
+ },
652
+ },
653
+ ];
654
+ }
655
+ /**
656
+ * Campo de intervalo de datas: dois campos (início e fim) que se escrevem em "dd/mm/aaaa"
657
+ * ou se escolhem num calendário, com atalhos opcionais.
658
+ *
659
+ * ```html
660
+ * <mx-form-field>
661
+ * <label mxLabel>Período</label>
662
+ * <mx-date-range-picker formControlName="periodo" [presets]="atalhos" />
663
+ * </mx-form-field>
664
+ * ```
665
+ *
666
+ * O valor é `{ start, end }` (datas sem horas) ou null. Cada campo é anunciado com o rótulo
667
+ * do campo e "início" ou "fim". Fim anterior ao início deixa o campo com erro.
668
+ */
669
+ class MxDateRangePicker {
670
+ value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
671
+ min = input(null, ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
672
+ max = input(null, ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
673
+ dateDisabled = input(() => false, ...(ngDevMode ? [{ debugName: "dateDisabled" }] : /* istanbul ignore next */ []));
674
+ presets = input([], ...(ngDevMode ? [{ debugName: "presets" }] : /* istanbul ignore next */ []));
675
+ locale = input('pt-AO', ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
676
+ placeholder = input('dd/mm/aaaa', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
677
+ startLabel = input('início', ...(ngDevMode ? [{ debugName: "startLabel" }] : /* istanbul ignore next */ []));
678
+ endLabel = input('fim', ...(ngDevMode ? [{ debugName: "endLabel" }] : /* istanbul ignore next */ []));
679
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
680
+ required = input(false, { ...(ngDevMode ? { debugName: "required" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
681
+ invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
682
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
683
+ id = input(undefined, ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
684
+ /** Nome do campo quando não há mxLabel. */
685
+ ariaLabel = input(undefined, { ...(ngDevMode ? { debugName: "ariaLabel" } : /* istanbul ignore next */ {}), alias: 'aria-label' });
686
+ field = inject(MxFormField, { optional: true });
687
+ ngControl = inject(NgControl, { self: true, optional: true });
688
+ host = inject(ElementRef).nativeElement;
689
+ calendar = viewChild('calendar', ...(ngDevMode ? [{ debugName: "calendar" }] : /* istanbul ignore next */ []));
690
+ toggleButton = viewChild.required('toggleButton');
691
+ positions = POSITIONS;
692
+ generatedId = mxUniqueId('mx-date-range');
693
+ controlId = computed(() => this.id() ?? this.generatedId, ...(ngDevMode ? [{ debugName: "controlId" }] : /* istanbul ignore next */ []));
694
+ startLabelId = mxUniqueId('mx-date-range-start');
695
+ endLabelId = mxUniqueId('mx-date-range-end');
696
+ nameId = mxUniqueId('mx-date-range-name');
697
+ isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
698
+ /** Intervalo a ser escolhido no calendário aberto. */
699
+ draftRange = signal(null, ...(ngDevMode ? [{ debugName: "draftRange" }] : /* istanbul ignore next */ []));
700
+ /** Texto escrito que ainda não é uma data válida, por campo. */
701
+ draftText = signal({ start: null, end: null }, ...(ngDevMode ? [{ debugName: "draftText" }] : /* istanbul ignore next */ []));
702
+ formDisabled = signal(false, ...(ngDevMode ? [{ debugName: "formDisabled" }] : /* istanbul ignore next */ []));
703
+ isDisabled = computed(() => this.disabled() || this.formDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
704
+ startText = computed(() => this.draftText().start ?? mxFormatDate(this.value()?.start ?? null), ...(ngDevMode ? [{ debugName: "startText" }] : /* istanbul ignore next */ []));
705
+ endText = computed(() => this.draftText().end ?? mxFormatDate(this.value()?.end ?? null), ...(ngDevMode ? [{ debugName: "endText" }] : /* istanbul ignore next */ []));
706
+ /** true quando há texto que não é data, ou o fim é anterior ao início. */
707
+ badFormat = computed(() => {
708
+ const d = this.draftText();
709
+ const v = this.value();
710
+ return (!!d.start && !mxParseDate(d.start)) || (!!d.end && !mxParseDate(d.end)) || (!!v?.start && !!v?.end && v.end < v.start);
711
+ }, ...(ngDevMode ? [{ debugName: "badFormat" }] : /* istanbul ignore next */ []));
712
+ toggleLabel = computed(() => {
713
+ const v = this.value();
714
+ if (!v?.start || !v?.end)
715
+ return 'Escolher intervalo';
716
+ const f = new Intl.DateTimeFormat(this.locale(), { day: 'numeric', month: 'long', year: 'numeric' });
717
+ return `Mudar intervalo, ${f.format(v.start)} a ${f.format(v.end)}`;
718
+ }, ...(ngDevMode ? [{ debugName: "toggleLabel" }] : /* istanbul ignore next */ []));
719
+ onChange = () => { };
720
+ onTouched = () => { };
721
+ constructor() {
722
+ if (this.ngControl)
723
+ this.ngControl.valueAccessor = this;
724
+ }
725
+ /** aria-labelledby: rótulo do form field (ou aria-label) + "início"/"fim". */
726
+ labelledBy(partId) {
727
+ const label = this.field?.labelId();
728
+ return `${label ?? this.nameId} ${partId}`;
729
+ }
730
+ // nome escondido quando se usa aria-label em vez de mxLabel
731
+ nameText = computed(() => this.ariaLabel() ?? '', ...(ngDevMode ? [{ debugName: "nameText" }] : /* istanbul ignore next */ []));
732
+ open() {
733
+ if (this.isDisabled())
734
+ return;
735
+ const v = this.value();
736
+ this.draftRange.set(v?.start ? { start: v.start, end: v.end } : null);
737
+ this.isOpen.set(true);
738
+ }
739
+ toggle() {
740
+ if (this.isOpen())
741
+ this.close();
742
+ else
743
+ this.open();
744
+ }
745
+ close(restoreFocus = true) {
746
+ if (!this.isOpen())
747
+ return;
748
+ this.isOpen.set(false);
749
+ if (restoreFocus)
750
+ this.toggleButton().nativeElement.focus();
751
+ }
752
+ onAttach() {
753
+ queueMicrotask(() => this.calendar()?.focus());
754
+ }
755
+ onOverlayKey(event) {
756
+ if (event.key === 'Escape') {
757
+ event.preventDefault();
758
+ this.close();
759
+ }
760
+ }
761
+ onOutside(event) {
762
+ if (!this.host.contains(event.target))
763
+ this.close(false);
764
+ }
765
+ choose(range) {
766
+ this.draftText.set({ start: null, end: null });
767
+ this.setValue({ start: mxStartOfDay(range.start), end: mxStartOfDay(range.end) });
768
+ this.close();
769
+ }
770
+ applyPreset(p) {
771
+ this.choose(p.range());
772
+ }
773
+ onInput(part, event) {
774
+ const text = event.target.value;
775
+ const parsed = mxParseDate(text);
776
+ this.draftText.update((d) => ({ ...d, [part]: parsed || !text ? null : text }));
777
+ const current = this.value() ?? { start: null, end: null };
778
+ const next = { ...current, [part]: parsed };
779
+ this.setValue(next.start || next.end ? next : null);
780
+ }
781
+ setValue(v) {
782
+ const prev = this.value();
783
+ const same = (a, b) => (a?.getTime() ?? null) === (b?.getTime() ?? null);
784
+ if (same(prev?.start, v?.start) && same(prev?.end, v?.end))
785
+ return;
786
+ this.value.set(v);
787
+ this.onChange(v);
788
+ }
789
+ // ---- MxFormFieldControl ----
790
+ errorState() {
791
+ if (this.invalid() || this.badFormat())
792
+ return true;
793
+ const c = this.ngControl;
794
+ return !!c && !!c.invalid && !!(c.touched || c.dirty);
795
+ }
796
+ isRequired() {
797
+ return this.required() || !!this.ngControl?.control?.hasValidator?.(Validators.required);
798
+ }
799
+ // ---- ControlValueAccessor ----
800
+ writeValue(value) {
801
+ this.value.set(value ? { start: value.start ? mxStartOfDay(value.start) : null, end: value.end ? mxStartOfDay(value.end) : null } : null);
802
+ this.draftText.set({ start: null, end: null });
803
+ }
804
+ registerOnChange(fn) {
805
+ this.onChange = fn;
806
+ }
807
+ registerOnTouched(fn) {
808
+ this.onTouched = fn;
809
+ }
810
+ setDisabledState(isDisabled) {
811
+ this.formDisabled.set(isDisabled);
812
+ }
813
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: MxDateRangePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
814
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: MxDateRangePicker, isStandalone: true, selector: "mx-date-range-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, dateDisabled: { classPropertyName: "dateDisabled", publicName: "dateDisabled", isSignal: true, isRequired: false, transformFunction: null }, presets: { classPropertyName: "presets", publicName: "presets", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, startLabel: { classPropertyName: "startLabel", publicName: "startLabel", isSignal: true, isRequired: false, transformFunction: null }, endLabel: { classPropertyName: "endLabel", publicName: "endLabel", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { properties: { "attr.aria-label": "null" }, classAttribute: "mx-date-range-picker" }, providers: [{ provide: MX_FORM_FIELD_CONTROL, useExisting: MxDateRangePicker }], viewQueries: [{ propertyName: "calendar", first: true, predicate: ["calendar"], descendants: true, isSignal: true }, { propertyName: "toggleButton", first: true, predicate: ["toggleButton"], descendants: true, isSignal: true }], exportAs: ["mxDateRangePicker"], ngImport: i0, template: `
815
+ <div
816
+ class="mx-date-range-field"
817
+ cdkOverlayOrigin
818
+ #origin="cdkOverlayOrigin"
819
+ [attr.data-size]="size() === 'md' ? null : size()"
820
+ [attr.data-invalid]="errorState() ? '' : null"
821
+ [attr.data-disabled]="isDisabled() ? '' : null"
822
+ >
823
+ <input
824
+ class="mx-date-range-input"
825
+ type="text"
826
+ inputmode="numeric"
827
+ autocomplete="off"
828
+ [id]="controlId()"
829
+ [placeholder]="placeholder()"
830
+ [value]="startText()"
831
+ [disabled]="isDisabled()"
832
+ [attr.aria-labelledby]="labelledBy(startLabelId)"
833
+ [attr.aria-invalid]="errorState() ? 'true' : null"
834
+ [attr.aria-required]="isRequired() ? 'true' : null"
835
+ [attr.aria-describedby]="field?.describedBy() ?? null"
836
+ (input)="onInput('start', $event)"
837
+ (blur)="onTouched()"
838
+ (keydown.alt.arrowdown)="$event.preventDefault(); open()"
839
+ />
840
+ <span class="mx-date-range-sep" aria-hidden="true">–</span>
841
+ <input
842
+ class="mx-date-range-input"
843
+ type="text"
844
+ inputmode="numeric"
845
+ autocomplete="off"
846
+ [placeholder]="placeholder()"
847
+ [value]="endText()"
848
+ [disabled]="isDisabled()"
849
+ [attr.aria-labelledby]="labelledBy(endLabelId)"
850
+ [attr.aria-invalid]="errorState() ? 'true' : null"
851
+ [attr.aria-required]="isRequired() ? 'true' : null"
852
+ [attr.aria-describedby]="field?.describedBy() ?? null"
853
+ (input)="onInput('end', $event)"
854
+ (blur)="onTouched()"
855
+ (keydown.alt.arrowdown)="$event.preventDefault(); open()"
856
+ />
857
+ <span class="mx-visually-hidden" [id]="startLabelId">{{ startLabel() }}</span>
858
+ <span class="mx-visually-hidden" [id]="endLabelId">{{ endLabel() }}</span>
859
+ @if (!field?.labelId()) {
860
+ <span class="mx-visually-hidden" [id]="nameId">{{ nameText() }}</span>
861
+ }
862
+ <button
863
+ #toggleButton
864
+ type="button"
865
+ class="mx-date-picker-toggle"
866
+ aria-haspopup="dialog"
867
+ [attr.aria-expanded]="isOpen()"
868
+ [attr.aria-label]="toggleLabel()"
869
+ [disabled]="isDisabled()"
870
+ (click)="toggle()"
871
+ >
872
+ <svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
873
+ <rect x="2.5" y="3.5" width="11" height="10" rx="1.5" stroke="currentColor" stroke-width="1.3" />
874
+ <path d="M2.5 6.5h11M5.5 2v3M10.5 2v3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" />
875
+ </svg>
876
+ </button>
877
+ </div>
878
+
879
+ <ng-template
880
+ cdkConnectedOverlay
881
+ [cdkConnectedOverlayOrigin]="origin"
882
+ [cdkConnectedOverlayOpen]="isOpen()"
883
+ [cdkConnectedOverlayPositions]="positions"
884
+ (overlayOutsideClick)="onOutside($event)"
885
+ (overlayKeydown)="onOverlayKey($event)"
886
+ (attach)="onAttach()"
887
+ (detach)="close(false)"
888
+ >
889
+ <div class="mx-date-picker-panel mx-date-range-panel" role="dialog" aria-modal="false" aria-label="Escolher intervalo">
890
+ @if (presets().length) {
891
+ <ul class="mx-date-range-presets" aria-label="Atalhos">
892
+ @for (p of presets(); track p.label) {
893
+ <li><button type="button" class="mx-date-range-preset" (click)="applyPreset(p)">{{ p.label }}</button></li>
894
+ }
895
+ </ul>
896
+ }
897
+ <mx-calendar
898
+ #calendar
899
+ mode="range"
900
+ [(range)]="draftRange"
901
+ [min]="min()"
902
+ [max]="max()"
903
+ [dateDisabled]="dateDisabled()"
904
+ [locale]="locale()"
905
+ (rangeSelected)="choose($event)"
906
+ />
907
+ </div>
908
+ </ng-template>
909
+ `, isInline: true, dependencies: [{ 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", "cdkConnectedOverlayUsePopover", "cdkConnectedOverlayMatchWidth", "cdkConnectedOverlay"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "component", type: MxCalendar, selector: "mx-calendar", inputs: ["value", "min", "max", "dateDisabled", "locale", "firstDayOfWeek", "mode", "range"], outputs: ["valueChange", "selected", "rangeChange", "rangeSelected"], exportAs: ["mxCalendar"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
910
+ }
911
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: MxDateRangePicker, decorators: [{
912
+ type: Component,
913
+ args: [{
914
+ selector: 'mx-date-range-picker',
915
+ exportAs: 'mxDateRangePicker',
916
+ imports: [CdkOverlayOrigin, CdkConnectedOverlay, MxCalendar],
917
+ providers: [{ provide: MX_FORM_FIELD_CONTROL, useExisting: MxDateRangePicker }],
918
+ changeDetection: ChangeDetectionStrategy.OnPush,
919
+ encapsulation: ViewEncapsulation.None,
920
+ host: { class: 'mx-date-range-picker', '[attr.aria-label]': 'null' },
921
+ template: `
922
+ <div
923
+ class="mx-date-range-field"
924
+ cdkOverlayOrigin
925
+ #origin="cdkOverlayOrigin"
926
+ [attr.data-size]="size() === 'md' ? null : size()"
927
+ [attr.data-invalid]="errorState() ? '' : null"
928
+ [attr.data-disabled]="isDisabled() ? '' : null"
929
+ >
930
+ <input
931
+ class="mx-date-range-input"
932
+ type="text"
933
+ inputmode="numeric"
934
+ autocomplete="off"
935
+ [id]="controlId()"
936
+ [placeholder]="placeholder()"
937
+ [value]="startText()"
938
+ [disabled]="isDisabled()"
939
+ [attr.aria-labelledby]="labelledBy(startLabelId)"
940
+ [attr.aria-invalid]="errorState() ? 'true' : null"
941
+ [attr.aria-required]="isRequired() ? 'true' : null"
942
+ [attr.aria-describedby]="field?.describedBy() ?? null"
943
+ (input)="onInput('start', $event)"
944
+ (blur)="onTouched()"
945
+ (keydown.alt.arrowdown)="$event.preventDefault(); open()"
946
+ />
947
+ <span class="mx-date-range-sep" aria-hidden="true">–</span>
948
+ <input
949
+ class="mx-date-range-input"
950
+ type="text"
951
+ inputmode="numeric"
952
+ autocomplete="off"
953
+ [placeholder]="placeholder()"
954
+ [value]="endText()"
955
+ [disabled]="isDisabled()"
956
+ [attr.aria-labelledby]="labelledBy(endLabelId)"
957
+ [attr.aria-invalid]="errorState() ? 'true' : null"
958
+ [attr.aria-required]="isRequired() ? 'true' : null"
959
+ [attr.aria-describedby]="field?.describedBy() ?? null"
960
+ (input)="onInput('end', $event)"
961
+ (blur)="onTouched()"
962
+ (keydown.alt.arrowdown)="$event.preventDefault(); open()"
963
+ />
964
+ <span class="mx-visually-hidden" [id]="startLabelId">{{ startLabel() }}</span>
965
+ <span class="mx-visually-hidden" [id]="endLabelId">{{ endLabel() }}</span>
966
+ @if (!field?.labelId()) {
967
+ <span class="mx-visually-hidden" [id]="nameId">{{ nameText() }}</span>
968
+ }
969
+ <button
970
+ #toggleButton
971
+ type="button"
972
+ class="mx-date-picker-toggle"
973
+ aria-haspopup="dialog"
974
+ [attr.aria-expanded]="isOpen()"
975
+ [attr.aria-label]="toggleLabel()"
976
+ [disabled]="isDisabled()"
977
+ (click)="toggle()"
978
+ >
979
+ <svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
980
+ <rect x="2.5" y="3.5" width="11" height="10" rx="1.5" stroke="currentColor" stroke-width="1.3" />
981
+ <path d="M2.5 6.5h11M5.5 2v3M10.5 2v3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" />
982
+ </svg>
983
+ </button>
984
+ </div>
985
+
986
+ <ng-template
987
+ cdkConnectedOverlay
988
+ [cdkConnectedOverlayOrigin]="origin"
989
+ [cdkConnectedOverlayOpen]="isOpen()"
990
+ [cdkConnectedOverlayPositions]="positions"
991
+ (overlayOutsideClick)="onOutside($event)"
992
+ (overlayKeydown)="onOverlayKey($event)"
993
+ (attach)="onAttach()"
994
+ (detach)="close(false)"
995
+ >
996
+ <div class="mx-date-picker-panel mx-date-range-panel" role="dialog" aria-modal="false" aria-label="Escolher intervalo">
997
+ @if (presets().length) {
998
+ <ul class="mx-date-range-presets" aria-label="Atalhos">
999
+ @for (p of presets(); track p.label) {
1000
+ <li><button type="button" class="mx-date-range-preset" (click)="applyPreset(p)">{{ p.label }}</button></li>
1001
+ }
1002
+ </ul>
1003
+ }
1004
+ <mx-calendar
1005
+ #calendar
1006
+ mode="range"
1007
+ [(range)]="draftRange"
1008
+ [min]="min()"
1009
+ [max]="max()"
1010
+ [dateDisabled]="dateDisabled()"
1011
+ [locale]="locale()"
1012
+ (rangeSelected)="choose($event)"
1013
+ />
1014
+ </div>
1015
+ </ng-template>
1016
+ `,
1017
+ }]
1018
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], dateDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateDisabled", required: false }] }], presets: [{ type: i0.Input, args: [{ isSignal: true, alias: "presets", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], startLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "startLabel", required: false }] }], endLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "endLabel", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }], calendar: [{ type: i0.ViewChild, args: ['calendar', { isSignal: true }] }], toggleButton: [{ type: i0.ViewChild, args: ['toggleButton', { isSignal: true }] }] } });
1019
+ /** Validador: exige início e fim. Usa com `Validators.required` não chega, porque {start, end: null} não é vazio. */
1020
+ function mxDateRangeComplete(control) {
1021
+ const v = control.value;
1022
+ return v && (!v.start || !v.end) ? { rangeIncomplete: true } : null;
1023
+ }
1024
+
543
1025
  /**
544
1026
  * Generated bundle index. Do not edit.
545
1027
  */
546
1028
 
547
- export { MxCalendar, MxDatePicker, mxFormatDate, mxParseDate, mxSameDay, mxStartOfDay };
1029
+ export { MxCalendar, MxDatePicker, MxDateRangePicker, mxDateRangeComplete, mxDefaultRangePresets, mxFormatDate, mxParseDate, mxSameDay, mxStartOfDay };
548
1030
  //# sourceMappingURL=muxima-ui-core-date-picker.mjs.map