@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,288 @@
|
|
|
1
|
+
export interface DateRange {
|
|
2
|
+
start: Date | null
|
|
3
|
+
end: Date | null
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface CalendarDay {
|
|
7
|
+
date: Date
|
|
8
|
+
isCurrentMonth: boolean
|
|
9
|
+
isToday: boolean
|
|
10
|
+
isSelected: boolean
|
|
11
|
+
isInRange: boolean
|
|
12
|
+
isRangeStart: boolean
|
|
13
|
+
isRangeEnd: boolean
|
|
14
|
+
isDisabled: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface CalendarMonth {
|
|
18
|
+
year: number
|
|
19
|
+
month: number // 0-indexed (0 = January)
|
|
20
|
+
days: CalendarDay[]
|
|
21
|
+
weekdays: string[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get the current date with time set to midnight
|
|
26
|
+
*/
|
|
27
|
+
export function today(): Date {
|
|
28
|
+
const now = new Date()
|
|
29
|
+
return new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Create a new date with time set to midnight
|
|
34
|
+
*/
|
|
35
|
+
export function normalizeDate(date: Date): Date {
|
|
36
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate())
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Check if two dates are the same day
|
|
41
|
+
*/
|
|
42
|
+
export function isSameDay(date1: Date, date2: Date): boolean {
|
|
43
|
+
return (
|
|
44
|
+
date1.getFullYear() === date2.getFullYear() &&
|
|
45
|
+
date1.getMonth() === date2.getMonth() &&
|
|
46
|
+
date1.getDate() === date2.getDate()
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Check if a date is between two other dates (inclusive)
|
|
52
|
+
*/
|
|
53
|
+
export function isBetween(date: Date, start: Date, end: Date): boolean {
|
|
54
|
+
const normalizedDate = normalizeDate(date)
|
|
55
|
+
const normalizedStart = normalizeDate(start)
|
|
56
|
+
const normalizedEnd = normalizeDate(end)
|
|
57
|
+
return normalizedDate >= normalizedStart && normalizedDate <= normalizedEnd
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Add months to a date
|
|
62
|
+
*/
|
|
63
|
+
export function addMonths(date: Date, months: number): Date {
|
|
64
|
+
const result = new Date(date)
|
|
65
|
+
result.setMonth(result.getMonth() + months)
|
|
66
|
+
return result
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Add days to a date
|
|
71
|
+
*/
|
|
72
|
+
export function addDays(date: Date, days: number): Date {
|
|
73
|
+
const result = new Date(date)
|
|
74
|
+
result.setDate(result.getDate() + days)
|
|
75
|
+
return result
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get the first day of the month
|
|
80
|
+
*/
|
|
81
|
+
export function getFirstDayOfMonth(year: number, month: number): Date {
|
|
82
|
+
return new Date(year, month, 1)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get the last day of the month
|
|
87
|
+
*/
|
|
88
|
+
export function getLastDayOfMonth(year: number, month: number): Date {
|
|
89
|
+
return new Date(year, month + 1, 0)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get the number of days in a month
|
|
94
|
+
*/
|
|
95
|
+
export function getDaysInMonth(year: number, month: number): number {
|
|
96
|
+
return new Date(year, month + 1, 0).getDate()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get weekday names for the current locale
|
|
101
|
+
*/
|
|
102
|
+
export function getWeekdayNames(locale?: string): string[] {
|
|
103
|
+
const formatter = new Intl.DateTimeFormat(locale, { weekday: 'short' })
|
|
104
|
+
const days: string[] = []
|
|
105
|
+
|
|
106
|
+
// Start from Sunday and get all 7 days
|
|
107
|
+
const date = new Date(2024, 0, 7) // January 7, 2024 is a Sunday
|
|
108
|
+
for (let i = 0; i < 7; i++) {
|
|
109
|
+
days.push(formatter.format(new Date(date.getTime() + i * 24 * 60 * 60 * 1000)))
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return days
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get month names for the current locale
|
|
117
|
+
*/
|
|
118
|
+
export function getMonthNames(locale?: string): string[] {
|
|
119
|
+
const formatter = new Intl.DateTimeFormat(locale, { month: 'short' })
|
|
120
|
+
const months: string[] = []
|
|
121
|
+
|
|
122
|
+
for (let i = 0; i < 12; i++) {
|
|
123
|
+
months.push(formatter.format(new Date(2024, i, 1)))
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return months
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Format a date according to locale
|
|
131
|
+
*/
|
|
132
|
+
export function formatDate(date: Date, locale?: string, options?: Intl.DateTimeFormatOptions): string {
|
|
133
|
+
const defaultOptions: Intl.DateTimeFormatOptions = {
|
|
134
|
+
year: 'numeric',
|
|
135
|
+
month: 'short',
|
|
136
|
+
day: 'numeric',
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return new Intl.DateTimeFormat(locale, { ...defaultOptions, ...options }).format(date)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Parse a date string in various formats
|
|
144
|
+
*/
|
|
145
|
+
export function parseDate(dateString: string): Date | null {
|
|
146
|
+
if (!dateString) return null
|
|
147
|
+
|
|
148
|
+
// Try ISO format first
|
|
149
|
+
const isoDate = new Date(dateString)
|
|
150
|
+
if (!isNaN(isoDate.getTime())) {
|
|
151
|
+
return normalizeDate(isoDate)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Try other common formats
|
|
155
|
+
const formats = [
|
|
156
|
+
/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/, // MM/DD/YYYY or M/D/YYYY
|
|
157
|
+
/^(\d{1,2})-(\d{1,2})-(\d{4})$/, // MM-DD-YYYY or M-D-YYYY
|
|
158
|
+
/^(\d{4})-(\d{1,2})-(\d{1,2})$/, // YYYY-MM-DD or YYYY-M-D
|
|
159
|
+
]
|
|
160
|
+
|
|
161
|
+
for (const format of formats) {
|
|
162
|
+
const match = dateString.match(format)
|
|
163
|
+
if (match) {
|
|
164
|
+
const [, part1, part2, part3] = match
|
|
165
|
+
let year: number, month: number, day: number
|
|
166
|
+
|
|
167
|
+
if (format === formats[2]) {
|
|
168
|
+
// YYYY-MM-DD
|
|
169
|
+
year = parseInt(part1, 10)
|
|
170
|
+
month = parseInt(part2, 10) - 1 // Convert to 0-indexed
|
|
171
|
+
day = parseInt(part3, 10)
|
|
172
|
+
} else {
|
|
173
|
+
// MM/DD/YYYY or MM-DD-YYYY
|
|
174
|
+
year = parseInt(part3, 10)
|
|
175
|
+
month = parseInt(part1, 10) - 1 // Convert to 0-indexed
|
|
176
|
+
day = parseInt(part2, 10)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const date = new Date(year, month, day)
|
|
180
|
+
if (!isNaN(date.getTime())) {
|
|
181
|
+
return date
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return null
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Generate calendar data for a specific month
|
|
191
|
+
*/
|
|
192
|
+
export function generateCalendarMonth(
|
|
193
|
+
year: number,
|
|
194
|
+
month: number,
|
|
195
|
+
selectedDate?: Date | null,
|
|
196
|
+
selectedRange?: DateRange | null,
|
|
197
|
+
disabledDates?: Date[],
|
|
198
|
+
locale?: string
|
|
199
|
+
): CalendarMonth {
|
|
200
|
+
const firstDay = getFirstDayOfMonth(year, month)
|
|
201
|
+
const todayDate = today()
|
|
202
|
+
|
|
203
|
+
// Get the first day of the week (0 = Sunday, 1 = Monday)
|
|
204
|
+
const firstDayOfWeek = firstDay.getDay()
|
|
205
|
+
|
|
206
|
+
// Calculate start date (might be from previous month)
|
|
207
|
+
const startDate = addDays(firstDay, -firstDayOfWeek)
|
|
208
|
+
|
|
209
|
+
// Calculate end date (might be from next month)
|
|
210
|
+
const daysToShow = 42 // 6 weeks * 7 days
|
|
211
|
+
const endDate = addDays(startDate, daysToShow - 1)
|
|
212
|
+
|
|
213
|
+
const days: CalendarDay[] = []
|
|
214
|
+
const currentDate = new Date(startDate)
|
|
215
|
+
|
|
216
|
+
while (currentDate <= endDate) {
|
|
217
|
+
const isCurrentMonth = currentDate.getMonth() === month
|
|
218
|
+
const isToday = isSameDay(currentDate, todayDate)
|
|
219
|
+
const isSelected = selectedDate ? isSameDay(currentDate, selectedDate) : false
|
|
220
|
+
|
|
221
|
+
let isInRange = false
|
|
222
|
+
let isRangeStart = false
|
|
223
|
+
let isRangeEnd = false
|
|
224
|
+
|
|
225
|
+
if (selectedRange?.start && selectedRange?.end) {
|
|
226
|
+
isRangeStart = isSameDay(currentDate, selectedRange.start)
|
|
227
|
+
isRangeEnd = isSameDay(currentDate, selectedRange.end)
|
|
228
|
+
isInRange = isBetween(currentDate, selectedRange.start, selectedRange.end)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const isDisabled = disabledDates?.some((disabledDate) => isSameDay(currentDate, disabledDate)) || false
|
|
232
|
+
|
|
233
|
+
days.push({
|
|
234
|
+
date: new Date(currentDate),
|
|
235
|
+
isCurrentMonth,
|
|
236
|
+
isToday,
|
|
237
|
+
isSelected,
|
|
238
|
+
isInRange,
|
|
239
|
+
isRangeStart,
|
|
240
|
+
isRangeEnd,
|
|
241
|
+
isDisabled,
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
currentDate.setDate(currentDate.getDate() + 1)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
year,
|
|
249
|
+
month,
|
|
250
|
+
days,
|
|
251
|
+
weekdays: getWeekdayNames(locale),
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Get the ISO string for a date (YYYY-MM-DD format)
|
|
257
|
+
*/
|
|
258
|
+
export function toISODateString(date: Date): string {
|
|
259
|
+
return date.toISOString().split('T')[0]
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Create a date from ISO string (YYYY-MM-DD format)
|
|
264
|
+
*/
|
|
265
|
+
export function fromISODateString(isoString: string): Date | null {
|
|
266
|
+
const match = isoString.match(/^(\d{4})-(\d{2})-(\d{2})$/)
|
|
267
|
+
if (!match) return null
|
|
268
|
+
|
|
269
|
+
const [, year, month, day] = match
|
|
270
|
+
return new Date(parseInt(year, 10), parseInt(month, 10) - 1, parseInt(day, 10))
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Validate if a date range is valid (start <= end)
|
|
275
|
+
*/
|
|
276
|
+
export function isValidRange(range: DateRange): boolean {
|
|
277
|
+
if (!range.start || !range.end) return true
|
|
278
|
+
return range.start <= range.end
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Get the number of days between two dates
|
|
283
|
+
*/
|
|
284
|
+
export function getDaysBetween(start: Date, end: Date): number {
|
|
285
|
+
const startTime = normalizeDate(start).getTime()
|
|
286
|
+
const endTime = normalizeDate(end).getTime()
|
|
287
|
+
return Math.abs(Math.floor((endTime - startTime) / (24 * 60 * 60 * 1000)))
|
|
288
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { LitElement, html, TemplateResult } from 'lit'
|
|
2
|
+
import { customElement, property, state, query } from 'lit/decorators.js'
|
|
3
|
+
import { inputStyles } from './internals/DatePicker.styles.js'
|
|
4
|
+
import { formatDate, parseDate } from './internals/DatePickerUtils.js'
|
|
5
|
+
import type { DateSelectEvent } from './internals/DatePickerCalendar.js'
|
|
6
|
+
import './internals/DatePickerCalendar.js'
|
|
7
|
+
import '../../md/text-field/ui-outlined-text-field.js'
|
|
8
|
+
import '../../md/icons/ui-icon.js'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A docked date picker that opens from a text field input.
|
|
12
|
+
* Ideal for forms and date selection in medium to large layouts.
|
|
13
|
+
*
|
|
14
|
+
* ## Usage
|
|
15
|
+
*
|
|
16
|
+
* ```html
|
|
17
|
+
* <ui-date-picker-input
|
|
18
|
+
* label="Select date"
|
|
19
|
+
* placeholder="MM/DD/YYYY"
|
|
20
|
+
* .value=${new Date()}
|
|
21
|
+
* ></ui-date-picker-input>
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* ### Custom date format
|
|
25
|
+
* ```html
|
|
26
|
+
* <ui-date-picker-input
|
|
27
|
+
* label="Birth date"
|
|
28
|
+
* .dateFormat=${date => date.toLocaleDateString('en-GB')}
|
|
29
|
+
* ></ui-date-picker-input>
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
@customElement('ui-date-picker-input')
|
|
33
|
+
export class UiDatePickerInput extends LitElement {
|
|
34
|
+
static override styles = inputStyles
|
|
35
|
+
static override shadowRootOptions: ShadowRootInit = {
|
|
36
|
+
mode: 'open',
|
|
37
|
+
delegatesFocus: true,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The label for the input field
|
|
42
|
+
*/
|
|
43
|
+
@property({ type: String }) accessor label = ''
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Placeholder text for the input
|
|
47
|
+
*/
|
|
48
|
+
@property({ type: String }) accessor placeholder = 'MM/DD/YYYY'
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The currently selected date
|
|
52
|
+
*/
|
|
53
|
+
@property({ type: Object }) accessor value: Date | null = null
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Whether the date picker is disabled
|
|
57
|
+
*/
|
|
58
|
+
@property({ type: Boolean }) accessor disabled = false
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Whether the input is required
|
|
62
|
+
*/
|
|
63
|
+
@property({ type: Boolean }) accessor required = false
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Error message to display
|
|
67
|
+
*/
|
|
68
|
+
@property({ type: String }) accessor errorMessage = ''
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Whether the input has an error state
|
|
72
|
+
*/
|
|
73
|
+
@property({ type: Boolean }) accessor error = false
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Minimum selectable date
|
|
77
|
+
*/
|
|
78
|
+
@property({ type: Object }) accessor minDate: Date | undefined = undefined
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Maximum selectable date
|
|
82
|
+
*/
|
|
83
|
+
@property({ type: Object }) accessor maxDate: Date | undefined = undefined
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Array of disabled dates
|
|
87
|
+
*/
|
|
88
|
+
@property({ type: Array }) accessor disabledDates: Date[] | undefined = undefined
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Locale for date formatting
|
|
92
|
+
*/
|
|
93
|
+
@property({ type: String }) accessor locale: string | undefined = undefined
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Custom date format function
|
|
97
|
+
*/
|
|
98
|
+
@property({ type: Object }) accessor dateFormat: ((date: Date) => string) | undefined = undefined
|
|
99
|
+
|
|
100
|
+
@state() private accessor _isOpen = false
|
|
101
|
+
|
|
102
|
+
@state() private accessor _inputValue = ''
|
|
103
|
+
|
|
104
|
+
@query('ui-outlined-text-field') private accessor _textField!: HTMLElement
|
|
105
|
+
|
|
106
|
+
override connectedCallback(): void {
|
|
107
|
+
super.connectedCallback()
|
|
108
|
+
this._updateInputValue()
|
|
109
|
+
document.addEventListener('click', this._handleDocumentClick.bind(this))
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
override disconnectedCallback(): void {
|
|
113
|
+
super.disconnectedCallback()
|
|
114
|
+
document.removeEventListener('click', this._handleDocumentClick.bind(this))
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
override willUpdate(changedProperties: Map<string | number | symbol, unknown>): void {
|
|
118
|
+
if (changedProperties.has('value')) {
|
|
119
|
+
this._updateInputValue()
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
override updated(changedProperties: Map<string | number | symbol, unknown>): void {
|
|
124
|
+
if (changedProperties.has('_isOpen') && this._isOpen) {
|
|
125
|
+
// Set anchor name on text field for CSS Anchor Positioning API
|
|
126
|
+
if (this._textField) {
|
|
127
|
+
// Using setProperty since anchorName is not in TypeScript types yet
|
|
128
|
+
this._textField.style.setProperty('anchor-name', '--ui-date-picker-anchor')
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private _updateInputValue(): void {
|
|
134
|
+
if (this.value) {
|
|
135
|
+
this._inputValue = this.dateFormat ? this.dateFormat(this.value) : formatDate(this.value, this.locale)
|
|
136
|
+
} else {
|
|
137
|
+
this._inputValue = ''
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private _handleDocumentClick(event: Event): void {
|
|
142
|
+
if (!this.contains(event.target as Node)) {
|
|
143
|
+
this._isOpen = false
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private _handleInputClick(): void {
|
|
148
|
+
if (!this.disabled) {
|
|
149
|
+
this._isOpen = !this._isOpen
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private _handleInputChange(event: Event): void {
|
|
154
|
+
const target = event.target as HTMLInputElement
|
|
155
|
+
this._inputValue = target.value
|
|
156
|
+
|
|
157
|
+
const parsedDate = parseDate(this._inputValue)
|
|
158
|
+
if (parsedDate) {
|
|
159
|
+
this.value = parsedDate
|
|
160
|
+
this._dispatchChangeEvent()
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private _handleCalendarDateSelect(event: CustomEvent<DateSelectEvent>): void {
|
|
165
|
+
this.value = event.detail.date
|
|
166
|
+
this._isOpen = false
|
|
167
|
+
this._dispatchChangeEvent()
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private _handleCalendarDateCancel(): void {
|
|
171
|
+
this._isOpen = false
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private _handleKeyDown(event: KeyboardEvent): void {
|
|
175
|
+
switch (event.key) {
|
|
176
|
+
case 'Escape':
|
|
177
|
+
this._isOpen = false
|
|
178
|
+
break
|
|
179
|
+
case 'ArrowDown':
|
|
180
|
+
if (!this._isOpen) {
|
|
181
|
+
event.preventDefault()
|
|
182
|
+
this._isOpen = true
|
|
183
|
+
}
|
|
184
|
+
break
|
|
185
|
+
case 'Enter':
|
|
186
|
+
if (this._isOpen) {
|
|
187
|
+
event.preventDefault()
|
|
188
|
+
this._isOpen = false
|
|
189
|
+
}
|
|
190
|
+
break
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private _dispatchChangeEvent(): void {
|
|
195
|
+
this.dispatchEvent(
|
|
196
|
+
new CustomEvent('change', {
|
|
197
|
+
detail: {
|
|
198
|
+
value: this.value,
|
|
199
|
+
formattedValue: this._inputValue,
|
|
200
|
+
},
|
|
201
|
+
bubbles: true,
|
|
202
|
+
composed: true,
|
|
203
|
+
})
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private _renderCalendarIcon(): TemplateResult {
|
|
208
|
+
return html`
|
|
209
|
+
<ui-icon slot="suffix" icon="calendarToday" @click=${this._handleInputClick} style="cursor: pointer;"></ui-icon>
|
|
210
|
+
`
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private _renderDropdown(): TemplateResult | null {
|
|
214
|
+
if (!this._isOpen) return null
|
|
215
|
+
|
|
216
|
+
const currentDate = this.value || new Date()
|
|
217
|
+
|
|
218
|
+
return html`
|
|
219
|
+
<div class="dropdown-container">
|
|
220
|
+
<ui-date-picker-calendar
|
|
221
|
+
.year=${currentDate.getFullYear()}
|
|
222
|
+
.month=${currentDate.getMonth()}
|
|
223
|
+
.selectedDate=${this.value}
|
|
224
|
+
.minDate=${this.minDate}
|
|
225
|
+
.maxDate=${this.maxDate}
|
|
226
|
+
.disabledDates=${this.disabledDates}
|
|
227
|
+
.locale=${this.locale}
|
|
228
|
+
showActions
|
|
229
|
+
@date-select=${this._handleCalendarDateSelect}
|
|
230
|
+
@date-cancel=${this._handleCalendarDateCancel}
|
|
231
|
+
></ui-date-picker-calendar>
|
|
232
|
+
</div>
|
|
233
|
+
`
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
override render(): TemplateResult {
|
|
237
|
+
return html`
|
|
238
|
+
<div class="input-container">
|
|
239
|
+
${this._isOpen ? html`<div class="backdrop" @click=${() => (this._isOpen = false)}></div>` : ''}
|
|
240
|
+
<ui-outlined-text-field
|
|
241
|
+
.label=${this.label}
|
|
242
|
+
.placeholder=${this.placeholder}
|
|
243
|
+
.value=${this._inputValue}
|
|
244
|
+
.disabled=${this.disabled}
|
|
245
|
+
.required=${this.required}
|
|
246
|
+
.errorMessage=${this.errorMessage}
|
|
247
|
+
.error=${this.error}
|
|
248
|
+
readonly
|
|
249
|
+
@click=${this._handleInputClick}
|
|
250
|
+
@input=${this._handleInputChange}
|
|
251
|
+
@keydown=${this._handleKeyDown}
|
|
252
|
+
>
|
|
253
|
+
${this._renderCalendarIcon()}
|
|
254
|
+
</ui-outlined-text-field>
|
|
255
|
+
${this._renderDropdown()}
|
|
256
|
+
</div>
|
|
257
|
+
`
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
declare global {
|
|
262
|
+
interface HTMLElementTagNameMap {
|
|
263
|
+
'ui-date-picker-input': UiDatePickerInput
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
interface HTMLElementEventMap {
|
|
267
|
+
change: CustomEvent<{
|
|
268
|
+
value: Date | null
|
|
269
|
+
formattedValue: string
|
|
270
|
+
}>
|
|
271
|
+
}
|
|
272
|
+
}
|