@vaadin/date-picker 24.2.0-dev.f254716fe → 24.3.0-alpha1

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.
@@ -0,0 +1,303 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2016 - 2023 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+ import { FocusMixin } from '@vaadin/a11y-base/src/focus-mixin.js';
7
+ import { addListener } from '@vaadin/component-base/src/gestures.js';
8
+ import { dateAllowed, dateEquals, getISOWeekNumber } from './vaadin-date-picker-helper.js';
9
+
10
+ /**
11
+ * @polymerMixin
12
+ * @mixes FocusMixin
13
+ */
14
+ export const MonthCalendarMixin = (superClass) =>
15
+ class MonthCalendarMixinClass extends FocusMixin(superClass) {
16
+ static get properties() {
17
+ return {
18
+ /**
19
+ * A `Date` object defining the month to be displayed. Only year and
20
+ * month properties are actually used.
21
+ */
22
+ month: {
23
+ type: Object,
24
+ value: new Date(),
25
+ sync: true,
26
+ },
27
+
28
+ /**
29
+ * A `Date` object for the currently selected date.
30
+ */
31
+ selectedDate: {
32
+ type: Object,
33
+ notify: true,
34
+ sync: true,
35
+ },
36
+
37
+ /**
38
+ * A `Date` object for the currently focused date.
39
+ */
40
+ focusedDate: {
41
+ type: Object,
42
+ },
43
+
44
+ /**
45
+ * Set true to display ISO-8601 week numbers in the calendar. Notice that
46
+ * displaying week numbers is only supported when `i18n.firstDayOfWeek`
47
+ * is 1 (Monday).
48
+ */
49
+ showWeekNumbers: {
50
+ type: Boolean,
51
+ value: false,
52
+ },
53
+
54
+ i18n: {
55
+ type: Object,
56
+ },
57
+
58
+ /**
59
+ * Flag stating whether taps on the component should be ignored.
60
+ */
61
+ ignoreTaps: {
62
+ type: Boolean,
63
+ },
64
+
65
+ /**
66
+ * The earliest date that can be selected. All earlier dates will be disabled.
67
+ */
68
+ minDate: {
69
+ type: Date,
70
+ value: null,
71
+ sync: true,
72
+ },
73
+
74
+ /**
75
+ * The latest date that can be selected. All later dates will be disabled.
76
+ */
77
+ maxDate: {
78
+ type: Date,
79
+ value: null,
80
+ sync: true,
81
+ },
82
+
83
+ disabled: {
84
+ type: Boolean,
85
+ reflectToAttribute: true,
86
+ },
87
+
88
+ /** @protected */
89
+ _days: {
90
+ type: Array,
91
+ },
92
+
93
+ /** @protected */
94
+ _weeks: {
95
+ type: Array,
96
+ },
97
+
98
+ /** @private */
99
+ _notTapping: {
100
+ type: Boolean,
101
+ },
102
+ };
103
+ }
104
+
105
+ static get observers() {
106
+ return ['__focusedDateChanged(focusedDate, _days)'];
107
+ }
108
+
109
+ get focusableDateElement() {
110
+ return [...this.shadowRoot.querySelectorAll('[part~=date]')].find((datePart) => {
111
+ return dateEquals(datePart.date, this.focusedDate);
112
+ });
113
+ }
114
+
115
+ /** @protected */
116
+ ready() {
117
+ super.ready();
118
+ addListener(this.$.monthGrid, 'tap', this._handleTap.bind(this));
119
+ }
120
+
121
+ /**
122
+ * Returns true if all the dates in the month are out of the allowed range
123
+ * @protected
124
+ */
125
+ _isDisabled(month, minDate, maxDate) {
126
+ // First day of the month
127
+ const firstDate = new Date(0, 0);
128
+ firstDate.setFullYear(month.getFullYear());
129
+ firstDate.setMonth(month.getMonth());
130
+ firstDate.setDate(1);
131
+
132
+ // Last day of the month
133
+ const lastDate = new Date(0, 0);
134
+ lastDate.setFullYear(month.getFullYear());
135
+ lastDate.setMonth(month.getMonth() + 1);
136
+ lastDate.setDate(0);
137
+
138
+ if (
139
+ minDate &&
140
+ maxDate &&
141
+ minDate.getMonth() === maxDate.getMonth() &&
142
+ minDate.getMonth() === month.getMonth() &&
143
+ maxDate.getDate() - minDate.getDate() >= 0
144
+ ) {
145
+ return false;
146
+ }
147
+
148
+ return !dateAllowed(firstDate, minDate, maxDate) && !dateAllowed(lastDate, minDate, maxDate);
149
+ }
150
+
151
+ /** @protected */
152
+ _getTitle(month, i18n) {
153
+ if (month === undefined || i18n === undefined) {
154
+ return;
155
+ }
156
+ return i18n.formatTitle(i18n.monthNames[month.getMonth()], month.getFullYear());
157
+ }
158
+
159
+ /** @protected */
160
+ _onMonthGridTouchStart() {
161
+ this._notTapping = false;
162
+ setTimeout(() => {
163
+ this._notTapping = true;
164
+ }, 300);
165
+ }
166
+
167
+ /** @private */
168
+ _dateAdd(date, delta) {
169
+ date.setDate(date.getDate() + delta);
170
+ }
171
+
172
+ /** @private */
173
+ _applyFirstDayOfWeek(weekDayNames, firstDayOfWeek) {
174
+ if (weekDayNames === undefined || firstDayOfWeek === undefined) {
175
+ return;
176
+ }
177
+
178
+ return weekDayNames.slice(firstDayOfWeek).concat(weekDayNames.slice(0, firstDayOfWeek));
179
+ }
180
+
181
+ /** @protected */
182
+ _getWeekDayNames(i18n, showWeekNumbers) {
183
+ if (i18n === undefined || showWeekNumbers === undefined) {
184
+ return [];
185
+ }
186
+ const { weekdays, weekdaysShort, firstDayOfWeek } = i18n;
187
+
188
+ const weekDayNamesShort = this._applyFirstDayOfWeek(weekdaysShort, firstDayOfWeek);
189
+ const weekDayNames = this._applyFirstDayOfWeek(weekdays, firstDayOfWeek);
190
+
191
+ return weekDayNames.map((day, index) => {
192
+ return {
193
+ weekDay: day,
194
+ weekDayShort: weekDayNamesShort[index],
195
+ };
196
+ });
197
+ }
198
+
199
+ /** @private */
200
+ __focusedDateChanged(focusedDate, days) {
201
+ if (Array.isArray(days) && days.some((date) => dateEquals(date, focusedDate))) {
202
+ this.removeAttribute('aria-hidden');
203
+ } else {
204
+ this.setAttribute('aria-hidden', 'true');
205
+ }
206
+ }
207
+
208
+ /** @protected */
209
+ _getDate(date) {
210
+ return date ? date.getDate() : '';
211
+ }
212
+
213
+ /** @protected */
214
+ _showWeekSeparator(showWeekNumbers, i18n) {
215
+ // Currently only supported for locales that start the week on Monday.
216
+ return showWeekNumbers && i18n && i18n.firstDayOfWeek === 1;
217
+ }
218
+
219
+ /** @protected */
220
+ _isToday(date) {
221
+ return dateEquals(new Date(), date);
222
+ }
223
+
224
+ /** @protected */
225
+ _getDays(month, i18n) {
226
+ if (month === undefined || i18n === undefined) {
227
+ return [];
228
+ }
229
+ // First day of the month (at midnight).
230
+ const date = new Date(0, 0);
231
+ date.setFullYear(month.getFullYear());
232
+ date.setMonth(month.getMonth());
233
+ date.setDate(1);
234
+
235
+ // Rewind to first day of the week.
236
+ while (date.getDay() !== i18n.firstDayOfWeek) {
237
+ this._dateAdd(date, -1);
238
+ }
239
+
240
+ const days = [];
241
+ const startMonth = date.getMonth();
242
+ const targetMonth = month.getMonth();
243
+ while (date.getMonth() === targetMonth || date.getMonth() === startMonth) {
244
+ days.push(date.getMonth() === targetMonth ? new Date(date.getTime()) : null);
245
+
246
+ // Advance to next day.
247
+ this._dateAdd(date, 1);
248
+ }
249
+ return days;
250
+ }
251
+
252
+ /** @protected */
253
+ _getWeeks(days) {
254
+ return days.reduce((acc, day, i) => {
255
+ if (i % 7 === 0) {
256
+ acc.push([]);
257
+ }
258
+ acc[acc.length - 1].push(day);
259
+ return acc;
260
+ }, []);
261
+ }
262
+
263
+ /** @protected */
264
+ _handleTap(e) {
265
+ if (!this.ignoreTaps && !this._notTapping && e.target.date && !e.target.hasAttribute('disabled')) {
266
+ this.selectedDate = e.target.date;
267
+ this.dispatchEvent(
268
+ new CustomEvent('date-tap', { detail: { date: e.target.date }, bubbles: true, composed: true }),
269
+ );
270
+ }
271
+ }
272
+
273
+ /** @protected */
274
+ _preventDefault(e) {
275
+ e.preventDefault();
276
+ }
277
+
278
+ /** @protected */
279
+ __getWeekNumber(days) {
280
+ const date = days.reduce((acc, d) => {
281
+ return !acc && d ? d : acc;
282
+ });
283
+
284
+ return getISOWeekNumber(date);
285
+ }
286
+
287
+ /** @protected */
288
+ __getDayAriaLabel(date) {
289
+ if (!date) {
290
+ return '';
291
+ }
292
+
293
+ let ariaLabel = `${this._getDate(date)} ${this.i18n.monthNames[date.getMonth()]} ${date.getFullYear()}, ${
294
+ this.i18n.weekdays[date.getDay()]
295
+ }`;
296
+
297
+ if (this._isToday(date)) {
298
+ ariaLabel += `, ${this.i18n.today}`;
299
+ }
300
+
301
+ return ariaLabel;
302
+ }
303
+ };
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2016 - 2023 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+ import { css } from 'lit';
7
+
8
+ export const monthCalendarStyles = css`
9
+ :host {
10
+ display: block;
11
+ }
12
+
13
+ #monthGrid {
14
+ width: 100%;
15
+ border-collapse: collapse;
16
+ }
17
+
18
+ #days-container tr,
19
+ #weekdays-container tr {
20
+ display: flex;
21
+ }
22
+
23
+ [part~='date'] {
24
+ outline: none;
25
+ }
26
+
27
+ [part~='disabled'] {
28
+ pointer-events: none;
29
+ }
30
+
31
+ [part='week-number'][hidden],
32
+ [part='weekday'][hidden] {
33
+ display: none;
34
+ }
35
+
36
+ [part='weekday'],
37
+ [part~='date'] {
38
+ width: calc(100% / 7);
39
+ padding: 0;
40
+ font-weight: normal;
41
+ }
42
+
43
+ [part='weekday']:empty,
44
+ [part='week-number'] {
45
+ width: 12.5%;
46
+ flex-shrink: 0;
47
+ padding: 0;
48
+ }
49
+
50
+ :host([week-numbers]) [part='weekday']:not(:empty),
51
+ :host([week-numbers]) [part~='date'] {
52
+ width: 12.5%;
53
+ }
54
+
55
+ @media (forced-colors: active) {
56
+ [part~='date'][part~='focused'] {
57
+ outline: 1px solid;
58
+ }
59
+
60
+ [part~='date'][part~='selected'] {
61
+ outline: 3px solid;
62
+ }
63
+ }
64
+ `;