@ditojs/ui 3.2.0 → 3.3.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.
@@ -10,62 +10,70 @@
10
10
  v-if="currentMode === 'day'"
11
11
  )
12
12
  .dito-calendar-header
13
- a.dito-calendar-step-prev.dito-calendar-step-year(
14
- role="button"
15
- tabindex="0"
13
+ button.dito-calendar-step-prev.dito-calendar-step-year(
14
+ type="button"
16
15
  aria-label="Previous year"
17
16
  @click="stepYear(-1)"
18
17
  )
19
- a.dito-calendar-step-prev.dito-calendar-step-month(
20
- role="button"
21
- tabindex="0"
18
+ button.dito-calendar-step-prev.dito-calendar-step-month(
19
+ type="button"
22
20
  aria-label="Previous month"
23
21
  @click="stepMonth(-1)"
24
22
  )
25
23
  span
26
- a.dito-calendar-select-year(
27
- role="button"
28
- tabindex="0"
24
+ button.dito-calendar-select-year(
25
+ type="button"
29
26
  aria-label="Switch to year selection"
30
27
  @click="setMode('year')"
31
28
  ) {{ dateToString(currentValue, { year: true }) }}
32
- a.dito-calendar-select-month(
33
- role="button"
34
- tabindex="0"
29
+ button.dito-calendar-select-month(
30
+ type="button"
35
31
  aria-label="Switch to month selection"
36
32
  @click="setMode('month')"
37
33
  ) {{ dateToString(currentValue, { month: true }) }}
38
- a.dito-calendar-step-next.dito-calendar-step-month(
39
- role="button"
40
- tabindex="0"
34
+ button.dito-calendar-step-next.dito-calendar-step-month(
35
+ type="button"
41
36
  aria-label="Next month"
42
37
  @click="stepMonth(1)"
43
38
  )
44
- a.dito-calendar-step-next.dito-calendar-step-year(
45
- role="button"
46
- tabindex="0"
39
+ button.dito-calendar-step-next.dito-calendar-step-year(
40
+ type="button"
47
41
  aria-label="Next year"
48
42
  @click="stepYear(1)"
49
43
  )
50
44
  .dito-calendar-body
51
- .dito-calendar-weekdays
52
- span(
53
- v-for="weekday in weekdayNames"
54
- ) {{ weekday.short }}
55
- .dito-calendar-dates(role="grid")
56
- span(
57
- v-for="date in dateRange"
58
- :class="date.state && `dito-calendar-item-${date.state}`"
59
- role="gridcell"
60
- :aria-label="dateToString(date.date)"
61
- :aria-selected="date.state === 'active'"
62
- :aria-disabled="date.state === 'disabled'"
63
- @click="date.state !== 'disabled' && selectDate(date.date)"
64
- ) {{ date.text }}
45
+ .dito-calendar-dates(
46
+ role="grid"
47
+ :aria-label="monthLabel"
48
+ @keydown="onDatesKeyDown"
49
+ )
50
+ .dito-calendar-weekdays(role="row")
51
+ span(
52
+ v-for="weekday in weekdayNames"
53
+ role="columnheader"
54
+ :aria-label="weekday.long"
55
+ ) {{ weekday.short }}
56
+ .dito-calendar__week(
57
+ v-for="week in weeks"
58
+ :key="+week[0].date"
59
+ role="row"
60
+ )
61
+ button.dito-calendar__item(
62
+ v-for="item in week"
63
+ :key="+item.date"
64
+ :ref="item.isCursor ? 'cursorItem' : undefined"
65
+ type="button"
66
+ role="gridcell"
67
+ :tabindex="item.isCursor ? 0 : -1"
68
+ :class="item.state && `dito-calendar-item-${item.state}`"
69
+ :aria-label="dateToString(item.date)"
70
+ :aria-selected="item.state === 'active' ? 'true' : 'false'"
71
+ :aria-disabled="item.isDisabled ? 'true' : null"
72
+ @click="selectDay(item.date)"
73
+ ) {{ item.date.getDate() }}
65
74
  .dito-calendar-footer
66
- a.dito-calendar-select-now(
67
- role="button"
68
- tabindex="0"
75
+ button.dito-calendar-select-now(
76
+ type="button"
69
77
  :aria-label="`Select today (${dateToString(new Date())})`"
70
78
  :title="dateToString(new Date())"
71
79
  @click="selectDate(new Date())"
@@ -74,72 +82,81 @@
74
82
  v-else-if="currentMode === 'month'"
75
83
  )
76
84
  .dito-calendar-header
77
- a.dito-calendar-step-prev(
78
- role="button"
79
- tabindex="0"
85
+ button.dito-calendar-step-prev(
86
+ type="button"
80
87
  aria-label="Previous year"
81
88
  @click="stepYear(-1)"
82
89
  )
83
90
  span
84
- a.dito-calendar-select-year(
85
- role="button"
86
- tabindex="0"
91
+ button.dito-calendar-select-year(
92
+ type="button"
87
93
  aria-label="Switch to year selection"
88
94
  @click="setMode('year')"
89
95
  ) {{ dateToString(currentValue, { year: true }) }}
90
- a.dito-calendar-step-next(
91
- role="button"
92
- tabindex="0"
96
+ button.dito-calendar-step-next(
97
+ type="button"
93
98
  aria-label="Next year"
94
99
  @click="stepYear(1)"
95
100
  )
96
101
  .dito-calendar-body
97
- .dito-calendar-months(
98
- role="listbox"
99
- aria-label="Month"
100
- )
101
- span(
102
+ .dito-calendar-months
103
+ button.dito-calendar__item(
102
104
  v-for="(month, index) in monthNames"
105
+ :ref="isCurrentMonth(index) ? 'cursorItem' : undefined"
106
+ type="button"
103
107
  :class="getMonthClass(index)"
104
- role="option"
105
108
  :aria-label="month.long"
106
- :aria-selected="getMonthClass(index)['dito-calendar-item-active']"
109
+ :aria-pressed="isActiveMonth(index) ? 'true' : 'false'"
107
110
  @click="selectMonth(index)"
108
111
  ) {{ month.short }}
109
112
  template(
110
113
  v-else-if="currentMode === 'year'"
111
114
  )
112
115
  .dito-calendar-header
113
- a.dito-calendar-step-prev(
114
- role="button"
115
- tabindex="0"
116
+ button.dito-calendar-step-prev(
117
+ type="button"
116
118
  aria-label="Previous decade"
117
119
  @click="stepDecade(-1)"
118
120
  )
119
121
  span {{ decadeToString(currentValue) }}
120
- a.dito-calendar-step-next(
121
- role="button"
122
- tabindex="0"
122
+ button.dito-calendar-step-next(
123
+ type="button"
123
124
  aria-label="Next decade"
124
125
  @click="stepDecade(1)"
125
126
  )
126
127
  .dito-calendar-body
127
- .dito-calendar-years(
128
- role="listbox"
129
- aria-label="Year"
130
- )
131
- span(
128
+ .dito-calendar-years
129
+ button.dito-calendar__item(
132
130
  v-for="year in yearRange"
131
+ :ref="isCurrentYear(year) ? 'cursorItem' : undefined"
132
+ type="button"
133
133
  :class="getYearClass(year)"
134
- role="option"
135
134
  :aria-label="String(year)"
136
- :aria-selected="getYearClass(year)['dito-calendar-item-active']"
135
+ :aria-pressed="isActiveYear(year) ? 'true' : 'false'"
137
136
  @click="selectYear(year)"
138
137
  ) {{ year }}
139
138
  </template>
140
139
 
141
140
  <script>
142
- import { describeDate, alterDate } from '../utils/date.js'
141
+ import { asArray } from '@ditojs/utils'
142
+ import { alterDate, stepDate } from '../utils/date.js'
143
+ import { getKeyNavigation } from '../utils/event.js'
144
+
145
+ // Returns the start of the current day, as the date that the calendar shows
146
+ // without a value, so that selecting a day selects it without a time.
147
+ function getStartOfToday() {
148
+ return alterDate(new Date(), {
149
+ hour: 0,
150
+ minute: 0,
151
+ second: 0,
152
+ millisecond: 0
153
+ })
154
+ }
155
+
156
+ // The most steps that `navigate()` takes past disabled dates, in any date part
157
+ // (day, month or year), before giving up, as a guard against `disabledDate`
158
+ // disabling every date.
159
+ const maxSkippedDateCount = 366
143
160
 
144
161
  export default {
145
162
  emits: ['update:modelValue', 'select'],
@@ -156,104 +173,182 @@ export default {
156
173
  return {
157
174
  weekdayNames,
158
175
  monthNames,
159
- dateRange: [],
160
- yearRange: [],
161
- currentValue: (
162
- this.modelValue ||
163
- // If no value is provided, use current date but clear time fields:
164
- alterDate(new Date(), { hour: 0, minute: 0, second: 0, millisecond: 0 })
165
- ),
176
+ currentValue: this.modelValue || getStartOfToday(),
166
177
  currentMode: this.mode
167
178
  }
168
179
  },
169
180
 
170
- watch: {
171
- modelValue(to, from) {
172
- if (+to !== +from) {
173
- this.currentValue = to || new Date()
174
- this.updateDateRange()
181
+ computed: {
182
+ // The 42 days of the six weeks shown for the month of `currentValue`.
183
+ dateRange() {
184
+ const { currentValue } = this
185
+ const year = currentValue.getFullYear()
186
+ const month = currentValue.getMonth()
187
+ // The weeks start on Sunday:
188
+ const firstDate = new Date(
189
+ year,
190
+ month,
191
+ 1 - new Date(year, month).getDay()
192
+ )
193
+ const isSameDay = (date, other) => (
194
+ !!other &&
195
+ date.getDate() === other.getDate() &&
196
+ date.getMonth() === other.getMonth() &&
197
+ date.getFullYear() === other.getFullYear()
198
+ )
199
+ const now = new Date()
200
+ const items = []
201
+ for (let i = 0; i < 42; i++) {
202
+ const date = new Date(
203
+ firstDate.getFullYear(),
204
+ firstDate.getMonth(),
205
+ firstDate.getDate() + i
206
+ )
207
+ const isDisabled = this.disabledDate(date)
208
+ const state =
209
+ date.getMonth() !== month
210
+ ? isDisabled
211
+ ? 'disabled'
212
+ : 'gray'
213
+ : isSameDay(date, this.modelValue)
214
+ ? 'active'
215
+ : isSameDay(date, now)
216
+ ? 'today'
217
+ : isDisabled
218
+ ? 'disabled'
219
+ : isSameDay(date, currentValue)
220
+ ? 'current'
221
+ : null
222
+ items.push({
223
+ date,
224
+ state,
225
+ isDisabled,
226
+ isCursor: isSameDay(date, currentValue)
227
+ })
175
228
  }
229
+ return items
176
230
  },
177
231
 
178
- currentValue(to, from) {
179
- if (+to !== +from) {
180
- this.updateDateRange()
232
+ monthLabel() {
233
+ return this.dateToString(this.currentValue, { year: true, month: true })
234
+ },
235
+
236
+ weeks() {
237
+ const weeks = []
238
+ for (let i = 0; i < this.dateRange.length; i += 7) {
239
+ weeks.push(this.dateRange.slice(i, i + 7))
181
240
  }
241
+ return weeks
182
242
  },
183
243
 
184
- disabledDate: 'updateDateRange',
244
+ yearRange() {
245
+ const startYear = this.getFirstYearOfDecade(
246
+ this.currentValue.getFullYear()
247
+ )
248
+ return Array.from({ length: 10 }, (_, index) => startYear + index)
249
+ }
250
+ },
251
+
252
+ watch: {
253
+ modelValue(to, from) {
254
+ if (+to !== +from) {
255
+ this.currentValue = to || getStartOfToday()
256
+ }
257
+ },
185
258
 
186
259
  mode(mode) {
187
260
  this.currentMode = mode
188
261
  }
189
262
  },
190
263
 
191
- mounted() {
192
- this.updateDateRange()
193
- },
194
-
195
264
  methods: {
265
+ isActiveMonth(month) {
266
+ return (
267
+ !!this.modelValue &&
268
+ month === this.modelValue.getMonth() &&
269
+ this.currentValue.getFullYear() === this.modelValue.getFullYear()
270
+ )
271
+ },
272
+
273
+ isCurrentMonth(month) {
274
+ return month === this.currentValue.getMonth()
275
+ },
276
+
277
+ isActiveYear(year) {
278
+ return !!this.modelValue && year === this.modelValue.getFullYear()
279
+ },
280
+
281
+ isCurrentYear(year) {
282
+ return year === this.currentValue.getFullYear()
283
+ },
284
+
196
285
  getMonthClass(month) {
197
286
  return {
198
- 'dito-calendar-item-active': (
199
- this.modelValue &&
200
- month === this.modelValue.getMonth() &&
201
- this.currentValue.getFullYear() === this.modelValue.getFullYear()
202
- ),
203
- 'dito-calendar-item-current': month === this.currentValue.getMonth()
287
+ 'dito-calendar-item-active': this.isActiveMonth(month),
288
+ 'dito-calendar-item-current': this.isCurrentMonth(month)
204
289
  }
205
290
  },
206
291
 
207
292
  getYearClass(year) {
208
293
  return {
209
- 'dito-calendar-item-active': (
210
- this.modelValue && year === this.modelValue.getFullYear()
211
- ),
212
- 'dito-calendar-item-current': year === this.currentValue.getFullYear()
294
+ 'dito-calendar-item-active': this.isActiveYear(year),
295
+ 'dito-calendar-item-current': this.isCurrentYear(year)
213
296
  }
214
297
  },
215
298
 
299
+ // Moves the cursor of the calendar to `date`, and also selects it if
300
+ // `update` is true.
216
301
  setDate(overrides, update = false) {
217
- this.currentValue = alterDate(this.currentValue, overrides)
302
+ const date = alterDate(this.currentValue, overrides)
303
+ this.currentValue = date
218
304
  if (update) {
219
- this.$emit('update:modelValue', this.currentValue)
305
+ this.commitDate(date)
306
+ }
307
+ },
308
+
309
+ // All changes of the value go through here, so disabled dates are never
310
+ // selected. Returns whether the date was selected.
311
+ commitDate(date) {
312
+ if (this.disabledDate(date)) {
313
+ return false
220
314
  }
315
+ this.currentValue = date
316
+ this.$emit('update:modelValue', date)
317
+ return true
221
318
  },
222
319
 
223
320
  stepDecade(step) {
224
- this.setDate({
225
- year: this.currentValue.getFullYear() + step * 10
226
- })
321
+ this.currentValue = stepDate(this.currentValue, 'year', step * 10)
227
322
  },
228
323
 
229
324
  setMode(mode) {
230
325
  this.currentMode = mode
326
+ this.focusCursorItemIfFocused()
231
327
  },
232
328
 
233
329
  stepMonth(step) {
234
- const { currentValue } = this
235
- const { year, month } = this.getYearMonth(
236
- currentValue.getFullYear(),
237
- currentValue.getMonth() + step
238
- )
239
- this.setDate({
240
- month,
241
- day: Math.min(
242
- this.getDaysInMonth(year, month),
243
- this.currentValue.getDate()
244
- )
245
- })
330
+ this.currentValue = stepDate(this.currentValue, 'month', step)
246
331
  },
247
332
 
248
333
  stepYear(step) {
249
- this.setDate({
250
- year: this.currentValue.getFullYear() + step
251
- })
334
+ this.currentValue = stepDate(this.currentValue, 'year', step)
252
335
  },
253
336
 
254
337
  selectDate(date) {
255
- this.setDate(describeDate(date), true)
256
- this.$emit('select')
338
+ if (this.commitDate(date)) {
339
+ this.$emit('select')
340
+ }
341
+ },
342
+
343
+ // Selects the day of `date`, keeping the time of the current value.
344
+ selectDay(date) {
345
+ this.selectDate(
346
+ alterDate(this.currentValue, {
347
+ year: date.getFullYear(),
348
+ month: date.getMonth(),
349
+ day: date.getDate()
350
+ })
351
+ )
257
352
  },
258
353
 
259
354
  selectMonth(month) {
@@ -268,17 +363,6 @@ export default {
268
363
  this.setDate({ year }, true)
269
364
  },
270
365
 
271
- getYearMonth(year, month) {
272
- if (month > 11) {
273
- year++
274
- month = 0
275
- } else if (month < 0) {
276
- year--
277
- month = 11
278
- }
279
- return { year, month }
280
- },
281
-
282
366
  dateToString(
283
367
  date,
284
368
  { year, month, day } = { year: true, month: true, day: true }
@@ -295,34 +379,37 @@ export default {
295
379
  return `${year} – ${year + 9}`
296
380
  },
297
381
 
298
- getDaysInMonth(year, month) {
299
- return new Date(year, month + 1, 0).getDate()
300
- },
301
-
302
382
  getFirstYearOfDecade(year) {
303
383
  const yearStr = year.toString()
304
384
  return +`${yearStr.slice(0, -1)}0`
305
385
  },
306
386
 
387
+ // Steps the date part `mode` of the cursor by `step`, or confirms the
388
+ // cursor with `enter`. With `update`, the stepped date is also selected,
389
+ // skipping disabled dates. Returns whether anything happened.
307
390
  navigate({ step, enter, mode = this.currentMode, update = false }) {
308
- const { currentValue } = this
309
391
  if (step) {
310
- switch (mode) {
311
- case 'day':
312
- this.setDate({ day: currentValue.getDate() + step }, update)
313
- break
314
- case 'month':
315
- this.setDate({ month: currentValue.getMonth() + step }, update)
316
- break
317
- case 'year':
318
- this.setDate({ year: currentValue.getFullYear() + step }, update)
319
- break
392
+ if (!['day', 'month', 'year'].includes(mode)) {
393
+ return false
394
+ }
395
+ let date = this.currentValue
396
+ let skippedCount = 0
397
+ do {
398
+ date = stepDate(date, mode, step)
399
+ } while (
400
+ update &&
401
+ this.disabledDate(date) &&
402
+ ++skippedCount < maxSkippedDateCount
403
+ )
404
+ if (update) {
405
+ return this.commitDate(date)
320
406
  }
407
+ this.currentValue = date
321
408
  return true
322
409
  } else if (enter) {
323
410
  switch (this.currentMode) {
324
411
  case 'day':
325
- this.selectDate(currentValue)
412
+ this.selectDate(this.currentValue)
326
413
  break
327
414
  case 'month':
328
415
  this.setMode('day')
@@ -333,80 +420,54 @@ export default {
333
420
  }
334
421
  return true
335
422
  }
423
+ return false
336
424
  },
337
425
 
338
- updateDateRange() {
339
- this.dateRange = []
340
- this.yearRange = []
341
-
342
- const { currentValue } = this
343
- const year = currentValue.getFullYear()
344
- const month = currentValue.getMonth()
345
- const startYear = this.getFirstYearOfDecade(year)
346
- for (let i = 0; i < 10; i++) {
347
- this.yearRange.push(startYear + i)
348
- }
349
- const currMonthFirstDay = new Date(year, month, 1)
350
- let firstDayWeek = currMonthFirstDay.getDay() + 1
351
- if (firstDayWeek === 0) {
352
- firstDayWeek = 7
353
- }
354
- const numDays = this.getDaysInMonth(year, month)
355
- if (firstDayWeek > 1) {
356
- const prevMonth = this.getYearMonth(year, month - 1)
357
- const prevMonthNumDays = this.getDaysInMonth(
358
- prevMonth.year,
359
- prevMonth.month
360
- )
361
- for (let i = 1; i < firstDayWeek; i++) {
362
- const day = prevMonthNumDays - firstDayWeek + i + 1
363
- const date = new Date(prevMonth.year, prevMonth.month, day)
364
- this.dateRange.push({
365
- text: day,
366
- date,
367
- state: this.disabledDate(date) ? 'disabled' : 'gray'
368
- })
369
- }
370
- }
426
+ // Handles a keydown event of a picker's input, with the caret inside the
427
+ // date part `part`, and returns whether it was handled.
428
+ handleKey(event, part) {
429
+ const { ver: step, enter } = getKeyNavigation(event)
430
+ return step
431
+ ? !!part && this.navigate({ step, mode: part.name, update: true })
432
+ : this.navigate({ enter })
433
+ },
371
434
 
372
- const now = new Date()
373
- for (let i = 1; i <= numDays; i++) {
374
- const date = new Date(year, month, i)
375
- const isDay = date => (
376
- date &&
377
- date.getDate() === i &&
378
- date.getFullYear() === year &&
379
- date.getMonth() === month
380
- )
381
- const state = isDay(this.modelValue)
382
- ? 'active'
383
- : isDay(now)
384
- ? 'today'
385
- : this.disabledDate(date)
386
- ? 'disabled'
387
- : isDay(currentValue)
388
- ? 'current'
389
- : null
390
- this.dateRange.push({
391
- text: i,
392
- date,
393
- state
394
- })
435
+ focusCursorItemIfFocused() {
436
+ if (this.$el?.contains(document.activeElement)) {
437
+ // Wait for the items of the current mode to be rendered.
438
+ this.$nextTick(() => asArray(this.$refs.cursorItem)[0]?.focus())
395
439
  }
440
+ },
396
441
 
397
- const nextMonthNeed = 42 - this.dateRange.length
398
- if (nextMonthNeed > 0) {
399
- const nextMonth = this.getYearMonth(year, month + 1)
400
- for (let i = 1; i <= nextMonthNeed; i++) {
401
- const date = new Date(nextMonth.year, nextMonth.month, i)
402
- const state = this.disabledDate(date) ? 'disabled' : 'gray'
403
- this.dateRange.push({
404
- text: i,
405
- date,
406
- state
407
- })
442
+ onDatesKeyDown(event) {
443
+ const { key, shiftKey } = event
444
+ const { currentValue } = this
445
+ const day = currentValue.getDate()
446
+ const dayOffset = {
447
+ ArrowLeft: -1,
448
+ ArrowRight: 1,
449
+ ArrowUp: -7,
450
+ ArrowDown: 7
451
+ }[key]
452
+ if (dayOffset) {
453
+ this.setDate({ day: day + dayOffset })
454
+ } else if (key === 'PageUp' || key === 'PageDown') {
455
+ const step = key === 'PageUp' ? -1 : 1
456
+ if (shiftKey) {
457
+ this.stepYear(step)
458
+ } else {
459
+ this.stepMonth(step)
408
460
  }
461
+ } else if (key === 'Home') {
462
+ this.setDate({ day: 1 })
463
+ } else if (key === 'End') {
464
+ // Day 0 of the next month is the last day of the current one.
465
+ this.setDate({ month: currentValue.getMonth() + 1, day: 0 })
466
+ } else {
467
+ return
409
468
  }
469
+ event.preventDefault()
470
+ this.focusCursorItemIfFocused()
410
471
  }
411
472
  }
412
473
  }
@@ -448,6 +509,22 @@ function getLocaleNames(locale) {
448
509
  .dito-calendar {
449
510
  min-width: 240px;
450
511
  box-sizing: border-box;
512
+
513
+ button {
514
+ padding: 0;
515
+ border: 0;
516
+ font: inherit;
517
+ color: inherit;
518
+ background: none;
519
+ white-space: nowrap;
520
+ cursor: pointer;
521
+ @include user-select(none);
522
+
523
+ &:focus-visible {
524
+ outline: none;
525
+ box-shadow: $shadow-focus;
526
+ }
527
+ }
451
528
  }
452
529
 
453
530
  .dito-calendar-popup {
@@ -461,17 +538,6 @@ function getLocaleNames(locale) {
461
538
  .dito-calendar-body {
462
539
  padding: 0 0.5em;
463
540
 
464
- span {
465
- display: inline-block;
466
- width: calc(100% / 7);
467
- height: $input-height;
468
- line-height: calc($input-height - 2 * $border-width);
469
- box-sizing: border-box;
470
- border-radius: $border-radius;
471
- border: $border-width solid transparent;
472
- text-align: center;
473
- }
474
-
475
541
  .dito-calendar-item-today {
476
542
  border: $border-width solid $color-active;
477
543
  color: $color-active;
@@ -496,39 +562,36 @@ function getLocaleNames(locale) {
496
562
  }
497
563
  }
498
564
 
565
+ .dito-calendar-weekdays,
566
+ .dito-calendar__week {
567
+ display: flex;
568
+ }
569
+
570
+ .dito-calendar-weekdays span,
571
+ .dito-calendar__item {
572
+ display: inline-block;
573
+ width: calc(100% / 7);
574
+ height: $input-height;
575
+ line-height: calc($input-height - 2 * $border-width);
576
+ box-sizing: border-box;
577
+ border-radius: $border-radius;
578
+ border: $border-width solid transparent;
579
+ text-align: center;
580
+ }
581
+
499
582
  .dito-calendar-months,
500
583
  .dito-calendar-years {
501
- span {
584
+ .dito-calendar__item {
502
585
  width: calc(100% / 4);
503
586
  margin: 0.5em 0;
504
587
  }
505
588
  }
506
589
 
507
- .dito-calendar-years span {
590
+ .dito-calendar-years .dito-calendar__item {
508
591
  width: calc(100% / 5);
509
592
  }
510
593
 
511
- .dito-calendar a,
512
- .dito-calendar-dates span,
513
- .dito-calendar-months span,
514
- .dito-calendar-years span {
515
- @include user-select(none);
516
-
517
- white-space: nowrap;
518
- cursor: pointer;
519
- }
520
-
521
- .dito-calendar-header span {
522
- cursor: default;
523
-
524
- a {
525
- padding: 0 0.2em;
526
- }
527
- }
528
-
529
- .dito-calendar-dates span:hover,
530
- .dito-calendar-months span:hover,
531
- .dito-calendar-years span:hover,
594
+ .dito-calendar__item:hover,
532
595
  .dito-calendar:not(:hover) .dito-calendar-item-current {
533
596
  background: $color-highlight;
534
597
  }
@@ -558,9 +621,14 @@ function getLocaleNames(locale) {
558
621
  > span {
559
622
  margin: auto;
560
623
  padding: 0 0.5em;
624
+ cursor: default;
625
+
626
+ button {
627
+ padding: 0 0.2em;
628
+ }
561
629
  }
562
630
 
563
- a {
631
+ button {
564
632
  font-weight: bold;
565
633
 
566
634
  &:hover {
@@ -578,8 +646,10 @@ function getLocaleNames(locale) {
578
646
 
579
647
  &::after {
580
648
  position: absolute;
581
- left: 0;
582
- right: 0;
649
+ inset: 0;
650
+ display: flex;
651
+ align-items: center;
652
+ justify-content: center;
583
653
  font-size: 1.25em;
584
654
  }
585
655
  }