@oslokommune/punkt-elements 13.4.1 → 13.5.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.
- package/CHANGELOG.md +35 -0
- package/dist/calendar-32W9p9uc.cjs +115 -0
- package/dist/{calendar-DevQhOup.js → calendar-CJSxvwAq.js} +353 -340
- package/dist/{card-uccD6Pnv.cjs → card-BUITGoqX.cjs} +10 -10
- package/dist/{card-BI1NZONj.js → card-Dtw26f7i.js} +96 -76
- package/dist/checkbox-Gn7Wtk9h.cjs +31 -0
- package/dist/checkbox-ym7z6cpt.js +142 -0
- package/dist/{combobox-BhcqC30d.cjs → combobox-DjO0RMUB.cjs} +1 -1
- package/dist/{combobox-D9dGKWuZ.js → combobox-yE4aYhTi.js} +1 -1
- package/dist/{datepicker-CYOn3tRm.js → datepicker-BJKJBoy_.js} +102 -59
- package/dist/datepicker-CmTrG5GE.cjs +164 -0
- package/dist/index.d.ts +9 -2
- package/dist/pkt-calendar.cjs +1 -1
- package/dist/pkt-calendar.js +1 -1
- package/dist/pkt-card.cjs +1 -1
- package/dist/pkt-card.js +1 -1
- package/dist/pkt-checkbox.cjs +1 -1
- package/dist/pkt-checkbox.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 +6 -6
- package/package.json +3 -3
- package/src/components/calendar/calendar.accessibility.test.ts +111 -0
- package/src/components/calendar/calendar.constraints.test.ts +110 -0
- package/src/components/calendar/calendar.core.test.ts +367 -0
- package/src/components/calendar/calendar.interaction.test.ts +139 -0
- package/src/components/calendar/calendar.selection.test.ts +273 -0
- package/src/components/calendar/calendar.ts +74 -42
- package/src/components/card/card.test.ts +606 -0
- package/src/components/card/card.ts +24 -1
- package/src/components/checkbox/checkbox.test.ts +535 -0
- package/src/components/checkbox/checkbox.ts +44 -1
- package/src/components/combobox/combobox.test.ts +737 -0
- package/src/components/combobox/combobox.ts +1 -1
- package/src/components/datepicker/datepicker.accessibility.test.ts +193 -0
- package/src/components/datepicker/datepicker.core.test.ts +322 -0
- package/src/components/datepicker/datepicker.input.test.ts +268 -0
- package/src/components/datepicker/datepicker.selection.test.ts +286 -0
- package/src/components/datepicker/datepicker.ts +121 -19
- package/src/components/datepicker/datepicker.validation.test.ts +176 -0
- package/dist/calendar-BZe2D4Sr.cjs +0 -108
- package/dist/checkbox-CTRbpbye.js +0 -120
- package/dist/checkbox-wJ26voZd.cjs +0 -30
- package/dist/datepicker-B9rhz_AF.cjs +0 -154
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import '@testing-library/jest-dom'
|
|
2
|
+
import { fireEvent } from '@testing-library/dom'
|
|
3
|
+
|
|
4
|
+
import './calendar'
|
|
5
|
+
import { PktCalendar } from './calendar'
|
|
6
|
+
|
|
7
|
+
const waitForCustomElements = async () => {
|
|
8
|
+
await customElements.whenDefined('pkt-calendar')
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Helper function to create calendar markup
|
|
12
|
+
const createCalendar = async (calendarProps = '') => {
|
|
13
|
+
const container = document.createElement('div')
|
|
14
|
+
container.innerHTML = `
|
|
15
|
+
<pkt-calendar ${calendarProps}></pkt-calendar>
|
|
16
|
+
`
|
|
17
|
+
document.body.appendChild(container)
|
|
18
|
+
await waitForCustomElements()
|
|
19
|
+
return container
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Cleanup after each test
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
document.body.innerHTML = ''
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe('PktCalendar', () => {
|
|
28
|
+
describe('Keyboard navigation', () => {
|
|
29
|
+
test('handles arrow key navigation', async () => {
|
|
30
|
+
const container = await createCalendar()
|
|
31
|
+
|
|
32
|
+
const calendar = container.querySelector('pkt-calendar') as PktCalendar
|
|
33
|
+
await calendar.updateComplete
|
|
34
|
+
|
|
35
|
+
const firstDate = calendar.querySelector(
|
|
36
|
+
'.pkt-calendar__date:not(.pkt-calendar__date--disabled)',
|
|
37
|
+
) as HTMLElement
|
|
38
|
+
firstDate?.focus()
|
|
39
|
+
|
|
40
|
+
// Test right arrow
|
|
41
|
+
fireEvent.keyDown(firstDate!, { key: 'ArrowRight' })
|
|
42
|
+
await calendar.updateComplete
|
|
43
|
+
|
|
44
|
+
// Check that focus moved (implementation dependent)
|
|
45
|
+
expect(document.activeElement).toBeTruthy()
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('handles enter key for date selection', async () => {
|
|
49
|
+
const container = await createCalendar()
|
|
50
|
+
|
|
51
|
+
const calendar = container.querySelector('pkt-calendar') as PktCalendar
|
|
52
|
+
await calendar.updateComplete
|
|
53
|
+
|
|
54
|
+
const firstDate = calendar.querySelector(
|
|
55
|
+
'.pkt-calendar__date:not(.pkt-calendar__date--disabled)',
|
|
56
|
+
) as HTMLElement
|
|
57
|
+
firstDate?.focus()
|
|
58
|
+
|
|
59
|
+
fireEvent.keyDown(firstDate!, { key: 'Enter' })
|
|
60
|
+
await calendar.updateComplete
|
|
61
|
+
|
|
62
|
+
expect(firstDate).toHaveClass('pkt-calendar__date--selected')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('handles escape key', async () => {
|
|
66
|
+
const container = await createCalendar()
|
|
67
|
+
|
|
68
|
+
const calendar = container.querySelector('pkt-calendar') as PktCalendar
|
|
69
|
+
await calendar.updateComplete
|
|
70
|
+
|
|
71
|
+
fireEvent.keyDown(calendar, { key: 'Escape' })
|
|
72
|
+
await calendar.updateComplete
|
|
73
|
+
|
|
74
|
+
// Should trigger close event if inside a datepicker
|
|
75
|
+
expect(calendar).toBeInTheDocument()
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
describe('Edge cases and error handling', () => {
|
|
80
|
+
test('handles invalid date formats gracefully', async () => {
|
|
81
|
+
const container = await createCalendar('earliest="invalid-date" latest="also-invalid"')
|
|
82
|
+
|
|
83
|
+
const calendar = container.querySelector('pkt-calendar') as PktCalendar
|
|
84
|
+
await calendar.updateComplete
|
|
85
|
+
|
|
86
|
+
// Should not crash and render normally
|
|
87
|
+
expect(calendar).toBeInTheDocument()
|
|
88
|
+
const calendarBody = calendar.querySelector('.pkt-calendar__body')
|
|
89
|
+
expect(calendarBody).toBeInTheDocument()
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('handles empty selected dates array', async () => {
|
|
93
|
+
const container = await createCalendar('selected=""')
|
|
94
|
+
|
|
95
|
+
const calendar = container.querySelector('pkt-calendar') as PktCalendar
|
|
96
|
+
await calendar.updateComplete
|
|
97
|
+
|
|
98
|
+
expect(calendar.selected).toEqual([])
|
|
99
|
+
const selectedDates = calendar.querySelectorAll('.pkt-calendar__date--selected')
|
|
100
|
+
expect(selectedDates.length).toBe(0)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
test('handles conflicting properties gracefully', async () => {
|
|
104
|
+
// Both multiple and range shouldn't typically be used together
|
|
105
|
+
const container = await createCalendar('multiple range')
|
|
106
|
+
|
|
107
|
+
const calendar = container.querySelector('pkt-calendar') as PktCalendar
|
|
108
|
+
await calendar.updateComplete
|
|
109
|
+
|
|
110
|
+
expect(calendar.multiple).toBe(true)
|
|
111
|
+
expect(calendar.range).toBe(true)
|
|
112
|
+
|
|
113
|
+
// Should still render without errors
|
|
114
|
+
expect(calendar).toBeInTheDocument()
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
test('handles very early dates', async () => {
|
|
118
|
+
const container = await createCalendar('currentmonth="1900-01-01"')
|
|
119
|
+
|
|
120
|
+
const calendar = container.querySelector('pkt-calendar') as PktCalendar
|
|
121
|
+
await calendar.updateComplete
|
|
122
|
+
|
|
123
|
+
expect(calendar).toBeInTheDocument()
|
|
124
|
+
const calendarBody = calendar.querySelector('.pkt-calendar__body')
|
|
125
|
+
expect(calendarBody).toBeInTheDocument()
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
test('handles far future dates', async () => {
|
|
129
|
+
const container = await createCalendar('currentmonth="2100-12-31"')
|
|
130
|
+
|
|
131
|
+
const calendar = container.querySelector('pkt-calendar') as PktCalendar
|
|
132
|
+
await calendar.updateComplete
|
|
133
|
+
|
|
134
|
+
expect(calendar).toBeInTheDocument()
|
|
135
|
+
const calendarBody = calendar.querySelector('.pkt-calendar__body')
|
|
136
|
+
expect(calendarBody).toBeInTheDocument()
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
})
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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(`
|
|
194
|
-
if (el instanceof
|
|
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(`
|
|
205
|
+
let nextElement = this.querySelector(`button[data-date="${formatISODate(nextDate)}"]`)
|
|
198
206
|
while (
|
|
199
207
|
nextElement &&
|
|
200
|
-
nextElement instanceof
|
|
208
|
+
nextElement instanceof HTMLButtonElement &&
|
|
201
209
|
nextElement.dataset.disabled
|
|
202
210
|
) {
|
|
203
211
|
nextDate = addDays(nextDate, direction)
|
|
204
|
-
nextElement = this.querySelector(`
|
|
212
|
+
nextElement = this.querySelector(`button[data-date="${formatISODate(nextDate)}"]`)
|
|
205
213
|
}
|
|
206
214
|
el = nextElement
|
|
207
215
|
}
|
|
208
|
-
if (el instanceof
|
|
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' :
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
<
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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
|
-
</
|
|
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
|
-
|
|
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(`
|
|
687
|
-
if (el instanceof
|
|
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
|
-
`
|
|
726
|
+
`button[data-date="${firstSelectable.currentDateISO}"]`,
|
|
695
727
|
)
|
|
696
|
-
if (firstSelectableEl instanceof
|
|
728
|
+
if (firstSelectableEl instanceof HTMLButtonElement) {
|
|
697
729
|
this.focusedDate = firstSelectable.currentDateISO
|
|
698
730
|
firstSelectableEl.focus()
|
|
699
731
|
}
|