@oslokommune/punkt-elements 14.5.3 → 15.0.0

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 (138) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/dist/{accordionitem-DB3tAjIZ.js → accordionitem-BbeS44TD.js} +1 -1
  3. package/dist/{accordionitem-DHYmPA-o.cjs → accordionitem-DUWhTUEI.cjs} +1 -1
  4. package/dist/{alert-GrKyqxuR.cjs → alert-B9flMht5.cjs} +1 -1
  5. package/dist/{alert-DqXar67l.js → alert-BpFxQviu.js} +3 -3
  6. package/dist/{backlink-QDcQd6vK.cjs → backlink-Bn16ZEIX.cjs} +1 -1
  7. package/dist/{backlink-CqUcmiPQ.js → backlink-DfMabyBp.js} +1 -1
  8. package/dist/{button-R4l10Nqd.cjs → button-BgCKV0zW.cjs} +1 -1
  9. package/dist/{button-f36menSU.js → button-CKgpX5QA.js} +2 -2
  10. package/dist/{calendar-DiNuZ8Ai.js → calendar-BMarIeVp.js} +333 -326
  11. package/dist/calendar-C6xgAJ7Z.cjs +90 -0
  12. package/dist/{card-C2hWaMev.cjs → card-DI3KodEc.cjs} +1 -1
  13. package/dist/{card-CnINxZma.js → card-h8ekgJRr.js} +3 -3
  14. package/dist/{checkbox-B-0FOdwi.cjs → checkbox-B2t7wfxe.cjs} +1 -1
  15. package/dist/{checkbox-CkAwPK_E.js → checkbox-BJ9pz3kV.js} +1 -1
  16. package/dist/{combobox-Dxa7XjLz.js → combobox-BjAvk8Y-.js} +6 -6
  17. package/dist/{combobox-yy6hIGmB.cjs → combobox-HNN7GTzh.cjs} +1 -1
  18. package/dist/{consent-FbFS1pmz.cjs → consent-C37tuFwZ.cjs} +1 -1
  19. package/dist/{consent-c9PhWxnP.js → consent-DAk2BJ7I.js} +2 -2
  20. package/dist/datepicker-2bneToiA.cjs +275 -0
  21. package/dist/datepicker-DvcH2QD9.js +1160 -0
  22. package/dist/{helptext-DebHGRsD.cjs → helptext-BG1P_9j0.cjs} +1 -1
  23. package/dist/{helptext-DuyRRfCn.js → helptext-Cm6FLBVf.js} +2 -2
  24. package/dist/icon-DBZvMard.js +103 -0
  25. package/dist/icon-n4-ve48P.cjs +9 -0
  26. package/dist/index.d.ts +10 -42
  27. package/dist/input-element-BBv4xjPb.cjs +1 -0
  28. package/dist/{input-element-DVZhYDJ_.js → input-element-CbjYtVou.js} +143 -147
  29. package/dist/{input-wrapper-3qfjLkrP.js → input-wrapper-Bwmva-69.js} +4 -4
  30. package/dist/{input-wrapper-rgXsRNkk.cjs → input-wrapper-CJqmwe3I.cjs} +1 -1
  31. package/dist/{link-CZvcdRek.js → link-BKnT-97D.js} +2 -2
  32. package/dist/{link-D85g20GS.cjs → link-DRPFqqEn.cjs} +1 -1
  33. package/dist/{linkcard-B-9K02q8.cjs → linkcard-02fxYQ2g.cjs} +1 -1
  34. package/dist/{linkcard-C3RTcMqc.js → linkcard-C2UENcqo.js} +2 -2
  35. package/dist/{loader-Dci0SrOz.js → loader-BW-AWQiE.js} +2 -2
  36. package/dist/{loader-1EXLdxfq.cjs → loader-Cj4kUNRE.cjs} +1 -1
  37. package/dist/{messagebox-dscnWgCd.cjs → messagebox-CK0sSRep.cjs} +1 -1
  38. package/dist/{messagebox-CnZlttZp.js → messagebox-DB8tEXrV.js} +2 -2
  39. package/dist/{modal-ncKVjcyZ.cjs → modal-BzVQSQy2.cjs} +1 -1
  40. package/dist/{modal-wSGthXAC.js → modal-ClYfWW2a.js} +2 -2
  41. package/dist/pkt-accordion.cjs +1 -1
  42. package/dist/pkt-accordion.js +2 -2
  43. package/dist/pkt-alert.cjs +1 -1
  44. package/dist/pkt-alert.js +1 -1
  45. package/dist/pkt-backlink.cjs +1 -1
  46. package/dist/pkt-backlink.js +1 -1
  47. package/dist/pkt-button.cjs +1 -1
  48. package/dist/pkt-button.js +1 -1
  49. package/dist/pkt-calendar.cjs +1 -1
  50. package/dist/pkt-calendar.js +1 -1
  51. package/dist/pkt-card.cjs +1 -1
  52. package/dist/pkt-card.js +1 -1
  53. package/dist/pkt-checkbox.cjs +1 -1
  54. package/dist/pkt-checkbox.js +1 -1
  55. package/dist/pkt-combobox.cjs +1 -1
  56. package/dist/pkt-combobox.js +1 -1
  57. package/dist/pkt-consent.cjs +1 -1
  58. package/dist/pkt-consent.js +1 -1
  59. package/dist/pkt-datepicker.cjs +1 -1
  60. package/dist/pkt-datepicker.js +2 -2
  61. package/dist/pkt-header.cjs +1 -1
  62. package/dist/pkt-header.js +5 -5
  63. package/dist/pkt-helptext.cjs +1 -1
  64. package/dist/pkt-helptext.js +1 -1
  65. package/dist/pkt-icon.cjs +1 -1
  66. package/dist/pkt-icon.js +1 -1
  67. package/dist/pkt-index.cjs +1 -1
  68. package/dist/pkt-index.js +25 -25
  69. package/dist/pkt-input-wrapper.cjs +1 -1
  70. package/dist/pkt-input-wrapper.js +1 -1
  71. package/dist/pkt-link.cjs +1 -1
  72. package/dist/pkt-link.js +1 -1
  73. package/dist/pkt-linkcard.cjs +1 -1
  74. package/dist/pkt-linkcard.js +1 -1
  75. package/dist/pkt-loader.cjs +1 -1
  76. package/dist/pkt-loader.js +1 -1
  77. package/dist/pkt-messagebox.cjs +1 -1
  78. package/dist/pkt-messagebox.js +1 -1
  79. package/dist/pkt-modal.cjs +1 -1
  80. package/dist/pkt-modal.js +1 -1
  81. package/dist/{pkt-options-controller-zn5cmMvL.js → pkt-options-controller-BcGywCmf.js} +1 -1
  82. package/dist/{pkt-options-controller-DjBCEHU4.cjs → pkt-options-controller-BnTmkl3g.cjs} +1 -1
  83. package/dist/pkt-progressbar.cjs +1 -1
  84. package/dist/pkt-progressbar.js +2 -2
  85. package/dist/pkt-radiobutton.cjs +1 -1
  86. package/dist/pkt-radiobutton.js +1 -1
  87. package/dist/pkt-select.cjs +1 -1
  88. package/dist/pkt-select.js +1 -1
  89. package/dist/pkt-slot-controller-D4nKlom5.cjs +1 -0
  90. package/dist/{pkt-slot-controller-BPGj-LC5.js → pkt-slot-controller-D7CrjM52.js} +27 -25
  91. package/dist/pkt-tabs.cjs +1 -1
  92. package/dist/pkt-tabs.js +2 -2
  93. package/dist/pkt-tag.cjs +1 -1
  94. package/dist/pkt-tag.js +1 -1
  95. package/dist/pkt-textarea.cjs +1 -1
  96. package/dist/pkt-textarea.js +1 -1
  97. package/dist/pkt-textinput.cjs +1 -1
  98. package/dist/pkt-textinput.js +1 -1
  99. package/dist/{progressbar-D5WBW1Dm.js → progressbar-CJa70NEo.js} +1 -1
  100. package/dist/{progressbar-ClY1WgnM.cjs → progressbar-ClZOyCz9.cjs} +1 -1
  101. package/dist/{radiobutton-oA20HijB.js → radiobutton-CvKKNFMd.js} +1 -1
  102. package/dist/{radiobutton-iHuLnuAn.cjs → radiobutton-DWoYQn8H.cjs} +1 -1
  103. package/dist/{select-jbIbD7hW.cjs → select-BQUp88lY.cjs} +1 -1
  104. package/dist/{select-Bug9sfr5.js → select-CFkxir_l.js} +4 -4
  105. package/dist/{tabitem-CypTmORF.js → tabitem-C8-tZyc_.js} +1 -1
  106. package/dist/{tabitem-CtltSqDK.cjs → tabitem-DaYfUaxw.cjs} +1 -1
  107. package/dist/{tag-DgoTYNVA.js → tag-DIJMJhyp.js} +2 -2
  108. package/dist/{tag-BfjOlIyS.cjs → tag-h0vD2Na0.cjs} +1 -1
  109. package/dist/{textarea-Da9E3RM7.js → textarea-CMuiBUee.js} +4 -4
  110. package/dist/{textarea-Dfa4_ZBy.cjs → textarea-DcCOfNlr.cjs} +1 -1
  111. package/dist/{textinput-DaMYc5gd.js → textinput-D30TCADP.js} +4 -4
  112. package/dist/{textinput-CLM9fRjm.cjs → textinput-DR3aaHTH.cjs} +1 -1
  113. package/package.json +4 -4
  114. package/src/components/calendar/calendar.core.test.ts +1 -1
  115. package/src/components/calendar/calendar.selection.test.ts +1 -1
  116. package/src/components/calendar/calendar.ts +32 -22
  117. package/src/components/datepicker/datepicker-popup.test.ts +1 -1
  118. package/src/components/datepicker/datepicker-popup.ts +73 -30
  119. package/src/components/datepicker/datepicker-range.ts +5 -1
  120. package/src/components/datepicker/datepicker-types.ts +4 -54
  121. package/src/components/datepicker/datepicker-utils.ts +38 -517
  122. package/src/components/datepicker/datepicker.core.test.ts +12 -10
  123. package/src/components/datepicker/datepicker.selection.test.ts +8 -6
  124. package/src/components/datepicker/datepicker.ts +26 -7
  125. package/src/components/icon/icon.test.ts +8 -8
  126. package/src/components/icon/icon.ts +56 -39
  127. package/dist/calendar-wuD6ZH0t.cjs +0 -90
  128. package/dist/datepicker-C7GWMtXH.js +0 -1444
  129. package/dist/datepicker-D_3vYgMt.cjs +0 -271
  130. package/dist/icon-BgG6oO4Q.js +0 -89
  131. package/dist/icon-Dl9ZPqe6.cjs +0 -9
  132. package/dist/input-element-RBQVA8i0.cjs +0 -1
  133. package/dist/pkt-slot-controller-BzddBp7z.cjs +0 -1
  134. package/src/components/calendar/helpers/calendar-grid.ts +0 -93
  135. package/src/components/calendar/helpers/date-validation.ts +0 -86
  136. package/src/components/calendar/helpers/index.ts +0 -49
  137. package/src/components/calendar/helpers/keyboard-navigation.ts +0 -54
  138. package/src/components/calendar/helpers/selection-manager.ts +0 -184
