@api-client/ui 0.5.5 → 0.5.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.
Files changed (82) hide show
  1. package/build/src/elements/highlight/MarkdownStyles.d.ts.map +1 -1
  2. package/build/src/elements/highlight/MarkdownStyles.js +0 -13
  3. package/build/src/elements/highlight/MarkdownStyles.js.map +1 -1
  4. package/build/src/elements/http/BodyEditor.d.ts +0 -13
  5. package/build/src/elements/http/BodyEditor.d.ts.map +1 -1
  6. package/build/src/elements/http/BodyEditor.js +0 -13
  7. package/build/src/elements/http/BodyEditor.js.map +1 -1
  8. package/build/src/elements/http/BodyTextEditor.d.ts +0 -13
  9. package/build/src/elements/http/BodyTextEditor.d.ts.map +1 -1
  10. package/build/src/elements/http/BodyTextEditor.js +0 -13
  11. package/build/src/elements/http/BodyTextEditor.js.map +1 -1
  12. package/build/src/elements/http/BodyUrlEncodedEditor.d.ts +0 -13
  13. package/build/src/elements/http/BodyUrlEncodedEditor.d.ts.map +1 -1
  14. package/build/src/elements/http/BodyUrlEncodedEditor.js +0 -13
  15. package/build/src/elements/http/BodyUrlEncodedEditor.js.map +1 -1
  16. package/build/src/elements/http/UrlInput.d.ts +0 -13
  17. package/build/src/elements/http/UrlInput.d.ts.map +1 -1
  18. package/build/src/elements/http/UrlInput.js +0 -13
  19. package/build/src/elements/http/UrlInput.js.map +1 -1
  20. package/build/src/index.d.ts +2 -0
  21. package/build/src/index.d.ts.map +1 -1
  22. package/build/src/index.js +2 -0
  23. package/build/src/index.js.map +1 -1
  24. package/build/src/md/button/internals/button.styles.js +1 -1
  25. package/build/src/md/button/internals/button.styles.js.map +1 -1
  26. package/build/src/md/date/internals/DateTime.d.ts +0 -13
  27. package/build/src/md/date/internals/DateTime.d.ts.map +1 -1
  28. package/build/src/md/date/internals/DateTime.js +0 -13
  29. package/build/src/md/date/internals/DateTime.js.map +1 -1
  30. package/build/src/md/date-picker/index.d.ts +13 -0
  31. package/build/src/md/date-picker/index.d.ts.map +1 -0
  32. package/build/src/md/date-picker/index.js +13 -0
  33. package/build/src/md/date-picker/index.js.map +1 -0
  34. package/build/src/md/date-picker/internals/DatePicker.styles.d.ts +4 -0
  35. package/build/src/md/date-picker/internals/DatePicker.styles.d.ts.map +1 -0
  36. package/build/src/md/date-picker/internals/DatePicker.styles.js +336 -0
  37. package/build/src/md/date-picker/internals/DatePicker.styles.js.map +1 -0
  38. package/build/src/md/date-picker/internals/DatePickerCalendar.d.ts +159 -0
  39. package/build/src/md/date-picker/internals/DatePickerCalendar.d.ts.map +1 -0
  40. package/build/src/md/date-picker/internals/DatePickerCalendar.js +770 -0
  41. package/build/src/md/date-picker/internals/DatePickerCalendar.js.map +1 -0
  42. package/build/src/md/date-picker/internals/DatePickerUtils.d.ts +93 -0
  43. package/build/src/md/date-picker/internals/DatePickerUtils.d.ts.map +1 -0
  44. package/build/src/md/date-picker/internals/DatePickerUtils.js +221 -0
  45. package/build/src/md/date-picker/internals/DatePickerUtils.js.map +1 -0
  46. package/build/src/md/date-picker/ui-date-picker-input.d.ts +108 -0
  47. package/build/src/md/date-picker/ui-date-picker-input.d.ts.map +1 -0
  48. package/build/src/md/date-picker/ui-date-picker-input.js +397 -0
  49. package/build/src/md/date-picker/ui-date-picker-input.js.map +1 -0
  50. package/build/src/md/date-picker/ui-date-picker-modal-input.d.ts +119 -0
  51. package/build/src/md/date-picker/ui-date-picker-modal-input.d.ts.map +1 -0
  52. package/build/src/md/date-picker/ui-date-picker-modal-input.js +473 -0
  53. package/build/src/md/date-picker/ui-date-picker-modal-input.js.map +1 -0
  54. package/build/src/md/date-picker/ui-date-picker-modal.d.ts +108 -0
  55. package/build/src/md/date-picker/ui-date-picker-modal.d.ts.map +1 -0
  56. package/build/src/md/date-picker/ui-date-picker-modal.js +344 -0
  57. package/build/src/md/date-picker/ui-date-picker-modal.js.map +1 -0
  58. package/build/src/md/dialog/internals/Dialog.styles.d.ts.map +1 -1
  59. package/build/src/md/dialog/internals/Dialog.styles.js +1 -0
  60. package/build/src/md/dialog/internals/Dialog.styles.js.map +1 -1
  61. package/demo/elements/har/har2.json +1 -1
  62. package/demo/md/date-picker/date-picker.ts +301 -0
  63. package/demo/md/date-picker/index.html +171 -0
  64. package/demo/md/index.html +2 -0
  65. package/package.json +1 -1
  66. package/src/elements/highlight/MarkdownStyles.ts +0 -13
  67. package/src/elements/http/BodyEditor.ts +0 -13
  68. package/src/elements/http/BodyTextEditor.ts +0 -13
  69. package/src/elements/http/BodyUrlEncodedEditor.ts +0 -13
  70. package/src/elements/http/UrlInput.ts +0 -13
  71. package/src/index.ts +17 -0
  72. package/src/md/button/internals/button.styles.ts +1 -1
  73. package/src/md/date/internals/DateTime.ts +0 -14
  74. package/src/md/date-picker/README.md +184 -0
  75. package/src/md/date-picker/index.ts +17 -0
  76. package/src/md/date-picker/internals/DatePicker.styles.ts +338 -0
  77. package/src/md/date-picker/internals/DatePickerCalendar.ts +697 -0
  78. package/src/md/date-picker/internals/DatePickerUtils.ts +288 -0
  79. package/src/md/date-picker/ui-date-picker-input.ts +272 -0
  80. package/src/md/date-picker/ui-date-picker-modal-input.ts +371 -0
  81. package/src/md/date-picker/ui-date-picker-modal.ts +263 -0
  82. package/src/md/dialog/internals/Dialog.styles.ts +1 -0
