@oslokommune/punkt-elements 13.4.2 → 13.5.1

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 (41) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/calendar-32W9p9uc.cjs +115 -0
  3. package/dist/{calendar-DevQhOup.js → calendar-CJSxvwAq.js} +353 -340
  4. package/dist/{card-Dtw26f7i.js → card-BDz4RWxK.js} +1 -1
  5. package/dist/{card-BUITGoqX.cjs → card-DBlFf1ry.cjs} +1 -1
  6. package/dist/{datepicker-CYOn3tRm.js → datepicker-BJKJBoy_.js} +102 -59
  7. package/dist/datepicker-CmTrG5GE.cjs +164 -0
  8. package/dist/{heading-D6jXE_Mz.js → heading-Bdh9absf.js} +22 -22
  9. package/dist/heading-CNycsyMj.cjs +1 -0
  10. package/dist/index.d.ts +6 -2
  11. package/dist/pkt-calendar.cjs +1 -1
  12. package/dist/pkt-calendar.js +1 -1
  13. package/dist/pkt-card.cjs +1 -1
  14. package/dist/pkt-card.js +1 -1
  15. package/dist/pkt-datepicker.cjs +1 -1
  16. package/dist/pkt-datepicker.js +1 -1
  17. package/dist/pkt-heading.cjs +1 -1
  18. package/dist/pkt-heading.js +1 -1
  19. package/dist/pkt-index.cjs +1 -1
  20. package/dist/pkt-index.js +5 -5
  21. package/package.json +3 -3
  22. package/src/components/calendar/calendar.accessibility.test.ts +111 -0
  23. package/src/components/calendar/calendar.constraints.test.ts +110 -0
  24. package/src/components/calendar/calendar.core.test.ts +367 -0
  25. package/src/components/calendar/calendar.interaction.test.ts +139 -0
  26. package/src/components/calendar/calendar.selection.test.ts +273 -0
  27. package/src/components/calendar/calendar.ts +74 -42
  28. package/src/components/card/card.test.ts +19 -5
  29. package/src/components/consent/consent.test.ts +436 -0
  30. package/src/components/datepicker/datepicker.accessibility.test.ts +193 -0
  31. package/src/components/datepicker/datepicker.core.test.ts +322 -0
  32. package/src/components/datepicker/datepicker.input.test.ts +268 -0
  33. package/src/components/datepicker/datepicker.selection.test.ts +286 -0
  34. package/src/components/datepicker/datepicker.ts +121 -19
  35. package/src/components/datepicker/datepicker.validation.test.ts +176 -0
  36. package/src/components/heading/heading.test.ts +458 -0
  37. package/src/components/heading/heading.ts +3 -0
  38. package/src/components/helptext/helptext.test.ts +474 -0
  39. package/dist/calendar-BZe2D4Sr.cjs +0 -108
  40. package/dist/datepicker-B9rhz_AF.cjs +0 -154
  41. package/dist/heading-BRE_iFtR.cjs +0 -1