@@ -1,135 +1,50 @@
1
- import {
2
- fromISOToDate,
3
- newDate,
4
- isValidDateRange,
5
- sortDateStrings,
6
- filterSelectableDates,
7
- } from 'shared-utils/date-utils'
8
- import { isIOS } from 'shared-utils/device-utils'
1
+ import { fromISOToDate, newDate } from 'shared-utils/date-utils'
9
2
  import { Ref } from 'lit/directives/ref.js'
10
3
  import { PktCalendar } from '@/components/calendar/calendar'
4
+ import {
5
+ validateRangeOrder,
6
+ sortDates,
7
+ filterDates,
8
+ getDatepickerInputType,
9
+ getDatepickerInputClasses,
10
+ getDatepickerButtonClasses,
11
+ getRangeLabelClasses,
12
+ processDateSelection as sharedProcessDateSelection,
13
+ handleCalendarPosition as sharedHandleCalendarPosition,
14
+ handleDatepickerKeydown,
15
+ handleDatepickerButtonKeydown,
16
+ } from 'shared-utils/datepicker-utils'
11
17
 
12
18
  /**
13
19
  * Utility functions for PktDatepicker component
14
20
  *
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
21
+ * This module provides helper functions organized by concern.
22
+ * Framework-agnostic functions are delegated to shared-utils/datepicker-utils.
23
+ * Lit-specific functions (using Ref, ElementInternals, PktCalendar) stay here.
24
24
  */
