@api-client/ui 0.5.5 → 0.5.7

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 (114) hide show
  1. package/.cursor/rules/html-and-css-best-practices.mdc +63 -0
  2. package/.cursor/rules/lit-best-practices.mdc +78 -0
  3. package/.github/instructions/html-and-css-best-practices.instructions.md +70 -0
  4. package/.github/instructions/lit-best-practices.instructions.md +86 -0
  5. package/build/src/elements/currency/currency-picker.d.ts +10 -0
  6. package/build/src/elements/currency/currency-picker.d.ts.map +1 -0
  7. package/build/src/elements/currency/currency-picker.js +27 -0
  8. package/build/src/elements/currency/currency-picker.js.map +1 -0
  9. package/build/src/elements/currency/internals/Picker.d.ts +311 -0
  10. package/build/src/elements/currency/internals/Picker.d.ts.map +1 -0
  11. package/build/src/elements/currency/internals/Picker.js +857 -0
  12. package/build/src/elements/currency/internals/Picker.js.map +1 -0
  13. package/build/src/elements/currency/internals/Picker.styles.d.ts +3 -0
  14. package/build/src/elements/currency/internals/Picker.styles.d.ts.map +1 -0
  15. package/build/src/elements/currency/internals/Picker.styles.js +58 -0
  16. package/build/src/elements/currency/internals/Picker.styles.js.map +1 -0
  17. package/build/src/elements/highlight/MarkdownStyles.d.ts.map +1 -1
  18. package/build/src/elements/highlight/MarkdownStyles.js +0 -13
  19. package/build/src/elements/highlight/MarkdownStyles.js.map +1 -1
  20. package/build/src/elements/http/BodyEditor.d.ts +0 -13
  21. package/build/src/elements/http/BodyEditor.d.ts.map +1 -1
  22. package/build/src/elements/http/BodyEditor.js +0 -13
  23. package/build/src/elements/http/BodyEditor.js.map +1 -1
  24. package/build/src/elements/http/BodyTextEditor.d.ts +0 -13
  25. package/build/src/elements/http/BodyTextEditor.d.ts.map +1 -1
  26. package/build/src/elements/http/BodyTextEditor.js +0 -13
  27. package/build/src/elements/http/BodyTextEditor.js.map +1 -1
  28. package/build/src/elements/http/BodyUrlEncodedEditor.d.ts +0 -13
  29. package/build/src/elements/http/BodyUrlEncodedEditor.d.ts.map +1 -1
  30. package/build/src/elements/http/BodyUrlEncodedEditor.js +0 -13
  31. package/build/src/elements/http/BodyUrlEncodedEditor.js.map +1 -1
  32. package/build/src/elements/http/UrlInput.d.ts +0 -13
  33. package/build/src/elements/http/UrlInput.d.ts.map +1 -1
  34. package/build/src/elements/http/UrlInput.js +0 -13
  35. package/build/src/elements/http/UrlInput.js.map +1 -1
  36. package/build/src/index.d.ts +2 -0
  37. package/build/src/index.d.ts.map +1 -1
  38. package/build/src/index.js +2 -0
  39. package/build/src/index.js.map +1 -1
  40. package/build/src/md/button/internals/base.d.ts +1 -0
  41. package/build/src/md/button/internals/base.d.ts.map +1 -1
  42. package/build/src/md/button/internals/base.js +7 -0
  43. package/build/src/md/button/internals/base.js.map +1 -1
  44. package/build/src/md/button/internals/button.styles.js +1 -1
  45. package/build/src/md/button/internals/button.styles.js.map +1 -1
  46. package/build/src/md/date/internals/DateTime.d.ts +0 -13
  47. package/build/src/md/date/internals/DateTime.d.ts.map +1 -1
  48. package/build/src/md/date/internals/DateTime.js +0 -13
  49. package/build/src/md/date/internals/DateTime.js.map +1 -1
  50. package/build/src/md/date-picker/index.d.ts +13 -0
  51. package/build/src/md/date-picker/index.d.ts.map +1 -0
  52. package/build/src/md/date-picker/index.js +13 -0
  53. package/build/src/md/date-picker/index.js.map +1 -0
  54. package/build/src/md/date-picker/internals/DatePicker.styles.d.ts +4 -0
  55. package/build/src/md/date-picker/internals/DatePicker.styles.d.ts.map +1 -0
  56. package/build/src/md/date-picker/internals/DatePicker.styles.js +409 -0
  57. package/build/src/md/date-picker/internals/DatePicker.styles.js.map +1 -0
  58. package/build/src/md/date-picker/internals/DatePickerCalendar.d.ts +272 -0
  59. package/build/src/md/date-picker/internals/DatePickerCalendar.d.ts.map +1 -0
  60. package/build/src/md/date-picker/internals/DatePickerCalendar.js +1062 -0
  61. package/build/src/md/date-picker/internals/DatePickerCalendar.js.map +1 -0
  62. package/build/src/md/date-picker/internals/DatePickerUtils.d.ts +93 -0
  63. package/build/src/md/date-picker/internals/DatePickerUtils.d.ts.map +1 -0
  64. package/build/src/md/date-picker/internals/DatePickerUtils.js +221 -0
  65. package/build/src/md/date-picker/internals/DatePickerUtils.js.map +1 -0
  66. package/build/src/md/date-picker/ui-date-picker-input.d.ts +160 -0
  67. package/build/src/md/date-picker/ui-date-picker-input.d.ts.map +1 -0
  68. package/build/src/md/date-picker/ui-date-picker-input.js +464 -0
  69. package/build/src/md/date-picker/ui-date-picker-input.js.map +1 -0
  70. package/build/src/md/date-picker/ui-date-picker-modal-input.d.ts +178 -0
  71. package/build/src/md/date-picker/ui-date-picker-modal-input.d.ts.map +1 -0
  72. package/build/src/md/date-picker/ui-date-picker-modal-input.js +538 -0
  73. package/build/src/md/date-picker/ui-date-picker-modal-input.js.map +1 -0
  74. package/build/src/md/date-picker/ui-date-picker-modal.d.ts +156 -0
  75. package/build/src/md/date-picker/ui-date-picker-modal.d.ts.map +1 -0
  76. package/build/src/md/date-picker/ui-date-picker-modal.js +423 -0
  77. package/build/src/md/date-picker/ui-date-picker-modal.js.map +1 -0
  78. package/build/src/md/dialog/internals/Dialog.styles.d.ts.map +1 -1
  79. package/build/src/md/dialog/internals/Dialog.styles.js +1 -0
  80. package/build/src/md/dialog/internals/Dialog.styles.js.map +1 -1
  81. package/demo/elements/currency/index.html +91 -0
  82. package/demo/elements/currency/index.ts +272 -0
  83. package/demo/elements/har/har2.json +1 -1
  84. package/demo/elements/index.html +3 -0
  85. package/demo/md/date-picker/date-picker.ts +336 -0
  86. package/demo/md/date-picker/index.html +171 -0
  87. package/demo/md/index.html +2 -0
  88. package/package.json +1 -1
  89. package/src/elements/currency/currency-picker.ts +14 -0
  90. package/src/elements/currency/internals/Picker.styles.ts +58 -0
  91. package/src/elements/currency/internals/Picker.ts +846 -0
  92. package/src/elements/highlight/MarkdownStyles.ts +0 -13
  93. package/src/elements/http/BodyEditor.ts +0 -13
  94. package/src/elements/http/BodyTextEditor.ts +0 -13
  95. package/src/elements/http/BodyUrlEncodedEditor.ts +0 -13
  96. package/src/elements/http/UrlInput.ts +0 -13
  97. package/src/index.ts +17 -0
  98. package/src/md/button/internals/base.ts +7 -0
  99. package/src/md/button/internals/button.styles.ts +1 -1
  100. package/src/md/date/internals/DateTime.ts +0 -14
  101. package/src/md/date-picker/README.md +184 -0
  102. package/src/md/date-picker/index.ts +17 -0
  103. package/src/md/date-picker/internals/DatePicker.styles.ts +411 -0
  104. package/src/md/date-picker/internals/DatePickerCalendar.ts +1031 -0
  105. package/src/md/date-picker/internals/DatePickerUtils.ts +288 -0
  106. package/src/md/date-picker/ui-date-picker-input.ts +333 -0
  107. package/src/md/date-picker/ui-date-picker-modal-input.ts +440 -0
  108. package/src/md/date-picker/ui-date-picker-modal.ts +346 -0
  109. package/src/md/dialog/internals/Dialog.styles.ts +1 -0
  110. package/test/README.md +3 -2
  111. package/test/elements/currency/CurrencyPicker.accessibility.test.ts +328 -0
  112. package/test/elements/currency/CurrencyPicker.core.test.ts +318 -0
  113. package/test/elements/currency/CurrencyPicker.integration.test.ts +482 -0
  114. package/test/elements/currency/CurrencyPicker.test.ts +486 -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,333 @@
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
+ * ## Features
15
+ * - Text field input with calendar dropdown
16
+ * - Keyboard navigation support (Arrow Down, Enter, Escape)
17
+ * - Custom date formatting
18
+ * - Input validation and error handling
19
+ * - Min/max date constraints
20
+ * - Disabled dates support
21
+ * - Accessible design with proper ARIA attributes
22
+ * - CSS Anchor Positioning API for dropdown placement
23
+ *
24
+ * ## Events
25
+ *
26
+ * ### `change`
27
+ * Fired when the selected date changes.
28
+ *
29
+ * **Detail:**
30
+ * ```typescript
31
+ * {
32
+ * value: Date | null,
33
+ * formattedValue: string
34
+ * }
35
+ * ```
36
+ *
37
+ * ## Usage
38
+ *
39
+ * ### Basic usage
40
+ * ```html
41
+ * <ui-date-picker-input
42
+ * label="Select date"
43
+ * name="birthDate"
44
+ * placeholder="MM/DD/YYYY"
45
+ * .value=${new Date()}
46
+ * @change=${this.handleDateChange}
47
+ * ></ui-date-picker-input>
48
+ * ```
49
+ *
50
+ * ### With validation
51
+ * ```html
52
+ * <ui-date-picker-input
53
+ * label="Birth date"
54
+ * name="birthDate"
55
+ * required
56
+ * .error=${this.hasError}
57
+ * .errorMessage=${"Please select a valid date"}
58
+ * .minDate=${new Date('1900-01-01')}
59
+ * .maxDate=${new Date()}
60
+ * ></ui-date-picker-input>
61
+ * ```
62
+ *
63
+ * ### Custom date format
64
+ * ```html
65
+ * <ui-date-picker-input
66
+ * label="Birth date"
67
+ * .dateFormat=${date => date.toLocaleDateString('en-GB')}
68
+ * ></ui-date-picker-input>
69
+ * ```
70
+ *
71
+ * ### With disabled dates
72
+ * ```html
73
+ * <ui-date-picker-input
74
+ * label="Appointment date"
75
+ * .disabledDates=${[new Date('2024-12-25'), new Date('2024-01-01')]}
76
+ * ></ui-date-picker-input>
77
+ * ```
78
+ */
79
+ @customElement('ui-date-picker-input')
80
+ export class UiDatePickerInput extends LitElement {
81
+ static override styles = inputStyles
82
+ static override shadowRootOptions: ShadowRootInit = {
83
+ mode: 'open',
84
+ delegatesFocus: true,
85
+ }
86
+
87
+ /**
88
+ * The label for the input field
89
+ */
90
+ @property({ type: String }) accessor label = ''
91
+
92
+ /**
93
+ * The name attribute for the input field (for form handling)
94
+ */
95
+ @property({ type: String }) accessor name = ''
96
+
97
+ /**
98
+ * Placeholder text for the input
99
+ */
100
+ @property({ type: String }) accessor placeholder = 'MM/DD/YYYY'
101
+
102
+ /**
103
+ * The currently selected date
104
+ */
105
+ @property({ type: Object }) accessor value: Date | null = null
106
+
107
+ /**
108
+ * Whether the date picker is disabled
109
+ */
110
+ @property({ type: Boolean }) accessor disabled = false
111
+
112
+ /**
113
+ * Whether the input is required
114
+ */
115
+ @property({ type: Boolean }) accessor required = false
116
+
117
+ /**
118
+ * Error message to display
119
+ */
120
+ @property({ type: String }) accessor errorMessage = ''
121
+
122
+ /**
123
+ * Whether the input has an error state
124
+ */
125
+ @property({ type: Boolean }) accessor error = false
126
+
127
+ /**
128
+ * Minimum selectable date
129
+ */
130
+ @property({ type: Object }) accessor minDate: Date | undefined = undefined
131
+
132
+ /**
133
+ * Maximum selectable date
134
+ */
135
+ @property({ type: Object }) accessor maxDate: Date | undefined = undefined
136
+
137
+ /**
138
+ * Array of disabled dates
139
+ */
140
+ @property({ type: Array }) accessor disabledDates: Date[] | undefined = undefined
141
+
142
+ /**
143
+ * Locale for date formatting
144
+ */
145
+ @property({ type: String }) accessor locale: string | undefined = undefined
146
+
147
+ /**
148
+ * Custom date format function
149
+ */
150
+ @property({ type: Object }) accessor dateFormat: ((date: Date) => string) | undefined = undefined
151
+
152
+ @state() private accessor isOpen = false
153
+
154
+ @state() private accessor inputValue = ''
155
+
156
+ @query('ui-outlined-text-field') private accessor textField!: HTMLElement
157
+
158
+ constructor() {
159
+ super()
160
+ // Initialize boolean properties to false as per Lit best practices
161
+ this.disabled = false
162
+ this.required = false
163
+ this.error = false
164
+ }
165
+
166
+ override connectedCallback(): void {
167
+ super.connectedCallback()
168
+ this.updateInputValue()
169
+ document.addEventListener('click', this.handleDocumentClick.bind(this))
170
+ }
171
+
172
+ override disconnectedCallback(): void {
173
+ super.disconnectedCallback()
174
+ document.removeEventListener('click', this.handleDocumentClick.bind(this))
175
+ }
176
+
177
+ override willUpdate(changedProperties: Map<string | number | symbol, unknown>): void {
178
+ if (changedProperties.has('value')) {
179
+ this.updateInputValue()
180
+ }
181
+ }
182
+
183
+ override updated(changedProperties: Map<string | number | symbol, unknown>): void {
184
+ if (changedProperties.has('isOpen') && this.isOpen) {
185
+ // Set anchor name on text field for CSS Anchor Positioning API
186
+ if (this.textField) {
187
+ // Using setProperty since anchorName is not in TypeScript types yet
188
+ this.textField.style.setProperty('anchor-name', '--ui-date-picker-anchor')
189
+ }
190
+ }
191
+ }
192
+
193
+ private updateInputValue(): void {
194
+ if (this.value) {
195
+ this.inputValue = this.dateFormat ? this.dateFormat(this.value) : formatDate(this.value, this.locale)
196
+ } else {
197
+ this.inputValue = ''
198
+ }
199
+ }
200
+
201
+ private handleDocumentClick(event: Event): void {
202
+ if (!this.contains(event.target as Node)) {
203
+ this.isOpen = false
204
+ }
205
+ }
206
+
207
+ private handleInputClick(): void {
208
+ if (!this.disabled) {
209
+ this.isOpen = !this.isOpen
210
+ }
211
+ }
212
+
213
+ private handleInputChange(event: Event): void {
214
+ const target = event.target as HTMLInputElement
215
+ this.inputValue = target.value
216
+
217
+ const parsedDate = parseDate(this.inputValue)
218
+ if (parsedDate) {
219
+ this.value = parsedDate
220
+ this.dispatchChangeEvent()
221
+ }
222
+ }
223
+
224
+ private handleCalendarDateSelect(event: CustomEvent<DateSelectEvent>): void {
225
+ this.value = event.detail.date
226
+ this.isOpen = false
227
+ this.dispatchChangeEvent()
228
+ }
229
+
230
+ private handleCalendarDateCancel(): void {
231
+ this.isOpen = false
232
+ }
233
+
234
+ private handleKeyDown(event: KeyboardEvent): void {
235
+ switch (event.key) {
236
+ case 'Escape':
237
+ this.isOpen = false
238
+ break
239
+ case 'ArrowDown':
240
+ if (!this.isOpen) {
241
+ event.preventDefault()
242
+ this.isOpen = true
243
+ }
244
+ break
245
+ case 'Enter':
246
+ if (this.isOpen) {
247
+ event.preventDefault()
248
+ this.isOpen = false
249
+ }
250
+ break
251
+ }
252
+ }
253
+
254
+ private dispatchChangeEvent(): void {
255
+ this.dispatchEvent(
256
+ new CustomEvent('change', {
257
+ detail: {
258
+ value: this.value,
259
+ formattedValue: this.inputValue,
260
+ },
261
+ bubbles: true,
262
+ composed: true,
263
+ })
264
+ )
265
+ }
266
+
267
+ private renderCalendarIcon(): TemplateResult {
268
+ return html`
269
+ <ui-icon slot="suffix" icon="calendarToday" @click=${this.handleInputClick} class="calendar-icon"></ui-icon>
270
+ `
271
+ }
272
+
273
+ private renderDropdown(): TemplateResult | null {
274
+ if (!this.isOpen) return null
275
+
276
+ const currentDate = this.value || new Date()
277
+
278
+ return html`
279
+ <div class="dropdown-container">
280
+ <ui-date-picker-calendar
281
+ .year=${currentDate.getFullYear()}
282
+ .month=${currentDate.getMonth()}
283
+ .selectedDate=${this.value}
284
+ .minDate=${this.minDate}
285
+ .maxDate=${this.maxDate}
286
+ .disabledDates=${this.disabledDates}
287
+ .locale=${this.locale}
288
+ showActions
289
+ @date-select=${this.handleCalendarDateSelect}
290
+ @date-cancel=${this.handleCalendarDateCancel}
291
+ ></ui-date-picker-calendar>
292
+ </div>
293
+ `
294
+ }
295
+
296
+ override render(): TemplateResult {
297
+ return html`
298
+ <div class="input-container">
299
+ ${this.isOpen ? html`<div class="backdrop" @click=${() => (this.isOpen = false)}></div>` : ''}
300
+ <ui-outlined-text-field
301
+ .label=${this.label}
302
+ .name=${this.name}
303
+ .placeholder=${this.placeholder}
304
+ .value=${this.inputValue}
305
+ .disabled=${this.disabled}
306
+ .required=${this.required}
307
+ ?invalid=${this.error}
308
+ .invalidText=${this.errorMessage}
309
+ readonly
310
+ @click=${this.handleInputClick}
311
+ @input=${this.handleInputChange}
312
+ @keydown=${this.handleKeyDown}
313
+ >
314
+ ${this.renderCalendarIcon()}
315
+ </ui-outlined-text-field>
316
+ ${this.renderDropdown()}
317
+ </div>
318
+ `
319
+ }
320
+ }
321
+
322
+ declare global {
323
+ interface HTMLElementTagNameMap {
324
+ 'ui-date-picker-input': UiDatePickerInput
325
+ }
326
+
327
+ interface HTMLElementEventMap {
328
+ change: CustomEvent<{
329
+ value: Date | null
330
+ formattedValue: string
331
+ }>
332
+ }
333
+ }