@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,440 @@
1
+ import { LitElement, html, TemplateResult } from 'lit'
2
+ import { customElement, property, state } from 'lit/decorators.js'
3
+ import { modalStyles } from './internals/DatePicker.styles.js'
4
+ import { DateRange, formatDate, parseDate } from './internals/DatePickerUtils.js'
5
+ import '../../md/dialog/ui-dialog.js'
6
+ import '../../md/button/ui-button.js'
7
+ import '../../md/icon-button/ui-icon-button.js'
8
+ import '../../md/text-field/ui-outlined-text-field.js'
9
+ import '../../md/icons/ui-icon.js'
10
+
11
+ export interface ModalInputDatePickerChangeEvent {
12
+ range: DateRange
13
+ formattedRange: {
14
+ start: string | null
15
+ end: string | null
16
+ }
17
+ }
18
+
19
+ /**
20
+ * A modal date input picker for manual date entry using keyboard.
21
+ * Ideal for compact layouts and precise date entry. Supports both single
22
+ * date selection and date range selection modes.
23
+ *
24
+ * ## Features
25
+ * - Manual date entry with keyboard input
26
+ * - Single date and date range modes
27
+ * - Input validation with error messages
28
+ * - Min/max date constraints
29
+ * - Multiple date format support (MM/DD/YYYY, MM-DD-YYYY, YYYY-MM-DD)
30
+ * - Accessible design with proper ARIA labels and semantic HTML
31
+ * - Real-time validation feedback
32
+ *
33
+ * ## Events
34
+ *
35
+ * ### `date-input-change`
36
+ * Fired when a valid date is entered in the input fields.
37
+ *
38
+ * **Detail for range mode:**
39
+ * ```typescript
40
+ * {
41
+ * range: DateRange,
42
+ * formattedRange: {
43
+ * start: string | null,
44
+ * end: string | null
45
+ * }
46
+ * }
47
+ * ```
48
+ *
49
+ * **Detail for single date mode:**
50
+ * ```typescript
51
+ * {
52
+ * date: Date,
53
+ * formattedDate: string
54
+ * }
55
+ * ```
56
+ *
57
+ * ### `modal-input-close`
58
+ * Fired when the modal is closed, either by confirmation or cancellation.
59
+ *
60
+ * **Detail:**
61
+ * ```typescript
62
+ * {
63
+ * confirmed: boolean,
64
+ * date?: Date | null, // Available in single date mode
65
+ * range?: DateRange | null // Available in range mode
66
+ * }
67
+ * ```
68
+ *
69
+ * ## Usage
70
+ *
71
+ * ### Range mode (default)
72
+ * ```html
73
+ * <ui-date-picker-modal-input
74
+ * .open=${true}
75
+ * .selectedRange=${{ start: new Date(), end: null }}
76
+ * @date-input-change=${this.handleRangeChange}
77
+ * @modal-input-close=${this.handleClose}
78
+ * ></ui-date-picker-modal-input>
79
+ * ```
80
+ *
81
+ * ### Single date mode
82
+ * ```html
83
+ * <ui-date-picker-modal-input
84
+ * .rangeMode=${false}
85
+ * .selectedDate=${new Date()}
86
+ * @date-input-change=${this.handleDateChange}
87
+ * @modal-input-close=${this.handleClose}
88
+ * ></ui-date-picker-modal-input>
89
+ * ```
90
+ *
91
+ * ### With constraints
92
+ * ```html
93
+ * <ui-date-picker-modal-input
94
+ * .minDate=${new Date('2024-01-01')}
95
+ * .maxDate=${new Date('2024-12-31')}
96
+ * .locale=${'en-US'}
97
+ * ></ui-date-picker-modal-input>
98
+ * ```
99
+ */
100
+ @customElement('ui-date-picker-modal-input')
101
+ export class UiDatePickerModalInput extends LitElement {
102
+ static override styles = modalStyles
103
+
104
+ /**
105
+ * Whether the modal is open
106
+ */
107
+ @property({ type: Boolean }) accessor open = false
108
+
109
+ /**
110
+ * The modal title
111
+ */
112
+ @property({ type: String }) override accessor title = 'Enter dates'
113
+
114
+ /**
115
+ * Whether to use range mode (two date inputs) or single date mode
116
+ */
117
+ @property({ type: Boolean }) accessor rangeMode = false
118
+
119
+ /**
120
+ * Label for the start date (or single date)
121
+ */
122
+ @property({ type: String }) accessor startLabel = 'Start date'
123
+
124
+ /**
125
+ * Label for the end date (only used in range mode)
126
+ */
127
+ @property({ type: String }) accessor endLabel = 'End date'
128
+
129
+ /**
130
+ * Placeholder for date inputs
131
+ */
132
+ @property({ type: String }) accessor placeholder = 'MM/DD/YYYY'
133
+
134
+ /**
135
+ * The selected date (single mode)
136
+ */
137
+ @property({ type: Object }) accessor selectedDate: Date | null = null
138
+
139
+ /**
140
+ * The selected date range (range mode)
141
+ */
142
+ @property({ type: Object }) accessor selectedRange: DateRange | null = null
143
+
144
+ /**
145
+ * Minimum selectable date
146
+ */
147
+ @property({ type: Object }) accessor minDate: Date | undefined = undefined
148
+
149
+ /**
150
+ * Maximum selectable date
151
+ */
152
+ @property({ type: Object }) accessor maxDate: Date | undefined = undefined
153
+
154
+ /**
155
+ * Locale for date formatting
156
+ */
157
+ @property({ type: String }) accessor locale: string | undefined = undefined
158
+
159
+ @state() private accessor startInput = ''
160
+
161
+ @state() private accessor endInput = ''
162
+
163
+ @state() private accessor startError = ''
164
+
165
+ @state() private accessor endError = ''
166
+
167
+ constructor() {
168
+ super()
169
+ // Initialize boolean properties to false first, then set intended defaults
170
+ this.open = false
171
+ this.rangeMode = true // Default to range mode as documented
172
+ }
173
+
174
+ override connectedCallback(): void {
175
+ super.connectedCallback()
176
+ this.updateInputValues()
177
+ }
178
+
179
+ override willUpdate(changedProperties: Map<string | number | symbol, unknown>): void {
180
+ if (changedProperties.has('selectedDate') || changedProperties.has('selectedRange')) {
181
+ this.updateInputValues()
182
+ }
183
+ }
184
+
185
+ private updateInputValues(): void {
186
+ if (this.rangeMode && this.selectedRange) {
187
+ this.startInput = this.selectedRange.start ? formatDate(this.selectedRange.start, this.locale) : ''
188
+ this.endInput = this.selectedRange.end ? formatDate(this.selectedRange.end, this.locale) : ''
189
+ } else if (!this.rangeMode && this.selectedDate) {
190
+ this.startInput = formatDate(this.selectedDate, this.locale)
191
+ }
192
+ }
193
+
194
+ private validateDate(dateString: string, isEndDate = false): Date | null {
195
+ if (!dateString.trim()) return null
196
+
197
+ const parsedDate = parseDate(dateString)
198
+ if (!parsedDate) return null
199
+
200
+ // Check min/max constraints
201
+ if (this.minDate && parsedDate < this.minDate) return null
202
+ if (this.maxDate && parsedDate > this.maxDate) return null
203
+
204
+ // For range mode, validate end date is after start date
205
+ if (this.rangeMode && isEndDate) {
206
+ const startDate = parseDate(this.startInput)
207
+ if (startDate && parsedDate < startDate) return null
208
+ }
209
+
210
+ return parsedDate
211
+ }
212
+
213
+ private handleStartInput(event: Event): void {
214
+ const target = event.target as HTMLInputElement
215
+ this.startInput = target.value
216
+ this.startError = ''
217
+
218
+ if (this.startInput.trim()) {
219
+ const date = this.validateDate(this.startInput)
220
+ if (!date) {
221
+ this.startError = 'Invalid date format or out of range'
222
+ return
223
+ }
224
+
225
+ if (this.rangeMode) {
226
+ const currentRange = this.selectedRange || { start: null, end: null }
227
+ this.selectedRange = { ...currentRange, start: date }
228
+ } else {
229
+ this.selectedDate = date
230
+ }
231
+
232
+ this.dispatchChangeEvent()
233
+ }
234
+ }
235
+
236
+ private handleEndInput(event: Event): void {
237
+ const target = event.target as HTMLInputElement
238
+ this.endInput = target.value
239
+ this.endError = ''
240
+
241
+ if (this.endInput.trim()) {
242
+ const date = this.validateDate(this.endInput, true)
243
+ if (!date) {
244
+ this.endError = 'Invalid date format, out of range, or before start date'
245
+ return
246
+ }
247
+
248
+ const currentRange = this.selectedRange || { start: null, end: null }
249
+ this.selectedRange = { ...currentRange, end: date }
250
+ this.dispatchChangeEvent()
251
+ }
252
+ }
253
+
254
+ private handleCancel(): void {
255
+ this.dispatchCloseEvent(false)
256
+ }
257
+
258
+ private handleConfirm(): void {
259
+ // Validate all inputs before confirming
260
+ let hasErrors = false
261
+
262
+ if (this.rangeMode) {
263
+ if (!this.startInput.trim()) {
264
+ this.startError = 'Start date is required'
265
+ hasErrors = true
266
+ } else if (!this.validateDate(this.startInput)) {
267
+ this.startError = 'Invalid start date'
268
+ hasErrors = true
269
+ }
270
+
271
+ if (!this.endInput.trim()) {
272
+ this.endError = 'End date is required'
273
+ hasErrors = true
274
+ } else if (!this.validateDate(this.endInput, true)) {
275
+ this.endError = 'Invalid end date'
276
+ hasErrors = true
277
+ }
278
+ } else {
279
+ if (!this.startInput.trim()) {
280
+ this.startError = 'Date is required'
281
+ hasErrors = true
282
+ } else if (!this.validateDate(this.startInput)) {
283
+ this.startError = 'Invalid date'
284
+ hasErrors = true
285
+ }
286
+ }
287
+
288
+ if (!hasErrors) {
289
+ this.dispatchCloseEvent(true)
290
+ }
291
+ }
292
+
293
+ private dispatchChangeEvent(): void {
294
+ if (this.rangeMode && this.selectedRange) {
295
+ const event: ModalInputDatePickerChangeEvent = {
296
+ range: this.selectedRange,
297
+ formattedRange: {
298
+ start: this.selectedRange.start ? formatDate(this.selectedRange.start, this.locale) : null,
299
+ end: this.selectedRange.end ? formatDate(this.selectedRange.end, this.locale) : null,
300
+ },
301
+ }
302
+
303
+ this.dispatchEvent(
304
+ new CustomEvent('date-input-change', {
305
+ detail: event,
306
+ bubbles: true,
307
+ composed: true,
308
+ })
309
+ )
310
+ } else if (!this.rangeMode && this.selectedDate) {
311
+ this.dispatchEvent(
312
+ new CustomEvent('date-input-change', {
313
+ detail: {
314
+ date: this.selectedDate,
315
+ formattedDate: formatDate(this.selectedDate, this.locale),
316
+ },
317
+ bubbles: true,
318
+ composed: true,
319
+ })
320
+ )
321
+ }
322
+ }
323
+
324
+ private dispatchCloseEvent(confirmed: boolean): void {
325
+ this.dispatchEvent(
326
+ new CustomEvent('modal-input-close', {
327
+ detail: {
328
+ confirmed,
329
+ date: confirmed && !this.rangeMode ? this.selectedDate : null,
330
+ range: confirmed && this.rangeMode ? this.selectedRange : null,
331
+ },
332
+ bubbles: true,
333
+ composed: true,
334
+ })
335
+ )
336
+ }
337
+
338
+ private renderHeader(): TemplateResult {
339
+ return html`
340
+ <header class="modal-header">
341
+ <h2 id="modal-title" class="modal-title">${this.title}</h2>
342
+ <ui-icon-button @click=${this.handleCancel} aria-label="Close" title="Close">
343
+ <ui-icon icon="close"></ui-icon>
344
+ </ui-icon-button>
345
+ </header>
346
+ `
347
+ }
348
+
349
+ private renderInputs(): TemplateResult {
350
+ return html`
351
+ <main class="modal-content">
352
+ <div class="input-container" role="form" aria-labelledby="modal-title">
353
+ <fieldset>
354
+ <legend class="visually-hidden">${this.rangeMode ? 'Enter date range' : 'Enter date'}</legend>
355
+
356
+ <ui-outlined-text-field
357
+ .label=${this.rangeMode ? this.startLabel : 'Date'}
358
+ .placeholder=${this.placeholder}
359
+ .value=${this.startInput}
360
+ .invalidText=${this.startError}
361
+ ?invalid=${!!this.startError}
362
+ @input=${this.handleStartInput}
363
+ required
364
+ aria-describedby="format-help"
365
+ >
366
+ <ui-icon slot="suffix" icon="calendarToday"></ui-icon>
367
+ </ui-outlined-text-field>
368
+
369
+ ${this.rangeMode
370
+ ? html`
371
+ <ui-outlined-text-field
372
+ .label=${this.endLabel}
373
+ .placeholder=${this.placeholder}
374
+ .value=${this.endInput}
375
+ .invalidText=${this.endError}
376
+ ?invalid=${!!this.endError}
377
+ @input=${this.handleEndInput}
378
+ required
379
+ aria-describedby="format-help"
380
+ >
381
+ <ui-icon slot="suffix" icon="calendarToday"></ui-icon>
382
+ </ui-outlined-text-field>
383
+ `
384
+ : ''}
385
+ </fieldset>
386
+
387
+ <aside id="format-help" class="format-help" role="note" aria-label="Date format information">
388
+ <p class="help-title">
389
+ <strong>Supported formats:</strong><br />
390
+ MM/DD/YYYY, MM-DD-YYYY, YYYY-MM-DD
391
+ </p>
392
+ <p class="help-examples"><strong>Examples:</strong> 12/25/2024, 12-25-2024, 2024-12-25</p>
393
+ </aside>
394
+ </div>
395
+ </main>
396
+ `
397
+ }
398
+
399
+ private renderActions(): TemplateResult {
400
+ const hasValidInput = this.rangeMode
401
+ ? this.startInput.trim() && this.endInput.trim() && !this.startError && !this.endError
402
+ : this.startInput.trim() && !this.startError
403
+
404
+ return html`
405
+ <footer class="modal-actions">
406
+ <ui-button color="text" @click=${this.handleCancel}>Cancel</ui-button>
407
+ <ui-button color="filled" @click=${this.handleConfirm} .disabled=${!hasValidInput}> Confirm </ui-button>
408
+ </footer>
409
+ `
410
+ }
411
+
412
+ override render(): TemplateResult {
413
+ return html`
414
+ <ui-dialog .open=${this.open} @close=${this.handleCancel}>
415
+ ${this.renderHeader()} ${this.renderInputs()} ${this.renderActions()}
416
+ </ui-dialog>
417
+ `
418
+ }
419
+ }
420
+
421
+ declare global {
422
+ interface HTMLElementTagNameMap {
423
+ 'ui-date-picker-modal-input': UiDatePickerModalInput
424
+ }
425
+
426
+ interface HTMLElementEventMap {
427
+ 'date-input-change': CustomEvent<
428
+ | ModalInputDatePickerChangeEvent
429
+ | {
430
+ date: Date
431
+ formattedDate: string
432
+ }
433
+ >
434
+ 'modal-input-close': CustomEvent<{
435
+ confirmed: boolean
436
+ date?: Date | null
437
+ range?: DateRange | null
438
+ }>
439
+ }
440
+ }