25
25
 
26
26
  /**
27
27
  * Value parsing and validation utilities
28
+ * Delegates to shared-utils/datepicker-utils
28
29
  */
29
30
  export const valueUtils = {
30
- /**
31
- * Validates that a range has valid order (start <= end)
32
- *
33
- * @param values - Array of date strings (ISO format YYYY-MM-DD)
34
- * @returns True if range is valid or incomplete, false if end date is before start date
35
- *
36
- * @example
37
- * validateRangeOrder(['2024-01-01', '2024-01-31']) // Returns true
38
- * validateRangeOrder(['2024-01-31', '2024-01-01']) // Returns false
39
- * validateRangeOrder(['2024-01-01']) // Returns true (incomplete range)
40
- */
41
- validateRangeOrder(values: string[]): boolean {
42
- if (!values || values.length !== 2) return true // Not a complete range
43
- return isValidDateRange(values[0], values[1])
44
- },
45
-
46
- /**
47
- * Sorts date strings chronologically
48
- *
49
- * @param dates - Array of date strings in ISO format (YYYY-MM-DD)
50
- * @returns New array with dates sorted from earliest to latest
51
- *
52
- * @example
53
- * sortDates(['2024-03-15', '2024-01-10', '2024-02-20'])
54
- * // Returns ['2024-01-10', '2024-02-20', '2024-03-15']
55
- */
56
- sortDates(dates: string[]): string[] {
57
- return sortDateStrings(dates)
58
- },
59
-
60
- /**
61
- * Filters dates to only include selectable ones based on constraints
62
- *
63
- * Removes dates that:
64
- * - Fall outside the min/max range
65
- * - Are in the excludedDates array
66
- * - Fall on excluded weekdays (0=Sunday, 6=Saturday)
67
- *
68
- * @param dates - Array of date strings to filter
69
- * @param min - Minimum allowed date (ISO format)
70
- * @param max - Maximum allowed date (ISO format)
71
- * @param excludedDates - Array of specific dates to exclude
72
- * @param excludedWeekdays - Array of weekday numbers to exclude ('0'-'6')
73
- * @returns Filtered array containing only selectable dates
74
- *
75
- * @example
76
- * filterSelectableDates(
77
- * ['2024-01-01', '2024-01-06', '2024-01-15'],
78
- * '2024-01-05',
79
- * '2024-01-20',
80
- * ['2024-01-15'],
81
- * ['0', '6'] // Exclude weekends
82
- * )
83
- * // Returns ['2024-01-06'] (after min, not excluded, not a weekend)
84
- */
85
- filterSelectableDates(
86
- dates: string[],
87
- min?: string | null,
88
- max?: string | null,
89
- excludedDates?: string[],
90
- excludedWeekdays?: string[],
91
- ): string[] {
92
- return filterSelectableDates(dates, min, max, excludedDates, excludedWeekdays)
93
- },
31
+ validateRangeOrder,
32
+ sortDates,
33
+ filterSelectableDates: filterDates,
94
34
  }
