@oslokommune/punkt-elements 14.0.2 → 14.0.3
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 +17 -0
- package/dist/calendar-BtShW7ER.cjs +90 -0
- package/dist/{calendar-Bz27nuTP.js → calendar-yxjSI4wd.js} +766 -682
- package/dist/datepicker-D0q75U1Z.js +1463 -0
- package/dist/datepicker-DDV382Uu.cjs +271 -0
- package/dist/index.d.ts +118 -83
- package/dist/pkt-calendar.cjs +1 -1
- package/dist/pkt-calendar.js +1 -1
- package/dist/pkt-datepicker.cjs +1 -1
- package/dist/pkt-datepicker.js +2 -2
- package/dist/pkt-index.cjs +1 -1
- package/dist/pkt-index.js +3 -3
- package/package.json +2 -2
- package/src/components/calendar/calendar.ts +372 -414
- package/src/components/calendar/helpers/calendar-grid.ts +93 -0
- package/src/components/calendar/helpers/date-validation.ts +86 -0
- package/src/components/calendar/helpers/index.ts +49 -0
- package/src/components/calendar/helpers/keyboard-navigation.ts +54 -0
- package/src/components/calendar/helpers/selection-manager.ts +184 -0
- package/src/components/datepicker/datepicker-base.ts +151 -0
- package/src/components/datepicker/datepicker-multiple.ts +7 -114
- package/src/components/datepicker/datepicker-range.ts +21 -141
- package/src/components/datepicker/datepicker-single.ts +7 -115
- package/src/components/datepicker/datepicker-types.ts +56 -0
- package/src/components/datepicker/datepicker-utils.test.ts +730 -0
- package/src/components/datepicker/datepicker-utils.ts +338 -9
- package/src/components/datepicker/datepicker.ts +25 -1
- package/dist/calendar-Dz1Cnzx5.cjs +0 -115
- package/dist/datepicker-CnCOXI2x.cjs +0 -289
- package/dist/datepicker-DsqM01iU.js +0 -1355
|
@@ -10,7 +10,17 @@ import { Ref } from 'lit/directives/ref.js'
|
|
|
10
10
|
import { PktCalendar } from '@/components/calendar/calendar'
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* Utility functions for PktDatepicker
|
|
13
|
+
* Utility functions for PktDatepicker component
|
|
14
|
+
*
|
|
15
|
+
* This module provides helper functions organized by concern:
|
|
16
|
+
* - valueUtils: Value parsing and validation
|
|
17
|
+
* - inputTypeUtils: Input type detection
|
|
18
|
+
* - formUtils: Form submission and validation
|
|
19
|
+
* - calendarUtils: Calendar interaction and positioning
|
|
20
|
+
* - eventUtils: Event listener creation
|
|
21
|
+
* - cssUtils: CSS class generation
|
|
22
|
+
* - dateProcessingUtils: Date value processing
|
|
23
|
+
* - keyboardUtils: Keyboard navigation handling
|
|
14
24
|
*/
|
|
15
25
|
|
|
16
26
|
/**
|
|
@@ -19,6 +29,19 @@ import { PktCalendar } from '@/components/calendar/calendar'
|
|
|
19
29
|
export const valueUtils = {
|
|
20
30
|
/**
|
|
21
31
|
* Ensures name attribute ends with [] for multiple/range inputs
|
|
32
|
+
*
|
|
33
|
+
* For form submission to work correctly with multiple values,
|
|
34
|
+
* the input name must end with [] to indicate it's an array.
|
|
35
|
+
*
|
|
36
|
+
* @param name - The original name attribute value
|
|
37
|
+
* @param isMultiple - Whether this is a multiple-date picker
|
|
38
|
+
* @param isRange - Whether this is a range date picker
|
|
39
|
+
* @returns The normalized name with [] suffix if needed, or null if name is null
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* normalizeNameForMultiple('dates', true, false) // Returns 'dates[]'
|
|
43
|
+
* normalizeNameForMultiple('dates[]', true, false) // Returns 'dates[]' (no change)
|
|
44
|
+
* normalizeNameForMultiple('date', false, false) // Returns 'date' (no change)
|
|
22
45
|
*/
|
|
23
46
|
normalizeNameForMultiple(
|
|
24
47
|
name: string | null,
|
|
@@ -34,6 +57,14 @@ export const valueUtils = {
|
|
|
34
57
|
|
|
35
58
|
/**
|
|
36
59
|
* Validates that a range has valid order (start <= end)
|
|
60
|
+
*
|
|
61
|
+
* @param values - Array of date strings (ISO format YYYY-MM-DD)
|
|
62
|
+
* @returns True if range is valid or incomplete, false if end date is before start date
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* validateRangeOrder(['2024-01-01', '2024-01-31']) // Returns true
|
|
66
|
+
* validateRangeOrder(['2024-01-31', '2024-01-01']) // Returns false
|
|
67
|
+
* validateRangeOrder(['2024-01-01']) // Returns true (incomplete range)
|
|
37
68
|
*/
|
|
38
69
|
validateRangeOrder(values: string[]): boolean {
|
|
39
70
|
if (!values || values.length !== 2) return true // Not a complete range
|
|
@@ -42,6 +73,13 @@ export const valueUtils = {
|
|
|
42
73
|
|
|
43
74
|
/**
|
|
44
75
|
* Sorts date strings chronologically
|
|
76
|
+
*
|
|
77
|
+
* @param dates - Array of date strings in ISO format (YYYY-MM-DD)
|
|
78
|
+
* @returns New array with dates sorted from earliest to latest
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* sortDates(['2024-03-15', '2024-01-10', '2024-02-20'])
|
|
82
|
+
* // Returns ['2024-01-10', '2024-02-20', '2024-03-15']
|
|
45
83
|
*/
|
|
46
84
|
sortDates(dates: string[]): string[] {
|
|
47
85
|
return sortDateStrings(dates)
|
|
@@ -49,6 +87,28 @@ export const valueUtils = {
|
|
|
49
87
|
|
|
50
88
|
/**
|
|
51
89
|
* Filters dates to only include selectable ones based on constraints
|
|
90
|
+
*
|
|
91
|
+
* Removes dates that:
|
|
92
|
+
* - Fall outside the min/max range
|
|
93
|
+
* - Are in the excludedDates array
|
|
94
|
+
* - Fall on excluded weekdays (0=Sunday, 6=Saturday)
|
|
95
|
+
*
|
|
96
|
+
* @param dates - Array of date strings to filter
|
|
97
|
+
* @param min - Minimum allowed date (ISO format)
|
|
98
|
+
* @param max - Maximum allowed date (ISO format)
|
|
99
|
+
* @param excludedDates - Array of specific dates to exclude
|
|
100
|
+
* @param excludedWeekdays - Array of weekday numbers to exclude ('0'-'6')
|
|
101
|
+
* @returns Filtered array containing only selectable dates
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* filterSelectableDates(
|
|
105
|
+
* ['2024-01-01', '2024-01-06', '2024-01-15'],
|
|
106
|
+
* '2024-01-05',
|
|
107
|
+
* '2024-01-20',
|
|
108
|
+
* ['2024-01-15'],
|
|
109
|
+
* ['0', '6'] // Exclude weekends
|
|
110
|
+
* )
|
|
111
|
+
* // Returns ['2024-01-06'] (after min, not excluded, not a weekend)
|
|
52
112
|
*/
|
|
53
113
|
filterSelectableDates(
|
|
54
114
|
dates: string[],
|
|
@@ -67,7 +127,16 @@ export const valueUtils = {
|
|
|
67
127
|
export const inputTypeUtils = {
|
|
68
128
|
/**
|
|
69
129
|
* Determines the appropriate input type based on device
|
|
70
|
-
*
|
|
130
|
+
*
|
|
131
|
+
* Mobile Safari does not play well with type="date" and custom datepickers,
|
|
132
|
+
* so we use type="text" on iOS devices to avoid the native date picker
|
|
133
|
+
* interfering with our custom calendar component.
|
|
134
|
+
*
|
|
135
|
+
* @returns 'text' for iOS devices, 'date' for all other devices
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* const inputType = getInputType()
|
|
139
|
+
* // Returns 'text' on iPhone/iPad, 'date' on desktop/Android
|
|
71
140
|
*/
|
|
72
141
|
getInputType(): string {
|
|
73
142
|
return isIOS() ? 'text' : 'date'
|
|
@@ -80,6 +149,14 @@ export const inputTypeUtils = {
|
|
|
80
149
|
export const formUtils = {
|
|
81
150
|
/**
|
|
82
151
|
* Submits the form that contains the given element
|
|
152
|
+
*
|
|
153
|
+
* Uses ElementInternals API to access the associated form and trigger submission.
|
|
154
|
+
* Requires the element to have attachInternals() called.
|
|
155
|
+
*
|
|
156
|
+
* @param element - The form-associated custom element
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* submitForm(this) // From within a custom element
|
|
83
160
|
*/
|
|
84
161
|
submitForm(element: HTMLElement): void {
|
|
85
162
|
const form = (element as any).internals?.form as HTMLFormElement
|
|
@@ -90,6 +167,18 @@ export const formUtils = {
|
|
|
90
167
|
|
|
91
168
|
/**
|
|
92
169
|
* Submits form if available, otherwise executes fallback action
|
|
170
|
+
*
|
|
171
|
+
* Useful for Enter key handling where we want to submit the form,
|
|
172
|
+
* but provide a fallback behavior (like moving focus) if not in a form.
|
|
173
|
+
*
|
|
174
|
+
* @param internals - ElementInternals instance with optional form reference
|
|
175
|
+
* @param fallbackAction - Function to call if no form is available
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* submitFormOrFallback(
|
|
179
|
+
* this.internals,
|
|
180
|
+
* () => this.inputRef.value?.blur()
|
|
181
|
+
* )
|
|
93
182
|
*/
|
|
94
183
|
submitFormOrFallback(internals: any, fallbackAction: () => void): void {
|
|
95
184
|
const form = internals?.form as HTMLFormElement
|
|
@@ -102,6 +191,24 @@ export const formUtils = {
|
|
|
102
191
|
|
|
103
192
|
/**
|
|
104
193
|
* Validates a date input and sets validity state
|
|
194
|
+
*
|
|
195
|
+
* Checks if the input value falls within the min/max range and
|
|
196
|
+
* sets appropriate validity flags using the ElementInternals API.
|
|
197
|
+
*
|
|
198
|
+
* @param input - The input element to validate
|
|
199
|
+
* @param internals - ElementInternals instance for setting validity
|
|
200
|
+
* @param min - Minimum allowed date (ISO format)
|
|
201
|
+
* @param max - Maximum allowed date (ISO format)
|
|
202
|
+
* @param strings - Optional localized error messages
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* validateDateInput(
|
|
206
|
+
* inputElement,
|
|
207
|
+
* this.internals,
|
|
208
|
+
* '2024-01-01',
|
|
209
|
+
* '2024-12-31',
|
|
210
|
+
* { forms: { messages: { rangeUnderflow: 'Date too early' } } }
|
|
211
|
+
* )
|
|
105
212
|
*/
|
|
106
213
|
validateDateInput(
|
|
107
214
|
input: HTMLInputElement,
|
|
@@ -135,6 +242,23 @@ export const formUtils = {
|
|
|
135
242
|
export const calendarUtils = {
|
|
136
243
|
/**
|
|
137
244
|
* Adds a date to selected dates if it's valid
|
|
245
|
+
*
|
|
246
|
+
* Used for multiple date selection - validates the input date against
|
|
247
|
+
* min/max constraints before adding it to the calendar's selected dates.
|
|
248
|
+
* Clears the input field after processing.
|
|
249
|
+
*
|
|
250
|
+
* @param event - Input or keyboard event containing the target input
|
|
251
|
+
* @param calendarRef - Lit ref to the calendar component
|
|
252
|
+
* @param min - Minimum allowed date (ISO format)
|
|
253
|
+
* @param max - Maximum allowed date (ISO format)
|
|
254
|
+
*
|
|
255
|
+
* @example
|
|
256
|
+
* addToSelected(
|
|
257
|
+
* blurEvent,
|
|
258
|
+
* this.calendarRef,
|
|
259
|
+
* '2024-01-01',
|
|
260
|
+
* '2024-12-31'
|
|
261
|
+
* )
|
|
138
262
|
*/
|
|
139
263
|
addToSelected(
|
|
140
264
|
event: Event | KeyboardEvent,
|
|
@@ -163,6 +287,25 @@ export const calendarUtils = {
|
|
|
163
287
|
|
|
164
288
|
/**
|
|
165
289
|
* Handles calendar positioning based on viewport and input position
|
|
290
|
+
*
|
|
291
|
+
* Intelligently positions the calendar popup either above or below the input
|
|
292
|
+
* depending on available viewport space. Accounts for optional counter elements.
|
|
293
|
+
*
|
|
294
|
+
* The positioning logic:
|
|
295
|
+
* 1. By default, positions calendar below the input (top: 100%)
|
|
296
|
+
* 2. If calendar would overflow viewport bottom AND there's space above, positions above
|
|
297
|
+
* 3. Accounts for counter height (30px) when hasCounter is true
|
|
298
|
+
*
|
|
299
|
+
* @param popupRef - Lit ref to the calendar popup element
|
|
300
|
+
* @param inputRef - Lit ref to the input element
|
|
301
|
+
* @param hasCounter - Whether the input has a character counter below it
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* handleCalendarPosition(
|
|
305
|
+
* this.popupRef,
|
|
306
|
+
* this.inputRef,
|
|
307
|
+
* this.multiple && this.maxlength !== null
|
|
308
|
+
* )
|
|
166
309
|
*/
|
|
167
310
|
handleCalendarPosition(
|
|
168
311
|
popupRef: Ref<HTMLDivElement>,
|
|
@@ -194,10 +337,34 @@ export const calendarUtils = {
|
|
|
194
337
|
|
|
195
338
|
/**
|
|
196
339
|
* Event handling utilities
|
|
340
|
+
*
|
|
341
|
+
* Factory functions for creating event listeners with proper closure over component state.
|
|
197
342
|
*/
|
|
198
343
|
export const eventUtils = {
|
|
199
344
|
/**
|
|
200
|
-
* Creates a document click listener for closing calendar
|
|
345
|
+
* Creates a document click listener for closing calendar on outside clicks
|
|
346
|
+
*
|
|
347
|
+
* Returns a function that closes the calendar when clicking outside the
|
|
348
|
+
* datepicker component (input, button, or calendar popup).
|
|
349
|
+
*
|
|
350
|
+
* @param inputRef - Lit ref to the primary input element
|
|
351
|
+
* @param inputRefTo - Lit ref to the secondary input (for range picker) or null
|
|
352
|
+
* @param btnRef - Lit ref to the calendar button
|
|
353
|
+
* @param getCalendarOpen - Function returning current calendar open state
|
|
354
|
+
* @param onBlur - Callback to execute on blur
|
|
355
|
+
* @param hideCalendar - Callback to hide the calendar
|
|
356
|
+
* @returns Event listener function to attach to document
|
|
357
|
+
*
|
|
358
|
+
* @example
|
|
359
|
+
* const listener = createDocumentClickListener(
|
|
360
|
+
* this.inputRef,
|
|
361
|
+
* null,
|
|
362
|
+
* this.btnRef,
|
|
363
|
+
* () => this.calendarOpen,
|
|
364
|
+
* () => this.handleBlur(),
|
|
365
|
+
* () => this.calendarOpen = false
|
|
366
|
+
* )
|
|
367
|
+
* document.addEventListener('click', listener)
|
|
201
368
|
*/
|
|
202
369
|
createDocumentClickListener(
|
|
203
370
|
inputRef: Ref<HTMLInputElement>,
|
|
@@ -225,6 +392,19 @@ export const eventUtils = {
|
|
|
225
392
|
|
|
226
393
|
/**
|
|
227
394
|
* Creates a document keydown listener for ESC key
|
|
395
|
+
*
|
|
396
|
+
* Returns a function that closes the calendar when Escape is pressed.
|
|
397
|
+
*
|
|
398
|
+
* @param getCalendarOpen - Function returning current calendar open state
|
|
399
|
+
* @param hideCalendar - Callback to hide the calendar
|
|
400
|
+
* @returns Event listener function to attach to document
|
|
401
|
+
*
|
|
402
|
+
* @example
|
|
403
|
+
* const listener = createDocumentKeydownListener(
|
|
404
|
+
* () => this.calendarOpen,
|
|
405
|
+
* () => this.calendarOpen = false
|
|
406
|
+
* )
|
|
407
|
+
* document.addEventListener('keydown', listener)
|
|
228
408
|
*/
|
|
229
409
|
createDocumentKeydownListener(
|
|
230
410
|
getCalendarOpen: () => boolean,
|
|
@@ -239,6 +419,22 @@ export const eventUtils = {
|
|
|
239
419
|
|
|
240
420
|
/**
|
|
241
421
|
* Handles focus out events for calendar popup
|
|
422
|
+
*
|
|
423
|
+
* Closes the calendar when focus leaves the datepicker component entirely.
|
|
424
|
+
* Used for keyboard navigation accessibility.
|
|
425
|
+
*
|
|
426
|
+
* @param event - The focusout event
|
|
427
|
+
* @param element - The container element to check focus against
|
|
428
|
+
* @param onBlur - Callback to execute on blur
|
|
429
|
+
* @param hideCalendar - Callback to hide the calendar
|
|
430
|
+
*
|
|
431
|
+
* @example
|
|
432
|
+
* handleFocusOut(
|
|
433
|
+
* event,
|
|
434
|
+
* this,
|
|
435
|
+
* () => this.handleBlur(),
|
|
436
|
+
* () => this.calendarOpen = false
|
|
437
|
+
* )
|
|
242
438
|
*/
|
|
243
439
|
handleFocusOut(
|
|
244
440
|
event: FocusEvent,
|
|
@@ -255,10 +451,24 @@ export const eventUtils = {
|
|
|
255
451
|
|
|
256
452
|
/**
|
|
257
453
|
* CSS class utilities
|
|
454
|
+
*
|
|
455
|
+
* Functions for generating CSS class objects for use with classMap directive.
|
|
258
456
|
*/
|
|
259
457
|
export const cssUtils = {
|
|
260
458
|
/**
|
|
261
459
|
* Generates input classes for datepicker
|
|
460
|
+
*
|
|
461
|
+
* @param fullwidth - Whether input should take full width
|
|
462
|
+
* @param showRangeLabels - Whether range labels are visible
|
|
463
|
+
* @param multiple - Whether this is a multiple date picker
|
|
464
|
+
* @param range - Whether this is a range date picker
|
|
465
|
+
* @param readonly - Whether input is readonly
|
|
466
|
+
* @param inputType - The input type ('date' or 'text')
|
|
467
|
+
* @returns Object with class names as keys and boolean values
|
|
468
|
+
*
|
|
469
|
+
* @example
|
|
470
|
+
* const classes = getInputClasses(true, false, false, false, false, 'date')
|
|
471
|
+
* // Returns { 'pkt-input': true, 'pkt-input--fullwidth': true, ... }
|
|
262
472
|
*/
|
|
263
473
|
getInputClasses(
|
|
264
474
|
fullwidth: boolean,
|
|
@@ -280,7 +490,13 @@ export const cssUtils = {
|
|
|
280
490
|
},
|
|
281
491
|
|
|
282
492
|
/**
|
|
283
|
-
* Generates button classes for
|
|
493
|
+
* Generates button classes for calendar button
|
|
494
|
+
*
|
|
495
|
+
* @returns Object with class names for the calendar toggle button
|
|
496
|
+
*
|
|
497
|
+
* @example
|
|
498
|
+
* const classes = getButtonClasses()
|
|
499
|
+
* // Returns { 'pkt-btn': true, 'pkt-btn--icon-only': true, ... }
|
|
284
500
|
*/
|
|
285
501
|
getButtonClasses() {
|
|
286
502
|
return {
|
|
@@ -294,6 +510,13 @@ export const cssUtils = {
|
|
|
294
510
|
|
|
295
511
|
/**
|
|
296
512
|
* Generates range label classes
|
|
513
|
+
*
|
|
514
|
+
* @param showRangeLabels - Whether range labels should be visible
|
|
515
|
+
* @returns Object with class names for range label elements
|
|
516
|
+
*
|
|
517
|
+
* @example
|
|
518
|
+
* const classes = getRangeLabelClasses(true)
|
|
519
|
+
* // Returns { 'pkt-input-prefix': true, 'pkt-hide': false }
|
|
297
520
|
*/
|
|
298
521
|
getRangeLabelClasses(showRangeLabels: boolean) {
|
|
299
522
|
return {
|
|
@@ -305,10 +528,24 @@ export const cssUtils = {
|
|
|
305
528
|
|
|
306
529
|
/**
|
|
307
530
|
* Date value processing utilities
|
|
531
|
+
*
|
|
532
|
+
* Functions for transforming and updating date values between calendar and input elements.
|
|
308
533
|
*/
|
|
309
534
|
export const dateProcessingUtils = {
|
|
310
535
|
/**
|
|
311
|
-
*
|
|
536
|
+
* Processes date selection from calendar events
|
|
537
|
+
*
|
|
538
|
+
* Converts the calendar's date selection into the appropriate string format
|
|
539
|
+
* for the input element's value.
|
|
540
|
+
*
|
|
541
|
+
* @param detail - Date selection detail from calendar event (string or array)
|
|
542
|
+
* @param multiple - Whether this is a multiple date picker
|
|
543
|
+
* @param range - Whether this is a range date picker
|
|
544
|
+
* @returns Formatted date string (single date or comma-separated dates)
|
|
545
|
+
*
|
|
546
|
+
* @example
|
|
547
|
+
* processDateSelection(['2024-01-15'], false, false) // Returns '2024-01-15'
|
|
548
|
+
* processDateSelection(['2024-01-15', '2024-01-20'], true, false) // Returns '2024-01-15,2024-01-20'
|
|
312
549
|
*/
|
|
313
550
|
processDateSelection(detail: any, multiple: boolean, range: boolean): string {
|
|
314
551
|
if (!multiple && !range) {
|
|
@@ -322,6 +559,30 @@ export const dateProcessingUtils = {
|
|
|
322
559
|
|
|
323
560
|
/**
|
|
324
561
|
* Updates input values after calendar selection
|
|
562
|
+
*
|
|
563
|
+
* Synchronizes input element values with the calendar's selected dates.
|
|
564
|
+
* Handles single, range, and multiple date scenarios differently.
|
|
565
|
+
*
|
|
566
|
+
* - Single: Updates single input with first date
|
|
567
|
+
* - Range: Updates two inputs with start and end dates
|
|
568
|
+
* - Multiple: Does nothing (dates shown as tags, not in input)
|
|
569
|
+
*
|
|
570
|
+
* @param inputRef - Lit ref to the primary input element
|
|
571
|
+
* @param inputRefTo - Lit ref to the secondary input (for range) or null
|
|
572
|
+
* @param values - Array of selected date strings
|
|
573
|
+
* @param range - Whether this is a range date picker
|
|
574
|
+
* @param multiple - Whether this is a multiple date picker
|
|
575
|
+
* @param manageValidity - Callback to validate the input
|
|
576
|
+
*
|
|
577
|
+
* @example
|
|
578
|
+
* updateInputValues(
|
|
579
|
+
* this.inputRef,
|
|
580
|
+
* this.inputRefTo,
|
|
581
|
+
* ['2024-01-15', '2024-01-20'],
|
|
582
|
+
* true, // range
|
|
583
|
+
* false,
|
|
584
|
+
* (input) => this.dispatchManageValidity(input)
|
|
585
|
+
* )
|
|
325
586
|
*/
|
|
326
587
|
updateInputValues(
|
|
327
588
|
inputRef: Ref<HTMLInputElement>,
|
|
@@ -346,6 +607,27 @@ export const dateProcessingUtils = {
|
|
|
346
607
|
|
|
347
608
|
/**
|
|
348
609
|
* Processes blur events for range inputs
|
|
610
|
+
*
|
|
611
|
+
* Handles the logic when a range input loses focus:
|
|
612
|
+
* - If input has value: validates it and updates the calendar to reflect the typed date
|
|
613
|
+
* - If input is empty but range has start date: clears the entire range
|
|
614
|
+
*
|
|
615
|
+
* This ensures the range picker calendar stays synchronized when users type dates directly.
|
|
616
|
+
*
|
|
617
|
+
* @param event - The blur event from the input
|
|
618
|
+
* @param values - Current array of selected dates [start, end]
|
|
619
|
+
* @param calendarRef - Lit ref to the calendar component
|
|
620
|
+
* @param clearInputValue - Callback to clear the date selection
|
|
621
|
+
* @param manageValidity - Callback to validate the input
|
|
622
|
+
*
|
|
623
|
+
* @example
|
|
624
|
+
* processRangeBlur(
|
|
625
|
+
* blurEvent,
|
|
626
|
+
* ['2024-01-15', '2024-01-20'],
|
|
627
|
+
* this.calendarRef,
|
|
628
|
+
* () => this.value = [],
|
|
629
|
+
* (input) => this.dispatchManageValidity(input)
|
|
630
|
+
* )
|
|
349
631
|
*/
|
|
350
632
|
processRangeBlur(
|
|
351
633
|
event: Event,
|
|
@@ -359,10 +641,8 @@ export const dateProcessingUtils = {
|
|
|
359
641
|
manageValidity(target)
|
|
360
642
|
const date = fromISOToDate(target.value)
|
|
361
643
|
if (date) {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
calendarRef?.value?.handleDateSelect(date)
|
|
365
|
-
}
|
|
644
|
+
// Always update the calendar when a valid date is typed
|
|
645
|
+
calendarRef?.value?.handleDateSelect(date)
|
|
366
646
|
}
|
|
367
647
|
} else if (values[0]) {
|
|
368
648
|
clearInputValue()
|
|
@@ -372,10 +652,47 @@ export const dateProcessingUtils = {
|
|
|
372
652
|
|
|
373
653
|
/**
|
|
374
654
|
* Keyboard navigation utilities
|
|
655
|
+
*
|
|
656
|
+
* Functions for handling keyboard interactions with proper accessibility support.
|
|
375
657
|
*/
|
|
376
658
|
export const keyboardUtils = {
|
|
377
659
|
/**
|
|
378
660
|
* Handles common keyboard interactions for datepicker inputs
|
|
661
|
+
*
|
|
662
|
+
* Provides consistent keyboard navigation across all datepicker types:
|
|
663
|
+
* - Space: Opens calendar
|
|
664
|
+
* - Enter: Submits form, focuses next input, or blurs (priority order)
|
|
665
|
+
* - Comma: Adds date to selection (multiple) or blurs input
|
|
666
|
+
*
|
|
667
|
+
* The handler prioritizes callbacks in the order provided, allowing
|
|
668
|
+
* different behaviors for single, multiple, and range pickers.
|
|
669
|
+
*
|
|
670
|
+
* @param event - The keyboard event
|
|
671
|
+
* @param toggleCalendar - Callback to toggle calendar visibility
|
|
672
|
+
* @param submitForm - Optional callback to submit the form (highest priority on Enter)
|
|
673
|
+
* @param focusNextInput - Optional callback to focus next input (medium priority on Enter)
|
|
674
|
+
* @param blurInput - Optional callback to blur current input (lowest priority on Enter/Comma)
|
|
675
|
+
* @param commaHandler - Optional callback for comma key (overrides default blur behavior)
|
|
676
|
+
*
|
|
677
|
+
* @example
|
|
678
|
+
* // Single datepicker: Enter submits form or blurs
|
|
679
|
+
* handleInputKeydown(
|
|
680
|
+
* event,
|
|
681
|
+
* (e) => this.toggleCalendar(e),
|
|
682
|
+
* () => formUtils.submitFormOrFallback(this.internals, () => this.inputRef.value?.blur()),
|
|
683
|
+
* undefined,
|
|
684
|
+
* () => this.inputRef.value?.blur()
|
|
685
|
+
* )
|
|
686
|
+
*
|
|
687
|
+
* @example
|
|
688
|
+
* // Range datepicker: Enter moves to next input
|
|
689
|
+
* handleInputKeydown(
|
|
690
|
+
* event,
|
|
691
|
+
* (e) => this.toggleCalendar(e),
|
|
692
|
+
* () => formUtils.submitFormOrFallback(this.internals, () => this.inputRefTo.value?.focus()),
|
|
693
|
+
* () => this.inputRefTo.value?.focus(),
|
|
694
|
+
* () => this.inputRef.value?.blur()
|
|
695
|
+
* )
|
|
379
696
|
*/
|
|
380
697
|
handleInputKeydown(
|
|
381
698
|
event: KeyboardEvent,
|
|
@@ -415,6 +732,18 @@ export const keyboardUtils = {
|
|
|
415
732
|
|
|
416
733
|
/**
|
|
417
734
|
* Handles keyboard interactions for calendar button
|
|
735
|
+
*
|
|
736
|
+
* Ensures the calendar button is keyboard accessible by responding
|
|
737
|
+
* to Enter and Space keys (standard button activation keys).
|
|
738
|
+
*
|
|
739
|
+
* @param event - The keyboard event
|
|
740
|
+
* @param toggleCalendar - Callback to toggle calendar visibility
|
|
741
|
+
*
|
|
742
|
+
* @example
|
|
743
|
+
* handleButtonKeydown(
|
|
744
|
+
* event,
|
|
745
|
+
* (e) => this.dispatchToggleCalendar(e)
|
|
746
|
+
* )
|
|
418
747
|
*/
|
|
419
748
|
handleButtonKeydown(event: KeyboardEvent, toggleCalendar: (e: Event) => void): void {
|
|
420
749
|
const { key } = event
|
|
@@ -347,6 +347,28 @@ export class PktDatepicker extends PktInputElement<Props> {
|
|
|
347
347
|
}
|
|
348
348
|
}}
|
|
349
349
|
@range-blur=${(e: CustomEvent) => {
|
|
350
|
+
const inputFrom = this.currentInputElement
|
|
351
|
+
const inputTo = this.currentInputElementTo
|
|
352
|
+
|
|
353
|
+
// Update _value with current input values to sync with calendar
|
|
354
|
+
if (inputFrom && inputTo) {
|
|
355
|
+
const fromValue = inputFrom.value
|
|
356
|
+
const toValue = inputTo.value
|
|
357
|
+
|
|
358
|
+
// If from date is after to date, clear the to date
|
|
359
|
+
if (fromValue && toValue && fromValue > toValue) {
|
|
360
|
+
inputTo.value = ''
|
|
361
|
+
this._value = [fromValue]
|
|
362
|
+
this.value = fromValue
|
|
363
|
+
} else {
|
|
364
|
+
const newValues = [fromValue, toValue].filter(Boolean)
|
|
365
|
+
if (newValues.length > 0 && (newValues[0] !== this._value[0] || newValues[1] !== this._value[1])) {
|
|
366
|
+
this._value = newValues
|
|
367
|
+
this.value = newValues.join(',')
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
350
372
|
dateProcessingUtils.processRangeBlur(
|
|
351
373
|
e.detail.event,
|
|
352
374
|
e.detail.values,
|
|
@@ -362,7 +384,9 @@ export class PktDatepicker extends PktInputElement<Props> {
|
|
|
362
384
|
@handle-date-select=${(e: CustomEvent) => {
|
|
363
385
|
const date = fromISOToDate(e.detail)
|
|
364
386
|
if (date) {
|
|
365
|
-
|
|
387
|
+
const formattedDate = formatISODate(date)
|
|
388
|
+
// Only update calendar if the date is different from current values
|
|
389
|
+
if (this._value[0] !== formattedDate && this._value[1] !== formattedDate) {
|
|
366
390
|
this.calRef?.value?.handleDateSelect(date)
|
|
367
391
|
}
|
|
368
392
|
}
|