@momentum-design/components 0.139.4 → 0.139.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.
@@ -20,6 +20,7 @@ declare const Calendar_base: import("../../utils/mixins/index.types").Constructo
20
20
  * @tagname mdc-calendar
21
21
  *
22
22
  * @dependency mdc-button
23
+ * @dependency mdc-screenreaderannouncer
23
24
  *
24
25
  * @event date-selected - Fires when a date or date range is selected.
25
26
  * @event month-changed - Fires when the displayed month changes.
@@ -31,6 +32,8 @@ declare const Calendar_base: import("../../utils/mixins/index.types").Constructo
31
32
  * @cssproperty --mdc-calendar-day-today-border-color - Border color for today's date.
32
33
  * @cssproperty --mdc-calendar-day-hover-bg - Background color on hover.
33
34
  * @cssproperty --mdc-calendar-range-bg - Background color for days within a range.
35
+ * @cssproperty --mdc-calendar-range-preview-bg - Background color for a provisional range.
36
+ * @cssproperty --mdc-calendar-range-preview-text-color - Text color for days within a provisional range.
34
37
  * @cssproperty --mdc-calendar-day-outside-month-text-color - Text color for days outside the current month.
35
38
  * @cssproperty --mdc-calendar-day-disabled-text-color - Text color for disabled days.
36
39
  */