95
35
 
96
36
  /**
97
37
  * Input type detection utilities
38
+ * Delegates to shared-utils/datepicker-utils
98
39
  */
99
40
  export const inputTypeUtils = {
100
- /**
101
- * Determines the appropriate input type based on device
102
- *
103
- * Mobile Safari does not play well with type="date" and custom datepickers,
104
- * so we use type="text" on iOS devices to avoid the native date picker
105
- * interfering with our custom calendar component.
106
- *
107
- * @returns 'text' for iOS devices, 'date' for all other devices
108
- *
109
- * @example
110
- * const inputType = getInputType()
111
- * // Returns 'text' on iPhone/iPad, 'date' on desktop/Android
112
- */
113
- getInputType(): string {
114
- return isIOS() ? 'text' : 'date'
115
- },
41
+ getInputType: getDatepickerInputType,
116
42
  }
117
43
 
118
44
  /**
119
- * Form and validation utilities
45
+ * Form and validation utilities (Lit-specific — uses ElementInternals)
120
46
  */
121
47
  export const formUtils = {
122
- /**
123
- * Submits the form that contains the given element
124
- *
125
- * Uses ElementInternals API to access the associated form and trigger submission.
126
- * Requires the element to have attachInternals() called.
127
- *
128
- * @param element - The form-associated custom element
129
- *
130
- * @example
131
- * submitForm(this) // From within a custom element
132
- */
133
48
  submitForm(element: HTMLElement): void {
134
49
  const form = (element as any).internals?.form as HTMLFormElement
135
50
  if (form) {
@@ -137,21 +52,6 @@ export const formUtils = {
137
52
  }
138
53
  },
139
54
 
140
- /**
141
- * Submits form if available, otherwise executes fallback action
142
- *
143
- * Useful for Enter key handling where we want to submit the form,
144
- * but provide a fallback behavior (like moving focus) if not in a form.
145
- *
146
- * @param internals - ElementInternals instance with optional form reference
147
- * @param fallbackAction - Function to call if no form is available
148
- *
149
- * @example
150
- * submitFormOrFallback(
151
- * this.internals,
152
- * () => this.inputRef.value?.blur()
153
- * )
154
- */
155
55
  submitFormOrFallback(internals: any, fallbackAction: () => void): void {
156
56
  const form = internals?.form as HTMLFormElement
157
57
  if (form) {
@@ -161,27 +61,6 @@ export const formUtils = {
161
61
  }
162
62
  },
163
63
 
164
- /**
165
- * Validates a date input and sets validity state
166
- *
167
- * Checks if the input value falls within the min/max range and
168
- * sets appropriate validity flags using the ElementInternals API.
169
- *
170
- * @param input - The input element to validate
171
- * @param internals - ElementInternals instance for setting validity
172
- * @param min - Minimum allowed date (ISO format)
173
- * @param max - Maximum allowed date (ISO format)
174
- * @param strings - Optional localized error messages
175
- *
176
- * @example
177
- * validateDateInput(
178
- * inputElement,
179
- * this.internals,
180
- * '2024-01-01',
181
- * '2024-12-31',
182
- * { forms: { messages: { rangeUnderflow: 'Date too early' } } }
183
- * )
184
- */
185
64
  validateDateInput(
186
65
  input: HTMLInputElement,
187
66
  internals: any,
@@ -210,28 +89,10 @@ export const formUtils = {
210
89
 
211
90
  /**
212
91
  * Calendar interaction utilities
92
+ * handleCalendarPosition delegates to shared-utils (with Lit ref unwrapping).
93
+ * addToSelected stays Lit-specific (uses Ref<PktCalendar>).
213
94
  */
214
95
  export const calendarUtils = {
215
- /**
216
- * Adds a date to selected dates if it's valid
217
- *
218
- * Used for multiple date selection - validates the input date against
219
- * min/max constraints before adding it to the calendar's selected dates.
220
- * Clears the input field after processing.
221
- *
222
- * @param event - Input or keyboard event containing the target input
223
- * @param calendarRef - Lit ref to the calendar component
224
- * @param min - Minimum allowed date (ISO format)
225
- * @param max - Maximum allowed date (ISO format)
226
- *
227
- * @example
228
- * addToSelected(
229
- * blurEvent,
230
- * this.calendarRef,
231
- * '2024-01-01',
232
- * '2024-12-31'
233
- * )
234
- */
235
96
  addToSelected(
236
97
  event: Event | KeyboardEvent,
237
98
  calendarRef: Ref<PktCalendar>,
@@ -257,87 +118,19 @@ export const calendarUtils = {
257
118
  target.value = ''
258
119
  },
259
120
 
260
- /**
261
- * Handles calendar positioning based on viewport and input position
262
- *
263
- * Intelligently positions the calendar popup either above or below the input
264
- * depending on available viewport space. Accounts for optional counter elements.
265
- *
266
- * The positioning logic:
267
- * 1. By default, positions calendar below the input (top: 100%)
268
- * 2. If calendar would overflow viewport bottom AND there's space above, positions above
269
- * 3. Accounts for counter height (30px) when hasCounter is true
270
- *
271
- * @param popupRef - Lit ref to the calendar popup element
272
- * @param inputRef - Lit ref to the input element
273
- * @param hasCounter - Whether the input has a character counter below it
274
- *
275
- * @example
276
- * handleCalendarPosition(
277
- * this.popupRef,
278
- * this.inputRef,
279
- * this.multiple && this.maxlength !== null
280
- * )
281
- */
282
121
  handleCalendarPosition(
283
122
  popupRef: Ref<HTMLDivElement>,
284
123
  inputRef: Ref<HTMLInputElement>,
285
124
  hasCounter: boolean = false,
286
125
  ): void {
287
- if (!popupRef.value || !inputRef.value) return
288
-
289
- const inputRect =
290
- inputRef.value.parentElement?.getBoundingClientRect() ||
291
- inputRef.value.getBoundingClientRect()
292
-
293
- const inputHeight = hasCounter ? inputRect.height + 30 : inputRect.height
294
- const popupHeight = popupRef.value.getBoundingClientRect().height
295
-
296
- let top = hasCounter ? 'calc(100% - 30px)' : '100%'
297
-
298
- if (
299
- inputRect &&
300
- inputRect.top + popupHeight > window.innerHeight &&
301
- inputRect.top - popupHeight > 0
302
- ) {
303
- top = `calc(100% - ${inputHeight}px - ${popupHeight}px)`
304
- }
305
-
306
- popupRef.value.style.top = top
126
+ sharedHandleCalendarPosition(popupRef.value ?? null, inputRef.value ?? null, hasCounter)
307
127
  },
308
128
  }
309
129
 
310
130
  /**
311
- * Event handling utilities
312
- *
313
- * Factory functions for creating event listeners with proper closure over component state.
131
+ * Event handling utilities (Lit-specific — uses Lit Ref types)
314
132
  */
315
133
  export const eventUtils = {
316
- /**
317
- * Creates a document click listener for closing calendar on outside clicks
318
- *
319
- * Returns a function that closes the calendar when clicking outside the
320
- * datepicker component (input, button, or calendar popup).
321
- *
322
- * @param inputRef - Lit ref to the primary input element
323
- * @param inputRefTo - Lit ref to the secondary input (for range picker) or null
324
- * @param btnRef - Lit ref to the calendar button
325
- * @param getCalendarOpen - Function returning current calendar open state
326
- * @param onBlur - Callback to execute on blur
327
- * @param hideCalendar - Callback to hide the calendar
328
- * @returns Event listener function to attach to document
329
- *
330
- * @example
331
- * const listener = createDocumentClickListener(
332
- * this.inputRef,
333
- * null,
334
- * this.btnRef,
335
- * () => this.calendarOpen,
336
- * () => this.handleBlur(),
337
- * () => this.calendarOpen = false
338
- * )
339
- * document.addEventListener('click', listener)
340
- */
341
134
  createDocumentClickListener(
342
135
  inputRef: Ref<HTMLInputElement>,
343
136
  inputRefTo: Ref<HTMLInputElement> | null,
@@ -362,22 +155,6 @@ export const eventUtils = {
362
155
  }
363
156
  },
364
157
 
365
- /**
366
- * Creates a document keydown listener for ESC key
367
- *
368
- * Returns a function that closes the calendar when Escape is pressed.
369
- *
370
- * @param getCalendarOpen - Function returning current calendar open state
371
- * @param hideCalendar - Callback to hide the calendar
372
- * @returns Event listener function to attach to document
373
- *
374
- * @example
375
- * const listener = createDocumentKeydownListener(
376
- * () => this.calendarOpen,
377
- * () => this.calendarOpen = false
378
- * )
379
- * document.addEventListener('keydown', listener)
380
- */
381
158
  createDocumentKeydownListener(
382
159
  getCalendarOpen: () => boolean,
383
160
  hideCalendar: () => void,
@@ -389,25 +166,6 @@ export const eventUtils = {
389
166
  }
390
167
  },
391
168
 
392
- /**
393
- * Handles focus out events for calendar popup
394
- *
395
- * Closes the calendar when focus leaves the datepicker component entirely.
396
- * Used for keyboard navigation accessibility.
397
- *
398
- * @param event - The focusout event
399
- * @param element - The container element to check focus against
400
- * @param onBlur - Callback to execute on blur
401
- * @param hideCalendar - Callback to hide the calendar
402
- *
403
- * @example
404
- * handleFocusOut(
405
- * event,
406
- * this,
407
- * () => this.handleBlur(),
408
- * () => this.calendarOpen = false
409
- * )
410
- */
411
169
  handleFocusOut(
412
170
  event: FocusEvent,
413
171
  element: HTMLElement,
@@ -423,139 +181,22 @@ export const eventUtils = {
423
181
 
424
182
  /**
425
183
  * CSS class utilities
426
- *
427
- * Functions for generating CSS class objects for use with classMap directive.
184
+ * Delegates to shared-utils/datepicker-utils
428
185
  */
429
186
  export const cssUtils = {
430
- /**
431
- * Generates input classes for datepicker
432
- *
433
- * @param fullwidth - Whether input should take full width
434
- * @param showRangeLabels - Whether range labels are visible
435
- * @param multiple - Whether this is a multiple date picker
436
- * @param range - Whether this is a range date picker
437
- * @param readonly - Whether input is readonly
438
- * @param inputType - The input type ('date' or 'text')
439
- * @returns Object with class names as keys and boolean values
440
- *
441
- * @example
442
- * const classes = getInputClasses(true, false, false, false, false, 'date')
443
- * // Returns { 'pkt-input': true, 'pkt-input--fullwidth': true, ... }
444
- */
445
- getInputClasses(
446
- fullwidth: boolean,
447
- showRangeLabels: boolean,
448
- multiple: boolean,
449
- range: boolean,
450
- readonly?: boolean,
451
- inputType?: string,
452
- ) {
453
- return {
454
- 'pkt-input': true,
455
- 'pkt-datepicker__input': true,
456
- 'pkt-input--fullwidth': fullwidth,
457
- 'pkt-datepicker--hasrangelabels': showRangeLabels,
458
- 'pkt-datepicker--multiple': multiple,
459
- 'pkt-datepicker--range': range,
460
- 'ios-readonly-hack': readonly === false && inputType === 'text',
461
- }
462
- },
463
-
464
- /**
465
- * Generates button classes for calendar button
466
- *
467
- * @returns Object with class names for the calendar toggle button
468
- *
469
- * @example
470
- * const classes = getButtonClasses()
471
- * // Returns { 'pkt-btn': true, 'pkt-btn--icon-only': true, ... }
472
- */
473
- getButtonClasses() {
474
- return {
475
- 'pkt-input-icon': true,
476
- 'pkt-btn': true,
477
- 'pkt-btn--icon-only': true,
478
- 'pkt-btn--tertiary': true,
479
- 'pkt-datepicker__calendar-button': true,
480
- }
481
- },
482
-
483
- /**
484
- * Generates range label classes
485
- *
486
- * @param showRangeLabels - Whether range labels should be visible
487
- * @returns Object with class names for range label elements
488
- *
489
- * @example
490
- * const classes = getRangeLabelClasses(true)
491
- * // Returns { 'pkt-input-prefix': true, 'pkt-hide': false }
492
- */
493
- getRangeLabelClasses(showRangeLabels: boolean) {
494
- return {
495
- 'pkt-input-prefix': showRangeLabels,
496
- 'pkt-hide': !showRangeLabels,
497
- }
498
- },
187
+ getInputClasses: getDatepickerInputClasses,
188
+ getButtonClasses: getDatepickerButtonClasses,
189
+ getRangeLabelClasses,
499
190
  }
500
191
 
501
192
  /**
502
193
  * Date value processing utilities
503
- *
504
- * Functions for transforming and updating date values between calendar and input elements.
194
+ * processDateSelection delegates to shared-utils.
195
+ * updateInputValues and processRangeBlur stay Lit-specific (use Lit Ref).
505
196
  */
506
197
  export const dateProcessingUtils = {
507
- /**
508
- * Processes date selection from calendar events
509
- *
510
- * Converts the calendar's date selection into the appropriate string format
511
- * for the input element's value.
512
- *
513
- * @param detail - Date selection detail from calendar event (string or array)
514
- * @param multiple - Whether this is a multiple date picker
515
- * @param range - Whether this is a range date picker
516
- * @returns Formatted date string (single date or comma-separated dates)
517
- *
518
- * @example
519
- * processDateSelection(['2024-01-15'], false, false) // Returns '2024-01-15'
520
- * processDateSelection(['2024-01-15', '2024-01-20'], true, false) // Returns '2024-01-15,2024-01-20'
521
- */
522
- processDateSelection(detail: any, multiple: boolean, range: boolean): string {
523
- if (!multiple && !range) {
524
- return detail[0] || ''
525
- }
526
- if (Array.isArray(detail)) {
527
- return detail.join(',')
528
- }
529
- return detail
530
- },
198
+ processDateSelection: sharedProcessDateSelection,
531
199
 
532
- /**
533
- * Updates input values after calendar selection
534
- *
535
- * Synchronizes input element values with the calendar's selected dates.
536
- * Handles single, range, and multiple date scenarios differently.
537
- *
538
- * - Single: Updates single input with first date
539
- * - Range: Updates two inputs with start and end dates
540
- * - Multiple: Does nothing (dates shown as tags, not in input)
541
- *
542
- * @param inputRef - Lit ref to the primary input element
543
- * @param inputRefTo - Lit ref to the secondary input (for range) or null
544
- * @param values - Array of selected date strings
545
- * @param range - Whether this is a range date picker
546
- * @param multiple - Whether this is a multiple date picker
547
- * @param manageValidity - Callback to validate the input
548
- *
549
- * @example
550
- * updateInputValues(
551
- * this.inputRef,
552
- * this.inputRefTo,
553
- * ['2024-01-15', '2024-01-20'],
554
- * true, // range
555
- * false,
556
- * (input) => this.dispatchManageValidity(input)
557
- * )
558
- */
559
200
  updateInputValues(
560
201
  inputRef: Ref<HTMLInputElement>,
561
202
  inputRefTo: Ref<HTMLInputElement> | null,
@@ -577,30 +218,6 @@ export const dateProcessingUtils = {
577
218
  }
578
219
  },
579
220
 
580
- /**
581
- * Processes blur events for range inputs
582
- *
583
- * Handles the logic when a range input loses focus:
584
- * - If input has value: validates it and updates the calendar to reflect the typed date
585
- * - If input is empty but range has start date: clears the entire range
586
- *
587
- * This ensures the range picker calendar stays synchronized when users type dates directly.
588
- *
589
- * @param event - The blur event from the input
590
- * @param values - Current array of selected dates [start, end]
591
- * @param calendarRef - Lit ref to the calendar component
592
- * @param clearInputValue - Callback to clear the date selection
593
- * @param manageValidity - Callback to validate the input
594
- *
595
- * @example
596
- * processRangeBlur(
597
- * blurEvent,
598
- * ['2024-01-15', '2024-01-20'],
599
- * this.calendarRef,
600
- * () => this.value = [],
601
- * (input) => this.dispatchManageValidity(input)
602
- * )
603
- */
604
221
  processRangeBlur(
605
222
  event: Event,
606
223
  values: string[],
@@ -613,7 +230,6 @@ export const dateProcessingUtils = {
613
230
  manageValidity(target)
614
231
  const date = fromISOToDate(target.value)
615
232
  if (date) {
616
- // Always update the calendar when a valid date is typed
617
233
  calendarRef?.value?.handleDateSelect(date)
618
234
  }
619
235
  } else if (values[0]) {
@@ -624,104 +240,9 @@ export const dateProcessingUtils = {
624
240
 
625
241
  /**
626
242
  * Keyboard navigation utilities
627
- *
628
- * Functions for handling keyboard interactions with proper accessibility support.
243
+ * Delegates to shared-utils/datepicker-utils
629
244
  */
630
245
  export const keyboardUtils = {
631
- /**
632
- * Handles common keyboard interactions for datepicker inputs
633
- *
634
- * Provides consistent keyboard navigation across all datepicker types:
635
- * - Space: Opens calendar
636
- * - Enter: Submits form, focuses next input, or blurs (priority order)
637
- * - Comma: Adds date to selection (multiple) or blurs input
638
- *
639
- * The handler prioritizes callbacks in the order provided, allowing
640
- * different behaviors for single, multiple, and range pickers.
641
- *
642
- * @param event - The keyboard event
643
- * @param toggleCalendar - Callback to toggle calendar visibility
644
- * @param submitForm - Optional callback to submit the form (highest priority on Enter)
645
- * @param focusNextInput - Optional callback to focus next input (medium priority on Enter)
646
- * @param blurInput - Optional callback to blur current input (lowest priority on Enter/Comma)
647
- * @param commaHandler - Optional callback for comma key (overrides default blur behavior)
648
- *
649
- * @example
650
- * // Single datepicker: Enter submits form or blurs
651
- * handleInputKeydown(
652
- * event,
653
- * (e) => this.toggleCalendar(e),
654
- * () => formUtils.submitFormOrFallback(this.internals, () => this.inputRef.value?.blur()),
655
- * undefined,
656
- * () => this.inputRef.value?.blur()
657
- * )
658
- *
659
- * @example
660
- * // Range datepicker: Enter moves to next input
661
- * handleInputKeydown(
662
- * event,
663
- * (e) => this.toggleCalendar(e),
664
- * () => formUtils.submitFormOrFallback(this.internals, () => this.inputRefTo.value?.focus()),
665
- * () => this.inputRefTo.value?.focus(),
666
- * () => this.inputRef.value?.blur()
667
- * )
668
- */
669
- handleInputKeydown(
670
- event: KeyboardEvent,
671
- toggleCalendar: (e: Event) => void,
672
- submitForm?: () => void,
673
- focusNextInput?: () => void,
674
- blurInput?: () => void,
675
- commaHandler?: (e: KeyboardEvent) => void,
676
- ): void {
677
- const { key } = event
678
-
679
- if (key === ',') {
680
- event.preventDefault()
681
- if (commaHandler) {
682
- commaHandler(event)
683
- } else if (blurInput) {
684
- blurInput()
685
- }
686
- }
687
-
688
- if (key === 'Space' || key === ' ') {
689
- event.preventDefault()
690
- toggleCalendar(event)
691
- }
692
-
693
- if (key === 'Enter') {
694
- event.preventDefault()
695
- if (submitForm) {
696
- submitForm()
697
- } else if (focusNextInput) {
698
- focusNextInput()
699
- } else if (blurInput) {
700
- blurInput()
701
- }
702
- }
703
- },
704
-
705
- /**
706
- * Handles keyboard interactions for calendar button
707
- *
708
- * Ensures the calendar button is keyboard accessible by responding
709
- * to Enter and Space keys (standard button activation keys).
710
- *
711
- * @param event - The keyboard event
712
- * @param toggleCalendar - Callback to toggle calendar visibility
713
- *
714
- * @example
715
- * handleButtonKeydown(
716
- * event,
717
- * (e) => this.dispatchToggleCalendar(e)
718
- * )
719
- */
720
- handleButtonKeydown(event: KeyboardEvent, toggleCalendar: (e: Event) => void): void {
721
- const { key } = event
722
- if (key === 'Enter' || key === ' ' || key === 'Space') {
723
- event.preventDefault()
724
- toggleCalendar(event)
725
- }
726
- },
246
+ handleInputKeydown: handleDatepickerKeydown,
247
+ handleButtonKeydown: handleDatepickerButtonKeydown,
727
248
  }