@@ -0,0 +1,697 @@
1
+ import { LitElement, html, TemplateResult, nothing } from 'lit'
2
+ import { customElement, property, state } from 'lit/decorators.js'
3
+ import { classMap } from 'lit/directives/class-map.js'
4
+ import { calendarStyles } from './DatePicker.styles.js'
5
+ import {
6
+ CalendarMonth,
7
+ CalendarDay,
8
+ DateRange,
9
+ generateCalendarMonth,
10
+ addMonths,
11
+ getMonthNames,
12
+ isSameDay,
13
+ formatDate,
14
+ } from './DatePickerUtils.js'
15
+ import '../../../md/icons/ui-icon.js'
16
+ import '../../../md/button/ui-button.js'
17
+ import '../../../md/icon-button/ui-icon-button.js'
18
+
19
+ export interface DateSelectEvent {
20
+ date: Date
21
+ formattedDate: string
22
+ }
23
+
24
+ export interface DateRangeSelectEvent {
25
+ range: DateRange
26
+ formattedRange: {
27
+ start: string | null
28
+ end: string | null
29
+ }
30
+ }
31
+
32
+ export interface DateRangeConfirmEvent {
33
+ range: DateRange | null
34
+ formattedRange: {
35
+ start: string | null
36
+ end: string | null
37
+ }
38
+ }
39
+
40
+ export interface DateCancelEvent {
41
+ reason?: string
42
+ }
43
+
44
+ /**
45
+ * A calendar grid component for date selection.
46
+ * Supports single date selection and date range selection.
47
+ *
48
+ * ## Usage
49
+ *
50
+ * ```html
51
+ * <ui-date-picker-calendar></ui-date-picker-calendar>
52
+ * ```
53
+ *
54
+ * ### Single date selection
55
+ * ```html
56
+ * <ui-date-picker-calendar
57
+ * .selectedDate=${new Date()}
58
+ * @date-select=${this.handleDateSelect}
59
+ * ></ui-date-picker-calendar>
60
+ * ```
61
+ *
62
+ * ### Date range selection
63
+ * ```html
64
+ * <ui-date-picker-calendar
65
+ * .rangeSelection=${true}
66
+ * .selectedRange=${{ start: new Date(), end: null }}
67
+ * @date-range-select=${this.handleRangeSelect}
68
+ * ></ui-date-picker-calendar>
69
+ * ```
70
+ */
71
+ @customElement('ui-date-picker-calendar')
72
+ export class UiDatePickerCalendar extends LitElement {
73
+ static override styles = calendarStyles
74
+
75
+ /**
76
+ * The currently displayed year
77
+ */
78
+ @property({ type: Number }) accessor year = new Date().getFullYear()
79
+
80
+ /**
81
+ * The currently displayed month (0-indexed)
82
+ */
83
+ @property({ type: Number }) accessor month = new Date().getMonth()
84
+
85
+ /**
86
+ * The currently selected date for single selection mode
87
+ */
88
+ @property({ type: Object }) accessor selectedDate: Date | null = null
89
+
90
+ /**
91
+ * The selected date range for range selection mode
92
+ */
93
+ @property({ type: Object }) accessor selectedRange: DateRange | null = null
94
+
95
+ /**
96
+ * Enable range selection mode
97
+ */
98
+ @property({ type: Boolean }) accessor rangeSelection = false
99
+
100
+ /**
101
+ * Minimum selectable date
102
+ */
103
+ @property({ type: Object }) accessor minDate: Date | undefined = undefined
104
+
105
+ /**
106
+ * Maximum selectable date
107
+ */
108
+ @property({ type: Object }) accessor maxDate: Date | undefined = undefined
109
+
110
+ /**
111
+ * Array of disabled dates
112
+ */
113
+ @property({ type: Array }) accessor disabledDates: Date[] | undefined = undefined
114
+
115
+ /**
116
+ * Locale for date formatting and month/day names
117
+ */
118
+ @property({ type: String }) accessor locale: string | undefined = undefined
119
+
120
+ /**
121
+ * Whether to show navigation controls
122
+ */
123
+ @property({ type: Boolean }) accessor showNavigation = true
124
+
125
+ /**
126
+ * Whether to show action buttons (OK/Cancel)
127
+ */
128
+ @property({ type: Boolean }) accessor showActions = false
129
+
130
+ /**
131
+ * Text for the OK button
132
+ */
133
+ @property({ type: String }) accessor okButtonText = 'OK'
134
+
135
+ /**
136
+ * Text for the Cancel button
137
+ */
138
+ @property({ type: String }) accessor cancelButtonText = 'Cancel'
139
+
140
+ @state() private accessor _calendarData: CalendarMonth | undefined = undefined
141
+
142
+ @state() private accessor _rangeStart: Date | undefined = undefined
143
+
144
+ @state() private accessor _monthNames: string[] = []
145
+
146
+ @state() private accessor _showMonthDropdown = false
147
+
148
+ @state() private accessor _showYearDropdown = false
149
+
150
+ @state() private accessor _pendingDate: Date | null = null
151
+
152
+ @state() private accessor _pendingRange: DateRange | null = null
153
+
154
+ override connectedCallback(): void {
155
+ super.connectedCallback()
156
+ this._updateCalendar()
157
+ this._updateMonthNames()
158
+ document.addEventListener('keydown', this._handleDocumentKeyDown.bind(this))
159
+ }
160
+
161
+ override disconnectedCallback(): void {
162
+ super.disconnectedCallback()
163
+ document.removeEventListener('keydown', this._handleDocumentKeyDown.bind(this))
164
+ }
165
+
166
+ override updated(changedProperties: Map<string | number | symbol, unknown>): void {
167
+ if (changedProperties.has('_showYearDropdown') && this._showYearDropdown) {
168
+ // Scroll selected year into view
169
+ this._scrollSelectedYearIntoView()
170
+ }
171
+ }
172
+
173
+ private _scrollSelectedYearIntoView(): void {
174
+ // Wait for next frame to ensure DOM is updated
175
+ requestAnimationFrame(() => {
176
+ const selectedYearButton = this.shadowRoot?.querySelector('.year-option.selected') as HTMLElement
177
+ if (selectedYearButton) {
178
+ selectedYearButton.scrollIntoView({
179
+ behavior: 'auto',
180
+ block: 'center',
181
+ })
182
+ }
183
+ })
184
+ }
185
+
186
+ override willUpdate(changedProperties: Map<string | number | symbol, unknown>): void {
187
+ if (
188
+ changedProperties.has('year') ||
189
+ changedProperties.has('month') ||
190
+ changedProperties.has('selectedDate') ||
191
+ changedProperties.has('selectedRange') ||
192
+ changedProperties.has('disabledDates') ||
193
+ changedProperties.has('locale')
194
+ ) {
195
+ this._updateCalendar()
196
+ }
197
+
198
+ if (changedProperties.has('locale')) {
199
+ this._updateMonthNames()
200
+ }
201
+ }
202
+
203
+ private _updateCalendar(): void {
204
+ this._calendarData = generateCalendarMonth(
205
+ this.year,
206
+ this.month,
207
+ this.selectedDate,
208
+ this.selectedRange,
209
+ this.disabledDates,
210
+ this.locale
211
+ )
212
+ }
213
+
214
+ private _updateMonthNames(): void {
215
+ this._monthNames = getMonthNames(this.locale)
216
+ }
217
+
218
+ private _navigateMonth(delta: number): void {
219
+ const newDate = addMonths(new Date(this.year, this.month), delta)
220
+ this.year = newDate.getFullYear()
221
+ this.month = newDate.getMonth()
222
+ }
223
+
224
+ private _handlePrevMonth(): void {
225
+ this._navigateMonth(-1)
226
+ }
227
+
228
+ private _handleNextMonth(): void {
229
+ this._navigateMonth(1)
230
+ }
231
+
232
+ private _handlePrevYear(): void {
233
+ this.year = this.year - 1
234
+ }
235
+
236
+ private _handleNextYear(): void {
237
+ this.year = this.year + 1
238
+ }
239
+
240
+ private _handleMonthClick(): void {
241
+ this._showMonthDropdown = !this._showMonthDropdown
242
+ this._showYearDropdown = false
243
+ }
244
+
245
+ private _handleYearClick(): void {
246
+ this._showYearDropdown = !this._showYearDropdown
247
+ this._showMonthDropdown = false
248
+ }
249
+
250
+ private _handleMonthSelect(selectedMonth: number): void {
251
+ this.month = selectedMonth
252
+ this._showMonthDropdown = false
253
+ }
254
+
255
+ private _handleYearSelect(selectedYear: number): void {
256
+ this.year = selectedYear
257
+ this._showYearDropdown = false
258
+ }
259
+
260
+ private _closeDropdowns(): void {
261
+ this._showMonthDropdown = false
262
+ this._showYearDropdown = false
263
+ }
264
+
265
+ private _handleDocumentKeyDown(event: KeyboardEvent): void {
266
+ if (event.key === 'Escape') {
267
+ this._closeDropdowns()
268
+ }
269
+ }
270
+
271
+ private _handleDayClick(day: CalendarDay): void {
272
+ if (day.isDisabled) return
273
+
274
+ if (this.rangeSelection) {
275
+ this._handleRangeSelection(day.date)
276
+ } else {
277
+ this._handleSingleSelection(day.date)
278
+ }
279
+ }
280
+
281
+ private _handleSingleSelection(date: Date): void {
282
+ if (this.showActions) {
283
+ // Use pending state when actions are enabled
284
+ this._pendingDate = date
285
+ } else {
286
+ // Immediate selection when no actions
287
+ this.selectedDate = date
288
+
289
+ const event: DateSelectEvent = {
290
+ date,
291
+ formattedDate: formatDate(date, this.locale),
292
+ }
293
+
294
+ this.dispatchEvent(
295
+ new CustomEvent('date-select', {
296
+ detail: event,
297
+ bubbles: true,
298
+ composed: true,
299
+ })
300
+ )
301
+ }
302
+ }
303
+
304
+ private _handleRangeSelection(date: Date): void {
305
+ if (!this._rangeStart || (this.selectedRange?.start && this.selectedRange?.end)) {
306
+ // Start new range
307
+ this._rangeStart = date
308
+ const newRange = { start: date, end: null }
309
+
310
+ if (this.showActions) {
311
+ // Use pending state when actions are enabled
312
+ this._pendingRange = newRange
313
+ } else {
314
+ this.selectedRange = newRange
315
+ }
316
+ } else {
317
+ // Complete range
318
+ const start = this._rangeStart
319
+ const end = date
320
+
321
+ // Ensure start is before end
322
+ const sortedRange: DateRange = start <= end ? { start, end } : { start: end, end: start }
323
+
324
+ this._rangeStart = undefined
325
+
326
+ if (this.showActions) {
327
+ // Use pending state when actions are enabled
328
+ this._pendingRange = sortedRange
329
+ } else {
330
+ // Immediate selection when no actions
331
+ this.selectedRange = sortedRange
332
+
333
+ const event: DateRangeSelectEvent = {
334
+ range: sortedRange,
335
+ formattedRange: {
336
+ start: sortedRange.start ? formatDate(sortedRange.start, this.locale) : null,
337
+ end: sortedRange.end ? formatDate(sortedRange.end, this.locale) : null,
338
+ },
339
+ }
340
+
341
+ this.dispatchEvent(
342
+ new CustomEvent('date-range-select', {
343
+ detail: event,
344
+ bubbles: true,
345
+ composed: true,
346
+ })
347
+ )
348
+ }
349
+ }
350
+ }
351
+
352
+ private _isDateDisabled(date: Date): boolean {
353
+ if (this.minDate && date < this.minDate) return true
354
+ if (this.maxDate && date > this.maxDate) return true
355
+ if (this.disabledDates?.some((disabledDate) => isSameDay(date, disabledDate))) return true
356
+ return false
357
+ }
358
+
359
+ private _handleConfirm(): void {
360
+ if (this.rangeSelection) {
361
+ if (this._pendingRange) {
362
+ this.selectedRange = this._pendingRange
363
+
364
+ const event: DateRangeConfirmEvent = {
365
+ range: this._pendingRange,
366
+ formattedRange: {
367
+ start: this._pendingRange.start ? formatDate(this._pendingRange.start, this.locale) : null,
368
+ end: this._pendingRange.end ? formatDate(this._pendingRange.end, this.locale) : null,
369
+ },
370
+ }
371
+
372
+ this.dispatchEvent(
373
+ new CustomEvent('date-range-confirm', {
374
+ detail: event,
375
+ bubbles: true,
376
+ composed: true,
377
+ })
378
+ )
379
+ }
380
+ } else {
381
+ if (this._pendingDate) {
382
+ this.selectedDate = this._pendingDate
383
+
384
+ const event: DateSelectEvent = {
385
+ date: this._pendingDate,
386
+ formattedDate: formatDate(this._pendingDate, this.locale),
387
+ }
388
+
389
+ this.dispatchEvent(
390
+ new CustomEvent('date-select', {
391
+ detail: event,
392
+ bubbles: true,
393
+ composed: true,
394
+ })
395
+ )
396
+ }
397
+ }
398
+ }
399
+
400
+ private _handleCancel(): void {
401
+ // Reset pending state
402
+ this._pendingDate = null
403
+ this._pendingRange = null
404
+ this._rangeStart = undefined
405
+
406
+ const event: DateCancelEvent = {
407
+ reason: 'user_cancelled',
408
+ }
409
+
410
+ this.dispatchEvent(
411
+ new CustomEvent('date-cancel', {
412
+ detail: event,
413
+ bubbles: true,
414
+ composed: true,
415
+ })
416
+ )
417
+ }
418
+
419
+ private _renderNavigation(): TemplateResult {
420
+ const monthName = this._monthNames[this.month] || ''
421
+
422
+ return html`
423
+ <div class="header">
424
+ <div class="month-year">
425
+ <div class="month-selector">
426
+ ${this.showNavigation && !this._showMonthDropdown && !this._showYearDropdown
427
+ ? html`
428
+ <ui-icon-button
429
+ class="nav-button month-nav"
430
+ size="xs"
431
+ @click=${this._handlePrevMonth}
432
+ aria-label="Previous month"
433
+ title="Previous month"
434
+ >
435
+ <ui-icon icon="chevronLeft"></ui-icon>
436
+ </ui-icon-button>
437
+ `
438
+ : ''}
439
+ <ui-button
440
+ class="month-button"
441
+ color="text"
442
+ size="xs"
443
+ @click=${this._handleMonthClick}
444
+ aria-label="Select month"
445
+ aria-expanded=${this._showMonthDropdown}
446
+ trailingIcon
447
+ >
448
+ ${monthName}
449
+ <ui-icon icon="arrowDropDown" slot="icon"></ui-icon>
450
+ </ui-button>
451
+ ${this.showNavigation && !this._showMonthDropdown && !this._showYearDropdown
452
+ ? html`
453
+ <ui-icon-button
454
+ class="nav-button month-nav"
455
+ size="xs"
456
+ @click=${this._handleNextMonth}
457
+ aria-label="Next month"
458
+ title="Next month"
459
+ >
460
+ <ui-icon icon="chevronRight"></ui-icon>
461
+ </ui-icon-button>
462
+ `
463
+ : ''}
464
+ </div>
465
+ <div class="year-selector">
466
+ ${this.showNavigation && !this._showMonthDropdown && !this._showYearDropdown
467
+ ? html`
468
+ <ui-icon-button
469
+ class="nav-button year-nav"
470
+ size="xs"
471
+ @click=${this._handlePrevYear}
472
+ aria-label="Previous year"
473
+ title="Previous year"
474
+ >
475
+ <ui-icon icon="chevronLeft"></ui-icon>
476
+ </ui-icon-button>
477
+ `
478
+ : ''}
479
+ <ui-button
480
+ class="year-button"
481
+ color="text"
482
+ size="xs"
483
+ @click=${this._handleYearClick}
484
+ aria-label="Select year"
485
+ aria-expanded=${this._showYearDropdown}
486
+ trailingIcon
487
+ >
488
+ ${this.year}
489
+ <ui-icon icon="arrowDropDown" slot="icon"></ui-icon>
490
+ </ui-button>
491
+ ${this.showNavigation && !this._showMonthDropdown && !this._showYearDropdown
492
+ ? html`
493
+ <ui-icon-button
494
+ class="nav-button year-nav"
495
+ size="xs"
496
+ @click=${this._handleNextYear}
497
+ aria-label="Next year"
498
+ title="Next year"
499
+ >
500
+ <ui-icon icon="chevronRight"></ui-icon>
501
+ </ui-icon-button>
502
+ `
503
+ : ''}
504
+ </div>
505
+ </div>
506
+ </div>
507
+ `
508
+ }
509
+
510
+ private _renderWeekdays(): TemplateResult {
511
+ if (!this._calendarData) return html``
512
+
513
+ return html`
514
+ <div class="weekdays">
515
+ ${this._calendarData.weekdays.map((weekday) => html`<div class="weekday">${weekday}</div>`)}
516
+ </div>
517
+ `
518
+ }
519
+
520
+ private _renderDay(day: CalendarDay): TemplateResult {
521
+ const isPendingSelected = this.showActions && this._pendingDate && isSameDay(day.date, this._pendingDate)
522
+ const isPendingRangeStart =
523
+ this.showActions && this._pendingRange?.start && isSameDay(day.date, this._pendingRange.start)
524
+ const isPendingRangeEnd = this.showActions && this._pendingRange?.end && isSameDay(day.date, this._pendingRange.end)
525
+ const isPendingInRange =
526
+ this.showActions &&
527
+ this._pendingRange?.start &&
528
+ this._pendingRange?.end &&
529
+ day.date >= this._pendingRange.start &&
530
+ day.date <= this._pendingRange.end &&
531
+ !isPendingRangeStart &&
532
+ !isPendingRangeEnd
533
+
534
+ // Determine button color based on selection state
535
+ let color: 'elevated' | 'filled' | 'outlined' | 'text' | 'tonal' = 'text'
536
+
537
+ if (this.showActions) {
538
+ if (isPendingRangeStart || isPendingRangeEnd || isPendingSelected) {
539
+ color = 'filled'
540
+ } else if (isPendingInRange) {
541
+ color = 'text'
542
+ }
543
+ } else {
544
+ if (day.isRangeStart || day.isRangeEnd || day.isSelected) {
545
+ color = 'filled'
546
+ } else if (day.isInRange) {
547
+ color = 'text'
548
+ }
549
+ }
550
+
551
+ if (day.isToday && color === 'text') {
552
+ color = 'outlined'
553
+ }
554
+
555
+ const classes = {
556
+ 'day-cell': true,
557
+ 'other-month': !day.isCurrentMonth,
558
+ 'today': day.isToday,
559
+ 'in-range': this.showActions ? !!isPendingInRange : day.isInRange,
560
+ 'range-start': this.showActions ? !!isPendingRangeStart : day.isRangeStart,
561
+ 'range-end': this.showActions ? !!isPendingRangeEnd : day.isRangeEnd,
562
+ 'has-complete-range': this.showActions
563
+ ? !!(this._pendingRange?.start && this._pendingRange?.end)
564
+ : !!(this.selectedRange?.start && this.selectedRange?.end),
565
+ }
566
+
567
+ return html`
568
+ <div class=${classMap(classes)}>
569
+ <ui-button
570
+ class="day-button"
571
+ color=${color}
572
+ size="s"
573
+ data-date=${day.date.toISOString().split('T')[0]}
574
+ tabindex=${day.isToday && !(day.isDisabled || this._isDateDisabled(day.date)) ? '0' : '-1'}
575
+ aria-label=${formatDate(day.date, this.locale)}
576
+ @click=${() => this._handleDayClick(day)}
577
+ ?disabled=${day.isDisabled || this._isDateDisabled(day.date)}
578
+ >
579
+ ${day.date.getDate()}
580
+ </ui-button>
581
+ </div>
582
+ `
583
+ }
584
+
585
+ private _renderDays(): TemplateResult {
586
+ if (!this._calendarData) return html``
587
+
588
+ return html`<div class="days">${this._calendarData.days.map((day) => this._renderDay(day))}</div>`
589
+ }
590
+
591
+ private _renderActions(): TemplateResult | typeof nothing {
592
+ if (!this.showActions) return nothing
593
+
594
+ const hasSelection = this.rangeSelection ? this._pendingRange?.start : this._pendingDate
595
+
596
+ return html`
597
+ <div class="actions">
598
+ <ui-button size="s" color="text" @click=${this._handleCancel}>${this.cancelButtonText}</ui-button>
599
+ <ui-button size="s" color="text" @click=${this._handleConfirm} ?disabled=${!hasSelection}>
600
+ ${this.okButtonText}
601
+ </ui-button>
602
+ </div>
603
+ `
604
+ }
605
+
606
+ private _renderMonthDropdown(): TemplateResult {
607
+ return html`
608
+ <div class="dropdown-view">
609
+ <div class="month-list">
610
+ ${this._monthNames.map(
611
+ (monthName, index) => html`
612
+ <ui-button
613
+ class="month-option ${index === this.month ? 'selected' : ''}"
614
+ color=${index === this.month ? 'filled' : 'text'}
615
+ size="s"
616
+ @click=${() => this._handleMonthSelect(index)}
617
+ aria-label=${monthName}
618
+ >
619
+ ${monthName}
620
+ </ui-button>
621
+ `
622
+ )}
623
+ </div>
624
+ </div>
625
+ `
626
+ }
627
+
628
+ private _renderYearDropdown(): TemplateResult {
629
+ const currentYear = this.year
630
+ const startYear = currentYear - 50
631
+ const endYear = currentYear + 50
632
+ const years: number[] = []
633
+
634
+ for (let year = startYear; year <= endYear; year++) {
635
+ years.push(year)
636
+ }
637
+
638
+ return html`
639
+ <div class="dropdown-view">
640
+ <div class="year-grid">
641
+ ${years.map(
642
+ (year) => html`
643
+ <ui-button
644
+ class="year-option ${year === this.year ? 'selected' : ''}"
645
+ color=${year === this.year ? 'filled' : 'text'}
646
+ size="s"
647
+ @click=${() => this._handleYearSelect(year)}
648
+ aria-label=${year.toString()}
649
+ >
650
+ ${year}
651
+ </ui-button>
652
+ `
653
+ )}
654
+ </div>
655
+ </div>
656
+ `
657
+ }
658
+
659
+ override render(): TemplateResult {
660
+ // Show dropdown views instead of calendar when dropdowns are open
661
+ if (this._showMonthDropdown) {
662
+ return html`
663
+ <div class="calendar" role="grid" aria-label="Calendar">
664
+ ${this._renderNavigation()} ${this._renderMonthDropdown()}
665
+ </div>
666
+ `
667
+ }
668
+
669
+ if (this._showYearDropdown) {
670
+ return html`
671
+ <div class="calendar" role="grid" aria-label="Calendar">
672
+ ${this._renderNavigation()} ${this._renderYearDropdown()}
673
+ </div>
674
+ `
675
+ }
676
+
677
+ // Default calendar view
678
+ return html`
679
+ <div class="calendar" role="grid" aria-label="Calendar">
680
+ ${this._renderNavigation()} ${this._renderWeekdays()} ${this._renderDays()} ${this._renderActions()}
681
+ </div>
682
+ `
683
+ }
684
+ }
685
+
686
+ declare global {
687
+ interface HTMLElementTagNameMap {
688
+ 'ui-date-picker-calendar': UiDatePickerCalendar
689
+ }
690
+
691
+ interface HTMLElementEventMap {
692
+ 'date-select': CustomEvent<DateSelectEvent>
693
+ 'date-range-select': CustomEvent<DateRangeSelectEvent>
694
+ 'date-range-confirm': CustomEvent<DateRangeConfirmEvent>
695
+ 'date-cancel': CustomEvent<DateCancelEvent>
696
+ }
697
+ }