@@ -79,22 +82,31 @@ declare class Calendar extends Calendar_base {
79
82
  * Localized aria-label for the next month navigation button.
80
83
  */
81
84
  localeNextMonthLabel: string;
85
+ /**
86
+ * Localized announcement made after the range start date is selected.
87
+ */
88
+ localeRangeStartSelectedLabel: string;
82
89
  private displayMonth;
83
90
  private displayYear;
84
91
  private focusedDate;
85
92
  private gridHasFocus;
86
93
  private rangeSelectionPhase;
94
+ private hoveredDate;
87
95
  connectedCallback(): void;
88
96
  protected willUpdate(changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void;
89
97
  private initializeDisplay;
98
+ /** @internal */
99
+ cancelRangeSelection(value: string, endValue: string): void;
90
100
  private get calendarGrid();
91
101
  private getSelectedDates;
92
- private getRangeStart;
93
- private getRangeEnd;
102
+ private get previewEndDate();
103
+ private get isPreviewingRange();
104
+ private getDisplayedRange;
94
105
  private canNavigatePrev;
95
106
  private canNavigateNext;
96
107
  private navigateMonth;
97
108
  private handleDayClick;
109
+ private handleDayPointerEnter;
98
110
  private selectDate;
99
111
  private dispatchDateSelected;
100
112
  private handleGridKeydown;
@@ -103,6 +115,7 @@ declare class Calendar extends Calendar_base {
103
115
  private renderDay;
104
116
  private handleGridFocusIn;
105
117
  private handleGridFocusOut;
118
+ private handleGridPointerLeave;
106
119
  private renderGrid;
107
120
  private isDateDisabled;
108
121
  private isTodayDisabled;
@@ -23,6 +23,7 @@ import { formatDateRangeForDisplay, generateCalendarGrid, getDatesInRange, getMo
23
23
  * @tagname mdc-calendar
24
24
  *
25
25
  * @dependency mdc-button
26
+ * @dependency mdc-screenreaderannouncer
26
27
  *
27
28
  * @event date-selected - Fires when a date or date range is selected.
28
29
  * @event month-changed - Fires when the displayed month changes.
@@ -34,6 +35,8 @@ import { formatDateRangeForDisplay, generateCalendarGrid, getDatesInRange, getMo
34
35
  * @cssproperty --mdc-calendar-day-today-border-color - Border color for today's date.
35
36
  * @cssproperty --mdc-calendar-day-hover-bg - Background color on hover.
36
37
  * @cssproperty --mdc-calendar-range-bg - Background color for days within a range.
38
+ * @cssproperty --mdc-calendar-range-preview-bg - Background color for a provisional range.
39
+ * @cssproperty --mdc-calendar-range-preview-text-color - Text color for days within a provisional range.
37
40
  * @cssproperty --mdc-calendar-day-outside-month-text-color - Text color for days outside the current month.
38
41
  * @cssproperty --mdc-calendar-day-disabled-text-color - Text color for disabled days.
39
42
  */
@@ -76,11 +79,16 @@ class Calendar extends KeyDownHandledMixin(KeyToActionMixin(Component)) {
76
79
  * Localized aria-label for the next month navigation button.
77
80
  */
78
81
  this.localeNextMonthLabel = '';
82
+ /**
83
+ * Localized announcement made after the range start date is selected.
84
+ */
85
+ this.localeRangeStartSelectedLabel = '';
79
86
  this.displayMonth = new Date().getMonth() + 1;
80
87
  this.displayYear = new Date().getFullYear();
81
88
  this.focusedDate = '';
82
89
  this.gridHasFocus = false;
83
90
  this.rangeSelectionPhase = 'start';
91
+ this.hoveredDate = '';
84
92
  }
85
93
  connectedCallback() {
86
94
  super.connectedCallback();
@@ -90,6 +98,7 @@ class Calendar extends KeyDownHandledMixin(KeyToActionMixin(Component)) {
90
98
  super.willUpdate(changedProperties);
91
99
  if (changedProperties.has('selectionMode')) {
92
100
  this.rangeSelectionPhase = 'start';
101
+ this.hoveredDate = '';
93
102
  }
94
103
  if (changedProperties.has('value') && this.value) {
95
104
  const dt = parseISO(this.value);
@@ -123,10 +132,20 @@ class Calendar extends KeyDownHandledMixin(KeyToActionMixin(Component)) {
123
132
  this.displayYear = focusDt.getFullYear();
124
133
  this.focusedDate = toISODate(focusDt);
125
134
  }
135
+ /** @internal */
136
+ cancelRangeSelection(value, endValue) {
137
+ if (this.selectionMode !== SELECTION_MODE.RANGE || this.rangeSelectionPhase !== 'end')
138
+ return;
139
+ this.value = value;
140
+ this.endValue = endValue;
141
+ this.rangeSelectionPhase = 'start';
142
+ this.hoveredDate = '';
143
+ this.gridHasFocus = false;
144
+ this.initializeDisplay();
145
+ }
126
146
  get calendarGrid() {
127
147
  const selectedDates = this.getSelectedDates();
128
- const rangeStart = this.getRangeStart();
129
- const rangeEnd = this.getRangeEnd();
148
+ const { start: rangeStart, end: rangeEnd } = this.getDisplayedRange();
130
149
  return generateCalendarGrid(this.displayYear, this.displayMonth, this.locale, selectedDates, rangeStart, rangeEnd, this.min, this.max);
131
150
  }
132
151
  getSelectedDates() {
@@ -139,17 +158,34 @@ class Calendar extends KeyDownHandledMixin(KeyToActionMixin(Component)) {
139
158
  }
140
159
  return [this.value];
141
160
  }
142
- getRangeStart() {
143
- if ((this.selectionMode === SELECTION_MODE.WEEK || this.selectionMode === SELECTION_MODE.RANGE) && this.value) {
144
- return this.value;
145
- }
146
- return undefined;
161
+ get previewEndDate() {
162
+ if (this.selectionMode !== SELECTION_MODE.RANGE || this.rangeSelectionPhase !== 'end' || !this.value)
163
+ return '';
164
+ if (this.hoveredDate && !this.isDateDisabled(this.hoveredDate))
165
+ return this.hoveredDate;
166
+ if (this.gridHasFocus && this.focusedDate && !this.isDateDisabled(this.focusedDate))
167
+ return this.focusedDate;
168
+ return '';
169
+ }
170
+ get isPreviewingRange() {
171
+ return Boolean(this.previewEndDate);
147
172
  }
148
- getRangeEnd() {
149
- if ((this.selectionMode === SELECTION_MODE.WEEK || this.selectionMode === SELECTION_MODE.RANGE) && this.endValue) {
150
- return this.endValue;
173
+ getDisplayedRange() {
174
+ if (this.selectionMode !== SELECTION_MODE.WEEK && this.selectionMode !== SELECTION_MODE.RANGE)
175
+ return {};
176
+ const previewEnd = this.previewEndDate;
177
+ if (this.selectionMode === SELECTION_MODE.RANGE && this.value && previewEnd) {
178
+ const startDt = parseISO(this.value);
179
+ const endDt = parseISO(previewEnd);
180
+ if (startDt && endDt && isBefore(endDt, startDt)) {
181
+ return { start: previewEnd, end: this.value };
182
+ }
183
+ return { start: this.value, end: previewEnd };
151
184
  }
152
- return undefined;
185
+ return {
186
+ start: this.value || undefined,
187
+ end: this.endValue || undefined,
188
+ };
153
189
  }
154
190
  canNavigatePrev() {
155
191
  if (!this.min)
@@ -190,6 +226,13 @@ class Calendar extends KeyDownHandledMixin(KeyToActionMixin(Component)) {
190
226
  return;
191
227
  this.selectDate(dayInfo.date);
192
228
  }
229
+ handleDayPointerEnter(dayInfo) {
230
+ if (this.rangeSelectionPhase !== 'end' || this.selectionMode !== SELECTION_MODE.RANGE || dayInfo.isDisabled) {
231
+ this.hoveredDate = '';
232
+ return;
233
+ }
234
+ this.hoveredDate = dayInfo.date;
235
+ }
193
236
  selectDate(dateIso) {
194
237
  switch (this.selectionMode) {
195
238
  case SELECTION_MODE.SINGLE: {
@@ -216,6 +259,7 @@ class Calendar extends KeyDownHandledMixin(KeyToActionMixin(Component)) {
216
259
  this.endValue = '';
217
260
  this.rangeSelectionPhase = 'end';
218
261
  this.focusedDate = dateIso;
262
+ this.hoveredDate = '';
219
263
  }
220
264
  else {
221
265
  const startDt = parseISO(this.value);
@@ -229,6 +273,7 @@ class Calendar extends KeyDownHandledMixin(KeyToActionMixin(Component)) {
229
273
  }
230
274
  this.rangeSelectionPhase = 'start';
231
275
  this.focusedDate = dateIso;
276
+ this.hoveredDate = '';
232
277
  this.dispatchEvent(new CustomEvent('date-selected', {
233
278
  detail: { date: dateIso, startDate: this.value, endDate: this.endValue },
234
279
  bubbles: true,
@@ -310,6 +355,7 @@ class Calendar extends KeyDownHandledMixin(KeyToActionMixin(Component)) {
310
355
  const newIso = toISODate(newDt);
311
356
  if (this.isDateDisabled(newIso))
312
357
  return;
358
+ this.hoveredDate = '';
313
359
  this.focusedDate = newIso;
314
360
  if (newDt.getMonth() + 1 !== this.displayMonth || newDt.getFullYear() !== this.displayYear) {
315
361
  this.displayMonth = newDt.getMonth() + 1;
@@ -361,6 +407,7 @@ class Calendar extends KeyDownHandledMixin(KeyToActionMixin(Component)) {
361
407
  }
362
408
  renderDay(dayInfo) {
363
409
  const isFocused = dayInfo.date === this.focusedDate;
410
+ const isRangePreview = this.isPreviewingRange;
364
411
  const dayClasses = {
365
412
  'calendar-day': true,
366
413
  'outside-month': !dayInfo.isCurrentMonth,
@@ -374,11 +421,12 @@ class Calendar extends KeyDownHandledMixin(KeyToActionMixin(Component)) {
374
421
  'in-range': dayInfo.isInRange,
375
422
  'range-start': dayInfo.isRangeStart,
376
423
  'range-end': dayInfo.isRangeEnd,
424
+ 'range-preview': isRangePreview && dayInfo.isInRange,
377
425
  };
378
426
  const tabIndex = isFocused ? 0 : -1;
379
- const isPartOfSelection = dayInfo.isSelected || dayInfo.isInRange;
427
+ const isPartOfSelection = dayInfo.isSelected || (dayInfo.isInRange && !isRangePreview);
380
428
  return html `
381
- <div class="${classMap(wrapperClasses)}">
429
+ <div class="${classMap(wrapperClasses)}" @pointerenter="${() => this.handleDayPointerEnter(dayInfo)}">
382
430
  <button
383
431
  class="${classMap(dayClasses)}"
384
432
  role="gridcell"
@@ -401,6 +449,9 @@ class Calendar extends KeyDownHandledMixin(KeyToActionMixin(Component)) {
401
449
  handleGridFocusOut() {
402
450
  this.gridHasFocus = false;
403
451
  }
452
+ handleGridPointerLeave() {
453
+ this.hoveredDate = '';
454
+ }
404
455
  renderGrid() {
405
456
  const grid = this.calendarGrid;
406
457
  const weekdays = getWeekdayNames(this.locale);
@@ -412,6 +463,7 @@ class Calendar extends KeyDownHandledMixin(KeyToActionMixin(Component)) {
412
463
  @keydown="${this.handleGridKeydown}"
413
464
  @focusin="${this.handleGridFocusIn}"
414
465
  @focusout="${this.handleGridFocusOut}"
466
+ @pointerleave="${this.handleGridPointerLeave}"
415
467
  >
416
468
  <div class="calendar-row calendar-weekdays" role="row">
417
469
  ${weekdays.map(day => html `<span class="calendar-weekday" role="columnheader">${day}</span>`)}
@@ -460,7 +512,14 @@ class Calendar extends KeyDownHandledMixin(KeyToActionMixin(Component)) {
460
512
  `;
461
513
  }
462
514
  render() {
463
- return html ` ${this.renderHeader()} ${this.renderGrid()} ${this.renderTodayButton()} `;
515
+ const rangeAnnouncement = this.rangeSelectionPhase === 'end' ? this.localeRangeStartSelectedLabel : '';
516
+ return html `
517
+ ${this.renderHeader()} ${this.renderGrid()} ${this.renderTodayButton()}
518
+ <mdc-screenreaderannouncer
519
+ announcement="${rangeAnnouncement}"
520
+ data-aria-live="polite"
521
+ ></mdc-screenreaderannouncer>
522
+ `;
464
523
  }
465
524
  }
466
525
  Calendar.styles = [...Component.styles, ...styles];
@@ -504,6 +563,10 @@ __decorate([
504
563
  property({ type: String, attribute: 'locale-next-month-label' }),
505
564
  __metadata("design:type", Object)
506
565
  ], Calendar.prototype, "localeNextMonthLabel", void 0);
566
+ __decorate([
567
+ property({ type: String, attribute: 'locale-range-start-selected-label' }),
568
+ __metadata("design:type", Object)
569
+ ], Calendar.prototype, "localeRangeStartSelectedLabel", void 0);
507
570
  __decorate([
508
571
  state(),
509
572
  __metadata("design:type", Number)
@@ -524,4 +587,8 @@ __decorate([
524
587
  state(),
525
588
  __metadata("design:type", String)
526
589
  ], Calendar.prototype, "rangeSelectionPhase", void 0);
590
+ __decorate([
591
+ state(),
592
+ __metadata("design:type", String)
593
+ ], Calendar.prototype, "hoveredDate", void 0);
527
594
  export default Calendar;
@@ -13,6 +13,8 @@ const styles = css `
13
13
  --mdc-calendar-day-outside-month-text-color: var(--mds-color-theme-text-secondary-normal);
14
14
  --mdc-calendar-day-disabled-text-color: var(--mds-color-theme-text-primary-disabled);
15
15
  --mdc-calendar-range-bg: var(--mds-color-theme-background-accent-active);
16
+ --mdc-calendar-range-preview-bg: var(--mds-color-theme-background-accent-normal);
17
+ --mdc-calendar-range-preview-text-color: var(--mds-color-theme-common-text-primary-normal);
16
18
 
17
19
  display: flex;
18
20
  flex-direction: column;
@@ -98,6 +100,10 @@ const styles = css `
98
100
  z-index: 1;
99
101
  }
100
102
 
103
+ .calendar-day-wrapper.range-preview::before {
104
+ background: var(--mdc-calendar-range-preview-bg);
105
+ }
106
+
101
107
  .calendar-day-wrapper.range-start::before,
102
108
  .calendar-day-wrapper.in-range:first-child::before {
103
109
  inset-inline-start: calc(50% - 0.875rem);
@@ -179,6 +185,10 @@ const styles = css `
179
185
  font-weight: var(--mds-font-apps-body-small-bold-font-weight);
180
186
  }
181
187
 
188
+ .calendar-day-wrapper.range-preview .calendar-day {
189
+ color: var(--mdc-calendar-range-preview-text-color);
190
+ }
191
+
182
192
  .calendar-day.disabled {
183
193
  color: var(--mdc-calendar-day-disabled-text-color);
184
194
  cursor: not-allowed;
@@ -1,4 +1,5 @@
1
1
  import '../button';
2
+ import '../screenreaderannouncer';
2
3
  import '../text';
3
4
  import Calendar from './calendar.component';
4
5
  declare global {
@@ -1,4 +1,5 @@
1
1
  import '../button';
2
+ import '../screenreaderannouncer';
2
3
  import '../text';
3
4
  import Calendar from './calendar.component';
4
5
  import { TAG_NAME } from './calendar.constants';
@@ -32,6 +32,7 @@ const styles = [
32
32
  display: flex;
33
33
  flex-direction: column;
34
34
  gap: 0.25rem;
35
+ align-self: center;
35
36
  }
36
37
 
37
38
  :host::part(label) {
@@ -147,6 +147,10 @@ declare class DatePicker extends DatePicker_base implements AssociatedFormContro
147
147
  * Localized aria-label for the next month button.
148
148
  */
149
149
  localeNextMonthLabel: string;
150
+ /**
151
+ * Localized announcement made after the range start date is selected.
152
+ */
153
+ localeRangeStartSelectedLabel: string;
150
154
  /** @internal */
151
155
  private monthInput;
152
156
  /** @internal */
@@ -154,6 +158,8 @@ declare class DatePicker extends DatePicker_base implements AssociatedFormContro
154
158
  /** @internal */
155
159
  private yearInput;
156
160
  /** @internal */
161
+ private calendar?;
162
+ /** @internal */
157
163
  private displayPopover;
158
164
  /** @internal */
159
165
  private internalMonth;
@@ -189,6 +195,7 @@ declare class DatePicker extends DatePicker_base implements AssociatedFormContro
189
195
  private handleSelectTriggerClick;
190
196
  private handleSelectKeydown;
191
197
  private focusCalendarGrid;
198
+ private closePopover;
192
199
  private handleDateSelected;
193
200
  private getSpinbuttonInput;
194
201
  private getFieldValue;
@@ -135,6 +135,10 @@ class DatePicker extends FormInternalsMixin(DataAriaLabelMixin(FormfieldWrapper)
135
135
  * Localized aria-label for the next month button.
136
136
  */
137
137
  this.localeNextMonthLabel = '';
138
+ /**
139
+ * Localized announcement made after the range start date is selected.
140
+ */
141
+ this.localeRangeStartSelectedLabel = '';
138
142
  /** @internal */
139
143
  this.displayPopover = false;
140
144
  /** @internal */
@@ -333,15 +337,21 @@ class DatePicker extends FormInternalsMixin(DataAriaLabelMixin(FormfieldWrapper)
333
337
  handleCalendarButtonClick() {
334
338
  if (this.disabled || this.readonly)
335
339
  return;
336
- if (!this.displayPopover) {
337
- this.flushPendingInput();
340
+ if (this.displayPopover) {
341
+ this.closePopover();
342
+ return;
338
343
  }
339
- this.displayPopover = !this.displayPopover;
344
+ this.flushPendingInput();
345
+ this.displayPopover = true;
340
346
  }
341
347
  handleSelectTriggerClick() {
342
348
  if (this.disabled || this.readonly)
343
349
  return;
344
- this.displayPopover = !this.displayPopover;
350
+ if (this.displayPopover) {
351
+ this.closePopover();
352
+ return;
353
+ }
354
+ this.displayPopover = true;
345
355
  }
346
356
  handleSelectKeydown(event) {
347
357
  if (this.disabled || this.readonly)
@@ -363,6 +373,15 @@ class DatePicker extends FormInternalsMixin(DataAriaLabelMixin(FormfieldWrapper)
363
373
  }
364
374
  }, 0);
365
375
  }
376
+ closePopover(focusTrigger = false) {
377
+ var _a;
378
+ if (this.effectiveSelectionMode === SELECTION_MODE.RANGE) {
379
+ (_a = this.calendar) === null || _a === void 0 ? void 0 : _a.cancelRangeSelection(this.value, this.endValue);
380
+ }
381
+ this.displayPopover = false;
382
+ if (focusTrigger)
383
+ this.focusBackToTrigger();
384
+ }
366
385
  handleDateSelected(event) {
367
386
  const { detail } = event;
368
387
  const mode = this.effectiveSelectionMode;
@@ -385,8 +404,7 @@ class DatePicker extends FormInternalsMixin(DataAriaLabelMixin(FormfieldWrapper)
385
404
  this.notifyValueChange();
386
405
  const rangeComplete = mode === SELECTION_MODE.RANGE && this.value && this.endValue;
387
406
  if (mode !== SELECTION_MODE.RANGE || rangeComplete) {
388
- this.displayPopover = false;
389
- this.focusBackToTrigger();
407
+ this.closePopover(true);
390
408
  }
391
409
  }
392
410
  // -- Spinbutton handling (input variant) --
@@ -726,13 +744,11 @@ class DatePicker extends FormInternalsMixin(DataAriaLabelMixin(FormfieldWrapper)
726
744
  @shown="${() => this.focusCalendarGrid()}"
727
745
  @closebyescape="${(event) => {
728
746
  if (event.target === event.currentTarget) {
729
- this.displayPopover = false;
730
- this.focusBackToTrigger();
747
+ this.closePopover(true);
731
748
  }
732
749
  }}"
733
750
  @closebyoutsideclick="${() => {
734
- this.displayPopover = false;
735
- this.focusBackToTrigger();
751
+ this.closePopover(true);
736
752
  }}"
737
753
  exportparts="popover-content"
738
754
  >
@@ -746,6 +762,7 @@ class DatePicker extends FormInternalsMixin(DataAriaLabelMixin(FormfieldWrapper)
746
762
  locale-today-label="${this.localeTodayLabel}"
747
763
  locale-prev-month-label="${this.localePrevMonthLabel}"
748
764
  locale-next-month-label="${this.localeNextMonthLabel}"
765
+ locale-range-start-selected-label="${this.localeRangeStartSelectedLabel}"
749
766
  @date-selected="${this.handleDateSelected}"
750
767
  ></mdc-calendar>
751
768
  </mdc-popover>
@@ -851,6 +868,10 @@ __decorate([
851
868
  property({ type: String, attribute: 'locale-next-month-label' }),
852
869
  __metadata("design:type", Object)
853
870
  ], DatePicker.prototype, "localeNextMonthLabel", void 0);
871
+ __decorate([
872
+ property({ type: String, attribute: 'locale-range-start-selected-label' }),
873
+ __metadata("design:type", Object)
874
+ ], DatePicker.prototype, "localeRangeStartSelectedLabel", void 0);
854
875
  __decorate([
855
876
  query('#month-spinbutton'),
856
877
  __metadata("design:type", HTMLInputElement)
@@ -863,6 +884,10 @@ __decorate([
863
884
  query('#year-spinbutton'),
864
885
  __metadata("design:type", HTMLInputElement)
865
886
  ], DatePicker.prototype, "yearInput", void 0);
887
+ __decorate([
888
+ query('mdc-calendar'),
889
+ __metadata("design:type", Object)
890
+ ], DatePicker.prototype, "calendar", void 0);
866
891
  __decorate([
867
892
  state(),
868
893
  __metadata("design:type", Object)