@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.
- package/build/src/elements/highlight/MarkdownStyles.d.ts.map +1 -1
- package/build/src/elements/highlight/MarkdownStyles.js +0 -13
- package/build/src/elements/highlight/MarkdownStyles.js.map +1 -1
- package/build/src/elements/http/BodyEditor.d.ts +0 -13
- package/build/src/elements/http/BodyEditor.d.ts.map +1 -1
- package/build/src/elements/http/BodyEditor.js +0 -13
- package/build/src/elements/http/BodyEditor.js.map +1 -1
- package/build/src/elements/http/BodyTextEditor.d.ts +0 -13
- package/build/src/elements/http/BodyTextEditor.d.ts.map +1 -1
- package/build/src/elements/http/BodyTextEditor.js +0 -13
- package/build/src/elements/http/BodyTextEditor.js.map +1 -1
- package/build/src/elements/http/BodyUrlEncodedEditor.d.ts +0 -13
- package/build/src/elements/http/BodyUrlEncodedEditor.d.ts.map +1 -1
- package/build/src/elements/http/BodyUrlEncodedEditor.js +0 -13
- package/build/src/elements/http/BodyUrlEncodedEditor.js.map +1 -1
- package/build/src/elements/http/UrlInput.d.ts +0 -13
- package/build/src/elements/http/UrlInput.d.ts.map +1 -1
- package/build/src/elements/http/UrlInput.js +0 -13
- package/build/src/elements/http/UrlInput.js.map +1 -1
- package/build/src/index.d.ts +2 -0
- package/build/src/index.d.ts.map +1 -1
- package/build/src/index.js +2 -0
- package/build/src/index.js.map +1 -1
- package/build/src/md/button/internals/button.styles.js +1 -1
- package/build/src/md/button/internals/button.styles.js.map +1 -1
- package/build/src/md/date/internals/DateTime.d.ts +0 -13
- package/build/src/md/date/internals/DateTime.d.ts.map +1 -1
- package/build/src/md/date/internals/DateTime.js +0 -13
- package/build/src/md/date/internals/DateTime.js.map +1 -1
- package/build/src/md/date-picker/index.d.ts +13 -0
- package/build/src/md/date-picker/index.d.ts.map +1 -0
- package/build/src/md/date-picker/index.js +13 -0
- package/build/src/md/date-picker/index.js.map +1 -0
- package/build/src/md/date-picker/internals/DatePicker.styles.d.ts +4 -0
- package/build/src/md/date-picker/internals/DatePicker.styles.d.ts.map +1 -0
- package/build/src/md/date-picker/internals/DatePicker.styles.js +336 -0
- package/build/src/md/date-picker/internals/DatePicker.styles.js.map +1 -0
- package/build/src/md/date-picker/internals/DatePickerCalendar.d.ts +159 -0
- package/build/src/md/date-picker/internals/DatePickerCalendar.d.ts.map +1 -0
- package/build/src/md/date-picker/internals/DatePickerCalendar.js +770 -0
- package/build/src/md/date-picker/internals/DatePickerCalendar.js.map +1 -0
- package/build/src/md/date-picker/internals/DatePickerUtils.d.ts +93 -0
- package/build/src/md/date-picker/internals/DatePickerUtils.d.ts.map +1 -0
- package/build/src/md/date-picker/internals/DatePickerUtils.js +221 -0
- package/build/src/md/date-picker/internals/DatePickerUtils.js.map +1 -0
- package/build/src/md/date-picker/ui-date-picker-input.d.ts +108 -0
- package/build/src/md/date-picker/ui-date-picker-input.d.ts.map +1 -0
- package/build/src/md/date-picker/ui-date-picker-input.js +397 -0
- package/build/src/md/date-picker/ui-date-picker-input.js.map +1 -0
- package/build/src/md/date-picker/ui-date-picker-modal-input.d.ts +119 -0
- package/build/src/md/date-picker/ui-date-picker-modal-input.d.ts.map +1 -0
- package/build/src/md/date-picker/ui-date-picker-modal-input.js +473 -0
- package/build/src/md/date-picker/ui-date-picker-modal-input.js.map +1 -0
- package/build/src/md/date-picker/ui-date-picker-modal.d.ts +108 -0
- package/build/src/md/date-picker/ui-date-picker-modal.d.ts.map +1 -0
- package/build/src/md/date-picker/ui-date-picker-modal.js +344 -0
- package/build/src/md/date-picker/ui-date-picker-modal.js.map +1 -0
- package/build/src/md/dialog/internals/Dialog.styles.d.ts.map +1 -1
- package/build/src/md/dialog/internals/Dialog.styles.js +1 -0
- package/build/src/md/dialog/internals/Dialog.styles.js.map +1 -1
- package/demo/elements/har/har2.json +1 -1
- package/demo/md/date-picker/date-picker.ts +301 -0
- package/demo/md/date-picker/index.html +171 -0
- package/demo/md/index.html +2 -0
- package/package.json +1 -1
- package/src/elements/highlight/MarkdownStyles.ts +0 -13
- package/src/elements/http/BodyEditor.ts +0 -13
- package/src/elements/http/BodyTextEditor.ts +0 -13
- package/src/elements/http/BodyUrlEncodedEditor.ts +0 -13
- package/src/elements/http/UrlInput.ts +0 -13
- package/src/index.ts +17 -0
- package/src/md/button/internals/button.styles.ts +1 -1
- package/src/md/date/internals/DateTime.ts +0 -14
- package/src/md/date-picker/README.md +184 -0
- package/src/md/date-picker/index.ts +17 -0
- package/src/md/date-picker/internals/DatePicker.styles.ts +338 -0
- package/src/md/date-picker/internals/DatePickerCalendar.ts +697 -0
- package/src/md/date-picker/internals/DatePickerUtils.ts +288 -0
- package/src/md/date-picker/ui-date-picker-input.ts +272 -0
- package/src/md/date-picker/ui-date-picker-modal-input.ts +371 -0
- package/src/md/date-picker/ui-date-picker-modal.ts +263 -0
- 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
|
+
}
|