@oslokommune/punkt-elements 13.6.5 → 13.6.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.
- package/CHANGELOG.md +34 -0
- package/dist/{calendar-ChphTIhk.js → calendar-CodWwTHM.js} +503 -471
- package/dist/calendar-DH-fCGyW.cjs +115 -0
- package/dist/{combobox-COuxR036.cjs → combobox-CNH-j9Za.cjs} +7 -7
- package/dist/{combobox-D3aPvdrE.js → combobox-C_pTRtsQ.js} +181 -180
- package/dist/datepicker-BEMo4X9s.js +770 -0
- package/dist/datepicker-n49TAIAt.cjs +169 -0
- package/dist/index.d.ts +7 -1
- package/dist/pkt-calendar.cjs +1 -1
- package/dist/pkt-calendar.js +1 -1
- package/dist/pkt-combobox.cjs +1 -1
- package/dist/pkt-combobox.js +1 -1
- package/dist/pkt-datepicker.cjs +1 -1
- package/dist/pkt-datepicker.js +1 -1
- package/dist/pkt-index.cjs +1 -1
- package/dist/pkt-index.js +4 -4
- package/package.json +2 -2
- package/src/components/calendar/calendar.ts +26 -28
- package/src/components/combobox/combobox.test.ts +22 -0
- package/src/components/combobox/combobox.ts +19 -8
- package/src/components/datepicker/date-tags.ts +2 -1
- package/src/components/datepicker/datepicker-utils.ts +475 -0
- package/src/components/datepicker/datepicker.selection.test.ts +62 -1
- package/src/components/datepicker/datepicker.ts +157 -271
- package/dist/calendar-CL9O0tLP.cjs +0 -115
- package/dist/datepicker-CV73pLqD.cjs +0 -169
- package/dist/datepicker-Ca552mbJ.js +0 -531
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
import {
|
|
2
|
+
fromISOToDate,
|
|
3
|
+
newDate,
|
|
4
|
+
isValidDateRange,
|
|
5
|
+
sortDateStrings,
|
|
6
|
+
filterSelectableDates,
|
|
7
|
+
} from '@/utils/dateutils'
|
|
8
|
+
import { Ref } from 'lit/directives/ref.js'
|
|
9
|
+
import { PktCalendar } from '@/components/calendar/calendar'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Utility functions for PktDatepicker
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Sleep utility function for async delays
|
|
17
|
+
*/
|
|
18
|
+
export const sleep = (ms: number): Promise<void> =>
|
|
19
|
+
new Promise((resolve) => setTimeout(resolve, ms))
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Device detection utilities
|
|
23
|
+
*/
|
|
24
|
+
export const deviceDetection = {
|
|
25
|
+
/**
|
|
26
|
+
* Detects if the current device is iOS (iPhone, iPad, iPod)
|
|
27
|
+
*/
|
|
28
|
+
isIOS(): boolean {
|
|
29
|
+
const ua = navigator.userAgent
|
|
30
|
+
return /iP(hone|od|ad)/.test(ua)
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Detects if the current device is Mobile Safari
|
|
35
|
+
*/
|
|
36
|
+
isMobileSafari(): boolean {
|
|
37
|
+
const ua = navigator.userAgent
|
|
38
|
+
return /iP(hone|od|ad)/.test(ua) && /Safari/.test(ua) && !/CriOS|FxiOS/.test(ua)
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Value parsing and validation utilities
|
|
44
|
+
*/
|
|
45
|
+
export const valueUtils = {
|
|
46
|
+
/**
|
|
47
|
+
* Parses a value (string or string array) into an array of strings
|
|
48
|
+
*/
|
|
49
|
+
parseValue(value: string | string[] | null | undefined): string[] {
|
|
50
|
+
if (!value) return []
|
|
51
|
+
if (Array.isArray(value)) {
|
|
52
|
+
return value.filter(Boolean)
|
|
53
|
+
}
|
|
54
|
+
if (typeof value === 'string') {
|
|
55
|
+
return value.split(',').filter(Boolean)
|
|
56
|
+
}
|
|
57
|
+
return String(value).split(',').filter(Boolean)
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Converts array of dates to comma-separated string
|
|
62
|
+
*/
|
|
63
|
+
formatValue(values: string[]): string {
|
|
64
|
+
return values.join(',')
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Ensures name attribute ends with [] for multiple/range inputs
|
|
69
|
+
*/
|
|
70
|
+
normalizeNameForMultiple(
|
|
71
|
+
name: string | null,
|
|
72
|
+
isMultiple: boolean,
|
|
73
|
+
isRange: boolean,
|
|
74
|
+
): string | null {
|
|
75
|
+
if (!name) return null
|
|
76
|
+
if ((isMultiple || isRange) && !name.endsWith('[]')) {
|
|
77
|
+
return name + '[]'
|
|
78
|
+
}
|
|
79
|
+
return name
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Converts string arrays to proper arrays if they come as strings
|
|
84
|
+
*/
|
|
85
|
+
normalizeStringArray(value: string | string[]): string[] {
|
|
86
|
+
if (typeof value === 'string') {
|
|
87
|
+
return value.split(',').filter(Boolean)
|
|
88
|
+
}
|
|
89
|
+
return Array.isArray(value) ? value : []
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Validates that a range has valid order (start <= end)
|
|
94
|
+
*/
|
|
95
|
+
validateRangeOrder(values: string[]): boolean {
|
|
96
|
+
if (!values || values.length !== 2) return true // Not a complete range
|
|
97
|
+
return isValidDateRange(values[0], values[1])
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Sorts date strings chronologically
|
|
102
|
+
*/
|
|
103
|
+
sortDates(dates: string[]): string[] {
|
|
104
|
+
return sortDateStrings(dates)
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Filters dates to only include selectable ones based on constraints
|
|
109
|
+
*/
|
|
110
|
+
filterSelectableDates(
|
|
111
|
+
dates: string[],
|
|
112
|
+
min?: string | null,
|
|
113
|
+
max?: string | null,
|
|
114
|
+
excludedDates?: string[],
|
|
115
|
+
excludedWeekdays?: string[],
|
|
116
|
+
): string[] {
|
|
117
|
+
return filterSelectableDates(dates, min, max, excludedDates, excludedWeekdays)
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Input type detection utilities
|
|
123
|
+
*/
|
|
124
|
+
export const inputTypeUtils = {
|
|
125
|
+
/**
|
|
126
|
+
* Determines the appropriate input type based on device
|
|
127
|
+
* Mobile Safari does not play well with type="date" amd custom datepickers
|
|
128
|
+
*/
|
|
129
|
+
getInputType(): string {
|
|
130
|
+
return deviceDetection.isIOS() ? 'text' : 'date'
|
|
131
|
+
},
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Form and validation utilities
|
|
136
|
+
*/
|
|
137
|
+
export const formUtils = {
|
|
138
|
+
/**
|
|
139
|
+
* Submits the form that contains the given element
|
|
140
|
+
*/
|
|
141
|
+
submitForm(element: HTMLElement): void {
|
|
142
|
+
const form = (element as any).internals?.form as HTMLFormElement
|
|
143
|
+
if (form) {
|
|
144
|
+
form.requestSubmit()
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Submits form if available, otherwise executes fallback action
|
|
150
|
+
*/
|
|
151
|
+
submitFormOrFallback(internals: any, fallbackAction: () => void): void {
|
|
152
|
+
const form = internals?.form as HTMLFormElement
|
|
153
|
+
if (form) {
|
|
154
|
+
form.requestSubmit()
|
|
155
|
+
} else {
|
|
156
|
+
fallbackAction()
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Validates a date input and sets validity state
|
|
162
|
+
*/
|
|
163
|
+
validateDateInput(
|
|
164
|
+
input: HTMLInputElement,
|
|
165
|
+
internals: any,
|
|
166
|
+
min?: string | null,
|
|
167
|
+
max?: string | null,
|
|
168
|
+
strings?: any,
|
|
169
|
+
): void {
|
|
170
|
+
const value = input.value
|
|
171
|
+
if (!value) return
|
|
172
|
+
|
|
173
|
+
if (min && min > value) {
|
|
174
|
+
internals.setValidity(
|
|
175
|
+
{ rangeUnderflow: true },
|
|
176
|
+
strings?.forms?.messages?.rangeUnderflow || 'Value is below minimum',
|
|
177
|
+
input,
|
|
178
|
+
)
|
|
179
|
+
} else if (max && max < value) {
|
|
180
|
+
internals.setValidity(
|
|
181
|
+
{ rangeOverflow: true },
|
|
182
|
+
strings?.forms?.messages?.rangeOverflow || 'Value is above maximum',
|
|
183
|
+
input,
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Calendar interaction utilities
|
|
191
|
+
*/
|
|
192
|
+
export const calendarUtils = {
|
|
193
|
+
/**
|
|
194
|
+
* Adds a date to selected dates if it's valid
|
|
195
|
+
*/
|
|
196
|
+
addToSelected(
|
|
197
|
+
event: Event | KeyboardEvent,
|
|
198
|
+
calendarRef: Ref<PktCalendar>,
|
|
199
|
+
min?: string | null,
|
|
200
|
+
max?: string | null,
|
|
201
|
+
): void {
|
|
202
|
+
const target = event.target as HTMLInputElement
|
|
203
|
+
if (!target.value) return
|
|
204
|
+
|
|
205
|
+
const minAsDate = min ? newDate(min) : null
|
|
206
|
+
const maxAsDate = max ? newDate(max) : null
|
|
207
|
+
const date = newDate(target.value.split(',')[0])
|
|
208
|
+
|
|
209
|
+
if (
|
|
210
|
+
date &&
|
|
211
|
+
!isNaN(date.getTime()) &&
|
|
212
|
+
(!minAsDate || date >= minAsDate) &&
|
|
213
|
+
(!maxAsDate || date <= maxAsDate) &&
|
|
214
|
+
calendarRef.value
|
|
215
|
+
) {
|
|
216
|
+
calendarRef.value.handleDateSelect(date)
|
|
217
|
+
}
|
|
218
|
+
target.value = ''
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Handles calendar positioning based on viewport and input position
|
|
223
|
+
*/
|
|
224
|
+
handleCalendarPosition(
|
|
225
|
+
popupRef: Ref<HTMLDivElement>,
|
|
226
|
+
inputRef: Ref<HTMLInputElement>,
|
|
227
|
+
hasCounter: boolean = false,
|
|
228
|
+
): void {
|
|
229
|
+
if (!popupRef.value || !inputRef.value) return
|
|
230
|
+
|
|
231
|
+
const inputRect =
|
|
232
|
+
inputRef.value.parentElement?.getBoundingClientRect() ||
|
|
233
|
+
inputRef.value.getBoundingClientRect()
|
|
234
|
+
|
|
235
|
+
const inputHeight = hasCounter ? inputRect.height + 30 : inputRect.height
|
|
236
|
+
const popupHeight = popupRef.value.getBoundingClientRect().height
|
|
237
|
+
|
|
238
|
+
let top = hasCounter ? 'calc(100% - 30px)' : '100%'
|
|
239
|
+
|
|
240
|
+
if (
|
|
241
|
+
inputRect &&
|
|
242
|
+
inputRect.top + popupHeight > window.innerHeight &&
|
|
243
|
+
inputRect.top - popupHeight > 0
|
|
244
|
+
) {
|
|
245
|
+
top = `calc(100% - ${inputHeight}px - ${popupHeight}px)`
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
popupRef.value.style.top = top
|
|
249
|
+
},
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Event handling utilities
|
|
254
|
+
*/
|
|
255
|
+
export const eventUtils = {
|
|
256
|
+
/**
|
|
257
|
+
* Creates a document click listener for closing calendar
|
|
258
|
+
*/
|
|
259
|
+
createDocumentClickListener(
|
|
260
|
+
inputRef: Ref<HTMLInputElement>,
|
|
261
|
+
inputRefTo: Ref<HTMLInputElement> | null,
|
|
262
|
+
btnRef: Ref<HTMLButtonElement>,
|
|
263
|
+
getCalendarOpen: () => boolean,
|
|
264
|
+
onBlur: () => void,
|
|
265
|
+
hideCalendar: () => void,
|
|
266
|
+
): (e: MouseEvent) => void {
|
|
267
|
+
return (e: MouseEvent) => {
|
|
268
|
+
if (
|
|
269
|
+
inputRef?.value &&
|
|
270
|
+
btnRef?.value &&
|
|
271
|
+
!inputRef.value.contains(e.target as Node) &&
|
|
272
|
+
!(inputRefTo?.value && inputRefTo.value.contains(e.target as Node)) &&
|
|
273
|
+
!btnRef.value.contains(e.target as Node) &&
|
|
274
|
+
!(e.target as Element).closest('.pkt-calendar-popup') &&
|
|
275
|
+
getCalendarOpen()
|
|
276
|
+
) {
|
|
277
|
+
onBlur()
|
|
278
|
+
hideCalendar()
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Creates a document keydown listener for ESC key
|
|
285
|
+
*/
|
|
286
|
+
createDocumentKeydownListener(
|
|
287
|
+
getCalendarOpen: () => boolean,
|
|
288
|
+
hideCalendar: () => void,
|
|
289
|
+
): (e: KeyboardEvent) => void {
|
|
290
|
+
return (e: KeyboardEvent) => {
|
|
291
|
+
if (e.key === 'Escape' && getCalendarOpen()) {
|
|
292
|
+
hideCalendar()
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Handles focus out events for calendar popup
|
|
299
|
+
*/
|
|
300
|
+
handleFocusOut(
|
|
301
|
+
event: FocusEvent,
|
|
302
|
+
element: HTMLElement,
|
|
303
|
+
onBlur: () => void,
|
|
304
|
+
hideCalendar: () => void,
|
|
305
|
+
): void {
|
|
306
|
+
if (!element.contains(event.target as Node)) {
|
|
307
|
+
onBlur()
|
|
308
|
+
hideCalendar()
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* CSS class utilities
|
|
315
|
+
*/
|
|
316
|
+
export const cssUtils = {
|
|
317
|
+
/**
|
|
318
|
+
* Generates input classes for datepicker
|
|
319
|
+
*/
|
|
320
|
+
getInputClasses(fullwidth: boolean, showRangeLabels: boolean, multiple: boolean, range: boolean) {
|
|
321
|
+
return {
|
|
322
|
+
'pkt-input': true,
|
|
323
|
+
'pkt-datepicker__input': true,
|
|
324
|
+
'pkt-input--fullwidth': fullwidth,
|
|
325
|
+
'pkt-datepicker--hasrangelabels': showRangeLabels,
|
|
326
|
+
'pkt-datepicker--multiple': multiple,
|
|
327
|
+
'pkt-datepicker--range': range,
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Generates button classes for datepicker
|
|
333
|
+
*/
|
|
334
|
+
getButtonClasses() {
|
|
335
|
+
return {
|
|
336
|
+
'pkt-input-icon': true,
|
|
337
|
+
'pkt-btn': true,
|
|
338
|
+
'pkt-btn--icon-only': true,
|
|
339
|
+
'pkt-btn--tertiary': true,
|
|
340
|
+
'pkt-datepicker__calendar-button': true,
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Generates range label classes
|
|
346
|
+
*/
|
|
347
|
+
getRangeLabelClasses(showRangeLabels: boolean) {
|
|
348
|
+
return {
|
|
349
|
+
'pkt-input-prefix': showRangeLabels,
|
|
350
|
+
'pkt-hide': !showRangeLabels,
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Date value processing utilities
|
|
357
|
+
*/
|
|
358
|
+
export const dateProcessingUtils = {
|
|
359
|
+
/**
|
|
360
|
+
* Handles date selection from calendar events
|
|
361
|
+
*/
|
|
362
|
+
processDateSelection(detail: any, multiple: boolean, range: boolean): string {
|
|
363
|
+
if (!multiple && !range) {
|
|
364
|
+
return detail[0] || ''
|
|
365
|
+
}
|
|
366
|
+
if (Array.isArray(detail)) {
|
|
367
|
+
return detail.join(',')
|
|
368
|
+
}
|
|
369
|
+
return detail
|
|
370
|
+
},
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Updates input values after calendar selection
|
|
374
|
+
*/
|
|
375
|
+
updateInputValues(
|
|
376
|
+
inputRef: Ref<HTMLInputElement>,
|
|
377
|
+
inputRefTo: Ref<HTMLInputElement> | null,
|
|
378
|
+
values: string[],
|
|
379
|
+
range: boolean,
|
|
380
|
+
multiple: boolean,
|
|
381
|
+
manageValidity: (input: HTMLInputElement) => void,
|
|
382
|
+
): void {
|
|
383
|
+
if (!inputRef.value) return
|
|
384
|
+
|
|
385
|
+
if (range && inputRefTo?.value) {
|
|
386
|
+
inputRef.value.value = values[0] ?? ''
|
|
387
|
+
inputRefTo.value.value = values[1] ?? ''
|
|
388
|
+
manageValidity(inputRef.value)
|
|
389
|
+
manageValidity(inputRefTo.value)
|
|
390
|
+
} else if (!multiple) {
|
|
391
|
+
inputRef.value.value = values.length ? values[0] : ''
|
|
392
|
+
manageValidity(inputRef.value)
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Processes blur events for range inputs
|
|
398
|
+
*/
|
|
399
|
+
processRangeBlur(
|
|
400
|
+
event: Event,
|
|
401
|
+
values: string[],
|
|
402
|
+
calendarRef: Ref<PktCalendar>,
|
|
403
|
+
clearInputValue: () => void,
|
|
404
|
+
manageValidity: (input: HTMLInputElement) => void,
|
|
405
|
+
): void {
|
|
406
|
+
const target = event.target as HTMLInputElement
|
|
407
|
+
if (target.value) {
|
|
408
|
+
manageValidity(target)
|
|
409
|
+
const date = fromISOToDate(target.value)
|
|
410
|
+
if (date) {
|
|
411
|
+
if (values[0] !== target.value && values[1]) {
|
|
412
|
+
clearInputValue()
|
|
413
|
+
calendarRef?.value?.handleDateSelect(date)
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
} else if (values[0]) {
|
|
417
|
+
clearInputValue()
|
|
418
|
+
}
|
|
419
|
+
},
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Keyboard navigation utilities
|
|
424
|
+
*/
|
|
425
|
+
export const keyboardUtils = {
|
|
426
|
+
/**
|
|
427
|
+
* Handles common keyboard interactions for datepicker inputs
|
|
428
|
+
*/
|
|
429
|
+
handleInputKeydown(
|
|
430
|
+
event: KeyboardEvent,
|
|
431
|
+
toggleCalendar: (e: Event) => void,
|
|
432
|
+
submitForm?: () => void,
|
|
433
|
+
focusNextInput?: () => void,
|
|
434
|
+
blurInput?: () => void,
|
|
435
|
+
commaHandler?: (e: KeyboardEvent) => void,
|
|
436
|
+
): void {
|
|
437
|
+
const { key } = event
|
|
438
|
+
|
|
439
|
+
if (key === ',') {
|
|
440
|
+
event.preventDefault()
|
|
441
|
+
if (commaHandler) {
|
|
442
|
+
commaHandler(event)
|
|
443
|
+
} else if (blurInput) {
|
|
444
|
+
blurInput()
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (key === 'Space' || key === ' ') {
|
|
449
|
+
event.preventDefault()
|
|
450
|
+
toggleCalendar(event)
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (key === 'Enter') {
|
|
454
|
+
event.preventDefault()
|
|
455
|
+
if (submitForm) {
|
|
456
|
+
submitForm()
|
|
457
|
+
} else if (focusNextInput) {
|
|
458
|
+
focusNextInput()
|
|
459
|
+
} else if (blurInput) {
|
|
460
|
+
blurInput()
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
},
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Handles keyboard interactions for calendar button
|
|
467
|
+
*/
|
|
468
|
+
handleButtonKeydown(event: KeyboardEvent, toggleCalendar: (e: Event) => void): void {
|
|
469
|
+
const { key } = event
|
|
470
|
+
if (key === 'Enter' || key === ' ' || key === 'Space') {
|
|
471
|
+
event.preventDefault()
|
|
472
|
+
toggleCalendar(event)
|
|
473
|
+
}
|
|
474
|
+
},
|
|
475
|
+
}
|
|
@@ -123,6 +123,26 @@ describe('PktDatepicker', () => {
|
|
|
123
123
|
expect(tagTexts[1]).toContain('20')
|
|
124
124
|
expect(tagTexts[2]).toContain('25')
|
|
125
125
|
})
|
|
126
|
+
|
|
127
|
+
test('sorts multiple dates chronologically across months and years', async () => {
|
|
128
|
+
// Test dates that would fail with simple string sorting
|
|
129
|
+
const complexUnsortedDates = '2024-12-01,2023-01-15,2024-01-01,2023-12-31'
|
|
130
|
+
const container = await createDatepicker(`value="${complexUnsortedDates}" multiple`)
|
|
131
|
+
|
|
132
|
+
const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
|
|
133
|
+
await datepicker.updateComplete
|
|
134
|
+
|
|
135
|
+
const tags = datepicker.querySelectorAll('pkt-tag')
|
|
136
|
+
const tagTexts = Array.from(tags).map((tag) =>
|
|
137
|
+
tag.querySelector('time')?.getAttribute('datetime'),
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
// Should be sorted chronologically: 2023-01-15, 2023-12-31, 2024-01-01, 2024-12-01
|
|
141
|
+
expect(tagTexts[0]).toBe('2023-01-15')
|
|
142
|
+
expect(tagTexts[1]).toBe('2023-12-31')
|
|
143
|
+
expect(tagTexts[2]).toBe('2024-01-01')
|
|
144
|
+
expect(tagTexts[3]).toBe('2024-12-01')
|
|
145
|
+
})
|
|
126
146
|
})
|
|
127
147
|
|
|
128
148
|
describe('Range selection', () => {
|
|
@@ -251,8 +271,49 @@ describe('PktDatepicker', () => {
|
|
|
251
271
|
const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
|
|
252
272
|
await datepicker.updateComplete
|
|
253
273
|
|
|
254
|
-
// Should
|
|
274
|
+
// Should reject invalid range and clear the value
|
|
275
|
+
expect(datepicker.value).toBe('')
|
|
276
|
+
expect(datepicker._value).toEqual([])
|
|
277
|
+
|
|
278
|
+
// Component should still render without errors
|
|
255
279
|
expect(datepicker).toBeInTheDocument()
|
|
280
|
+
|
|
281
|
+
// Test with valid range should work
|
|
282
|
+
datepicker.value = '2024-06-15,2024-06-20'
|
|
283
|
+
await datepicker.updateComplete
|
|
284
|
+
|
|
285
|
+
expect(datepicker.value).toBe('2024-06-15,2024-06-20')
|
|
286
|
+
expect(datepicker._value).toEqual(['2024-06-15', '2024-06-20'])
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
test('handles range validation edge cases', async () => {
|
|
290
|
+
const container = await createDatepicker('range')
|
|
291
|
+
const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
|
|
292
|
+
await datepicker.updateComplete
|
|
293
|
+
|
|
294
|
+
// Same start and end date should be valid
|
|
295
|
+
datepicker.value = '2024-06-15,2024-06-15'
|
|
296
|
+
await datepicker.updateComplete
|
|
297
|
+
expect(datepicker.value).toBe('2024-06-15,2024-06-15')
|
|
298
|
+
|
|
299
|
+
// Single date should be valid (incomplete range)
|
|
300
|
+
datepicker.value = '2024-06-15'
|
|
301
|
+
await datepicker.updateComplete
|
|
302
|
+
expect(datepicker.value).toBe('2024-06-15')
|
|
303
|
+
|
|
304
|
+
// Empty value should be valid
|
|
305
|
+
datepicker.value = ''
|
|
306
|
+
await datepicker.updateComplete
|
|
307
|
+
expect(datepicker.value).toBe('')
|
|
308
|
+
|
|
309
|
+
// Multiple invalid attempts should be consistently rejected
|
|
310
|
+
datepicker.value = '2024-06-25,2024-06-10'
|
|
311
|
+
await datepicker.updateComplete
|
|
312
|
+
expect(datepicker.value).toBe('')
|
|
313
|
+
|
|
314
|
+
datepicker.value = '2024-12-31,2024-01-01'
|
|
315
|
+
await datepicker.updateComplete
|
|
316
|
+
expect(datepicker.value).toBe('')
|
|
256
317
|
})
|
|
257
318
|
|
|
258
319
|
test('shows range preview on hover', async () => {
|