@@ -0,0 +1,273 @@
1
+ import '@testing-library/jest-dom'
2
+ import { fireEvent } from '@testing-library/dom'
3
+ import { parseISODateString } from '@/utils/dateutils'
4
+
5
+ import './calendar'
6
+ import { PktCalendar } from './calendar'
7
+
8
+ const waitForCustomElements = async () => {
9
+ await customElements.whenDefined('pkt-calendar')
10
+ }
11
+
12
+ // Helper function to create calendar markup
13
+ const createCalendar = async (calendarProps = '') => {
14
+ const container = document.createElement('div')
15
+ container.innerHTML = `
16
+ <pkt-calendar ${calendarProps}></pkt-calendar>
17
+ `
18
+ document.body.appendChild(container)
19
+ await waitForCustomElements()
20
+ return container
21
+ }
22
+
23
+ // Cleanup after each test
24
+ afterEach(() => {
25
+ document.body.innerHTML = ''
26
+ })
27
+
28
+ describe('PktCalendar', () => {
29
+ describe('Date selection functionality', () => {
30
+ test('selects single date correctly', async () => {
31
+ const container = await createCalendar()
32
+
33
+ const calendar = container.querySelector('pkt-calendar') as PktCalendar
34
+ await calendar.updateComplete
35
+
36
+ // Find and click on a date
37
+ const availableDate = calendar.querySelector(
38
+ '.pkt-calendar__date:not(.pkt-calendar__date--disabled)',
39
+ )
40
+ expect(availableDate).toBeInTheDocument()
41
+
42
+ fireEvent.click(availableDate!)
43
+ await calendar.updateComplete
44
+
45
+ expect(availableDate).toHaveClass('pkt-calendar__date--selected')
46
+ })
47
+
48
+ test('handles multiple date selection', async () => {
49
+ const container = await createCalendar('multiple')
50
+
51
+ const calendar = container.querySelector('pkt-calendar') as PktCalendar
52
+ await calendar.updateComplete
53
+
54
+ // Find and click on multiple dates
55
+ const availableDates = calendar.querySelectorAll(
56
+ '.pkt-calendar__date:not(.pkt-calendar__date--disabled)',
57
+ )
58
+ expect(availableDates.length).toBeGreaterThan(1)
59
+
60
+ fireEvent.click(availableDates[0])
61
+ await calendar.updateComplete
62
+ fireEvent.click(availableDates[1])
63
+ await calendar.updateComplete
64
+
65
+ expect(availableDates[0]).toHaveClass('pkt-calendar__date--selected')
66
+ expect(availableDates[1]).toHaveClass('pkt-calendar__date--selected')
67
+ })
68
+
69
+ test('respects maxMultiple limit', async () => {
70
+ const container = await createCalendar('multiple maxMultiple="2"')
71
+
72
+ const calendar = container.querySelector('pkt-calendar') as PktCalendar
73
+ await calendar.updateComplete
74
+
75
+ // Try to select more than maxMultiple dates
76
+ const availableDates = calendar.querySelectorAll(
77
+ '.pkt-calendar__date:not(.pkt-calendar__date--disabled)',
78
+ )
79
+ expect(availableDates.length).toBeGreaterThan(2)
80
+
81
+ // Select 3 dates but only 2 should be selected
82
+ fireEvent.click(availableDates[0])
83
+ await calendar.updateComplete
84
+ fireEvent.click(availableDates[1])
85
+ await calendar.updateComplete
86
+ fireEvent.click(availableDates[2])
87
+ await calendar.updateComplete
88
+
89
+ const selectedDates = calendar.querySelectorAll('.pkt-calendar__date--selected')
90
+ expect(selectedDates.length).toBeLessThanOrEqual(2)
91
+ })
92
+
93
+ test('handles pre-selected dates', async () => {
94
+ const preSelectedDates = '2024-06-15,2024-06-20'
95
+ const container = await createCalendar(
96
+ `selected="${preSelectedDates}" currentmonth="2024-06-01"`,
97
+ )
98
+
99
+ const calendar = container.querySelector('pkt-calendar') as PktCalendar
100
+ await calendar.updateComplete
101
+
102
+ expect(calendar.selected).toEqual(['2024-06-15', '2024-06-20'])
103
+
104
+ const selectedDates = calendar.querySelectorAll('.pkt-calendar__date--selected')
105
+ expect(selectedDates.length).toBe(2)
106
+ })
107
+
108
+ test('toggles date selection when clicking same date twice', async () => {
109
+ const container = await createCalendar()
110
+
111
+ const calendar = container.querySelector('pkt-calendar') as PktCalendar
112
+ await calendar.updateComplete
113
+
114
+ const availableDate = calendar.querySelector(
115
+ '.pkt-calendar__date:not(.pkt-calendar__date--disabled)',
116
+ )
117
+ expect(availableDate).toBeInTheDocument()
118
+
119
+ // First click - select
120
+ fireEvent.click(availableDate!)
121
+ await calendar.updateComplete
122
+ expect(availableDate).toHaveClass('pkt-calendar__date--selected')
123
+
124
+ // Second click - deselect
125
+ fireEvent.click(availableDate!)
126
+ await calendar.updateComplete
127
+ expect(availableDate).not.toHaveClass('pkt-calendar__date--selected')
128
+ })
129
+ })
130
+
131
+ describe('Range selection functionality', () => {
132
+ test('handles range selection correctly', async () => {
133
+ const container = await createCalendar('range')
134
+
135
+ const calendar = container.querySelector('pkt-calendar') as PktCalendar
136
+ await calendar.updateComplete
137
+
138
+ const availableDates = calendar.querySelectorAll(
139
+ '.pkt-calendar__date:not(.pkt-calendar__date--disabled)',
140
+ )
141
+ expect(availableDates.length).toBeGreaterThan(3)
142
+
143
+ // Select start date
144
+ fireEvent.click(availableDates[5])
145
+ await calendar.updateComplete
146
+
147
+ // Select end date
148
+ fireEvent.click(availableDates[10])
149
+ await calendar.updateComplete
150
+
151
+ // Check for range styling
152
+ const rangeStart = calendar.querySelector('.pkt-calendar__date--range-start')
153
+ const rangeEnd = calendar.querySelector('.pkt-calendar__date--range-end')
154
+ const rangeInBetween = calendar.querySelectorAll('.pkt-calendar__date--in-range')
155
+
156
+ expect(rangeStart).toBeInTheDocument()
157
+ expect(rangeEnd).toBeInTheDocument()
158
+ expect(rangeInBetween.length).toBeGreaterThan(0)
159
+ })
160
+
161
+ test('shows range hover preview', async () => {
162
+ const container = await createCalendar('range')
163
+
164
+ const calendar = container.querySelector('pkt-calendar') as PktCalendar
165
+ await calendar.updateComplete
166
+
167
+ const availableDates = calendar.querySelectorAll(
168
+ '.pkt-calendar__date:not(.pkt-calendar__date--disabled)',
169
+ )
170
+
171
+ // Select start date
172
+ fireEvent.click(availableDates[5])
173
+ await calendar.updateComplete
174
+
175
+ // Hover over potential end date
176
+ fireEvent.mouseOver(availableDates[10])
177
+ await calendar.updateComplete
178
+
179
+ // Should show hover preview styling
180
+ const hoveredRanges = calendar.querySelectorAll('.pkt-calendar__date--in-range-hover')
181
+ expect(hoveredRanges.length).toBeGreaterThan(0)
182
+ })
183
+
184
+ test('clears range when selecting new start date', async () => {
185
+ const container = await createCalendar('range')
186
+
187
+ const calendar = container.querySelector('pkt-calendar') as PktCalendar
188
+ await calendar.updateComplete
189
+
190
+ const availableDates = calendar.querySelectorAll(
191
+ '.pkt-calendar__date:not(.pkt-calendar__date--disabled)',
192
+ )
193
+
194
+ // Create initial range
195
+ fireEvent.click(availableDates[5])
196
+ await calendar.updateComplete
197
+ fireEvent.click(availableDates[10])
198
+ await calendar.updateComplete
199
+
200
+ // Select new start date
201
+ fireEvent.click(availableDates[2])
202
+ await calendar.updateComplete
203
+
204
+ // Old range should be cleared
205
+ const selectedDates = calendar.querySelectorAll('.pkt-calendar__date--selected')
206
+ expect(selectedDates.length).toBe(1)
207
+ })
208
+ })
209
+
210
+ describe('API methods', () => {
211
+ test('addToSelected method works correctly', async () => {
212
+ const container = await createCalendar('multiple')
213
+
214
+ const calendar = container.querySelector('pkt-calendar') as PktCalendar
215
+ await calendar.updateComplete
216
+
217
+ const testDate = parseISODateString('2024-06-15')
218
+ calendar.addToSelected(testDate)
219
+ await calendar.updateComplete
220
+
221
+ expect(calendar['_selected']).toContainEqual(testDate)
222
+ })
223
+
224
+ test('removeFromSelected method works correctly', async () => {
225
+ const container = await createCalendar('multiple')
226
+
227
+ const calendar = container.querySelector('pkt-calendar') as PktCalendar
228
+ await calendar.updateComplete
229
+
230
+ const testDate = new Date('2024-06-15')
231
+ calendar.addToSelected(testDate)
232
+ await calendar.updateComplete
233
+
234
+ calendar.removeFromSelected(testDate)
235
+ await calendar.updateComplete
236
+
237
+ expect(calendar['_selected']).not.toContainEqual(testDate)
238
+ })
239
+
240
+ test('toggleSelected method works correctly', async () => {
241
+ const container = await createCalendar()
242
+
243
+ const calendar = container.querySelector('pkt-calendar') as PktCalendar
244
+ await calendar.updateComplete
245
+
246
+ const testDate = parseISODateString('2024-06-15')
247
+
248
+ // Toggle on
249
+ calendar.toggleSelected(testDate)
250
+ await calendar.updateComplete
251
+ expect(calendar['_selected']).toContainEqual(testDate)
252
+
253
+ // Toggle off
254
+ calendar.toggleSelected(testDate)
255
+ await calendar.updateComplete
256
+ expect(calendar['_selected']).not.toContainEqual(testDate)
257
+ })
258
+
259
+ test('focusOnCurrentDate method works correctly', async () => {
260
+ const container = await createCalendar()
261
+
262
+ const calendar = container.querySelector('pkt-calendar') as PktCalendar
263
+ await calendar.updateComplete
264
+
265
+ calendar.focusOnCurrentDate()
266
+ await calendar.updateComplete
267
+
268
+ // Should focus on a date element
269
+ const focusedElement = document.activeElement
270
+ expect(focusedElement).toHaveClass('pkt-calendar__date')
271
+ })
272
+ })
273
+ })
@@ -133,7 +133,10 @@ export class PktCalendar extends PktElement {
133
133
  if (Array.isArray(days) && days.length) {
134
134
  const inRange: DatesInRange = {}
135
135
  for (let i = 0; i < days.length; i++) {
136
- inRange[formatISODate(days[i])] = this.isInRange(days[i])
136
+ const day = days[i]
137
+ // A date is in range if it's between the start and end dates (exclusive)
138
+ const isInRange = day > this._selected[0] && day < this._selected[1]
139
+ inRange[formatISODate(day)] = isInRange
137
140
  }
138
141
  this.inRange = inRange
139
142
  }
@@ -144,7 +147,7 @@ export class PktCalendar extends PktElement {
144
147
  setCurrentMonth() {
145
148
  if (this.currentmonth === null && !this.currentmonthtouched) {
146
149
  this.currentmonthtouched = true
147
- return
150
+ // Don't return here - continue to set a default currentmonth
148
151
  }
149
152
  if (this.selected.length && this.selected[0] !== '') {
150
153
  const d = parseISODateString(this.selected[this.selected.length - 1])
@@ -182,7 +185,12 @@ export class PktCalendar extends PktElement {
182
185
  handleArrowKey(e: KeyboardEvent, direction: number) {
183
186
  if ((e.target as HTMLElement)?.nodeName === 'INPUT') return
184
187
  if ((e.target as HTMLElement)?.nodeName === 'SELECT') return
185
- if ((e.target as HTMLElement)?.nodeName === 'BUTTON') return
188
+ // Allow arrow keys on date buttons (which have data-date attribute), but not on navigation buttons
189
+ if (
190
+ (e.target as HTMLElement)?.nodeName === 'BUTTON' &&
191
+ !(e.target as HTMLElement)?.dataset?.date
192
+ )
193
+ return
186
194
  e.preventDefault()
187
195
  if (!this.focusedDate) {
188
196
  this.focusOnCurrentDate()
@@ -190,22 +198,22 @@ export class PktCalendar extends PktElement {
190
198
  const date = this.focusedDate ? newDate(this.focusedDate) : newDateYMD(this.year, this.month, 1)
191
199
  let nextDate = addDays(date, direction)
192
200
  if (nextDate) {
193
- let el = this.querySelector(`div[data-date="${formatISODate(nextDate)}"]`)
194
- if (el instanceof HTMLDivElement) {
201
+ let el = this.querySelector(`button[data-date="${formatISODate(nextDate)}"]`)
202
+ if (el instanceof HTMLButtonElement) {
195
203
  if (el.dataset.disabled) {
196
204
  nextDate = addDays(nextDate, direction)
197
- let nextElement = this.querySelector(`div[data-date="${formatISODate(nextDate)}"]`)
205
+ let nextElement = this.querySelector(`button[data-date="${formatISODate(nextDate)}"]`)
198
206
  while (
199
207
  nextElement &&
200
- nextElement instanceof HTMLDivElement &&
208
+ nextElement instanceof HTMLButtonElement &&
201
209
  nextElement.dataset.disabled
202
210
  ) {
203
211
  nextDate = addDays(nextDate, direction)
204
- nextElement = this.querySelector(`div[data-date="${formatISODate(nextDate)}"]`)
212
+ nextElement = this.querySelector(`button[data-date="${formatISODate(nextDate)}"]`)
205
213
  }
206
214
  el = nextElement
207
215
  }
208
- if (el instanceof HTMLDivElement && !el.dataset.disabled) {
216
+ if (el instanceof HTMLButtonElement && !el.dataset.disabled) {
209
217
  this.focusedDate = formatISODate(nextDate)
210
218
  el.focus()
211
219
  }
@@ -218,7 +226,7 @@ export class PktCalendar extends PktElement {
218
226
  render() {
219
227
  return html`
220
228
  <div
221
- class="pkt-calendar ${this.weeknumbers ? 'pkt-cal-weeknumbers' : nothing}"
229
+ class="pkt-calendar ${this.weeknumbers ? 'pkt-cal-weeknumbers' : ''}"
222
230
  @focusout=${this.closeEvent}
223
231
  @keydown=${(e: KeyboardEvent) => {
224
232
  if (e.key === 'Escape') {
@@ -231,14 +239,15 @@ export class PktCalendar extends PktElement {
231
239
  <div>
232
240
  <button
233
241
  type="button"
234
- @click=${this.isPrevMonthAllowed() && this.prevMonth}
242
+ aria-label="${this.prevMonthString}"
243
+ @click=${() => this.isPrevMonthAllowed() && this.prevMonth()}
235
244
  @keydown=${(e: KeyboardEvent) => {
236
245
  if (e.key === 'Enter' || e.key === ' ') {
237
246
  e.preventDefault()
238
- this.isNextMonthAllowed() && this.prevMonth()
247
+ this.isPrevMonthAllowed() && this.prevMonth()
239
248
  }
240
249
  }}
241
- class="pkt-btn pkt-btn--tertiary pkt-btn--small pkt-btn--icon-only ${this.isPrevMonthAllowed()
250
+ class="pkt-btn pkt-btn--tertiary pkt-btn--small pkt-btn--icon-only pkt-calendar__prev-month ${this.isPrevMonthAllowed()
242
251
  ? ''
243
252
  : 'pkt-hide'}"
244
253
  .data-disabled=${!this.isPrevMonthAllowed() ? 'disabled' : nothing}
@@ -253,14 +262,15 @@ export class PktCalendar extends PktElement {
253
262
  <div>
254
263
  <button
255
264
  type="button"
256
- @click=${this.isNextMonthAllowed() && this.nextMonth}
265
+ aria-label="${this.nextMonthString}"
266
+ @click=${() => this.isNextMonthAllowed() && this.nextMonth()}
257
267
  @keydown=${(e: KeyboardEvent) => {
258
268
  if (e.key === 'Enter' || e.key === ' ') {
259
269
  e.preventDefault()
260
270
  this.isNextMonthAllowed() && this.nextMonth()
261
271
  }
262
272
  }}
263
- class="pkt-btn pkt-btn--tertiary pkt-btn--small pkt-btn--icon-only ${this.isNextMonthAllowed()
273
+ class="pkt-btn pkt-btn--tertiary pkt-btn--small pkt-btn--icon-only pkt-calendar__next-month ${this.isNextMonthAllowed()
264
274
  ? ''
265
275
  : 'pkt-hide'}"
266
276
  .data-disabled=${!this.isNextMonthAllowed() ? 'disabled' : nothing}
@@ -273,7 +283,7 @@ export class PktCalendar extends PktElement {
273
283
  </div>
274
284
  </nav>
275
285
  <table
276
- class="pkt-cal-days pkt-txt-12-medium"
286
+ class="pkt-cal-days pkt-txt-12-medium pkt-calendar__body"
277
287
  role="grid"
278
288
  ?aria-multiselectable=${this.range || this.multiple}
279
289
  >
@@ -289,13 +299,17 @@ export class PktCalendar extends PktElement {
289
299
  }
290
300
 
291
301
  private renderDayNames() {
292
- const days = []
302
+ const days: any[] = []
293
303
  if (this.weeknumbers) {
294
- days.push(html`<th><div>${this.weekString}</div></th>`)
304
+ days.push(html`<th><div class="pkt-calendar__week-number">${this.weekString}</div></th>`)
295
305
  }
296
306
  for (let i = 0; i < this.dayStrings.length; i++) {
297
307
  days.push(
298
- html`<th><div aria-label="${this.dayStringsLong[i]}">${this.dayStrings[i]}</div></th>`,
308
+ html`<th>
309
+ <div class="pkt-calendar__day-name" aria-label="${this.dayStringsLong[i]}">
310
+ ${this.dayStrings[i]}
311
+ </div>
312
+ </th>`,
299
313
  )
300
314
  }
301
315
  return html`<tr class="pkt-cal-week-row">
@@ -304,7 +318,7 @@ export class PktCalendar extends PktElement {
304
318
  }
305
319
 
306
320
  private renderMonthNav() {
307
- let monthView = []
321
+ let monthView: any[] = []
308
322
  if (this.withcontrols) {
309
323
  monthView.push(
310
324
  html`<div class="pkt-cal-month-picker">
@@ -341,7 +355,7 @@ export class PktCalendar extends PktElement {
341
355
  )
342
356
  } else {
343
357
  monthView.push(
344
- html`<div class="pkt-txt-16-medium" aria-live="polite">
358
+ html`<div class="pkt-txt-16-medium pkt-calendar__month-title" aria-live="polite">
345
359
  ${this.monthStrings[this.month]} ${this.year}
346
360
  </div>`,
347
361
  )
@@ -391,11 +405,31 @@ export class PktCalendar extends PktElement {
391
405
  'pkt-cal-range-hover':
392
406
  this.rangeHovered !== null && currentDateISO === formatISODate(this.rangeHovered),
393
407
  }
408
+
409
+ const buttonClasses = {
410
+ 'pkt-calendar__date': true,
411
+ 'pkt-calendar__date--today': isToday,
412
+ 'pkt-calendar__date--selected': isSelected,
413
+ 'pkt-calendar__date--disabled': isDisabled,
414
+ 'pkt-calendar__date--in-range': this.inRange[currentDateISO],
415
+ 'pkt-calendar__date--in-range-hover':
416
+ this.rangeHovered !== null && currentDateISO === formatISODate(this.rangeHovered),
417
+ 'pkt-calendar__date--range-start':
418
+ this.range &&
419
+ (this.selected.length === 2 || this.rangeHovered !== null) &&
420
+ currentDateISO === this.selected[0],
421
+ 'pkt-calendar__date--range-end':
422
+ this.range && this.selected.length === 2 && currentDateISO === this.selected[1],
423
+ }
424
+
394
425
  return html`<td class=${classMap(classes)}>
395
- <div
396
- ?aria-selected=${isSelected}
397
- role="gridcell"
398
- class="pkt-btn pkt-btn--tertiary pkt-btn--small pkt-btn--label-only"
426
+ <button
427
+ type="button"
428
+ aria-pressed=${isSelected ? 'true' : 'false'}
429
+ ?disabled=${isDisabled}
430
+ class="pkt-btn pkt-btn--tertiary pkt-btn--small pkt-btn--label-only ${classMap(
431
+ buttonClasses,
432
+ )}"
399
433
  @mouseover=${() =>
400
434
  this.range && !this.isExcluded(j, currentDate) && this.handleRangeHover(currentDate)}
401
435
  @focus=${() => {
@@ -420,7 +454,7 @@ export class PktCalendar extends PktElement {
420
454
  }}
421
455
  >
422
456
  <span class="pkt-btn__text pkt-txt-14-light">${dayCounter}</span>
423
- </div>
457
+ </button>
424
458
  </td>`
425
459
  }
426
460
 
@@ -437,10 +471,10 @@ export class PktCalendar extends PktElement {
437
471
  let dayCounter = 1
438
472
  this.week = getWeek(newDateYMD(this.year, this.month, 1))
439
473
 
440
- const rows = []
474
+ const rows: any[] = []
441
475
 
442
476
  for (let i = 0; i < numRows; i++) {
443
- const cells = []
477
+ const cells: any[] = []
444
478
 
445
479
  this.weeknumbers && cells.push(html`<td class="pkt-cal-week">${this.week}</td>`)
446
480
  this.week++
@@ -530,20 +564,12 @@ export class PktCalendar extends PktElement {
530
564
  private changeMonth(year: number, month: number) {
531
565
  this.year = typeof year === 'string' ? parseInt(year) : year
532
566
  this.month = typeof month === 'string' ? parseInt(month) : month
567
+ this.currentmonth = new Date(this.year, this.month, 1)
533
568
  this.tabIndexSet = 0
534
569
  this.focusedDate = null
535
570
  this.selectableDates = []
536
571
  }
537
572
 
538
- private isInRange(date: Date) {
539
- if (this.range && this.selected.length === 2) {
540
- if (date > newDate(this.selected[0]) && date < newDate(this.selected[1])) return true
541
- } else if (this.range && this.selected.length === 1 && this.rangeHovered) {
542
- if (date > newDate(this.selected[0]) && date < this.rangeHovered) return true
543
- }
544
- return false
545
- }
546
-
547
573
  private isRangeAllowed(date: Date) {
548
574
  let allowed = true
549
575
  if (this._selected.length === 1) {
@@ -578,7 +604,10 @@ export class PktCalendar extends PktElement {
578
604
  if (this.selected.includes(formatISODate(selectedDate))) return
579
605
  this.selected = [...this.selected, formatISODate(selectedDate)]
580
606
  this._selected = [...this._selected, selectedDate]
607
+
608
+ // Update range styling if in range mode
581
609
  if (this.range && this.selected.length === 2) {
610
+ this.convertSelected()
582
611
  this.close()
583
612
  }
584
613
  }
@@ -648,7 +677,10 @@ export class PktCalendar extends PktElement {
648
677
 
649
678
  if (Array.isArray(days) && days.length) {
650
679
  for (let i = 0; i < days.length; i++) {
651
- this.inRange[formatISODate(days[i])] = this.isInRange(days[i])
680
+ const day = days[i]
681
+ // A date is in range if it's between the start and hovered end dates (exclusive)
682
+ const isInRange = day > this._selected[0] && day < date
683
+ this.inRange[formatISODate(day)] = isInRange
652
684
  }
653
685
  }
654
686
  } else {
@@ -683,17 +715,17 @@ export class PktCalendar extends PktElement {
683
715
 
684
716
  public focusOnCurrentDate() {
685
717
  const currentDateISO = formatISODate(newDate())
686
- const el = this.querySelector(`div[data-date="${currentDateISO}"]`)
687
- if (el instanceof HTMLDivElement) {
718
+ const el = this.querySelector(`button[data-date="${currentDateISO}"]`)
719
+ if (el instanceof HTMLButtonElement) {
688
720
  this.focusedDate = currentDateISO
689
721
  el.focus()
690
722
  } else {
691
723
  const firstSelectable = this.selectableDates.find((x) => !x.isDisabled)
692
724
  if (firstSelectable) {
693
725
  const firstSelectableEl = this.querySelector(
694
- `div[data-date="${firstSelectable.currentDateISO}"]`,
726
+ `button[data-date="${firstSelectable.currentDateISO}"]`,
695
727
  )
696
- if (firstSelectableEl instanceof HTMLDivElement) {
728
+ if (firstSelectableEl instanceof HTMLButtonElement) {
697
729
  this.focusedDate = firstSelectable.currentDateISO
698
730
  firstSelectableEl.focus()
699
731
  }
@@ -28,6 +28,18 @@ afterEach(() => {
28
28
  document.body.innerHTML = ''
29
29
  })
30
30
 
31
+ // Global console.warn spy to suppress validation warnings in tests
32
+ let consoleWarnSpy: jest.SpyInstance
33
+ beforeEach(() => {
34
+ consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {})
35
+ })
36
+
37
+ afterEach(() => {
38
+ if (consoleWarnSpy) {
39
+ consoleWarnSpy.mockRestore()
40
+ }
41
+ })
42
+
31
43
  describe('PktCard', () => {
32
44
  describe('Rendering and basic functionality', () => {
33
45
  test('renders without errors', async () => {
@@ -138,23 +150,25 @@ describe('PktCard', () => {
138
150
  })
139
151
 
140
152
  test('validates skin values and logs warnings for invalid skins', async () => {
141
- // Spy on console.warn to check if warning is logged
142
- const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {})
153
+ // Clear the global spy and create a new one for this specific test
154
+ consoleWarnSpy.mockRestore()
155
+ const localConsoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {})
143
156
 
144
157
  const container = await createCard(`skin="zebra"`)
145
158
  const card = container.querySelector('pkt-card') as PktCard
146
159
  await card.updateComplete
147
160
 
148
161
  // Should have logged a warning with the correct default value from spec
149
- expect(consoleSpy).toHaveBeenCalledWith(
162
+ expect(localConsoleSpy).toHaveBeenCalledWith(
150
163
  'Invalid skin value "zebra". Using default skin "outlined".',
151
164
  )
152
165
 
153
166
  // Should fall back to default from spec
154
167
  expect(card.skin).toBe('outlined')
155
168
 
156
- // Restore console.warn
157
- consoleSpy.mockRestore()
169
+ // Restore and recreate global spy for subsequent tests
170
+ localConsoleSpy.mockRestore()
171
+ consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {})
158
172
  })
159
173
 
160
174
  test('applies different layout properties correctly', async () => {